tin-spa 20.11.6 → 20.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,7 +24,7 @@ import * as i1$2 from '@angular/router';
24
24
  import { NavigationEnd, Router, RouterModule } from '@angular/router';
25
25
  import * as signalR from '@microsoft/signalr';
26
26
  import * as i2$1 from '@abacritt/angularx-social-login';
27
- import { SocialLoginModule } from '@abacritt/angularx-social-login';
27
+ import { GoogleLoginProvider, SocialLoginModule } from '@abacritt/angularx-social-login';
28
28
  import * as i3$2 from '@angular/platform-browser';
29
29
  import * as i6 from '@angular/material/chips';
30
30
  import { MatChipsModule } from '@angular/material/chips';
@@ -32,7 +32,7 @@ import * as i2$2 from '@angular/forms';
32
32
  import { FormControl, Validators, NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms';
33
33
  import * as i7 from '@angular/material/table';
34
34
  import { MatTableDataSource, MatTableModule } from '@angular/material/table';
35
- import * as i8 from '@angular/material/checkbox';
35
+ import * as i3$3 from '@angular/material/checkbox';
36
36
  import { MatCheckboxModule } from '@angular/material/checkbox';
37
37
  import * as i7$1 from '@angular/material/select';
38
38
  import { MatSelectModule } from '@angular/material/select';
@@ -44,9 +44,9 @@ import * as i14 from '@angular/material/progress-bar';
44
44
  import { MatProgressBarModule } from '@angular/material/progress-bar';
45
45
  import * as i1$3 from '@angular/cdk/layout';
46
46
  import * as i1$4 from '@angular/service-worker';
47
- import * as i18 from '@angular/material/card';
47
+ import * as i6$1 from '@angular/material/card';
48
48
  import { MatCardModule } from '@angular/material/card';
49
- import * as i6$1 from '@angular/material/expansion';
49
+ import * as i8 from '@angular/material/expansion';
50
50
  import { MatExpansionModule } from '@angular/material/expansion';
51
51
  import * as i4$2 from '@angular/material/badge';
52
52
  import { MatBadgeModule } from '@angular/material/badge';
@@ -218,9 +218,21 @@ class Core {
218
218
  return RoleAccess.None;
219
219
  return role[capName] ?? RoleAccess.None;
220
220
  }
221
- static testVisible(config, data, field, currentRole) {
222
- if ((field.type == 'section' || field.type == 'blank') && field.hidden)
221
+ // Changed: central resolver for the unified visible/hidden properties (boolean | condition)
222
+ static resolveVisibility(v, data) {
223
+ if (v === undefined || v === null)
224
+ return undefined;
225
+ return typeof v === 'function' ? !!v(data) : v;
226
+ }
227
+ // Changed: single visibility rule — hidden wins over visible on collision; undefined defaults to visible
228
+ static isItemVisible(item, data) {
229
+ if (!item)
230
+ return true;
231
+ if (this.resolveVisibility(item.hidden, data) === true)
223
232
  return false;
233
+ return this.resolveVisibility(item.visible, data) !== false;
234
+ }
235
+ static testVisible(config, data, field, currentRole) {
224
236
  // First check form level security if exists
225
237
  if (config.security && !this.hasFormAccess(config, currentRole)) {
226
238
  return false;
@@ -232,12 +244,16 @@ class Core {
232
244
  if (!this.checkSecurity(sectionField.securityConfig, currentRole)) {
233
245
  return false;
234
246
  }
235
- // if (sectionField.hidden) return false;
236
247
  if (sectionField.hideOnCreate && config.mode == 'create')
237
248
  return false;
238
249
  if (sectionField.hideOnExists && config.mode != 'create')
239
250
  return false;
240
- if (sectionField.hiddenCondition && sectionField.hiddenCondition(data))
251
+ // Changed: members inherit ONLY the section's condition functions (legacy hiddenCondition behavior).
252
+ // A static hidden:true hides just the section header — members stay rendered so field types that
253
+ // produce values on render (e.g. select defaultFirstValue) still populate the submitted object.
254
+ if (typeof sectionField.hidden === 'function' && sectionField.hidden(data))
255
+ return false;
256
+ if (typeof sectionField.visible === 'function' && !sectionField.visible(data))
241
257
  return false;
242
258
  }
243
259
  }
@@ -245,18 +261,13 @@ class Core {
245
261
  if (!this.checkSecurity(field.securityConfig, currentRole)) {
246
262
  return false;
247
263
  }
248
- if (field.hidden)
249
- return false;
250
264
  if (config.mode == 'create' && field.hideOnCreate) {
251
265
  return false;
252
266
  }
253
267
  if (config.mode != 'create' && field.hideOnExists) {
254
268
  return false;
255
269
  }
256
- if (field.hiddenCondition) {
257
- return !field.hiddenCondition(data);
258
- }
259
- return true;
270
+ return this.isItemVisible(field, data); // Changed: unified visible/hidden replaces hidden + hiddenCondition
260
271
  }
261
272
  static testReadOnly(config, data, field) {
262
273
  // Default readonly conditions
@@ -283,10 +294,7 @@ class Core {
283
294
  return false;
284
295
  }
285
296
  static testVisibleHeaderButton(data, btn) {
286
- if (btn.hiddenCondition) {
287
- return !btn.hiddenCondition(data);
288
- }
289
- return true;
297
+ return this.isItemVisible(btn, data); // Changed: unified visible/hidden replaces hiddenCondition
290
298
  }
291
299
  static getVisibleSubfields(config, data, field, currentRole) {
292
300
  return field.subfields?.filter(subfield => Core.testVisible(config, data, subfield, currentRole)) || [];
@@ -350,8 +358,8 @@ class Core {
350
358
  return this.nowDate(true);
351
359
  return field.defaultValue;
352
360
  }
353
- if (field.type == 'select' && (field.hidden || field.hideOnCreate))
354
- return 0;
361
+ if (field.type == 'select' && (field.hidden === true || field.hideOnCreate))
362
+ return 0; // Changed: only statically-hidden selects default to 0 (hidden may now be a condition)
355
363
  switch (field.type) {
356
364
  case 'text':
357
365
  return '';
@@ -1170,6 +1178,7 @@ class CapItem {
1170
1178
  this.color = "black";
1171
1179
  this.isBool = false;
1172
1180
  this.featureKey = ""; // Added: default empty — no feature restriction
1181
+ this.moduleKey = ""; // Added (Setup v2): default empty — no module restriction
1173
1182
  }
1174
1183
  }
1175
1184
  class AppConfig {
@@ -1387,6 +1396,109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
1387
1396
  }]
1388
1397
  }], ctorParameters: () => [] });
1389
1398
 
1399
+ // ───────────────────────────────────────────────────────────────────────────
1400
+ // tin-spa runtime app settings — assets/appsettings.json
1401
+ //
1402
+ // Mirrors ASP.NET Core configuration on the frontend: a deployed, server-editable
1403
+ // `assets/appsettings.json` is read at startup (before Angular bootstraps), and if it
1404
+ // declares an "Environment" (e.g. "Production"), `assets/appsettings.{Environment}.json`
1405
+ // is loaded on top of it — same file names, same layering as the backend. Changing a
1406
+ // value is a file edit on the server, never a rebuild: the same build artifact is
1407
+ // promoted through SIT/UAT/PROD.
1408
+ //
1409
+ // Consuming apps call the loader in main.ts, before bootstrapModule:
1410
+ //
1411
+ // loadTinSpaAppSettings().then(() =>
1412
+ // platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)));
1413
+ //
1414
+ // The loaded settings are then visible to DI factories via getTinSpaAppSettings().
1415
+ // provideTinSpaRuntime() consumes them automatically (see tinspa-runtime.ts): values in
1416
+ // the file override the code-declared runtime config. The file is OPTIONAL — apps that
1417
+ // don't ship one keep their code-declared config unchanged.
1418
+ //
1419
+ // Everything in this file is public-by-design SPA configuration (client IDs, authorities,
1420
+ // redirect URIs). Secrets never belong here — they stay in the backend's appsettings.
1421
+ // ───────────────────────────────────────────────────────────────────────────
1422
+ const LOG = '[tin-spa appsettings]';
1423
+ let loadedSettings = null;
1424
+ /** The settings loaded by loadTinSpaAppSettings(), or null when no file was found / loading failed. */
1425
+ function getTinSpaAppSettings() {
1426
+ return loadedSettings;
1427
+ }
1428
+ /** Overlay values win; plain objects merge recursively, scalars and arrays replace. */
1429
+ function deepMerge(base, overlay) {
1430
+ if (overlay === undefined)
1431
+ return base;
1432
+ if (base === undefined)
1433
+ return overlay;
1434
+ const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
1435
+ if (!isObj(base) || !isObj(overlay))
1436
+ return overlay;
1437
+ const out = { ...base };
1438
+ for (const key of Object.keys(overlay))
1439
+ out[key] = deepMerge(base[key], overlay[key]);
1440
+ return out;
1441
+ }
1442
+ /** null = file absent (fine for the base file, warned for a declared overlay); throws never. */
1443
+ async function fetchSettingsFile(file, required) {
1444
+ // no-store: server admins edit these files in place — a cached copy would silently
1445
+ // keep serving the old environment's values.
1446
+ let response;
1447
+ try {
1448
+ response = await fetch(`assets/${file}`, { cache: 'no-store' });
1449
+ }
1450
+ catch (err) {
1451
+ console.error(`${LOG} could not request assets/${file}:`, err);
1452
+ return null;
1453
+ }
1454
+ if (!response.ok) {
1455
+ if (required) {
1456
+ console.warn(`${LOG} assets/${file} was not found (HTTP ${response.status}) — it is declared by the "Environment" setting, so it should exist. Using the base file only.`);
1457
+ }
1458
+ else {
1459
+ console.info(`${LOG} no assets/${file} found (HTTP ${response.status}) — runtime settings come from code only.`);
1460
+ }
1461
+ return null;
1462
+ }
1463
+ try {
1464
+ return await response.json();
1465
+ }
1466
+ catch (err) {
1467
+ console.error(`${LOG} assets/${file} exists but is not valid JSON — its values are IGNORED. Fix the file syntax.`, err);
1468
+ return null;
1469
+ }
1470
+ }
1471
+ /**
1472
+ * Loads assets/appsettings.json (+ the appsettings.{Environment}.json overlay when the base file
1473
+ * declares an Environment) and caches the merged result for getTinSpaAppSettings().
1474
+ * Never rejects — a missing or broken file logs to the console and resolves null so the app
1475
+ * still boots with its code-declared configuration.
1476
+ */
1477
+ async function loadTinSpaAppSettings() {
1478
+ if (typeof fetch === 'undefined')
1479
+ return null;
1480
+ const base = await fetchSettingsFile('appsettings.json', false);
1481
+ if (!base)
1482
+ return null;
1483
+ let merged = base;
1484
+ const env = typeof base.Environment === 'string' ? base.Environment.trim() : '';
1485
+ if (env) {
1486
+ const overlay = await fetchSettingsFile(`appsettings.${env}.json`, true);
1487
+ if (overlay)
1488
+ merged = deepMerge(base, overlay);
1489
+ }
1490
+ loadedSettings = merged;
1491
+ console.info(`${LOG} loaded appsettings.json` +
1492
+ (env ? ` + appsettings.${env}.json (Environment: ${env})` : ' (no Environment overlay declared)') +
1493
+ ` — MicrosoftAuth: ${merged.MicrosoftAuth ?? 'not set'}, GoogleAuth: ${merged.GoogleAuth ?? 'not set'},` +
1494
+ ` Msal.ClientId: ${merged.Msal?.ClientId ? 'present' : 'MISSING'}, GoogleClientID: ${merged.GoogleClientID ? 'present' : 'MISSING'},` +
1495
+ // PathRouting is reported explicitly because getting it wrong produces a confusing failure: the
1496
+ // app loads fine and only breaks on refresh/deep-link. "not set" here means the code default is
1497
+ // in force, which is the first thing to check when an environment routes unexpectedly.
1498
+ ` PathRouting: ${merged.PathRouting ?? 'not set (code default)'}`);
1499
+ return merged;
1500
+ }
1501
+
1390
1502
  // ───────────────────────────────────────────────────────────────────────────
1391
1503
  // tin-spa runtime configuration
1392
1504
  //
@@ -1404,9 +1516,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
1404
1516
  // }),
1405
1517
  // ]
1406
1518
  //
1407
- // The config is the single source of truth and is read live at startup — nothing is cached in
1408
- // localStorage. So when you change a flag here and redeploy, every user picks up the new value on
1409
- // their next load. To change a flag, edit the call above and rebuild.
1519
+ // The config is read live at startup — nothing is cached in localStorage. So when you change a
1520
+ // flag here and redeploy, every user picks up the new value on their next load.
1521
+ //
1522
+ // Changed: the code-declared config is no longer the only source. When the app ships an
1523
+ // assets/appsettings.json (loaded pre-bootstrap via loadTinSpaAppSettings(), see appsettings.ts),
1524
+ // its values OVERRIDE the code config:
1525
+ // MicrosoftAuth → microsoftAuth
1526
+ // PathRouting → pathRouting (added 20.11.15)
1527
+ // RealTime → realTime (added 20.11.15)
1528
+ // Offline → offline (added 20.11.15)
1529
+ // Msal.{ClientId,Authority,RedirectUri} → msal
1530
+ // so SSO, the routing strategy and the real-time/offline switches are all per-environment
1531
+ // server-editable — one build artifact promoted through SIT/UAT/PROD, no rebuild.
1532
+ // Precedence: DEFAULTS < provideTinSpaRuntime(config) < assets/appsettings.json.
1533
+ //
1534
+ // A key is only honoured when the file declares it as a real boolean; anything else (absent,
1535
+ // string, null) falls through to the code-declared value. So provideTinSpaRuntime() stays the
1536
+ // DEFAULT for every environment, and appsettings.json is the per-server exception — which is the
1537
+ // behaviour a deployer expects, and it means shipping no appsettings.json changes nothing.
1410
1538
  // ───────────────────────────────────────────────────────────────────────────
1411
1539
  const DEFAULTS = {
1412
1540
  pathRouting: false,
@@ -1423,16 +1551,56 @@ function tinSpaLocationStrategyFactory(platformLocation, baseHref, config) {
1423
1551
  ? new PathLocationStrategy(platformLocation, baseHref)
1424
1552
  : new HashLocationStrategy(platformLocation, baseHref);
1425
1553
  }
1554
+ /** Overlays assets/appsettings.json values (when the app loaded one) onto the code-declared config. */
1555
+ function tinSpaRuntimeConfigFactory(codeConfig) {
1556
+ const settings = getTinSpaAppSettings();
1557
+ if (!settings)
1558
+ return codeConfig;
1559
+ const merged = { ...codeConfig };
1560
+ // Each flag is overridden ONLY when the file declares an actual boolean. That is why these are
1561
+ // `typeof === 'boolean'` tests rather than `??` or truthiness: an absent key must fall through to
1562
+ // the code-declared value, and a key present as `false` must still win. `settings.X ?? code.X`
1563
+ // would behave correctly for absent keys but silently accept a string "true" from a hand-edited
1564
+ // file as a truthy override — these files are edited by server admins, not by a compiler.
1565
+ if (typeof settings.MicrosoftAuth === 'boolean')
1566
+ merged.microsoftAuth = settings.MicrosoftAuth;
1567
+ // Routing strategy per environment. Previously code-only, which forced every environment onto the
1568
+ // same strategy and meant a server needing path routing required a rebuild — defeating the point
1569
+ // of runtime appsettings. Note the consequence of getting this wrong: path routing without a
1570
+ // server rewrite rule gives a working app that 404s on refresh and on every deep link.
1571
+ if (typeof settings.PathRouting === 'boolean')
1572
+ merged.pathRouting = settings.PathRouting;
1573
+ if (typeof settings.RealTime === 'boolean')
1574
+ merged.realTime = settings.RealTime;
1575
+ if (typeof settings.Offline === 'boolean')
1576
+ merged.offline = settings.Offline;
1577
+ if (settings.Msal) {
1578
+ merged.msal = {
1579
+ clientId: settings.Msal.ClientId ?? codeConfig.msal?.clientId ?? '',
1580
+ authority: settings.Msal.Authority ?? codeConfig.msal?.authority ?? '',
1581
+ redirectUri: settings.Msal.RedirectUri ?? codeConfig.msal?.redirectUri ?? '',
1582
+ };
1583
+ }
1584
+ return merged;
1585
+ }
1426
1586
  function tinSpaMsalInstanceFactory(config) {
1427
1587
  const wantsMsal = !!config.microsoftAuth;
1428
1588
  const secure = typeof window === 'undefined' || window.isSecureContext;
1429
- if (wantsMsal && !config.msal) {
1430
- console.warn('[tin-spa] microsoftAuth is enabled but no msal credentials were provided to provideTinSpaRuntime(). Microsoft SSO is INACTIVE.');
1589
+ // A credential is "provided" only if non-empty appsettings.json ships empty strings as
1590
+ // placeholders, and those must trigger the same diagnostics as a missing section.
1591
+ const missing = [];
1592
+ if (!config.msal?.clientId?.trim())
1593
+ missing.push('ClientId');
1594
+ if (!config.msal?.authority?.trim())
1595
+ missing.push('Authority');
1596
+ if (wantsMsal && missing.length) {
1597
+ console.error(`[tin-spa] Microsoft SSO is enabled but its credentials are incomplete — missing: ${missing.join(', ')}. ` +
1598
+ `Provide them in assets/appsettings.json under "Msal" (or in provideTinSpaRuntime({ msal })). Microsoft SSO is INACTIVE.`);
1431
1599
  }
1432
1600
  if (wantsMsal && !secure) {
1433
1601
  console.warn('[tin-spa] microsoftAuth is enabled but the app is NOT running over HTTPS (secure context). Microsoft SSO is INACTIVE until served over HTTPS.');
1434
1602
  }
1435
- const active = wantsMsal && !!config.msal && secure;
1603
+ const active = wantsMsal && !missing.length && secure;
1436
1604
  if (!active) {
1437
1605
  // Stub: MsalService's constructor calls instance.initializeWrapperLibrary(), so it must exist
1438
1606
  // or the app crashes at bootstrap. loginPopup rejects so callers' error handlers report it.
@@ -1442,14 +1610,25 @@ function tinSpaMsalInstanceFactory(config) {
1442
1610
  getAllAccounts: () => [],
1443
1611
  getActiveAccount: () => null,
1444
1612
  loginPopup: () => Promise.reject(new Error(!wantsMsal ? 'Microsoft login is disabled in app config.'
1445
- : !config.msal ? 'Microsoft login is not configured (missing msal credentials).'
1613
+ : missing.length ? `Microsoft login is not configured (missing: ${missing.join(', ')}).`
1446
1614
  : 'Microsoft login requires HTTPS (a secure context).')),
1447
1615
  };
1448
1616
  }
1449
- return new PublicClientApplication({
1450
- auth: { ...config.msal },
1617
+ // RedirectUri rule: use the configured value when provided; otherwise fall back to
1618
+ // `${origin}/login` correct whenever each environment's own /login URL is registered
1619
+ // on the app registration.
1620
+ const redirectUri = config.msal.redirectUri?.trim() || `${window.location.origin}/login`;
1621
+ console.info(`[tin-spa] Microsoft SSO active — redirectUri: ${redirectUri}` +
1622
+ (config.msal.redirectUri?.trim() ? ' (configured)' : ' (not configured — derived from window.location.origin)'));
1623
+ const pca = new PublicClientApplication({
1624
+ auth: { ...config.msal, redirectUri },
1451
1625
  cache: { cacheLocation: BrowserCacheLocation.LocalStorage, storeAuthStateInCookie: false },
1452
1626
  });
1627
+ // MSAL v3 requires initialize() before any other API call. Kick it off now so it has
1628
+ // normally completed by the time the user clicks a login button; callers still await
1629
+ // initialize() themselves (it is idempotent) to close the race.
1630
+ pca.initialize().catch(e => console.error('[tin-spa] MSAL initialize failed:', e));
1631
+ return pca;
1453
1632
  }
1454
1633
  // ── Public API ───────────────────────────────────────────────────────────────
1455
1634
  /**
@@ -1459,7 +1638,9 @@ function tinSpaMsalInstanceFactory(config) {
1459
1638
  function provideTinSpaRuntime(config) {
1460
1639
  const merged = { ...DEFAULTS, ...config };
1461
1640
  return makeEnvironmentProviders([
1462
- { provide: TIN_SPA_RUNTIME_CONFIG, useValue: merged },
1641
+ // useFactory (not useValue): this function runs at module-import time, BEFORE main.ts has
1642
+ // fetched assets/appsettings.json — the factory defers reading the file until injection.
1643
+ { provide: TIN_SPA_RUNTIME_CONFIG, useFactory: () => tinSpaRuntimeConfigFactory(merged) },
1463
1644
  {
1464
1645
  provide: LocationStrategy,
1465
1646
  useFactory: tinSpaLocationStrategyFactory,
@@ -4809,7 +4990,7 @@ class ImportDialogComponent {
4809
4990
  this.stepper.selectedIndex = index; }, 0);
4810
4991
  }
4811
4992
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ImportDialogComponent, deps: [{ token: HttpService }, { token: i1$1.HttpClient }, { token: MessageService }, { token: i4.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
4812
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ImportDialogComponent, isStandalone: false, selector: "spa-import-dialog", viewQueries: [{ propertyName: "stepper", first: true, predicate: ["stepper"], descendants: true }], ngImport: i0, template: "<div class=\"import-dialog\">\r\n\r\n <div class=\"import-header\">\r\n <h2>{{ title }}</h2>\r\n <button mat-icon-button (click)=\"close()\" aria-label=\"Close\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-progress-bar *ngIf=\"loading\" mode=\"indeterminate\"></mat-progress-bar>\r\n\r\n <mat-horizontal-stepper #stepper [linear]=\"false\" class=\"import-stepper\">\r\n\r\n <!-- Step 1: Upload -->\r\n <mat-step label=\"Upload\">\r\n <div class=\"step-body\">\r\n <p class=\"hint\">Upload an Excel (.xlsx) file. Not sure of the format? Download a template with the expected columns.</p>\r\n\r\n <div class=\"upload-row\">\r\n <input type=\"file\" accept=\".xlsx\" (change)=\"onFileSelected($event)\" />\r\n <button mat-stroked-button color=\"primary\" (click)=\"downloadTemplate()\">\r\n <mat-icon>download</mat-icon> Download template\r\n </button>\r\n </div>\r\n\r\n <div class=\"step-actions\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"!selectedFile || loading\" (click)=\"doUpload()\">Upload</button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n <!-- Step 2: Map columns -->\r\n <mat-step label=\"Map columns\">\r\n <div class=\"step-body\">\r\n <div *ngIf=\"unmappedRequired.length\" class=\"alert alert-warn\">\r\n Required fields not yet mapped: <strong>{{ unmappedRequired.join(', ') }}</strong>\r\n </div>\r\n\r\n <table class=\"map-table\">\r\n <thead>\r\n <tr><th>Spreadsheet column</th><th>Sample values</th><th>Maps to field</th><th>Match</th></tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let m of mappings\">\r\n <td class=\"col-header\">{{ m.header }}</td>\r\n <td class=\"col-samples\">\r\n <span *ngFor=\"let s of samplesFor(m.header)\" class=\"sample\">{{ s }}</span>\r\n </td>\r\n <td>\r\n <mat-select [(ngModel)]=\"m.property\" (selectionChange)=\"onMappingChange(m)\" placeholder=\"\u2014 Ignore \u2014\">\r\n <mat-option [value]=\"null\">\u2014 Ignore \u2014</mat-option>\r\n <mat-option *ngFor=\"let f of availableFields(m.header)\" [value]=\"f.property\">\r\n {{ f.display }}<span *ngIf=\"f.required\"> *</span>\r\n </mat-option>\r\n </mat-select>\r\n </td>\r\n <td>\r\n <span class=\"chip\" [ngClass]=\"confidenceClass(m)\" [matTooltip]=\"m.reasoning || ''\">\r\n {{ m.property ? (m.method === 'AI' ? ((m.confidence * 100) | number:'1.0-0') + '%' : m.method) : 'Unmapped' }}\r\n </span>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div class=\"step-actions\">\r\n <button mat-button (click)=\"startOver()\">Start over</button>\r\n <button mat-flat-button color=\"primary\" [disabled]=\"unmappedRequired.length > 0 || loading\" (click)=\"confirmMapping()\">Next</button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n <!-- Step 3: Review -->\r\n <mat-step label=\"Review\">\r\n <div class=\"step-body\">\r\n <div class=\"tiles\">\r\n <div class=\"tile\"><span class=\"tile-num\">{{ counts.total }}</span><span class=\"tile-label\">Total</span></div>\r\n <div class=\"tile tile-ready\"><span class=\"tile-num\">{{ counts.ready }}</span><span class=\"tile-label\">Ready</span></div>\r\n <div class=\"tile tile-warn\"><span class=\"tile-num\">{{ counts.warning }}</span><span class=\"tile-label\">Warnings</span></div>\r\n <div class=\"tile tile-error\"><span class=\"tile-num\">{{ counts.error }}</span><span class=\"tile-label\">Errors</span></div>\r\n </div>\r\n\r\n <mat-checkbox [(ngModel)]=\"onlyProblems\">Only show rows with problems</mat-checkbox>\r\n\r\n <div class=\"grid-scroll\">\r\n <table mat-table [dataSource]=\"visibleRows\" class=\"review-table\">\r\n\r\n <ng-container matColumnDef=\"rowNumber\">\r\n <th mat-header-cell *matHeaderCellDef>#</th>\r\n <td mat-cell *matCellDef=\"let row\">{{ row.rowNumber }}</td>\r\n </ng-container>\r\n\r\n <ng-container *ngFor=\"let col of reviewColumns\" [matColumnDef]=\"col.property\">\r\n <th mat-header-cell *matHeaderCellDef>{{ col.display }}</th>\r\n <td mat-cell *matCellDef=\"let row\"\r\n [class.cell-error]=\"cellSeverity(row, col.property) === 'error'\"\r\n [class.cell-warn]=\"cellSeverity(row, col.property) === 'warning'\">\r\n <ng-container *ngIf=\"editingRowId === row.importRowID; else showVal\">\r\n <input class=\"cell-input\" [(ngModel)]=\"editModel[col.property]\" />\r\n </ng-container>\r\n <ng-template #showVal>{{ cellValue(row, col.property) }}</ng-template>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"status\">\r\n <th mat-header-cell *matHeaderCellDef>Status</th>\r\n <td mat-cell *matCellDef=\"let row\">\r\n <span class=\"status-chip\" [ngClass]=\"{\r\n 'chip-green': row.status === 1,\r\n 'chip-orange': row.status === 2,\r\n 'chip-red': row.status === 3\r\n }\" [matTooltip]=\"rowIssueText(row)\">{{ row.statusName }}</span>\r\n <button *ngIf=\"editingRowId !== row.importRowID\" mat-button color=\"primary\" (click)=\"startEdit(row)\">Edit</button>\r\n <ng-container *ngIf=\"editingRowId === row.importRowID\">\r\n <button mat-button color=\"primary\" (click)=\"saveEdit(row)\">Save</button>\r\n <button mat-button (click)=\"cancelEdit()\">Cancel</button>\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n </table>\r\n </div>\r\n\r\n <div class=\"step-actions\">\r\n <button mat-button (click)=\"startOver()\">Start over</button>\r\n <button mat-flat-button color=\"primary\" [disabled]=\"counts.error > 0 || loading\" (click)=\"doCommit()\">\r\n Import {{ counts.total - counts.error }} record(s)\r\n </button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n <!-- Step 4: Done -->\r\n <mat-step label=\"Done\">\r\n <div class=\"step-body done-body\">\r\n <mat-icon class=\"done-icon\">check_circle</mat-icon>\r\n <h3>Imported {{ committedCount }} record(s)</h3>\r\n <p *ngIf=\"counts.warning > 0\" class=\"hint\">{{ counts.warning }} row(s) imported with warnings.</p>\r\n <div class=\"step-actions\">\r\n <button mat-flat-button color=\"primary\" (click)=\"close()\">Close</button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n </mat-horizontal-stepper>\r\n</div>\r\n", styles: [".import-dialog{display:flex;flex-direction:column;min-width:60vw;max-width:90vw}.import-header{display:flex;align-items:center;justify-content:space-between;padding:4px 8px}.import-header h2{margin:0;font-size:18px}.import-stepper{background:transparent}.step-body{padding:12px 8px;display:flex;flex-direction:column;gap:14px}.hint{color:#0009;font-size:13px;margin:0}.upload-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.step-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.alert{padding:8px 12px;border-radius:4px;font-size:13px}.alert-warn{background:#fff3e0;color:#8a5300;border:1px solid #ffcc80}.map-table{width:100%;border-collapse:collapse}.map-table th,.map-table td{text-align:left;padding:6px 8px;border-bottom:1px solid #eee;vertical-align:middle}.map-table th{font-size:12px;color:#0009;font-weight:600}.col-header{font-weight:600}.col-samples .sample{display:inline-block;background:#f2f2f2;border-radius:3px;padding:1px 6px;margin:1px 3px 1px 0;font-size:12px;color:#555}.chip{display:inline-block;padding:2px 8px;border-radius:10px;font-size:12px;color:#fff}.chip-green{background:#2e7d32}.chip-blue{background:#1565c0}.chip-orange{background:#ef6c00}.chip-grey{background:#9e9e9e}.chip-red{background:#c62828}.tiles{display:flex;gap:12px;flex-wrap:wrap}.tile{display:flex;flex-direction:column;align-items:center;min-width:72px;padding:8px 14px;border-radius:6px;background:#f5f5f5}.tile-num{font-size:20px;font-weight:700}.tile-label{font-size:12px;color:#0009}.tile-ready{background:#e8f5e9}.tile-warn{background:#fff3e0}.tile-error{background:#ffebee}.grid-scroll{overflow-x:auto;max-height:46vh;overflow-y:auto;border:1px solid #eee;border-radius:4px}.review-table{width:100%}.cell-error{background:#ffebee;color:#b71c1c}.cell-warn{background:#fff3e0;color:#8a5300}.cell-input{width:100%;box-sizing:border-box;padding:2px 4px}.status-chip{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;color:#fff;margin-right:6px}.done-body{align-items:center;text-align:center;padding:32px 8px}.done-icon{color:#2e7d32;font-size:56px;height:56px;width:56px}@media (prefers-color-scheme: dark){.hint,.map-table th,.tile-label{color:#ffffffb3}.col-samples .sample{background:#3a3a3a;color:#ccc}.tile{background:#333}.grid-scroll,.map-table th,.map-table td{border-color:#444}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i7.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i7.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i7.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i3.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: "component", type: i7$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i13.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "component", type: i13.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
4993
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ImportDialogComponent, isStandalone: false, selector: "spa-import-dialog", viewQueries: [{ propertyName: "stepper", first: true, predicate: ["stepper"], descendants: true }], ngImport: i0, template: "<div class=\"import-dialog\">\r\n\r\n <div class=\"import-header\">\r\n <h2>{{ title }}</h2>\r\n <button mat-icon-button (click)=\"close()\" aria-label=\"Close\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-progress-bar *ngIf=\"loading\" mode=\"indeterminate\"></mat-progress-bar>\r\n\r\n <mat-horizontal-stepper #stepper [linear]=\"false\" class=\"import-stepper\">\r\n\r\n <!-- Step 1: Upload -->\r\n <mat-step label=\"Upload\">\r\n <div class=\"step-body\">\r\n <p class=\"hint\">Upload an Excel (.xlsx) file. Not sure of the format? Download a template with the expected columns.</p>\r\n\r\n <div class=\"upload-row\">\r\n <input type=\"file\" accept=\".xlsx\" (change)=\"onFileSelected($event)\" />\r\n <button mat-stroked-button color=\"primary\" (click)=\"downloadTemplate()\">\r\n <mat-icon>download</mat-icon> Download template\r\n </button>\r\n </div>\r\n\r\n <div class=\"step-actions\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"!selectedFile || loading\" (click)=\"doUpload()\">Upload</button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n <!-- Step 2: Map columns -->\r\n <mat-step label=\"Map columns\">\r\n <div class=\"step-body\">\r\n <div *ngIf=\"unmappedRequired.length\" class=\"alert alert-warn\">\r\n Required fields not yet mapped: <strong>{{ unmappedRequired.join(', ') }}</strong>\r\n </div>\r\n\r\n <table class=\"map-table\">\r\n <thead>\r\n <tr><th>Spreadsheet column</th><th>Sample values</th><th>Maps to field</th><th>Match</th></tr>\r\n </thead>\r\n <tbody>\r\n <tr *ngFor=\"let m of mappings\">\r\n <td class=\"col-header\">{{ m.header }}</td>\r\n <td class=\"col-samples\">\r\n <span *ngFor=\"let s of samplesFor(m.header)\" class=\"sample\">{{ s }}</span>\r\n </td>\r\n <td>\r\n <mat-select [(ngModel)]=\"m.property\" (selectionChange)=\"onMappingChange(m)\" placeholder=\"\u2014 Ignore \u2014\">\r\n <mat-option [value]=\"null\">\u2014 Ignore \u2014</mat-option>\r\n <mat-option *ngFor=\"let f of availableFields(m.header)\" [value]=\"f.property\">\r\n {{ f.display }}<span *ngIf=\"f.required\"> *</span>\r\n </mat-option>\r\n </mat-select>\r\n </td>\r\n <td>\r\n <span class=\"chip\" [ngClass]=\"confidenceClass(m)\" [matTooltip]=\"m.reasoning || ''\">\r\n {{ m.property ? (m.method === 'AI' ? ((m.confidence * 100) | number:'1.0-0') + '%' : m.method) : 'Unmapped' }}\r\n </span>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div class=\"step-actions\">\r\n <button mat-button (click)=\"startOver()\">Start over</button>\r\n <button mat-flat-button color=\"primary\" [disabled]=\"unmappedRequired.length > 0 || loading\" (click)=\"confirmMapping()\">Next</button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n <!-- Step 3: Review -->\r\n <mat-step label=\"Review\">\r\n <div class=\"step-body\">\r\n <div class=\"tiles\">\r\n <div class=\"tile\"><span class=\"tile-num\">{{ counts.total }}</span><span class=\"tile-label\">Total</span></div>\r\n <div class=\"tile tile-ready\"><span class=\"tile-num\">{{ counts.ready }}</span><span class=\"tile-label\">Ready</span></div>\r\n <div class=\"tile tile-warn\"><span class=\"tile-num\">{{ counts.warning }}</span><span class=\"tile-label\">Warnings</span></div>\r\n <div class=\"tile tile-error\"><span class=\"tile-num\">{{ counts.error }}</span><span class=\"tile-label\">Errors</span></div>\r\n </div>\r\n\r\n <mat-checkbox [(ngModel)]=\"onlyProblems\">Only show rows with problems</mat-checkbox>\r\n\r\n <div class=\"grid-scroll\">\r\n <table mat-table [dataSource]=\"visibleRows\" class=\"review-table\">\r\n\r\n <ng-container matColumnDef=\"rowNumber\">\r\n <th mat-header-cell *matHeaderCellDef>#</th>\r\n <td mat-cell *matCellDef=\"let row\">{{ row.rowNumber }}</td>\r\n </ng-container>\r\n\r\n <ng-container *ngFor=\"let col of reviewColumns\" [matColumnDef]=\"col.property\">\r\n <th mat-header-cell *matHeaderCellDef>{{ col.display }}</th>\r\n <td mat-cell *matCellDef=\"let row\"\r\n [class.cell-error]=\"cellSeverity(row, col.property) === 'error'\"\r\n [class.cell-warn]=\"cellSeverity(row, col.property) === 'warning'\">\r\n <ng-container *ngIf=\"editingRowId === row.importRowID; else showVal\">\r\n <input class=\"cell-input\" [(ngModel)]=\"editModel[col.property]\" />\r\n </ng-container>\r\n <ng-template #showVal>{{ cellValue(row, col.property) }}</ng-template>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"status\">\r\n <th mat-header-cell *matHeaderCellDef>Status</th>\r\n <td mat-cell *matCellDef=\"let row\">\r\n <span class=\"status-chip\" [ngClass]=\"{\r\n 'chip-green': row.status === 1,\r\n 'chip-orange': row.status === 2,\r\n 'chip-red': row.status === 3\r\n }\" [matTooltip]=\"rowIssueText(row)\">{{ row.statusName }}</span>\r\n <button *ngIf=\"editingRowId !== row.importRowID\" mat-button color=\"primary\" (click)=\"startEdit(row)\">Edit</button>\r\n <ng-container *ngIf=\"editingRowId === row.importRowID\">\r\n <button mat-button color=\"primary\" (click)=\"saveEdit(row)\">Save</button>\r\n <button mat-button (click)=\"cancelEdit()\">Cancel</button>\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n </table>\r\n </div>\r\n\r\n <div class=\"step-actions\">\r\n <button mat-button (click)=\"startOver()\">Start over</button>\r\n <button mat-flat-button color=\"primary\" [disabled]=\"counts.error > 0 || loading\" (click)=\"doCommit()\">\r\n Import {{ counts.total - counts.error }} record(s)\r\n </button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n <!-- Step 4: Done -->\r\n <mat-step label=\"Done\">\r\n <div class=\"step-body done-body\">\r\n <mat-icon class=\"done-icon\">check_circle</mat-icon>\r\n <h3>Imported {{ committedCount }} record(s)</h3>\r\n <p *ngIf=\"counts.warning > 0\" class=\"hint\">{{ counts.warning }} row(s) imported with warnings.</p>\r\n <div class=\"step-actions\">\r\n <button mat-flat-button color=\"primary\" (click)=\"close()\">Close</button>\r\n </div>\r\n </div>\r\n </mat-step>\r\n\r\n </mat-horizontal-stepper>\r\n</div>\r\n", styles: [".import-dialog{display:flex;flex-direction:column;min-width:60vw;max-width:90vw}.import-header{display:flex;align-items:center;justify-content:space-between;padding:4px 8px}.import-header h2{margin:0;font-size:18px}.import-stepper{background:transparent}.step-body{padding:12px 8px;display:flex;flex-direction:column;gap:14px}.hint{color:#0009;font-size:13px;margin:0}.upload-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.step-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.alert{padding:8px 12px;border-radius:4px;font-size:13px}.alert-warn{background:#fff3e0;color:#8a5300;border:1px solid #ffcc80}.map-table{width:100%;border-collapse:collapse}.map-table th,.map-table td{text-align:left;padding:6px 8px;border-bottom:1px solid #eee;vertical-align:middle}.map-table th{font-size:12px;color:#0009;font-weight:600}.col-header{font-weight:600}.col-samples .sample{display:inline-block;background:#f2f2f2;border-radius:3px;padding:1px 6px;margin:1px 3px 1px 0;font-size:12px;color:#555}.chip{display:inline-block;padding:2px 8px;border-radius:10px;font-size:12px;color:#fff}.chip-green{background:#2e7d32}.chip-blue{background:#1565c0}.chip-orange{background:#ef6c00}.chip-grey{background:#9e9e9e}.chip-red{background:#c62828}.tiles{display:flex;gap:12px;flex-wrap:wrap}.tile{display:flex;flex-direction:column;align-items:center;min-width:72px;padding:8px 14px;border-radius:6px;background:#f5f5f5}.tile-num{font-size:20px;font-weight:700}.tile-label{font-size:12px;color:#0009}.tile-ready{background:#e8f5e9}.tile-warn{background:#fff3e0}.tile-error{background:#ffebee}.grid-scroll{overflow-x:auto;max-height:46vh;overflow-y:auto;border:1px solid #eee;border-radius:4px}.review-table{width:100%}.cell-error{background:#ffebee;color:#b71c1c}.cell-warn{background:#fff3e0;color:#8a5300}.cell-input{width:100%;box-sizing:border-box;padding:2px 4px}.status-chip{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;color:#fff;margin-right:6px}.done-body{align-items:center;text-align:center;padding:32px 8px}.done-icon{color:#2e7d32;font-size:56px;height:56px;width:56px}@media (prefers-color-scheme: dark){.hint,.map-table th,.tile-label{color:#ffffffb3}.col-samples .sample{background:#3a3a3a;color:#ccc}.tile{background:#333}.grid-scroll,.map-table th,.map-table td{border-color:#444}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i7.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i7.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i7.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i3.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: "component", type: i7$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i13.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "component", type: i13.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
4813
4994
  }
4814
4995
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ImportDialogComponent, decorators: [{
4815
4996
  type: Component,
@@ -4862,7 +5043,7 @@ class DialogService {
4862
5043
  let config = button.detailsConfig;
4863
5044
  //approval message
4864
5045
  if (row?.pendingApproval && !['Approval', 'Approval Action'].includes(config?.formConfig.title)) { //if pending and not the actual view approval dialog //&& config?.formConfig.title != 'Approval'
4865
- let approvalAlerts = { messages: [{ type: 'info', message: 'Waiting for approval', hiddenCondition: x => !x.pendingApproval }] }; // Changed: content to message
5046
+ let approvalAlerts = { messages: [{ type: 'info', message: 'Waiting for approval', hidden: x => !x.pendingApproval }] }; // Changed: content to message
4866
5047
  if (!config.formConfig.alertConfig) {
4867
5048
  config.formConfig.alertConfig = approvalAlerts;
4868
5049
  }
@@ -5024,8 +5205,8 @@ class AccountingService {
5024
5205
  { name: 'type', type: 'select', required: true, loadAction: { url: 'accounts/list/accountTypes' } },
5025
5206
  { name: 'currencyID', type: 'select', alias: 'Currency', loadAction: { url: 'currencies/list/x' } }, // Changed: Added currency select for multi-currency support
5026
5207
  { name: 'balanceDisplay', alias: 'Balance', type: 'label', readonly: true, hideOnCreate: true },
5027
- { name: 'includeInCashTotal', type: 'checkbox', alias: 'Include in Cash Total', hiddenCondition: (data) => data.type !== 0 }, // Changed: Only visible for Asset accounts (type 0)
5028
- { name: 'includeInBankTotal', type: 'checkbox', alias: 'Include in Bank Total', hiddenCondition: (data) => data.type !== 0 }, // Changed: Only visible for Asset accounts (type 0)
5208
+ { name: 'includeInCashTotal', type: 'checkbox', alias: 'Include in Cash Total', hidden: (data) => data.type !== 0 }, // Changed: Only visible for Asset accounts (type 0)
5209
+ { name: 'includeInBankTotal', type: 'checkbox', alias: 'Include in Bank Total', hidden: (data) => data.type !== 0 }, // Changed: Only visible for Asset accounts (type 0)
5029
5210
  { name: 'cashFlow', type: 'select', alias: 'Cash Flow Section', infoMessage: 'IAS 7 cash flow statement classification', // Changed: C5 cash flow category
5030
5211
  options: [
5031
5212
  { name: 'Operating', value: 0 },
@@ -5127,7 +5308,7 @@ class AccountingService {
5127
5308
  fields: [
5128
5309
  { name: 'itemType', type: 'select', required: true, alias: 'Item Type', loadAction: { url: 'invoiceitems/list/item-types' }, defaultValue: 0 }, // Added: Item type selector
5129
5310
  { name: 'productID', type: 'select', alias: 'Product', loadAction: { url: 'products/list/invoice-items' },
5130
- hiddenCondition: x => x.itemType !== 1, // Added: Product selector - visible only when itemType = Product (1)
5311
+ hidden: x => x.itemType !== 1, // Added: Product selector - visible only when itemType = Product (1)
5131
5312
  onSelectChange: (selectedId, formData, option) => {
5132
5313
  if (option) {
5133
5314
  formData.description = option.description || option.name;
@@ -5136,7 +5317,7 @@ class AccountingService {
5136
5317
  }
5137
5318
  },
5138
5319
  { name: 'serviceItemID', type: 'select', alias: 'Service', loadAction: { url: 'serviceitems/list/active' },
5139
- hiddenCondition: x => x.itemType !== 2, // Added: Service selector - visible only when itemType = Service (2)
5320
+ hidden: x => x.itemType !== 2, // Added: Service selector - visible only when itemType = Service (2)
5140
5321
  onSelectChange: (selectedId, formData, option) => {
5141
5322
  if (option) {
5142
5323
  formData.description = option.description || option.name;
@@ -5147,8 +5328,8 @@ class AccountingService {
5147
5328
  { name: 'description', type: 'text', required: true, alias: 'Description', span: true }, // Changed: Description field (backend will populate for Product/Service)
5148
5329
  { name: 'quantity', type: 'number', required: true, defaultValue: 1 },
5149
5330
  { name: 'unitPrice', type: 'money', alias: 'Unit Price', required: true }, // Changed: Unit price field (backend will populate for Product/Service)
5150
- { name: 'taxRateID', type: 'select', alias: 'Tax Rate', nullable: true, loadAction: { url: 'products/list/tax-rates' }, hiddenCondition: x => x.itemType !== 0, infoMessage: 'VAT rate for this line — Product/Service lines use their own tax setting' }, // Changed: manual (General) lines can now carry VAT
5151
- { name: 'isTaxInclusive', type: 'checkbox', alias: 'Price includes Tax', hiddenCondition: x => x.itemType !== 0, infoMessage: 'If checked, the unit price already includes tax' }, // Changed: tax-inclusive flag for manual lines
5331
+ { name: 'taxRateID', type: 'select', alias: 'Tax Rate', nullable: true, loadAction: { url: 'products/list/tax-rates' }, hidden: x => x.itemType !== 0, infoMessage: 'VAT rate for this line — Product/Service lines use their own tax setting' }, // Changed: manual (General) lines can now carry VAT
5332
+ { name: 'isTaxInclusive', type: 'checkbox', alias: 'Price includes Tax', hidden: x => x.itemType !== 0, infoMessage: 'If checked, the unit price already includes tax' }, // Changed: tax-inclusive flag for manual lines
5152
5333
  { name: 'poNumber', type: 'text', alias: 'PO Number', }, // Added: PO Number for invoice bundling
5153
5334
  ],
5154
5335
  loadAction: { url: 'invoiceitems/id' }, // Fixed: removed placeholder - TinCore appends ID automatically
@@ -5192,7 +5373,7 @@ class AccountingService {
5192
5373
  minColumns: ['description', 'quantity', 'amount'],
5193
5374
  causeFormRefresh: true,
5194
5375
  columns: [
5195
- { name: 'invoiceItemID', type: 'number', alias: 'ID', hiddenCondition: () => true },
5376
+ { name: 'invoiceItemID', type: 'number', alias: 'ID', hidden: () => true },
5196
5377
  { name: 'description', type: 'chip', alias: 'Description', detailsConfig: this.invoiceItemDetailsConfig },
5197
5378
  { name: 'poNumber', type: 'text', alias: 'PO#' }, // Added: Purchase Order Number column
5198
5379
  { name: 'quantity', type: 'number', alias: 'Qty' },
@@ -5658,7 +5839,7 @@ class AccountingService {
5658
5839
  title: 'Import Transactions',
5659
5840
  fixedTitle: true,
5660
5841
  fields: [
5661
- { name: 'file', type: 'file', alias: 'Excel File (.xlsx)', required: true, span: true, hiddenCondition: (x) => !!x.hasItems }, // Changed: Use hasItems instead of batchID
5842
+ { name: 'file', type: 'file', alias: 'Excel File (.xlsx)', required: true, span: true, hidden: (x) => !!x.hasItems }, // Changed: Use hasItems instead of batchID
5662
5843
  { name: 'batchID', type: 'label', alias: 'Batch ID', size: '10', readonly: true, hideOnCreate: true },
5663
5844
  { name: 'totalCount', type: 'label', alias: 'Total Items', readonly: true, hideOnCreate: true },
5664
5845
  { name: 'resolvedCount', type: 'label', alias: 'Resolved', readonly: true, hideOnCreate: true },
@@ -5895,9 +6076,9 @@ class AccountingService {
5895
6076
  ]
5896
6077
  },
5897
6078
  { name: 'dayOfMonth', type: 'number', alias: 'Day of Month (1-28)', min: 1, max: 28, nullable: true,
5898
- hiddenCondition: (x) => x.frequency !== 3 },
6079
+ hidden: (x) => x.frequency !== 3 },
5899
6080
  { name: 'dayOfWeek', type: 'select', alias: 'Day of Week', nullable: true,
5900
- hiddenCondition: (x) => x.frequency !== 2,
6081
+ hidden: (x) => x.frequency !== 2,
5901
6082
  options: [
5902
6083
  { name: 'Sunday', value: 0 }, { name: 'Monday', value: 1 },
5903
6084
  { name: 'Tuesday', value: 2 }, { name: 'Wednesday', value: 3 },
@@ -6857,7 +7038,7 @@ class AssetsService {
6857
7038
  fixedTitle: true,
6858
7039
  fields: [
6859
7040
  { name: 'disposalType', alias: 'Disposal Type', type: 'select', required: true, loadAction: { url: 'fixedassets/list/disposal-types' }, defaultValue: 0 },
6860
- { name: 'disposalAmount', alias: 'Sale Amount', type: 'money', defaultValue: 0, hiddenCondition: x => x.disposalType !== 0 },
7041
+ { name: 'disposalAmount', alias: 'Sale Amount', type: 'money', defaultValue: 0, hidden: x => x.disposalType !== 0 },
6861
7042
  ]
6862
7043
  };
6863
7044
  this.assetDisposeButton = { name: 'dispose', display: 'Dispose', icon: { name: 'delete_forever', color: 'red' }, dialog: true, detailsConfig: { formConfig: this.disposeFormConfig, heroField: 'fixedAssetID', mode: 'edit', buttons: [{ name: 'dispose', display: 'Dispose Asset', inDialog: true, action: { url: 'fixedassets?action=dispose', method: 'post', successMessage: 'Asset disposed' } }] }, visible: x => x.status == AssetStatus.Active || x.status == AssetStatus.FullyDepreciated };
@@ -6980,11 +7161,11 @@ class LoansService {
6980
7161
  },
6981
7162
  { name: 'customerID', type: 'select', alias: 'Customer', required: true,
6982
7163
  loadAction: { url: 'customers/list/x' },
6983
- hiddenCondition: (x) => x.loanType == 1
7164
+ hidden: (x) => x.loanType == 1
6984
7165
  },
6985
7166
  { name: 'employeeID', type: 'select', alias: 'Employee', required: true,
6986
7167
  loadAction: { url: 'employees/list/x' },
6987
- hiddenCondition: (x) => x.loanType == 0
7168
+ hidden: (x) => x.loanType == 0
6988
7169
  },
6989
7170
  { name: 'loanProductID', type: 'select', alias: 'Loan Product', loadAction: { url: 'loanproducts/list/x' } },
6990
7171
  { name: 'principalAmount', type: 'money', alias: 'Principal Amount', required: true },
@@ -7334,9 +7515,9 @@ class InventoryService {
7334
7515
  { name: 'taxRateID', type: 'select', alias: 'Tax Rate', nullable: true, section: 'pricingInfo', loadAction: { url: 'products/list/tax-rates' }, infoMessage: 'VAT rate applied to this product' }, // Changed: Tax rate selector
7335
7516
  { name: 'isTaxInclusive', type: 'checkbox', alias: 'Price includes Tax', section: 'pricingInfo', infoMessage: 'If checked, the selling price already includes tax' }, // Changed: Tax-inclusive flag
7336
7517
  { name: 'inventoryInfo', type: 'section', alias: 'Inventory Settings', collapsedCondition: x => x.productID },
7337
- { name: 'isSerialized', type: 'checkbox', alias: 'Serialized Product', span: true, section: 'inventoryInfo', infoMessage: 'Track individual items by serial number', hiddenCondition: x => x.isBundle }, // Changed: Hide for bundles
7518
+ { name: 'isSerialized', type: 'checkbox', alias: 'Serialized Product', span: true, section: 'inventoryInfo', infoMessage: 'Track individual items by serial number', hidden: x => x.isBundle }, // Changed: Hide for bundles
7338
7519
  { name: 'baseUnit', type: 'select', required: true, alias: 'Base Unit', section: 'inventoryInfo', loadAction: { url: 'products/list/base-unit' }, defaultFirstValue: true, infoMessage: 'Unit of measure for inventory tracking' }, // Changed: Added defaultFirstValue flag
7339
- { name: 'minimumInventoryLevel', type: 'number', required: true, alias: 'Minimum Inventory Level', section: 'inventoryInfo', infoMessage: 'Alert threshold for low stock', hiddenCondition: x => x.isBundle }, // Changed: Hide for bundles
7520
+ { name: 'minimumInventoryLevel', type: 'number', required: true, alias: 'Minimum Inventory Level', section: 'inventoryInfo', infoMessage: 'Alert threshold for low stock', hidden: x => x.isBundle }, // Changed: Hide for bundles
7340
7521
  { name: 'categoryInfo', type: 'section', alias: 'Categorization', collapsed: true },
7341
7522
  { name: 'categoryID', type: 'select', alias: 'Category', nullable: true, section: 'categoryInfo', loadAction: { url: 'categories/list/x' }, detailsConfig: this.generalService.categoryDetailsConfig, infoMessage: 'The main product category' }, // Changed: Added nullable flag
7342
7523
  { name: 'subCategoryID', type: 'select', alias: 'Sub Category', section: 'categoryInfo', loadAction: { url: 'subcategories/list/x' }, detailsConfig: this.generalService.subCategoryDetailsConfig, infoMessage: 'More specific product classification' },
@@ -7727,9 +7908,9 @@ class InventoryService {
7727
7908
  { name: 'quantity', type: 'number', required: true, alias: 'Quantity' },
7728
7909
  { name: 'unitValue', type: 'money', required: true, alias: 'Unit Value' },
7729
7910
  { name: 'lineTotal', type: 'money', alias: 'Line Total', readonly: true },
7730
- { name: 'restockApproved', type: 'checkbox', alias: 'Restock', hiddenCondition: (x) => x._parentReturnType === 2 },
7731
- { name: 'isGoodCondition', type: 'checkbox', alias: 'Good Condition', hiddenCondition: (x) => x._parentReturnType !== 2 },
7732
- { name: 'conditionNotes', type: 'text', alias: 'Condition Notes', span: true, hiddenCondition: (x) => x._parentReturnType !== 2 }
7911
+ { name: 'restockApproved', type: 'checkbox', alias: 'Restock', hidden: (x) => x._parentReturnType === 2 },
7912
+ { name: 'isGoodCondition', type: 'checkbox', alias: 'Good Condition', hidden: (x) => x._parentReturnType !== 2 },
7913
+ { name: 'conditionNotes', type: 'text', alias: 'Condition Notes', span: true, hidden: (x) => x._parentReturnType !== 2 }
7733
7914
  ],
7734
7915
  loadAction: { url: 'inventoryreturnitems/id' },
7735
7916
  heroField: 'inventoryReturnItemID'
@@ -7770,11 +7951,11 @@ class InventoryService {
7770
7951
  { name: 'returnDate', type: 'date', alias: 'Return Date', readonly: true, section: 'returnInfo' },
7771
7952
  { name: 'status', type: 'select', alias: 'Status', readonly: true, section: 'returnInfo', loadAction: { url: 'inventoryreturns/list/status' } },
7772
7953
  { name: 'sourceInfo', type: 'section', alias: 'Source Details' },
7773
- { name: 'customerID', type: 'select', alias: 'Customer', section: 'sourceInfo', loadAction: { url: 'customers/list/x' }, hiddenCondition: (x) => x.returnType !== 0 },
7774
- { name: 'saleID', type: 'select', alias: 'Sale', section: 'sourceInfo', loadAction: { url: 'sales/list/x' }, hiddenCondition: (x) => x.returnType !== 0 },
7775
- { name: 'supplierID', type: 'select', alias: 'Supplier', section: 'sourceInfo', loadAction: { url: 'suppliers/list/x' }, hiddenCondition: (x) => x.returnType !== 1 },
7776
- { name: 'purchaseID', type: 'select', alias: 'Purchase', section: 'sourceInfo', loadAction: { url: 'purchases/list/x' }, hiddenCondition: (x) => x.returnType !== 1 }, // Changed: inventoryReceiptID → purchaseID, inventoryreceipts → purchases
7777
- { name: 'requisitionID', type: 'select', alias: 'Requisition', section: 'sourceInfo', loadAction: { url: 'requisitions/list/x' }, hiddenCondition: (x) => x.returnType !== 2 },
7954
+ { name: 'customerID', type: 'select', alias: 'Customer', section: 'sourceInfo', loadAction: { url: 'customers/list/x' }, hidden: (x) => x.returnType !== 0 },
7955
+ { name: 'saleID', type: 'select', alias: 'Sale', section: 'sourceInfo', loadAction: { url: 'sales/list/x' }, hidden: (x) => x.returnType !== 0 },
7956
+ { name: 'supplierID', type: 'select', alias: 'Supplier', section: 'sourceInfo', loadAction: { url: 'suppliers/list/x' }, hidden: (x) => x.returnType !== 1 },
7957
+ { name: 'purchaseID', type: 'select', alias: 'Purchase', section: 'sourceInfo', loadAction: { url: 'purchases/list/x' }, hidden: (x) => x.returnType !== 1 }, // Changed: inventoryReceiptID → purchaseID, inventoryreceipts → purchases
7958
+ { name: 'requisitionID', type: 'select', alias: 'Requisition', section: 'sourceInfo', loadAction: { url: 'requisitions/list/x' }, hidden: (x) => x.returnType !== 2 },
7778
7959
  { name: 'details', type: 'section', alias: 'Details' },
7779
7960
  { name: 'reason', type: 'text', required: true, alias: 'Reason', span: true, section: 'details' },
7780
7961
  { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'details' },
@@ -7990,7 +8171,7 @@ class ButtonService {
7990
8171
  return modeMap[mode] || '';
7991
8172
  }
7992
8173
  isTabVisible(tableConfig, details) {
7993
- return tableConfig.hideTabCondition ? !tableConfig.hideTabCondition(details) : true;
8174
+ return Core.isItemVisible(tableConfig, details); // Changed: unified visible/hidden replaces hideTabCondition
7994
8175
  }
7995
8176
  getIcon(button, config) {
7996
8177
  if (button.icon) {
@@ -8023,7 +8204,7 @@ class ButtonService {
8023
8204
  return buttons.filter(button => this.testVisible(button, row, tableConfig));
8024
8205
  }
8025
8206
  testVisible(button, row, tableConfig) {
8026
- if (button.visible && !button.visible(row)) {
8207
+ if (!Core.isItemVisible(button, row)) { // Changed: unified visible/hidden (boolean | condition), hidden wins
8027
8208
  return false;
8028
8209
  }
8029
8210
  const currentRole = this.authService.currentRoleSource.value;
@@ -8120,7 +8301,8 @@ class TableConfigService {
8120
8301
  return columnNames;
8121
8302
  }
8122
8303
  getVisibleColumns(config) {
8123
- return config.columns.filter(column => !column.hiddenCondition || !column.hiddenCondition(config.parentData));
8304
+ return config.columns.filter(column => Core.isItemVisible(column, config.parentData) // Changed: unified visible/hidden replaces hiddenCondition
8305
+ );
8124
8306
  }
8125
8307
  getActionsWidth(buttons, smallScreen, config) {
8126
8308
  // Changed: Width based on maxButtonsCount (More button now included in that count when overflow exists)
@@ -8662,6 +8844,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
8662
8844
 
8663
8845
  // Setup readiness ("Getting Started") service — NotificationsService mirror without SignalR.
8664
8846
  // Dormant unless appConfig.setupConfig.enabled; API failures leave count at 0 so the badge stays hidden.
8847
+ // v2 adds the module catalog (drives step + nav-menu gating) and predefined role templates.
8665
8848
  class SetupService {
8666
8849
  constructor(dataService) {
8667
8850
  this.dataService = dataService;
@@ -8669,6 +8852,9 @@ class SetupService {
8669
8852
  this.pendingCount$ = this.pendingCount.asObservable();
8670
8853
  this.status = new BehaviorSubject(null);
8671
8854
  this.status$ = this.status.asObservable();
8855
+ // Added (v2): module catalog — empty means the app has no module concept (nothing is ever hidden)
8856
+ this.modules = new BehaviorSubject([]);
8857
+ this.modules$ = this.modules.asObservable();
8672
8858
  }
8673
8859
  get enabled() {
8674
8860
  return !!this.dataService.appConfig?.setupConfig?.enabled;
@@ -8694,11 +8880,46 @@ class SetupService {
8694
8880
  if (apiResponse.success) {
8695
8881
  this.status.next(apiResponse.data);
8696
8882
  this.pendingCount.next(apiResponse.data?.pending ?? 0);
8883
+ this.modules.next(apiResponse.data?.modules ?? []); // Added (v2): status carries the module catalog too
8697
8884
  }
8698
8885
  },
8699
8886
  error: () => { }
8700
8887
  });
8701
8888
  }
8889
+ // Added (v2): module catalog alone — called on nav init so menu gating works without visiting the page
8890
+ loadModules() {
8891
+ if (!this.enabled)
8892
+ return;
8893
+ this.dataService.CallApi({ url: 'setup/modules', skipCache: true }).subscribe({
8894
+ next: (apiResponse) => {
8895
+ if (apiResponse.success)
8896
+ this.modules.next(apiResponse.data ?? []);
8897
+ },
8898
+ error: () => { } // Backend without module support — nothing gets hidden
8899
+ });
8900
+ }
8901
+ // Added (v2): nav-menu gate — unknown keys and apps without modules always pass (fail open)
8902
+ isModuleEnabled(key) {
8903
+ if (!key)
8904
+ return true;
8905
+ const mods = this.modules.value;
8906
+ if (!mods || mods.length === 0)
8907
+ return true;
8908
+ const mod = mods.find(m => m.key === key);
8909
+ return !mod || mod.enabled;
8910
+ }
8911
+ // Added (v2): toggle a module (or 'confirm' the selection) — server persists onto app configuration
8912
+ setModule(key, enabled) {
8913
+ return this.dataService.CallApi({ url: 'setup/module', method: 'post', skipCache: true }, { key, enabled });
8914
+ }
8915
+ // Added (v2): predefined role templates for the roles step
8916
+ loadRoleTemplates() {
8917
+ return this.dataService.CallApi({ url: 'setup/roletemplates', skipCache: true });
8918
+ }
8919
+ // Added (v2): create tenant roles from the selected templates
8920
+ createRoles(keys) {
8921
+ return this.dataService.CallApi({ url: 'setup/roles', method: 'post', skipCache: true }, { keys });
8922
+ }
8702
8923
  refresh() {
8703
8924
  this.loadStatus();
8704
8925
  }
@@ -8794,7 +9015,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
8794
9015
  }], ctorParameters: () => [{ type: i1$2.Router }, { type: StorageService }, { type: DataServiceLib }] });
8795
9016
 
8796
9017
  // Getting Started page — renders the per-app setup readiness catalog (top-down groups, no wizard).
8797
- // Step actions come from appConfig.setupConfig.stepActions (create dialog Button and/or route link).
9018
+ // Step actions come from appConfig.setupConfig.stepActions (create dialog Button, route link, Excel
9019
+ // import wizard, or the predefined role-template picker). v2 adds the module picker: optional modules
9020
+ // toggle on/off, hiding their step groups and (via CapItem.moduleKey) their menus app-wide.
8798
9021
  class SetupGuideComponent {
8799
9022
  constructor() {
8800
9023
  this.dataService = inject(DataServiceLib);
@@ -8803,6 +9026,11 @@ class SetupGuideComponent {
8803
9026
  this.status = null;
8804
9027
  this.groups = [];
8805
9028
  this.expandedKey = '';
9029
+ this.modules = [];
9030
+ this.togglingKey = ''; // module key with an in-flight toggle — disables its card until the server answers
9031
+ this.roleTemplates = [];
9032
+ this.selectedTemplates = {};
9033
+ this.creatingRoles = false;
8806
9034
  }
8807
9035
  get title() {
8808
9036
  return this.dataService.appConfig?.setupConfig?.title || 'Getting Started';
@@ -8810,7 +9038,10 @@ class SetupGuideComponent {
8810
9038
  ngOnInit() {
8811
9039
  this.setupService.status$.subscribe(status => {
8812
9040
  this.status = status;
9041
+ this.modules = (status?.modules || []).slice().sort((a, b) => a.order - b.order);
8813
9042
  this.buildGroups();
9043
+ if (this.hasRoleTemplatesStep())
9044
+ this.loadRoleTemplates();
8814
9045
  });
8815
9046
  this.setupService.loadStatus();
8816
9047
  }
@@ -8860,12 +9091,95 @@ class SetupGuideComponent {
8860
9091
  const act = this.getAction(step);
8861
9092
  this.dataService.Navigate(act.link || step.link);
8862
9093
  }
9094
+ //---------- Modules (v2) ----------
9095
+ // Optimistic toggle — flip locally, persist, revert on failure; success refreshes steps + menus
9096
+ toggleModule(mod) {
9097
+ if (mod.core || this.togglingKey)
9098
+ return;
9099
+ const target = !mod.enabled;
9100
+ mod.enabled = target;
9101
+ this.togglingKey = mod.key;
9102
+ this.setupService.setModule(mod.key, target).subscribe({
9103
+ next: resp => {
9104
+ this.togglingKey = '';
9105
+ if (resp.success)
9106
+ this.setupService.refresh();
9107
+ else
9108
+ mod.enabled = !target;
9109
+ },
9110
+ error: () => { this.togglingKey = ''; mod.enabled = !target; }
9111
+ });
9112
+ }
9113
+ // Confirms the current selection as reviewed — completes the choose-modules step without a toggle
9114
+ confirmModules() {
9115
+ this.togglingKey = 'confirm';
9116
+ this.setupService.setModule('confirm', true).subscribe({
9117
+ next: () => { this.togglingKey = ''; this.setupService.refresh(); },
9118
+ error: () => this.togglingKey = ''
9119
+ });
9120
+ }
9121
+ get modulesConfirmed() {
9122
+ const step = this.status?.steps?.find(s => s.key === 'choose-modules');
9123
+ return !step || step.complete;
9124
+ }
9125
+ //---------- Excel import (v2) ----------
9126
+ hasImport(step) {
9127
+ return !!this.getAction(step).import;
9128
+ }
9129
+ doImport(step) {
9130
+ const act = this.getAction(step);
9131
+ if (!act.import)
9132
+ return;
9133
+ this.dialogService.openImportDialog({ ...act.import }).subscribe(result => {
9134
+ if (result === 'success')
9135
+ this.setupService.refresh(); // Committed import — recount immediately
9136
+ });
9137
+ }
9138
+ //---------- Predefined roles (v2) ----------
9139
+ hasRoleTemplates(step) {
9140
+ return !!this.getAction(step).roleTemplates;
9141
+ }
9142
+ hasRoleTemplatesStep() {
9143
+ return (this.status?.steps || []).some(s => this.getAction(s).roleTemplates);
9144
+ }
9145
+ loadRoleTemplates() {
9146
+ this.setupService.loadRoleTemplates().subscribe({
9147
+ next: resp => {
9148
+ if (!resp.success)
9149
+ return;
9150
+ this.roleTemplates = resp.data || [];
9151
+ // Preselect everything not yet created — the common case is "give me all of these"
9152
+ this.roleTemplates.forEach(t => { if (this.selectedTemplates[t.key] === undefined)
9153
+ this.selectedTemplates[t.key] = !t.exists; });
9154
+ },
9155
+ error: () => { }
9156
+ });
9157
+ }
9158
+ get selectedTemplateCount() {
9159
+ return this.roleTemplates.filter(t => !t.exists && this.selectedTemplates[t.key]).length;
9160
+ }
9161
+ createSelectedRoles() {
9162
+ const keys = this.roleTemplates.filter(t => !t.exists && this.selectedTemplates[t.key]).map(t => t.key);
9163
+ if (keys.length === 0 || this.creatingRoles)
9164
+ return;
9165
+ this.creatingRoles = true;
9166
+ this.setupService.createRoles(keys).subscribe({
9167
+ next: resp => {
9168
+ this.creatingRoles = false;
9169
+ if (resp.success) {
9170
+ this.loadRoleTemplates(); // refresh the Created chips
9171
+ this.setupService.refresh();
9172
+ }
9173
+ },
9174
+ error: () => this.creatingRoles = false
9175
+ });
9176
+ }
8863
9177
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SetupGuideComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8864
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SetupGuideComponent, isStandalone: false, selector: "spa-setup-guide", ngImport: i0, template: "<div class=\"setup-page\" *ngIf=\"status\">\r\n\r\n <!-- Hero: overall readiness + celebration state at 100% -->\r\n <mat-card class=\"setup-hero\" [class.celebrate]=\"status.percent === 100\">\r\n <div class=\"hero-content\" *ngIf=\"status.percent < 100\">\r\n <div class=\"hero-text\">\r\n <h1>{{ title }}</h1>\r\n <p>Complete these steps to get your system ready for day-to-day operation.</p>\r\n <span class=\"hero-counter\">{{ status.completed }} of {{ status.total }} steps completed</span>\r\n </div>\r\n <div class=\"hero-progress\">\r\n <span class=\"hero-percent\">{{ status.percent }}%</span>\r\n <mat-progress-bar mode=\"determinate\" [value]=\"status.percent\"></mat-progress-bar>\r\n </div>\r\n </div>\r\n <div class=\"hero-content celebration\" *ngIf=\"status.percent === 100\">\r\n <mat-icon class=\"celebrate-icon\">celebration</mat-icon>\r\n <div class=\"hero-text\">\r\n <h1>You're all set!</h1>\r\n <p>All setup steps are complete \u2014 your system is ready to operate.</p>\r\n </div>\r\n </div>\r\n </mat-card>\r\n\r\n <!-- Category groups, top-down -->\r\n <mat-card class=\"setup-group\" *ngFor=\"let group of groups\">\r\n <div class=\"group-header\">\r\n <h2>{{ group.name }}</h2>\r\n <span class=\"group-counter\">{{ groupCompleted(group) }} of {{ group.steps.length }}</span>\r\n </div>\r\n <mat-accordion displayMode=\"flat\">\r\n <mat-expansion-panel *ngFor=\"let step of group.steps\" [expanded]=\"step.key === expandedKey\" [class.step-complete]=\"step.complete\">\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <mat-icon class=\"step-status-icon\" [class.done]=\"step.complete\" [class.required]=\"!step.complete && step.required\">{{ step.complete ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n <span class=\"step-title\" [class.done]=\"step.complete\">{{ step.title }}</span>\r\n </mat-panel-title>\r\n <mat-panel-description>\r\n <span class=\"step-chip\" *ngIf=\"step.target > 1\">{{ progressLabel(step) }}</span>\r\n <span class=\"step-optional\" *ngIf=\"!step.required && !step.complete\">Optional</span>\r\n </mat-panel-description>\r\n </mat-expansion-panel-header>\r\n <p class=\"step-description\">{{ step.description }}</p>\r\n <div class=\"step-actions\" *ngIf=\"hasAction(step)\">\r\n <button mat-flat-button color=\"primary\" (click)=\"doPrimary(step)\">{{ primaryLabel(step) }}</button>\r\n <button mat-button *ngIf=\"hasDialog(step)\" (click)=\"navigate(step)\">View page</button>\r\n </div>\r\n </mat-expansion-panel>\r\n </mat-accordion>\r\n </mat-card>\r\n\r\n</div>\r\n\r\n<!-- Graceful empty state (feature disabled or status unavailable) -->\r\n<div class=\"setup-empty\" *ngIf=\"!status\">\r\n <mat-icon>rocket_launch</mat-icon>\r\n <p>Setup status is not available yet.</p>\r\n</div>\r\n", styles: [".setup-page{max-width:860px;margin:0 auto;padding:16px;display:flex;flex-direction:column;gap:16px}.setup-hero{padding:24px}.hero-content{display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap}.hero-text h1{margin:0 0 4px;font-size:24px}.hero-text p{margin:0 0 8px;color:#0009}.hero-counter{font-size:13px;color:#0009}.hero-progress{flex:1;min-width:220px;max-width:340px}.hero-percent{display:block;font-size:28px;font-weight:600;color:#2e7d32;margin-bottom:6px;text-align:right}.hero-progress mat-progress-bar{height:10px;border-radius:5px}.setup-hero.celebrate{background:linear-gradient(135deg,#e8f5e9,#f1f8e9)}.celebration{justify-content:flex-start}.celebrate-icon{font-size:48px;width:48px;height:48px;color:#2e7d32;animation:celebrate-pop .6s ease-out}@keyframes celebrate-pop{0%{transform:scale(.3);opacity:0}70%{transform:scale(1.15)}to{transform:scale(1);opacity:1}}.setup-group{padding:16px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.group-header h2{margin:0;font-size:17px}.group-counter{font-size:12px;color:#0000008c}.step-status-icon{margin-right:10px;color:#9e9e9e}.step-status-icon.done{color:#4caf50}.step-status-icon.required{color:#ff9800}.step-title.done{color:#00000073;text-decoration:line-through}.step-chip{background:#e3f2fd;color:#1565c0;border-radius:12px;padding:2px 10px;font-size:12px;margin-right:8px}.step-optional{font-size:12px;color:#00000073;font-style:italic}.step-description{margin:4px 0 12px;color:#000000a6}.step-actions{display:flex;gap:8px;margin-bottom:4px}mat-panel-title{align-items:center}mat-panel-description{justify-content:flex-end;align-items:center}.setup-empty{text-align:center;padding:48px 16px;color:#00000080}.setup-empty mat-icon{font-size:40px;width:40px;height:40px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "directive", type: i6$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i6$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i6$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i6$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i6$1.MatExpansionPanelDescription, selector: "mat-panel-description" }] }); }
9178
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SetupGuideComponent, isStandalone: false, selector: "spa-setup-guide", ngImport: i0, template: "<div class=\"setup-page\" *ngIf=\"status\">\r\n\r\n <!-- Hero: overall readiness + celebration state at 100% -->\r\n <mat-card class=\"setup-hero\" [class.celebrate]=\"status.percent === 100\">\r\n <div class=\"hero-content\" *ngIf=\"status.percent < 100\">\r\n <div class=\"hero-text\">\r\n <h1>{{ title }}</h1>\r\n <p>Complete these steps to get your system ready for day-to-day operation.</p>\r\n <span class=\"hero-counter\">{{ status.completed }} of {{ status.total }} steps completed</span>\r\n </div>\r\n <div class=\"hero-progress\">\r\n <span class=\"hero-percent\">{{ status.percent }}%</span>\r\n <mat-progress-bar mode=\"determinate\" [value]=\"status.percent\"></mat-progress-bar>\r\n </div>\r\n </div>\r\n <div class=\"hero-content celebration\" *ngIf=\"status.percent === 100\">\r\n <mat-icon class=\"celebrate-icon\">celebration</mat-icon>\r\n <div class=\"hero-text\">\r\n <h1>You're all set!</h1>\r\n <p>All setup steps are complete \u2014 your system is ready to operate.</p>\r\n </div>\r\n </div>\r\n </mat-card>\r\n\r\n <!-- Module picker (v2): choose what the business uses; optional modules toggle steps + menus -->\r\n <mat-card class=\"setup-modules\" *ngIf=\"modules.length > 0\">\r\n <div class=\"group-header\">\r\n <h2>Your modules</h2>\r\n <span class=\"group-counter\">Tap a module to turn it on or off \u2014 you can change this anytime.</span>\r\n </div>\r\n <div class=\"module-grid\">\r\n <div class=\"module-card\" *ngFor=\"let mod of modules\"\r\n [class.enabled]=\"mod.enabled\" [class.core]=\"mod.core\" [class.busy]=\"togglingKey === mod.key\"\r\n (click)=\"toggleModule(mod)\">\r\n <div class=\"module-head\">\r\n <mat-icon class=\"module-icon\">{{ mod.icon || 'extension' }}</mat-icon>\r\n <mat-icon class=\"module-state\" [class.on]=\"mod.enabled\">{{ mod.enabled ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n </div>\r\n <div class=\"module-title\">{{ mod.title }}</div>\r\n <div class=\"module-description\">{{ mod.description }}</div>\r\n <span class=\"module-chip\" *ngIf=\"mod.core\">Always on</span>\r\n </div>\r\n </div>\r\n <div class=\"module-actions\" *ngIf=\"!modulesConfirmed\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"togglingKey !== ''\" (click)=\"confirmModules()\">Confirm selection</button>\r\n <span class=\"module-hint\">Happy with this selection? Confirm it to complete the step below.</span>\r\n </div>\r\n </mat-card>\r\n\r\n <!-- Category groups, top-down -->\r\n <mat-card class=\"setup-group\" *ngFor=\"let group of groups\">\r\n <div class=\"group-header\">\r\n <h2>{{ group.name }}</h2>\r\n <span class=\"group-counter\">{{ groupCompleted(group) }} of {{ group.steps.length }}</span>\r\n </div>\r\n <mat-accordion displayMode=\"flat\">\r\n <mat-expansion-panel *ngFor=\"let step of group.steps\" [expanded]=\"step.key === expandedKey\" [class.step-complete]=\"step.complete\">\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <mat-icon class=\"step-status-icon\" [class.done]=\"step.complete\" [class.required]=\"!step.complete && step.required\">{{ step.complete ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n <span class=\"step-title\" [class.done]=\"step.complete\">{{ step.title }}</span>\r\n </mat-panel-title>\r\n <mat-panel-description>\r\n <span class=\"step-chip\" *ngIf=\"step.target > 1\">{{ progressLabel(step) }}</span>\r\n <span class=\"step-optional\" *ngIf=\"!step.required && !step.complete\">Optional</span>\r\n </mat-panel-description>\r\n </mat-expansion-panel-header>\r\n <p class=\"step-description\">{{ step.description }}</p>\r\n\r\n <!-- Predefined roles picker (v2) \u2014 replaces the generic actions on the roles step -->\r\n <div class=\"role-templates\" *ngIf=\"hasRoleTemplates(step) && roleTemplates.length > 0\">\r\n <div class=\"role-template\" *ngFor=\"let tpl of roleTemplates\" [class.created]=\"tpl.exists\">\r\n <mat-checkbox [(ngModel)]=\"selectedTemplates[tpl.key]\" [disabled]=\"tpl.exists\">\r\n <span class=\"role-name\">{{ tpl.name }}</span>\r\n </mat-checkbox>\r\n <span class=\"role-created\" *ngIf=\"tpl.exists\"><mat-icon>check</mat-icon>Created</span>\r\n <div class=\"role-description\">{{ tpl.description }}</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"step-actions\" *ngIf=\"hasAction(step) || hasImport(step) || hasRoleTemplates(step)\">\r\n <button mat-flat-button color=\"primary\" *ngIf=\"hasRoleTemplates(step) && roleTemplates.length > 0\" [disabled]=\"selectedTemplateCount === 0 || creatingRoles\" (click)=\"createSelectedRoles()\">\r\n Create selected roles\r\n </button>\r\n <button mat-flat-button color=\"primary\" *ngIf=\"!hasRoleTemplates(step) && hasAction(step)\" (click)=\"doPrimary(step)\">{{ primaryLabel(step) }}</button>\r\n <button mat-stroked-button color=\"primary\" *ngIf=\"hasImport(step)\" (click)=\"doImport(step)\"><mat-icon>upload_file</mat-icon>Import from Excel</button>\r\n <button mat-button *ngIf=\"(hasDialog(step) || hasRoleTemplates(step) || hasImport(step)) && (getAction(step).link || step.link)\" (click)=\"navigate(step)\">View page</button>\r\n </div>\r\n </mat-expansion-panel>\r\n </mat-accordion>\r\n </mat-card>\r\n\r\n</div>\r\n\r\n<!-- Graceful empty state (feature disabled or status unavailable) -->\r\n<div class=\"setup-empty\" *ngIf=\"!status\">\r\n <mat-icon>rocket_launch</mat-icon>\r\n <p>Setup status is not available yet.</p>\r\n</div>\r\n", styles: [".setup-page{max-width:860px;margin:0 auto;padding:16px;display:flex;flex-direction:column;gap:16px}.setup-hero{padding:24px}.hero-content{display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap}.hero-text h1{margin:0 0 4px;font-size:24px}.hero-text p{margin:0 0 8px;color:#0009}.hero-counter{font-size:13px;color:#0009}.hero-progress{flex:1;min-width:220px;max-width:340px}.hero-percent{display:block;font-size:28px;font-weight:600;color:#2e7d32;margin-bottom:6px;text-align:right}.hero-progress mat-progress-bar{height:10px;border-radius:5px}.setup-hero.celebrate{background:linear-gradient(135deg,#e8f5e9,#f1f8e9)}.celebration{justify-content:flex-start}.celebrate-icon{font-size:48px;width:48px;height:48px;color:#2e7d32;animation:celebrate-pop .6s ease-out}@keyframes celebrate-pop{0%{transform:scale(.3);opacity:0}70%{transform:scale(1.15)}to{transform:scale(1);opacity:1}}.setup-group{padding:16px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.group-header h2{margin:0;font-size:17px}.group-counter{font-size:12px;color:#0000008c}.step-status-icon{margin-right:10px;color:#9e9e9e}.step-status-icon.done{color:#4caf50}.step-status-icon.required{color:#ff9800}.step-title.done{color:#00000073;text-decoration:line-through}.step-chip{background:#e3f2fd;color:#1565c0;border-radius:12px;padding:2px 10px;font-size:12px;margin-right:8px}.step-optional{font-size:12px;color:#00000073;font-style:italic}.step-description{margin:4px 0 12px;color:#000000a6}.step-actions{display:flex;gap:8px;margin-bottom:4px}mat-panel-title{align-items:center}mat-panel-description{justify-content:flex-end;align-items:center}.setup-modules{padding:16px}.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;margin-top:8px}.module-card{border:1px solid rgba(0,0,0,.12);border-radius:10px;padding:12px;cursor:pointer;transition:border-color .15s,background .15s,opacity .15s;opacity:.72;position:relative}.module-card:hover{border-color:#90a4ae}.module-card.enabled{border-color:#4caf50;background:#f6fbf6;opacity:1}.module-card.core{cursor:default}.module-card.busy{pointer-events:none;opacity:.5}.module-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.module-icon{color:#546e7a}.module-card.enabled .module-icon{color:#2e7d32}.module-state{color:#b0bec5}.module-state.on{color:#4caf50}.module-title{font-weight:600;font-size:14px;margin-bottom:4px}.module-description{font-size:12px;color:#0009;min-height:30px}.module-chip{display:inline-block;margin-top:8px;background:#eceff1;color:#546e7a;border-radius:12px;padding:2px 10px;font-size:11px}.module-actions{display:flex;align-items:center;gap:12px;margin-top:14px}.module-hint{font-size:12px;color:#0000008c}.role-templates{display:flex;flex-direction:column;gap:10px;margin:4px 0 14px}.role-template{border:1px solid rgba(0,0,0,.1);border-radius:8px;padding:10px 12px}.role-template.created{background:#f6fbf6;border-color:#c8e6c9}.role-name{font-weight:600}.role-created{float:right;display:inline-flex;align-items:center;gap:4px;font-size:12px;color:#2e7d32}.role-created mat-icon{font-size:16px;width:16px;height:16px}.role-description{font-size:12px;color:#0009;margin:2px 0 0 32px}.step-actions button mat-icon{margin-right:6px}.setup-empty{text-align:center;padding:48px 16px;color:#00000080}.setup-empty mat-icon{font-size:40px;width:40px;height:40px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "directive", type: i8.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i8.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i8.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i8.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i8.MatExpansionPanelDescription, selector: "mat-panel-description" }] }); }
8865
9179
  }
8866
9180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SetupGuideComponent, decorators: [{
8867
9181
  type: Component,
8868
- args: [{ selector: 'spa-setup-guide', standalone: false, template: "<div class=\"setup-page\" *ngIf=\"status\">\r\n\r\n <!-- Hero: overall readiness + celebration state at 100% -->\r\n <mat-card class=\"setup-hero\" [class.celebrate]=\"status.percent === 100\">\r\n <div class=\"hero-content\" *ngIf=\"status.percent < 100\">\r\n <div class=\"hero-text\">\r\n <h1>{{ title }}</h1>\r\n <p>Complete these steps to get your system ready for day-to-day operation.</p>\r\n <span class=\"hero-counter\">{{ status.completed }} of {{ status.total }} steps completed</span>\r\n </div>\r\n <div class=\"hero-progress\">\r\n <span class=\"hero-percent\">{{ status.percent }}%</span>\r\n <mat-progress-bar mode=\"determinate\" [value]=\"status.percent\"></mat-progress-bar>\r\n </div>\r\n </div>\r\n <div class=\"hero-content celebration\" *ngIf=\"status.percent === 100\">\r\n <mat-icon class=\"celebrate-icon\">celebration</mat-icon>\r\n <div class=\"hero-text\">\r\n <h1>You're all set!</h1>\r\n <p>All setup steps are complete \u2014 your system is ready to operate.</p>\r\n </div>\r\n </div>\r\n </mat-card>\r\n\r\n <!-- Category groups, top-down -->\r\n <mat-card class=\"setup-group\" *ngFor=\"let group of groups\">\r\n <div class=\"group-header\">\r\n <h2>{{ group.name }}</h2>\r\n <span class=\"group-counter\">{{ groupCompleted(group) }} of {{ group.steps.length }}</span>\r\n </div>\r\n <mat-accordion displayMode=\"flat\">\r\n <mat-expansion-panel *ngFor=\"let step of group.steps\" [expanded]=\"step.key === expandedKey\" [class.step-complete]=\"step.complete\">\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <mat-icon class=\"step-status-icon\" [class.done]=\"step.complete\" [class.required]=\"!step.complete && step.required\">{{ step.complete ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n <span class=\"step-title\" [class.done]=\"step.complete\">{{ step.title }}</span>\r\n </mat-panel-title>\r\n <mat-panel-description>\r\n <span class=\"step-chip\" *ngIf=\"step.target > 1\">{{ progressLabel(step) }}</span>\r\n <span class=\"step-optional\" *ngIf=\"!step.required && !step.complete\">Optional</span>\r\n </mat-panel-description>\r\n </mat-expansion-panel-header>\r\n <p class=\"step-description\">{{ step.description }}</p>\r\n <div class=\"step-actions\" *ngIf=\"hasAction(step)\">\r\n <button mat-flat-button color=\"primary\" (click)=\"doPrimary(step)\">{{ primaryLabel(step) }}</button>\r\n <button mat-button *ngIf=\"hasDialog(step)\" (click)=\"navigate(step)\">View page</button>\r\n </div>\r\n </mat-expansion-panel>\r\n </mat-accordion>\r\n </mat-card>\r\n\r\n</div>\r\n\r\n<!-- Graceful empty state (feature disabled or status unavailable) -->\r\n<div class=\"setup-empty\" *ngIf=\"!status\">\r\n <mat-icon>rocket_launch</mat-icon>\r\n <p>Setup status is not available yet.</p>\r\n</div>\r\n", styles: [".setup-page{max-width:860px;margin:0 auto;padding:16px;display:flex;flex-direction:column;gap:16px}.setup-hero{padding:24px}.hero-content{display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap}.hero-text h1{margin:0 0 4px;font-size:24px}.hero-text p{margin:0 0 8px;color:#0009}.hero-counter{font-size:13px;color:#0009}.hero-progress{flex:1;min-width:220px;max-width:340px}.hero-percent{display:block;font-size:28px;font-weight:600;color:#2e7d32;margin-bottom:6px;text-align:right}.hero-progress mat-progress-bar{height:10px;border-radius:5px}.setup-hero.celebrate{background:linear-gradient(135deg,#e8f5e9,#f1f8e9)}.celebration{justify-content:flex-start}.celebrate-icon{font-size:48px;width:48px;height:48px;color:#2e7d32;animation:celebrate-pop .6s ease-out}@keyframes celebrate-pop{0%{transform:scale(.3);opacity:0}70%{transform:scale(1.15)}to{transform:scale(1);opacity:1}}.setup-group{padding:16px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.group-header h2{margin:0;font-size:17px}.group-counter{font-size:12px;color:#0000008c}.step-status-icon{margin-right:10px;color:#9e9e9e}.step-status-icon.done{color:#4caf50}.step-status-icon.required{color:#ff9800}.step-title.done{color:#00000073;text-decoration:line-through}.step-chip{background:#e3f2fd;color:#1565c0;border-radius:12px;padding:2px 10px;font-size:12px;margin-right:8px}.step-optional{font-size:12px;color:#00000073;font-style:italic}.step-description{margin:4px 0 12px;color:#000000a6}.step-actions{display:flex;gap:8px;margin-bottom:4px}mat-panel-title{align-items:center}mat-panel-description{justify-content:flex-end;align-items:center}.setup-empty{text-align:center;padding:48px 16px;color:#00000080}.setup-empty mat-icon{font-size:40px;width:40px;height:40px}\n"] }]
9182
+ args: [{ selector: 'spa-setup-guide', standalone: false, template: "<div class=\"setup-page\" *ngIf=\"status\">\r\n\r\n <!-- Hero: overall readiness + celebration state at 100% -->\r\n <mat-card class=\"setup-hero\" [class.celebrate]=\"status.percent === 100\">\r\n <div class=\"hero-content\" *ngIf=\"status.percent < 100\">\r\n <div class=\"hero-text\">\r\n <h1>{{ title }}</h1>\r\n <p>Complete these steps to get your system ready for day-to-day operation.</p>\r\n <span class=\"hero-counter\">{{ status.completed }} of {{ status.total }} steps completed</span>\r\n </div>\r\n <div class=\"hero-progress\">\r\n <span class=\"hero-percent\">{{ status.percent }}%</span>\r\n <mat-progress-bar mode=\"determinate\" [value]=\"status.percent\"></mat-progress-bar>\r\n </div>\r\n </div>\r\n <div class=\"hero-content celebration\" *ngIf=\"status.percent === 100\">\r\n <mat-icon class=\"celebrate-icon\">celebration</mat-icon>\r\n <div class=\"hero-text\">\r\n <h1>You're all set!</h1>\r\n <p>All setup steps are complete \u2014 your system is ready to operate.</p>\r\n </div>\r\n </div>\r\n </mat-card>\r\n\r\n <!-- Module picker (v2): choose what the business uses; optional modules toggle steps + menus -->\r\n <mat-card class=\"setup-modules\" *ngIf=\"modules.length > 0\">\r\n <div class=\"group-header\">\r\n <h2>Your modules</h2>\r\n <span class=\"group-counter\">Tap a module to turn it on or off \u2014 you can change this anytime.</span>\r\n </div>\r\n <div class=\"module-grid\">\r\n <div class=\"module-card\" *ngFor=\"let mod of modules\"\r\n [class.enabled]=\"mod.enabled\" [class.core]=\"mod.core\" [class.busy]=\"togglingKey === mod.key\"\r\n (click)=\"toggleModule(mod)\">\r\n <div class=\"module-head\">\r\n <mat-icon class=\"module-icon\">{{ mod.icon || 'extension' }}</mat-icon>\r\n <mat-icon class=\"module-state\" [class.on]=\"mod.enabled\">{{ mod.enabled ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n </div>\r\n <div class=\"module-title\">{{ mod.title }}</div>\r\n <div class=\"module-description\">{{ mod.description }}</div>\r\n <span class=\"module-chip\" *ngIf=\"mod.core\">Always on</span>\r\n </div>\r\n </div>\r\n <div class=\"module-actions\" *ngIf=\"!modulesConfirmed\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"togglingKey !== ''\" (click)=\"confirmModules()\">Confirm selection</button>\r\n <span class=\"module-hint\">Happy with this selection? Confirm it to complete the step below.</span>\r\n </div>\r\n </mat-card>\r\n\r\n <!-- Category groups, top-down -->\r\n <mat-card class=\"setup-group\" *ngFor=\"let group of groups\">\r\n <div class=\"group-header\">\r\n <h2>{{ group.name }}</h2>\r\n <span class=\"group-counter\">{{ groupCompleted(group) }} of {{ group.steps.length }}</span>\r\n </div>\r\n <mat-accordion displayMode=\"flat\">\r\n <mat-expansion-panel *ngFor=\"let step of group.steps\" [expanded]=\"step.key === expandedKey\" [class.step-complete]=\"step.complete\">\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <mat-icon class=\"step-status-icon\" [class.done]=\"step.complete\" [class.required]=\"!step.complete && step.required\">{{ step.complete ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n <span class=\"step-title\" [class.done]=\"step.complete\">{{ step.title }}</span>\r\n </mat-panel-title>\r\n <mat-panel-description>\r\n <span class=\"step-chip\" *ngIf=\"step.target > 1\">{{ progressLabel(step) }}</span>\r\n <span class=\"step-optional\" *ngIf=\"!step.required && !step.complete\">Optional</span>\r\n </mat-panel-description>\r\n </mat-expansion-panel-header>\r\n <p class=\"step-description\">{{ step.description }}</p>\r\n\r\n <!-- Predefined roles picker (v2) \u2014 replaces the generic actions on the roles step -->\r\n <div class=\"role-templates\" *ngIf=\"hasRoleTemplates(step) && roleTemplates.length > 0\">\r\n <div class=\"role-template\" *ngFor=\"let tpl of roleTemplates\" [class.created]=\"tpl.exists\">\r\n <mat-checkbox [(ngModel)]=\"selectedTemplates[tpl.key]\" [disabled]=\"tpl.exists\">\r\n <span class=\"role-name\">{{ tpl.name }}</span>\r\n </mat-checkbox>\r\n <span class=\"role-created\" *ngIf=\"tpl.exists\"><mat-icon>check</mat-icon>Created</span>\r\n <div class=\"role-description\">{{ tpl.description }}</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"step-actions\" *ngIf=\"hasAction(step) || hasImport(step) || hasRoleTemplates(step)\">\r\n <button mat-flat-button color=\"primary\" *ngIf=\"hasRoleTemplates(step) && roleTemplates.length > 0\" [disabled]=\"selectedTemplateCount === 0 || creatingRoles\" (click)=\"createSelectedRoles()\">\r\n Create selected roles\r\n </button>\r\n <button mat-flat-button color=\"primary\" *ngIf=\"!hasRoleTemplates(step) && hasAction(step)\" (click)=\"doPrimary(step)\">{{ primaryLabel(step) }}</button>\r\n <button mat-stroked-button color=\"primary\" *ngIf=\"hasImport(step)\" (click)=\"doImport(step)\"><mat-icon>upload_file</mat-icon>Import from Excel</button>\r\n <button mat-button *ngIf=\"(hasDialog(step) || hasRoleTemplates(step) || hasImport(step)) && (getAction(step).link || step.link)\" (click)=\"navigate(step)\">View page</button>\r\n </div>\r\n </mat-expansion-panel>\r\n </mat-accordion>\r\n </mat-card>\r\n\r\n</div>\r\n\r\n<!-- Graceful empty state (feature disabled or status unavailable) -->\r\n<div class=\"setup-empty\" *ngIf=\"!status\">\r\n <mat-icon>rocket_launch</mat-icon>\r\n <p>Setup status is not available yet.</p>\r\n</div>\r\n", styles: [".setup-page{max-width:860px;margin:0 auto;padding:16px;display:flex;flex-direction:column;gap:16px}.setup-hero{padding:24px}.hero-content{display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap}.hero-text h1{margin:0 0 4px;font-size:24px}.hero-text p{margin:0 0 8px;color:#0009}.hero-counter{font-size:13px;color:#0009}.hero-progress{flex:1;min-width:220px;max-width:340px}.hero-percent{display:block;font-size:28px;font-weight:600;color:#2e7d32;margin-bottom:6px;text-align:right}.hero-progress mat-progress-bar{height:10px;border-radius:5px}.setup-hero.celebrate{background:linear-gradient(135deg,#e8f5e9,#f1f8e9)}.celebration{justify-content:flex-start}.celebrate-icon{font-size:48px;width:48px;height:48px;color:#2e7d32;animation:celebrate-pop .6s ease-out}@keyframes celebrate-pop{0%{transform:scale(.3);opacity:0}70%{transform:scale(1.15)}to{transform:scale(1);opacity:1}}.setup-group{padding:16px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.group-header h2{margin:0;font-size:17px}.group-counter{font-size:12px;color:#0000008c}.step-status-icon{margin-right:10px;color:#9e9e9e}.step-status-icon.done{color:#4caf50}.step-status-icon.required{color:#ff9800}.step-title.done{color:#00000073;text-decoration:line-through}.step-chip{background:#e3f2fd;color:#1565c0;border-radius:12px;padding:2px 10px;font-size:12px;margin-right:8px}.step-optional{font-size:12px;color:#00000073;font-style:italic}.step-description{margin:4px 0 12px;color:#000000a6}.step-actions{display:flex;gap:8px;margin-bottom:4px}mat-panel-title{align-items:center}mat-panel-description{justify-content:flex-end;align-items:center}.setup-modules{padding:16px}.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;margin-top:8px}.module-card{border:1px solid rgba(0,0,0,.12);border-radius:10px;padding:12px;cursor:pointer;transition:border-color .15s,background .15s,opacity .15s;opacity:.72;position:relative}.module-card:hover{border-color:#90a4ae}.module-card.enabled{border-color:#4caf50;background:#f6fbf6;opacity:1}.module-card.core{cursor:default}.module-card.busy{pointer-events:none;opacity:.5}.module-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.module-icon{color:#546e7a}.module-card.enabled .module-icon{color:#2e7d32}.module-state{color:#b0bec5}.module-state.on{color:#4caf50}.module-title{font-weight:600;font-size:14px;margin-bottom:4px}.module-description{font-size:12px;color:#0009;min-height:30px}.module-chip{display:inline-block;margin-top:8px;background:#eceff1;color:#546e7a;border-radius:12px;padding:2px 10px;font-size:11px}.module-actions{display:flex;align-items:center;gap:12px;margin-top:14px}.module-hint{font-size:12px;color:#0000008c}.role-templates{display:flex;flex-direction:column;gap:10px;margin:4px 0 14px}.role-template{border:1px solid rgba(0,0,0,.1);border-radius:8px;padding:10px 12px}.role-template.created{background:#f6fbf6;border-color:#c8e6c9}.role-name{font-weight:600}.role-created{float:right;display:inline-flex;align-items:center;gap:4px;font-size:12px;color:#2e7d32}.role-created mat-icon{font-size:16px;width:16px;height:16px}.role-description{font-size:12px;color:#0009;margin:2px 0 0 32px}.step-actions button mat-icon{margin-right:6px}.setup-empty{text-align:center;padding:48px 16px;color:#00000080}.setup-empty mat-icon{font-size:40px;width:40px;height:40px}\n"] }]
8869
9183
  }] });
8870
9184
 
8871
9185
  // TinSync toolbar indicator — a persistent connection light that lives in the nav bar next to Notifications.
@@ -8942,18 +9256,11 @@ class AlertComponent {
8942
9256
  return this.config?.messages?.some(x => this.testVisible(x)) || false;
8943
9257
  }
8944
9258
  testVisible(msg) {
8945
- if (msg.showCondition) {
8946
- // Guard: Don't evaluate condition if data is not loaded yet
8947
- if (!this.data) {
8948
- return false;
8949
- }
8950
- return msg.showCondition(this.data);
8951
- }
8952
- // Guard: Don't evaluate hiddenCondition if data is not loaded yet
8953
- if (msg.hiddenCondition && !this.data) {
9259
+ // Guard: don't evaluate condition functions until data is loaded
9260
+ if (!this.data && (typeof msg.visible === 'function' || typeof msg.hidden === 'function')) {
8954
9261
  return false;
8955
9262
  }
8956
- return msg.hiddenCondition ? !msg.hiddenCondition(this.data) : true;
9263
+ return Core.isItemVisible(msg, this.data); // Changed: unified visible/hidden replaces showCondition/hiddenCondition
8957
9264
  }
8958
9265
  getMessageContent(msg) {
8959
9266
  // If messageField is specified and data exists, use dynamic message
@@ -10352,11 +10659,11 @@ class OptionComponent {
10352
10659
  }
10353
10660
  }
10354
10661
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: OptionComponent, deps: [{ token: MessageService }], target: i0.ɵɵFactoryTarget.Component }); }
10355
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: OptionComponent, isStandalone: false, selector: "spa-option", inputs: { options: "options", optionValue: "optionValue", optionDisplay: "optionDisplay", readonly: "readonly", type: "type", value: "value", display: "display", show: "show", required: "required", infoMessage: "infoMessage", copyContent: "copyContent", suffix: "suffix", editorConfig: "editorConfig", loadAction: "loadAction" }, outputs: { valueChange: "valueChange", enterPress: "enterPress" }, ngImport: i0, template: "\r\n<div class=\"tin-row gap-0\" style=\"align-items: center;\">\r\n\r\n <mat-checkbox color=\"primary\" style=\"margin-right: 2px;\" [(ngModel)]=\"show\" (change)=\"resetValue()\" labelPosition=\"after\" [disabled]=\"required\" >{{display}}</mat-checkbox>\r\n\r\n <ng-container *ngIf=\"show\" [ngSwitch]=\"type\">\r\n\r\n <spa-date class=\"opt\" *ngSwitchCase=\"'date'\" [display]=\"display\" width=\"120px\" [(value)]=\"value\" [display]=\"display\" (valueChange)=\"dateChanged($event)\" [infoMessage]=\"infoMessage\"\r\n ></spa-date>\r\n\r\n <spa-select-multi class=\"opt\" *ngSwitchCase=\"'select-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-multi>\r\n\r\n <spa-text-multi class=\"opt\" *ngSwitchCase=\"'text-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-multi>\r\n\r\n <spa-select-lite class=\"opt\" *ngSwitchCase=\"'select'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-lite>\r\n\r\n <spa-editor class=\"opt\" *ngSwitchCase=\"'editor'\" [display]=\"display\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [required]=\"required\" [infoMessage]=\"infoMessage\" [editorConfig]=\"editorConfig\"\r\n ></spa-editor>\r\n\r\n <spa-text-single class=\"opt\" *ngSwitchDefault [display]=\"display\" (enterPress)=\"enterPressed()\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [suffix]=\"suffix\" [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-single>\r\n\r\n </ng-container>\r\n\r\n <button mat-icon-button *ngIf=\"infoMessage && !show\" (click)=\"onInfoClick($event)\" matTooltip=\"Info\" matTooltipPosition=\"above\" style=\"opacity: 1;\">\r\n <mat-icon class=\"tinyIcon\" style=\"color: steelblue;\">info</mat-icon>\r\n </button>\r\n\r\n</div>\r\n", styles: [".opt{margin-left:0;align-self:center}.opt ::ng-deep mat-form-field{margin-bottom:-1.25em}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: TextSingleComponent, selector: "spa-text-single", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction", "required", "min", "max", "regex", "suffix", "infoMessage", "field", "data", "detailsConfig", "masterField"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: DateComponent, selector: "spa-date", inputs: ["required", "min", "max", "readonly", "hint", "value", "display", "placeholder", "width", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "component", type: TextMultiComponent, selector: "spa-text-multi", inputs: ["display", "value", "readonly", "required", "hint", "strict", "suffix", "infoMessage", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: SelectMultiComponent, selector: "spa-select-multi", inputs: ["display", "value", "readonly", "required", "hint", "options", "optionDisplay", "optionValue", "infoMessage", "copyContent", "clearContent", "nullable", "placeholder", "width", "suffix", "loadAction", "selectAll"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: EditorComponent, selector: "spa-editor", inputs: ["display", "value", "readonly", "required", "hint", "infoMessage", "placeholder", "width", "height", "minHeight", "defaultFontName", "editorConfig"], outputs: ["valueChange"] }] }); }
10662
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: OptionComponent, isStandalone: false, selector: "spa-option", inputs: { options: "options", optionValue: "optionValue", optionDisplay: "optionDisplay", readonly: "readonly", type: "type", value: "value", display: "display", show: "show", required: "required", infoMessage: "infoMessage", copyContent: "copyContent", suffix: "suffix", editorConfig: "editorConfig", loadAction: "loadAction" }, outputs: { valueChange: "valueChange", enterPress: "enterPress" }, ngImport: i0, template: "\r\n<div class=\"tin-row gap-0\" style=\"align-items: center;\">\r\n\r\n <mat-checkbox color=\"primary\" style=\"margin-right: 2px;\" [(ngModel)]=\"show\" (change)=\"resetValue()\" labelPosition=\"after\" [disabled]=\"required\" >{{display}}</mat-checkbox>\r\n\r\n <ng-container *ngIf=\"show\" [ngSwitch]=\"type\">\r\n\r\n <spa-date class=\"opt\" *ngSwitchCase=\"'date'\" [display]=\"display\" width=\"120px\" [(value)]=\"value\" [display]=\"display\" (valueChange)=\"dateChanged($event)\" [infoMessage]=\"infoMessage\"\r\n ></spa-date>\r\n\r\n <spa-select-multi class=\"opt\" *ngSwitchCase=\"'select-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-multi>\r\n\r\n <spa-text-multi class=\"opt\" *ngSwitchCase=\"'text-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-multi>\r\n\r\n <spa-select-lite class=\"opt\" *ngSwitchCase=\"'select'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-lite>\r\n\r\n <spa-editor class=\"opt\" *ngSwitchCase=\"'editor'\" [display]=\"display\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [required]=\"required\" [infoMessage]=\"infoMessage\" [editorConfig]=\"editorConfig\"\r\n ></spa-editor>\r\n\r\n <spa-text-single class=\"opt\" *ngSwitchDefault [display]=\"display\" (enterPress)=\"enterPressed()\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [suffix]=\"suffix\" [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-single>\r\n\r\n </ng-container>\r\n\r\n <button mat-icon-button *ngIf=\"infoMessage && !show\" (click)=\"onInfoClick($event)\" matTooltip=\"Info\" matTooltipPosition=\"above\" style=\"opacity: 1;\">\r\n <mat-icon class=\"tinyIcon\" style=\"color: steelblue;\">info</mat-icon>\r\n </button>\r\n\r\n</div>\r\n", styles: [".opt{margin-left:0;align-self:center}.opt ::ng-deep mat-form-field{margin-bottom:0}.opt ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: TextSingleComponent, selector: "spa-text-single", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction", "required", "min", "max", "regex", "suffix", "infoMessage", "field", "data", "detailsConfig", "masterField"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: DateComponent, selector: "spa-date", inputs: ["required", "min", "max", "readonly", "hint", "value", "display", "placeholder", "width", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "component", type: TextMultiComponent, selector: "spa-text-multi", inputs: ["display", "value", "readonly", "required", "hint", "strict", "suffix", "infoMessage", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: SelectMultiComponent, selector: "spa-select-multi", inputs: ["display", "value", "readonly", "required", "hint", "options", "optionDisplay", "optionValue", "infoMessage", "copyContent", "clearContent", "nullable", "placeholder", "width", "suffix", "loadAction", "selectAll"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: EditorComponent, selector: "spa-editor", inputs: ["display", "value", "readonly", "required", "hint", "infoMessage", "placeholder", "width", "height", "minHeight", "defaultFontName", "editorConfig"], outputs: ["valueChange"] }] }); }
10356
10663
  }
10357
10664
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: OptionComponent, decorators: [{
10358
10665
  type: Component,
10359
- args: [{ selector: 'spa-option', standalone: false, template: "\r\n<div class=\"tin-row gap-0\" style=\"align-items: center;\">\r\n\r\n <mat-checkbox color=\"primary\" style=\"margin-right: 2px;\" [(ngModel)]=\"show\" (change)=\"resetValue()\" labelPosition=\"after\" [disabled]=\"required\" >{{display}}</mat-checkbox>\r\n\r\n <ng-container *ngIf=\"show\" [ngSwitch]=\"type\">\r\n\r\n <spa-date class=\"opt\" *ngSwitchCase=\"'date'\" [display]=\"display\" width=\"120px\" [(value)]=\"value\" [display]=\"display\" (valueChange)=\"dateChanged($event)\" [infoMessage]=\"infoMessage\"\r\n ></spa-date>\r\n\r\n <spa-select-multi class=\"opt\" *ngSwitchCase=\"'select-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-multi>\r\n\r\n <spa-text-multi class=\"opt\" *ngSwitchCase=\"'text-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-multi>\r\n\r\n <spa-select-lite class=\"opt\" *ngSwitchCase=\"'select'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-lite>\r\n\r\n <spa-editor class=\"opt\" *ngSwitchCase=\"'editor'\" [display]=\"display\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [required]=\"required\" [infoMessage]=\"infoMessage\" [editorConfig]=\"editorConfig\"\r\n ></spa-editor>\r\n\r\n <spa-text-single class=\"opt\" *ngSwitchDefault [display]=\"display\" (enterPress)=\"enterPressed()\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [suffix]=\"suffix\" [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-single>\r\n\r\n </ng-container>\r\n\r\n <button mat-icon-button *ngIf=\"infoMessage && !show\" (click)=\"onInfoClick($event)\" matTooltip=\"Info\" matTooltipPosition=\"above\" style=\"opacity: 1;\">\r\n <mat-icon class=\"tinyIcon\" style=\"color: steelblue;\">info</mat-icon>\r\n </button>\r\n\r\n</div>\r\n", styles: [".opt{margin-left:0;align-self:center}.opt ::ng-deep mat-form-field{margin-bottom:-1.25em}\n"] }]
10666
+ args: [{ selector: 'spa-option', standalone: false, template: "\r\n<div class=\"tin-row gap-0\" style=\"align-items: center;\">\r\n\r\n <mat-checkbox color=\"primary\" style=\"margin-right: 2px;\" [(ngModel)]=\"show\" (change)=\"resetValue()\" labelPosition=\"after\" [disabled]=\"required\" >{{display}}</mat-checkbox>\r\n\r\n <ng-container *ngIf=\"show\" [ngSwitch]=\"type\">\r\n\r\n <spa-date class=\"opt\" *ngSwitchCase=\"'date'\" [display]=\"display\" width=\"120px\" [(value)]=\"value\" [display]=\"display\" (valueChange)=\"dateChanged($event)\" [infoMessage]=\"infoMessage\"\r\n ></spa-date>\r\n\r\n <spa-select-multi class=\"opt\" *ngSwitchCase=\"'select-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-multi>\r\n\r\n <spa-text-multi class=\"opt\" *ngSwitchCase=\"'text-multi'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-multi>\r\n\r\n <spa-select-lite class=\"opt\" *ngSwitchCase=\"'select'\" [display]=\"display\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-select-lite>\r\n\r\n <spa-editor class=\"opt\" *ngSwitchCase=\"'editor'\" [display]=\"display\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [required]=\"required\" [infoMessage]=\"infoMessage\" [editorConfig]=\"editorConfig\"\r\n ></spa-editor>\r\n\r\n <spa-text-single class=\"opt\" *ngSwitchDefault [display]=\"display\" (enterPress)=\"enterPressed()\" [options]=\"options\" [optionDisplay]=\"optionDisplay\" [optionValue]=\"optionValue\" [(value)]=\"value\" (valueChange)=\"changed()\"\r\n [suffix]=\"suffix\" [copyContent]=\"copyContent\" [loadAction]=\"loadAction\" [infoMessage]=\"infoMessage\"\r\n ></spa-text-single>\r\n\r\n </ng-container>\r\n\r\n <button mat-icon-button *ngIf=\"infoMessage && !show\" (click)=\"onInfoClick($event)\" matTooltip=\"Info\" matTooltipPosition=\"above\" style=\"opacity: 1;\">\r\n <mat-icon class=\"tinyIcon\" style=\"color: steelblue;\">info</mat-icon>\r\n </button>\r\n\r\n</div>\r\n", styles: [".opt{margin-left:0;align-self:center}.opt ::ng-deep mat-form-field{margin-bottom:0}.opt ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}\n"] }]
10360
10667
  }], ctorParameters: () => [{ type: MessageService }], propDecorators: { options: [{
10361
10668
  type: Input
10362
10669
  }], optionValue: [{
@@ -10417,11 +10724,11 @@ class SearchComponent {
10417
10724
  this.searchClick.emit(this.data);
10418
10725
  }
10419
10726
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10420
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SearchComponent, isStandalone: false, selector: "spa-search", inputs: { config: "config", smallScreen: "smallScreen", tableDataSource: "tableDataSource" }, outputs: { searchClick: "searchClick" }, ngImport: i0, template: "\r\n<div class=\"tin-between\">\r\n\r\n <div class=\"col tin-row\">\r\n\r\n <div *ngFor=\"let field of config.fields\">\r\n\r\n <spa-option \r\n [type]=\"field.type\" \r\n [required]=\"field.required\" \r\n [show]=\"field.show\" \r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [options]=\"field.options\" \r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\" \r\n [optionValue]=\"field.optionValue ?? 'value'\" \r\n [(value)]=\"data[field.name]\" (enterPress)=\"search()\"\r\n [infoMessage]=\"field.infoMessage\" \r\n [suffix]=\"field.suffix\" \r\n [copyContent]=\"field.copyContent\" \r\n [loadAction]=\"field.loadAction\"\r\n ></spa-option>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-2 tin-end\">\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.length > 10)\" [showButton]=\"false\" [data]=\"tableDataSource\" ></spa-filter>\r\n <button mat-icon-button color=\"primary\" (click)=\"search()\" matTooltip=\"Search\" matTooltipPosition=\"right\">\r\n <mat-icon>search</mat-icon>\r\n </button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".tin-row{column-gap:30px;row-gap:0px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i3.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: i7$2.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", "filterChange"] }, { kind: "component", type: OptionComponent, selector: "spa-option", inputs: ["options", "optionValue", "optionDisplay", "readonly", "type", "value", "display", "show", "required", "infoMessage", "copyContent", "suffix", "editorConfig", "loadAction"], outputs: ["valueChange", "enterPress"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
10727
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SearchComponent, isStandalone: false, selector: "spa-search", inputs: { config: "config", smallScreen: "smallScreen", tableDataSource: "tableDataSource" }, outputs: { searchClick: "searchClick" }, ngImport: i0, template: "\r\n<div class=\"tin-between\">\r\n\r\n <div class=\"col tin-row\">\r\n\r\n <div *ngFor=\"let field of config.fields\">\r\n\r\n <spa-option \r\n [type]=\"field.type\" \r\n [required]=\"field.required\" \r\n [show]=\"field.show\" \r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [options]=\"field.options\" \r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\" \r\n [optionValue]=\"field.optionValue ?? 'value'\" \r\n [(value)]=\"data[field.name]\" (enterPress)=\"search()\"\r\n [infoMessage]=\"field.infoMessage\" \r\n [suffix]=\"field.suffix\" \r\n [copyContent]=\"field.copyContent\" \r\n [loadAction]=\"field.loadAction\"\r\n ></spa-option>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-2 tin-end\">\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.length > 10)\" [showButton]=\"false\" [data]=\"tableDataSource\" ></spa-filter>\r\n <button mat-icon-button color=\"primary\" (click)=\"search()\" matTooltip=\"Search\" matTooltipPosition=\"right\">\r\n <mat-icon>search</mat-icon>\r\n </button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".tin-row{column-gap:30px;row-gap:14px}:host{display:block;padding-bottom:10px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i3.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: i7$2.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", "filterChange"] }, { kind: "component", type: OptionComponent, selector: "spa-option", inputs: ["options", "optionValue", "optionDisplay", "readonly", "type", "value", "display", "show", "required", "infoMessage", "copyContent", "suffix", "editorConfig", "loadAction"], outputs: ["valueChange", "enterPress"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
10421
10728
  }
10422
10729
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SearchComponent, decorators: [{
10423
10730
  type: Component,
10424
- args: [{ selector: 'spa-search', standalone: false, template: "\r\n<div class=\"tin-between\">\r\n\r\n <div class=\"col tin-row\">\r\n\r\n <div *ngFor=\"let field of config.fields\">\r\n\r\n <spa-option \r\n [type]=\"field.type\" \r\n [required]=\"field.required\" \r\n [show]=\"field.show\" \r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [options]=\"field.options\" \r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\" \r\n [optionValue]=\"field.optionValue ?? 'value'\" \r\n [(value)]=\"data[field.name]\" (enterPress)=\"search()\"\r\n [infoMessage]=\"field.infoMessage\" \r\n [suffix]=\"field.suffix\" \r\n [copyContent]=\"field.copyContent\" \r\n [loadAction]=\"field.loadAction\"\r\n ></spa-option>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-2 tin-end\">\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.length > 10)\" [showButton]=\"false\" [data]=\"tableDataSource\" ></spa-filter>\r\n <button mat-icon-button color=\"primary\" (click)=\"search()\" matTooltip=\"Search\" matTooltipPosition=\"right\">\r\n <mat-icon>search</mat-icon>\r\n </button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".tin-row{column-gap:30px;row-gap:0px}\n"] }]
10731
+ args: [{ selector: 'spa-search', standalone: false, template: "\r\n<div class=\"tin-between\">\r\n\r\n <div class=\"col tin-row\">\r\n\r\n <div *ngFor=\"let field of config.fields\">\r\n\r\n <spa-option \r\n [type]=\"field.type\" \r\n [required]=\"field.required\" \r\n [show]=\"field.show\" \r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [options]=\"field.options\" \r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\" \r\n [optionValue]=\"field.optionValue ?? 'value'\" \r\n [(value)]=\"data[field.name]\" (enterPress)=\"search()\"\r\n [infoMessage]=\"field.infoMessage\" \r\n [suffix]=\"field.suffix\" \r\n [copyContent]=\"field.copyContent\" \r\n [loadAction]=\"field.loadAction\"\r\n ></spa-option>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-2 tin-end\">\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.length > 10)\" [showButton]=\"false\" [data]=\"tableDataSource\" ></spa-filter>\r\n <button mat-icon-button color=\"primary\" (click)=\"search()\" matTooltip=\"Search\" matTooltipPosition=\"right\">\r\n <mat-icon>search</mat-icon>\r\n </button>\r\n </div>\r\n\r\n</div>\r\n", styles: [".tin-row{column-gap:30px;row-gap:14px}:host{display:block;padding-bottom:10px}\n"] }]
10425
10732
  }], ctorParameters: () => [], propDecorators: { config: [{
10426
10733
  type: Input
10427
10734
  }], searchClick: [{
@@ -10674,13 +10981,7 @@ class TilesComponent {
10674
10981
  }
10675
10982
  }
10676
10983
  isHidden(tile) {
10677
- if (tile.hidden) {
10678
- return true;
10679
- }
10680
- if (tile.hiddenCondition) {
10681
- return tile.hiddenCondition(this.data);
10682
- }
10683
- return false;
10984
+ return !Core.isItemVisible(tile, this.data); // Changed: unified visible/hidden replaces hidden + hiddenCondition
10684
10985
  }
10685
10986
  // Changed: Returns true only when data[tile.name] is a displayable primitive and not already shown by the chart
10686
10987
  isTileValuePrimitive(tile) {
@@ -10846,11 +11147,11 @@ class TilesComponent {
10846
11147
  return [];
10847
11148
  }
10848
11149
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TilesComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
10849
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TilesComponent, isStandalone: false, selector: "spa-tiles", inputs: { config: "config", lastSearch: "lastSearch", data: "data", reload: "reload" }, outputs: { tileActionSelected: "tileActionSelected", tileClick: "tileClick", tileUnClick: "tileUnClick" }, usesOnChanges: true, ngImport: i0, template: "<!-- Changed: align-items-center vertically centers tiles of different heights for clean alignment -->\r\n<div class=\"d-flex row align-items-center justify-content-between\">\r\n <ng-container *ngFor=\"let tile of tiles\">\r\n <!-- Changed: tile-clickable class gives pointer + hover lift only on tiles that actually respond to clicks -->\r\n <mat-card *ngIf=\"!isHidden(tile)\" class=\"col\" [class.tile-clickable]=\"isClickable(tile)\" [class.selected-tile]=\"tile.name === selectedTile\" style=\"margin-left: 5px;margin-right: 5px; padding: 10px 16px ; min-width: 150px; margin-top: 5px;\" (click)=\"clicked(tile)\">\r\n\r\n <!-- Changed: Chart-style tile \u2014 header, prominent chart, optional value, footer -->\r\n <ng-container *ngIf=\"tile.chart; else standardTile\">\r\n <!-- Changed: Header with tile name \u2014 left-aligned for better hierarchy -->\r\n <div class=\"tile-chart-header\">\r\n <span>{{tile.alias ?? tile.name | camelToWords}}</span>\r\n </div>\r\n\r\n <!-- Changed: Chart fills tile \u2014 uses helper method for reliable data detection -->\r\n <div class=\"tile-chart\" *ngIf=\"hasTileChartData(tile)\" [style.height.px]=\"tile.chart.height ?? 120\">\r\n <canvas baseChart\r\n [type]=\"tile.chart.type\"\r\n [data]=\"getTileMiniChartData(tile)\"\r\n [options]=\"getMiniChartOptions(tile)\"\r\n [plugins]=\"getTileChartPlugins(tile)\">\r\n </canvas>\r\n </div>\r\n\r\n <!-- Changed: Optional value display below chart \u2014 only show primitive values, skip chart data objects -->\r\n <div class=\"tile-chart-value\" *ngIf=\"tile.name && isTileValuePrimitive(tile)\">\r\n <span class=\"tile-chart-value-text\" [style.color]=\"tile.color\">{{tile.prefix ?? ''}}{{data?.[tile.name]}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span></span>\r\n </div>\r\n\r\n <!-- Changed: Footer with divider for chart tiles -->\r\n <div class=\"tile-footer\" *ngIf=\"tile.footer || tile.info\">\r\n <mat-divider></mat-divider>\r\n <div class=\"d-flex align-items-center\" style=\"gap: 4px; color: #9a9a9a; font-size: 12px; margin-top: 6px;\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" style=\"font-size: 16px; width: 16px; height: 16px;\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" style=\"font-size: 16px; width: 16px; height: 16px; color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Changed: Standard tile \u2014 Paper Dashboard style: icon left, label+value right, optional footer -->\r\n <ng-template #standardTile>\r\n <!-- Changed: Icon-style tile \u2014 icon left, label top-right, large value below -->\r\n <ng-container *ngIf=\"tile.icon; else basicTile\">\r\n <div class=\"tile-icon-row\">\r\n <div class=\"tile-icon-wrap\" [style.color]=\"tile.color ?? '#2196f3'\">\r\n <mat-icon>{{tile.icon}}</mat-icon>\r\n </div>\r\n <div class=\"tile-icon-content\">\r\n <div class=\"tile-icon-label\">{{tile.alias ?? tile.name | camelToWords}}</div>\r\n <div class=\"tile-icon-value\" [style.color]=\"tile.color\">\r\n <span *ngIf=\"tile.prefix\">{{tile.prefix}}</span>{{data?.[tile.name] ?? 0}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Changed: Footer with divider \u2014 info tooltip or custom footer text -->\r\n <div class=\"tile-icon-footer\" *ngIf=\"tile.info || tile.footer\">\r\n <mat-divider></mat-divider>\r\n <div class=\"tile-icon-footer-content\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" class=\"tile-icon-footer-icon\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" class=\"tile-icon-footer-icon\" style=\"color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Basic tile fallback \u2014 centered number display (no icon) -->\r\n <ng-template #basicTile>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div style=\"text-align: center;font-size: 30px;\">\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.prefix\" >{{tile.prefix}}</mat-label> &nbsp;\r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label>&nbsp;\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.suffix\">{{tile.suffix}}</mat-label>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"text-align: center;\">\r\n <mat-label style=\"padding-left:5px;padding-right:5px; text-align: center;font-size: 14px;\">{{tile.alias ?? tile.name | camelToWords}}</mat-label>\r\n <mat-icon *ngIf=\"tile.info\" [matTooltip]=\"tile.info\" matTooltipPosition=\"above\" style=\"font-size: 20px; color:steelblue;\">info</mat-icon>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </ng-template>\r\n\r\n </mat-card>\r\n </ng-container>\r\n</div>\r\n", styles: [".card{min-width:180px;flex:1;display:flex;flex-direction:column;align-items:center;padding:5px 10px}.tiles{gap:1;row-gap:5px}.col{transition:all .2s ease}.tile-clickable{cursor:pointer}.tile-clickable:hover{transform:translateY(-2px);box-shadow:0 4px 10px #00000021;background-color:#f5f5f5}.selected-tile{background-color:#e0e0e0;box-shadow:0 4px 8px #0003;transform:translateY(-2px);border:2px solid #3f51b5}.selected-tile mat-label{font-weight:700}.selected-tile:hover{background-color:#e0e0e0}.tile-chart-header{display:flex;justify-content:flex-start;font-size:11px;font-weight:500;color:#999;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tile-chart{width:100%;position:relative;margin-top:4px;display:flex;align-items:center;justify-content:center}.tile-chart canvas{width:100%!important;height:100%!important;max-height:inherit}.tile-chart-value{text-align:center;margin-top:6px}.tile-chart-value-text{font-size:22px;font-weight:600;letter-spacing:-.5px}.tile-badge{display:inline-block;font-size:12px;font-weight:500;color:#fff;padding:2px 8px;border-radius:12px;vertical-align:middle;margin-left:4px}.tile-footer{margin-top:8px}.tile-icon-row{display:flex;align-items:flex-start;gap:12px;padding:4px 0}.tile-icon-wrap{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:48px;height:48px}.tile-icon-wrap mat-icon{font-size:36px;width:36px;height:36px;opacity:.85}.tile-icon-content{flex:1;text-align:right;min-width:0}.tile-icon-label{font-size:12px;color:#999;text-transform:uppercase;letter-spacing:.3px;line-height:1.4}.tile-icon-value{font-size:26px;font-weight:600;line-height:1.2;letter-spacing:-.5px}.tile-icon-footer{margin-top:8px}.tile-icon-footer mat-divider{margin-bottom:6px}.tile-icon-footer-content{display:flex;align-items:center;gap:4px;font-size:12px;color:#999}.tile-icon-footer-icon{font-size:16px;width:16px;height:16px;color:#bbb}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i9.BaseChartDirective, selector: "canvas[baseChart]", inputs: ["type", "legend", "data", "options", "plugins", "labels", "datasets"], outputs: ["chartClick", "chartHover"], exportAs: ["base-chart"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11150
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TilesComponent, isStandalone: false, selector: "spa-tiles", inputs: { config: "config", lastSearch: "lastSearch", data: "data", reload: "reload" }, outputs: { tileActionSelected: "tileActionSelected", tileClick: "tileClick", tileUnClick: "tileUnClick" }, usesOnChanges: true, ngImport: i0, template: "<!-- Changed: align-items-center vertically centers tiles of different heights for clean alignment -->\r\n<div class=\"d-flex row align-items-center justify-content-between\">\r\n <ng-container *ngFor=\"let tile of tiles\">\r\n <!-- Changed: tile-clickable class gives pointer + hover lift only on tiles that actually respond to clicks -->\r\n <mat-card *ngIf=\"!isHidden(tile)\" class=\"col\" [class.tile-clickable]=\"isClickable(tile)\" [class.selected-tile]=\"tile.name === selectedTile\" style=\"margin-left: 5px;margin-right: 5px; padding: 10px 16px ; min-width: 150px; margin-top: 5px;\" (click)=\"clicked(tile)\">\r\n\r\n <!-- Changed: Chart-style tile \u2014 header, prominent chart, optional value, footer -->\r\n <ng-container *ngIf=\"tile.chart; else standardTile\">\r\n <!-- Changed: Header with tile name \u2014 left-aligned for better hierarchy -->\r\n <div class=\"tile-chart-header\">\r\n <span>{{tile.alias ?? tile.name | camelToWords}}</span>\r\n </div>\r\n\r\n <!-- Changed: Chart fills tile \u2014 uses helper method for reliable data detection -->\r\n <div class=\"tile-chart\" *ngIf=\"hasTileChartData(tile)\" [style.height.px]=\"tile.chart.height ?? 120\">\r\n <canvas baseChart\r\n [type]=\"tile.chart.type\"\r\n [data]=\"getTileMiniChartData(tile)\"\r\n [options]=\"getMiniChartOptions(tile)\"\r\n [plugins]=\"getTileChartPlugins(tile)\">\r\n </canvas>\r\n </div>\r\n\r\n <!-- Changed: Optional value display below chart \u2014 only show primitive values, skip chart data objects -->\r\n <div class=\"tile-chart-value\" *ngIf=\"tile.name && isTileValuePrimitive(tile)\">\r\n <span class=\"tile-chart-value-text\" [style.color]=\"tile.color\">{{tile.prefix ?? ''}}{{data?.[tile.name]}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span></span>\r\n </div>\r\n\r\n <!-- Changed: Footer with divider for chart tiles -->\r\n <div class=\"tile-footer\" *ngIf=\"tile.footer || tile.info\">\r\n <mat-divider></mat-divider>\r\n <div class=\"d-flex align-items-center\" style=\"gap: 4px; color: #9a9a9a; font-size: 12px; margin-top: 6px;\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" style=\"font-size: 16px; width: 16px; height: 16px;\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" style=\"font-size: 16px; width: 16px; height: 16px; color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Changed: Standard tile \u2014 Paper Dashboard style: icon left, label+value right, optional footer -->\r\n <ng-template #standardTile>\r\n <!-- Changed: Icon-style tile \u2014 icon left, label top-right, large value below -->\r\n <ng-container *ngIf=\"tile.icon; else basicTile\">\r\n <div class=\"tile-icon-row\">\r\n <div class=\"tile-icon-wrap\" [style.color]=\"tile.color ?? '#2196f3'\">\r\n <mat-icon>{{tile.icon}}</mat-icon>\r\n </div>\r\n <div class=\"tile-icon-content\">\r\n <div class=\"tile-icon-label\">{{tile.alias ?? tile.name | camelToWords}}</div>\r\n <div class=\"tile-icon-value\" [style.color]=\"tile.color\">\r\n <span *ngIf=\"tile.prefix\">{{tile.prefix}}</span>{{data?.[tile.name] ?? 0}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Changed: Footer with divider \u2014 info tooltip or custom footer text -->\r\n <div class=\"tile-icon-footer\" *ngIf=\"tile.info || tile.footer\">\r\n <mat-divider></mat-divider>\r\n <div class=\"tile-icon-footer-content\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" class=\"tile-icon-footer-icon\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" class=\"tile-icon-footer-icon\" style=\"color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Basic tile fallback \u2014 centered number display (no icon) -->\r\n <ng-template #basicTile>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div style=\"text-align: center;font-size: 30px;\">\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.prefix\" >{{tile.prefix}}</mat-label> &nbsp;\r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label>&nbsp;\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.suffix\">{{tile.suffix}}</mat-label>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"text-align: center;\">\r\n <mat-label style=\"padding-left:5px;padding-right:5px; text-align: center;font-size: 14px;\">{{tile.alias ?? tile.name | camelToWords}}</mat-label>\r\n <mat-icon *ngIf=\"tile.info\" [matTooltip]=\"tile.info\" matTooltipPosition=\"above\" style=\"font-size: 20px; color:steelblue;\">info</mat-icon>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </ng-template>\r\n\r\n </mat-card>\r\n </ng-container>\r\n</div>\r\n", styles: [".card{min-width:180px;flex:1;display:flex;flex-direction:column;align-items:center;padding:5px 10px}.tiles{gap:1;row-gap:5px}.col{transition:all .2s ease}.tile-clickable{cursor:pointer}.tile-clickable:hover{transform:translateY(-2px);box-shadow:0 4px 10px #00000021;background-color:#2196f312}.selected-tile{background-color:#e0e0e0;box-shadow:0 4px 8px #0003;transform:translateY(-2px);border:2px solid #3f51b5}.selected-tile mat-label{font-weight:700}.selected-tile:hover{background-color:#e0e0e0}.tile-chart-header{display:flex;justify-content:flex-start;font-size:11px;font-weight:500;color:#999;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tile-chart{width:100%;position:relative;margin-top:4px;display:flex;align-items:center;justify-content:center}.tile-chart canvas{width:100%!important;height:100%!important;max-height:inherit}.tile-chart-value{text-align:center;margin-top:6px}.tile-chart-value-text{font-size:22px;font-weight:600;letter-spacing:-.5px}.tile-badge{display:inline-block;font-size:12px;font-weight:500;color:#fff;padding:2px 8px;border-radius:12px;vertical-align:middle;margin-left:4px}.tile-footer{margin-top:8px}.tile-icon-row{display:flex;align-items:flex-start;gap:12px;padding:4px 0}.tile-icon-wrap{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:48px;height:48px}.tile-icon-wrap mat-icon{font-size:36px;width:36px;height:36px;opacity:.85}.tile-icon-content{flex:1;text-align:right;min-width:0}.tile-icon-label{font-size:12px;color:#999;text-transform:uppercase;letter-spacing:.3px;line-height:1.4}.tile-icon-value{font-size:26px;font-weight:600;line-height:1.2;letter-spacing:-.5px}.tile-icon-footer{margin-top:8px}.tile-icon-footer mat-divider{margin-bottom:6px}.tile-icon-footer-content{display:flex;align-items:center;gap:4px;font-size:12px;color:#999}.tile-icon-footer-icon{font-size:16px;width:16px;height:16px;color:#bbb}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i9.BaseChartDirective, selector: "canvas[baseChart]", inputs: ["type", "legend", "data", "options", "plugins", "labels", "datasets"], outputs: ["chartClick", "chartHover"], exportAs: ["base-chart"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10850
11151
  }
10851
11152
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TilesComponent, decorators: [{
10852
11153
  type: Component,
10853
- args: [{ selector: 'spa-tiles', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<!-- Changed: align-items-center vertically centers tiles of different heights for clean alignment -->\r\n<div class=\"d-flex row align-items-center justify-content-between\">\r\n <ng-container *ngFor=\"let tile of tiles\">\r\n <!-- Changed: tile-clickable class gives pointer + hover lift only on tiles that actually respond to clicks -->\r\n <mat-card *ngIf=\"!isHidden(tile)\" class=\"col\" [class.tile-clickable]=\"isClickable(tile)\" [class.selected-tile]=\"tile.name === selectedTile\" style=\"margin-left: 5px;margin-right: 5px; padding: 10px 16px ; min-width: 150px; margin-top: 5px;\" (click)=\"clicked(tile)\">\r\n\r\n <!-- Changed: Chart-style tile \u2014 header, prominent chart, optional value, footer -->\r\n <ng-container *ngIf=\"tile.chart; else standardTile\">\r\n <!-- Changed: Header with tile name \u2014 left-aligned for better hierarchy -->\r\n <div class=\"tile-chart-header\">\r\n <span>{{tile.alias ?? tile.name | camelToWords}}</span>\r\n </div>\r\n\r\n <!-- Changed: Chart fills tile \u2014 uses helper method for reliable data detection -->\r\n <div class=\"tile-chart\" *ngIf=\"hasTileChartData(tile)\" [style.height.px]=\"tile.chart.height ?? 120\">\r\n <canvas baseChart\r\n [type]=\"tile.chart.type\"\r\n [data]=\"getTileMiniChartData(tile)\"\r\n [options]=\"getMiniChartOptions(tile)\"\r\n [plugins]=\"getTileChartPlugins(tile)\">\r\n </canvas>\r\n </div>\r\n\r\n <!-- Changed: Optional value display below chart \u2014 only show primitive values, skip chart data objects -->\r\n <div class=\"tile-chart-value\" *ngIf=\"tile.name && isTileValuePrimitive(tile)\">\r\n <span class=\"tile-chart-value-text\" [style.color]=\"tile.color\">{{tile.prefix ?? ''}}{{data?.[tile.name]}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span></span>\r\n </div>\r\n\r\n <!-- Changed: Footer with divider for chart tiles -->\r\n <div class=\"tile-footer\" *ngIf=\"tile.footer || tile.info\">\r\n <mat-divider></mat-divider>\r\n <div class=\"d-flex align-items-center\" style=\"gap: 4px; color: #9a9a9a; font-size: 12px; margin-top: 6px;\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" style=\"font-size: 16px; width: 16px; height: 16px;\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" style=\"font-size: 16px; width: 16px; height: 16px; color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Changed: Standard tile \u2014 Paper Dashboard style: icon left, label+value right, optional footer -->\r\n <ng-template #standardTile>\r\n <!-- Changed: Icon-style tile \u2014 icon left, label top-right, large value below -->\r\n <ng-container *ngIf=\"tile.icon; else basicTile\">\r\n <div class=\"tile-icon-row\">\r\n <div class=\"tile-icon-wrap\" [style.color]=\"tile.color ?? '#2196f3'\">\r\n <mat-icon>{{tile.icon}}</mat-icon>\r\n </div>\r\n <div class=\"tile-icon-content\">\r\n <div class=\"tile-icon-label\">{{tile.alias ?? tile.name | camelToWords}}</div>\r\n <div class=\"tile-icon-value\" [style.color]=\"tile.color\">\r\n <span *ngIf=\"tile.prefix\">{{tile.prefix}}</span>{{data?.[tile.name] ?? 0}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Changed: Footer with divider \u2014 info tooltip or custom footer text -->\r\n <div class=\"tile-icon-footer\" *ngIf=\"tile.info || tile.footer\">\r\n <mat-divider></mat-divider>\r\n <div class=\"tile-icon-footer-content\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" class=\"tile-icon-footer-icon\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" class=\"tile-icon-footer-icon\" style=\"color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Basic tile fallback \u2014 centered number display (no icon) -->\r\n <ng-template #basicTile>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div style=\"text-align: center;font-size: 30px;\">\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.prefix\" >{{tile.prefix}}</mat-label> &nbsp;\r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label>&nbsp;\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.suffix\">{{tile.suffix}}</mat-label>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"text-align: center;\">\r\n <mat-label style=\"padding-left:5px;padding-right:5px; text-align: center;font-size: 14px;\">{{tile.alias ?? tile.name | camelToWords}}</mat-label>\r\n <mat-icon *ngIf=\"tile.info\" [matTooltip]=\"tile.info\" matTooltipPosition=\"above\" style=\"font-size: 20px; color:steelblue;\">info</mat-icon>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </ng-template>\r\n\r\n </mat-card>\r\n </ng-container>\r\n</div>\r\n", styles: [".card{min-width:180px;flex:1;display:flex;flex-direction:column;align-items:center;padding:5px 10px}.tiles{gap:1;row-gap:5px}.col{transition:all .2s ease}.tile-clickable{cursor:pointer}.tile-clickable:hover{transform:translateY(-2px);box-shadow:0 4px 10px #00000021;background-color:#f5f5f5}.selected-tile{background-color:#e0e0e0;box-shadow:0 4px 8px #0003;transform:translateY(-2px);border:2px solid #3f51b5}.selected-tile mat-label{font-weight:700}.selected-tile:hover{background-color:#e0e0e0}.tile-chart-header{display:flex;justify-content:flex-start;font-size:11px;font-weight:500;color:#999;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tile-chart{width:100%;position:relative;margin-top:4px;display:flex;align-items:center;justify-content:center}.tile-chart canvas{width:100%!important;height:100%!important;max-height:inherit}.tile-chart-value{text-align:center;margin-top:6px}.tile-chart-value-text{font-size:22px;font-weight:600;letter-spacing:-.5px}.tile-badge{display:inline-block;font-size:12px;font-weight:500;color:#fff;padding:2px 8px;border-radius:12px;vertical-align:middle;margin-left:4px}.tile-footer{margin-top:8px}.tile-icon-row{display:flex;align-items:flex-start;gap:12px;padding:4px 0}.tile-icon-wrap{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:48px;height:48px}.tile-icon-wrap mat-icon{font-size:36px;width:36px;height:36px;opacity:.85}.tile-icon-content{flex:1;text-align:right;min-width:0}.tile-icon-label{font-size:12px;color:#999;text-transform:uppercase;letter-spacing:.3px;line-height:1.4}.tile-icon-value{font-size:26px;font-weight:600;line-height:1.2;letter-spacing:-.5px}.tile-icon-footer{margin-top:8px}.tile-icon-footer mat-divider{margin-bottom:6px}.tile-icon-footer-content{display:flex;align-items:center;gap:4px;font-size:12px;color:#999}.tile-icon-footer-icon{font-size:16px;width:16px;height:16px;color:#bbb}\n"] }]
11154
+ args: [{ selector: 'spa-tiles', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<!-- Changed: align-items-center vertically centers tiles of different heights for clean alignment -->\r\n<div class=\"d-flex row align-items-center justify-content-between\">\r\n <ng-container *ngFor=\"let tile of tiles\">\r\n <!-- Changed: tile-clickable class gives pointer + hover lift only on tiles that actually respond to clicks -->\r\n <mat-card *ngIf=\"!isHidden(tile)\" class=\"col\" [class.tile-clickable]=\"isClickable(tile)\" [class.selected-tile]=\"tile.name === selectedTile\" style=\"margin-left: 5px;margin-right: 5px; padding: 10px 16px ; min-width: 150px; margin-top: 5px;\" (click)=\"clicked(tile)\">\r\n\r\n <!-- Changed: Chart-style tile \u2014 header, prominent chart, optional value, footer -->\r\n <ng-container *ngIf=\"tile.chart; else standardTile\">\r\n <!-- Changed: Header with tile name \u2014 left-aligned for better hierarchy -->\r\n <div class=\"tile-chart-header\">\r\n <span>{{tile.alias ?? tile.name | camelToWords}}</span>\r\n </div>\r\n\r\n <!-- Changed: Chart fills tile \u2014 uses helper method for reliable data detection -->\r\n <div class=\"tile-chart\" *ngIf=\"hasTileChartData(tile)\" [style.height.px]=\"tile.chart.height ?? 120\">\r\n <canvas baseChart\r\n [type]=\"tile.chart.type\"\r\n [data]=\"getTileMiniChartData(tile)\"\r\n [options]=\"getMiniChartOptions(tile)\"\r\n [plugins]=\"getTileChartPlugins(tile)\">\r\n </canvas>\r\n </div>\r\n\r\n <!-- Changed: Optional value display below chart \u2014 only show primitive values, skip chart data objects -->\r\n <div class=\"tile-chart-value\" *ngIf=\"tile.name && isTileValuePrimitive(tile)\">\r\n <span class=\"tile-chart-value-text\" [style.color]=\"tile.color\">{{tile.prefix ?? ''}}{{data?.[tile.name]}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span></span>\r\n </div>\r\n\r\n <!-- Changed: Footer with divider for chart tiles -->\r\n <div class=\"tile-footer\" *ngIf=\"tile.footer || tile.info\">\r\n <mat-divider></mat-divider>\r\n <div class=\"d-flex align-items-center\" style=\"gap: 4px; color: #9a9a9a; font-size: 12px; margin-top: 6px;\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" style=\"font-size: 16px; width: 16px; height: 16px;\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" style=\"font-size: 16px; width: 16px; height: 16px; color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Changed: Standard tile \u2014 Paper Dashboard style: icon left, label+value right, optional footer -->\r\n <ng-template #standardTile>\r\n <!-- Changed: Icon-style tile \u2014 icon left, label top-right, large value below -->\r\n <ng-container *ngIf=\"tile.icon; else basicTile\">\r\n <div class=\"tile-icon-row\">\r\n <div class=\"tile-icon-wrap\" [style.color]=\"tile.color ?? '#2196f3'\">\r\n <mat-icon>{{tile.icon}}</mat-icon>\r\n </div>\r\n <div class=\"tile-icon-content\">\r\n <div class=\"tile-icon-label\">{{tile.alias ?? tile.name | camelToWords}}</div>\r\n <div class=\"tile-icon-value\" [style.color]=\"tile.color\">\r\n <span *ngIf=\"tile.prefix\">{{tile.prefix}}</span>{{data?.[tile.name] ?? 0}}<span *ngIf=\"tile.suffix\"> {{tile.suffix}}</span>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Changed: Footer with divider \u2014 info tooltip or custom footer text -->\r\n <div class=\"tile-icon-footer\" *ngIf=\"tile.info || tile.footer\">\r\n <mat-divider></mat-divider>\r\n <div class=\"tile-icon-footer-content\">\r\n <mat-icon *ngIf=\"tile.footerIcon\" class=\"tile-icon-footer-icon\">{{tile.footerIcon}}</mat-icon>\r\n <mat-icon *ngIf=\"!tile.footerIcon && tile.info\" class=\"tile-icon-footer-icon\" style=\"color: steelblue;\">info</mat-icon>\r\n <span>{{tile.footer ?? tile.info}}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Basic tile fallback \u2014 centered number display (no icon) -->\r\n <ng-template #basicTile>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div style=\"text-align: center;font-size: 30px;\">\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.prefix\" >{{tile.prefix}}</mat-label> &nbsp;\r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label>&nbsp;\r\n <mat-label style=\"font-weight:bold;\" *ngIf=\"tile.suffix\">{{tile.suffix}}</mat-label>\r\n <span *ngIf=\"tile.badge && data?.[tile.badge]\" class=\"tile-badge\" [style.backgroundColor]=\"tile.badgeColor ?? '#4caf50'\">{{data?.[tile.badge]}}</span>\r\n </div>\r\n </div>\r\n <div class=\"row d-flex justify-content-center align-items-center\">\r\n <div class=\"d-flex justify-content-center align-items-center\" style=\"text-align: center;\">\r\n <mat-label style=\"padding-left:5px;padding-right:5px; text-align: center;font-size: 14px;\">{{tile.alias ?? tile.name | camelToWords}}</mat-label>\r\n <mat-icon *ngIf=\"tile.info\" [matTooltip]=\"tile.info\" matTooltipPosition=\"above\" style=\"font-size: 20px; color:steelblue;\">info</mat-icon>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </ng-template>\r\n\r\n </mat-card>\r\n </ng-container>\r\n</div>\r\n", styles: [".card{min-width:180px;flex:1;display:flex;flex-direction:column;align-items:center;padding:5px 10px}.tiles{gap:1;row-gap:5px}.col{transition:all .2s ease}.tile-clickable{cursor:pointer}.tile-clickable:hover{transform:translateY(-2px);box-shadow:0 4px 10px #00000021;background-color:#2196f312}.selected-tile{background-color:#e0e0e0;box-shadow:0 4px 8px #0003;transform:translateY(-2px);border:2px solid #3f51b5}.selected-tile mat-label{font-weight:700}.selected-tile:hover{background-color:#e0e0e0}.tile-chart-header{display:flex;justify-content:flex-start;font-size:11px;font-weight:500;color:#999;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tile-chart{width:100%;position:relative;margin-top:4px;display:flex;align-items:center;justify-content:center}.tile-chart canvas{width:100%!important;height:100%!important;max-height:inherit}.tile-chart-value{text-align:center;margin-top:6px}.tile-chart-value-text{font-size:22px;font-weight:600;letter-spacing:-.5px}.tile-badge{display:inline-block;font-size:12px;font-weight:500;color:#fff;padding:2px 8px;border-radius:12px;vertical-align:middle;margin-left:4px}.tile-footer{margin-top:8px}.tile-icon-row{display:flex;align-items:flex-start;gap:12px;padding:4px 0}.tile-icon-wrap{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:48px;height:48px}.tile-icon-wrap mat-icon{font-size:36px;width:36px;height:36px;opacity:.85}.tile-icon-content{flex:1;text-align:right;min-width:0}.tile-icon-label{font-size:12px;color:#999;text-transform:uppercase;letter-spacing:.3px;line-height:1.4}.tile-icon-value{font-size:26px;font-weight:600;line-height:1.2;letter-spacing:-.5px}.tile-icon-footer{margin-top:8px}.tile-icon-footer mat-divider{margin-bottom:6px}.tile-icon-footer-content{display:flex;align-items:center;gap:4px;font-size:12px;color:#999}.tile-icon-footer-icon{font-size:16px;width:16px;height:16px;color:#bbb}\n"] }]
10854
11155
  }], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i0.ChangeDetectorRef }], propDecorators: { config: [{
10855
11156
  type: Input
10856
11157
  }], tileActionSelected: [{
@@ -11081,7 +11382,7 @@ class CheckComponent {
11081
11382
  this.infoClick.emit();
11082
11383
  }
11083
11384
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CheckComponent, deps: [{ token: MessageService }], target: i0.ɵɵFactoryTarget.Component }); }
11084
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: CheckComponent, isStandalone: false, selector: "spa-check", inputs: { readonly: "readonly", display: "display", value: "value", infoMessage: "infoMessage" }, outputs: { valueChange: "valueChange", click: "click", check: "check", uncheck: "uncheck", infoClick: "infoClick" }, ngImport: i0, template: "\r\n\r\n<mat-checkbox color=\"primary\" [(ngModel)]=\"value\" (change)=\"changed()\" (click)=\"clicked()\" [disabled]=\"readonly\">{{display}}</mat-checkbox>\r\n\r\n<!-- <div class=\"suffix-icons\">\r\n\r\n <mat-checkbox color=\"primary\" [(ngModel)]=\"value\" (change)=\"changed()\" (click)=\"clicked()\" [disabled]=\"readonly\">{{display}}</mat-checkbox>\r\n\r\n <mat-icon *ngIf=\"infoMessage\" (click)=\"onInfoClick($event)\" matTooltip=\"Info\" matTooltipPosition=\"above\" style=\"color: steelblue;font-size: 15px;margin-left: 5px;margin-top: 8px;\">info</mat-icon>\r\n\r\n</div> -->\r\n\r\n", styles: [".suffix-icons{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }] }); }
11385
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: CheckComponent, isStandalone: false, selector: "spa-check", inputs: { readonly: "readonly", display: "display", value: "value", infoMessage: "infoMessage" }, outputs: { valueChange: "valueChange", click: "click", check: "check", uncheck: "uncheck", infoClick: "infoClick" }, ngImport: i0, template: "\r\n\r\n<mat-checkbox color=\"primary\" [(ngModel)]=\"value\" (change)=\"changed()\" (click)=\"clicked()\" [disabled]=\"readonly\">{{display}}</mat-checkbox>\r\n\r\n<!-- <div class=\"suffix-icons\">\r\n\r\n <mat-checkbox color=\"primary\" [(ngModel)]=\"value\" (change)=\"changed()\" (click)=\"clicked()\" [disabled]=\"readonly\">{{display}}</mat-checkbox>\r\n\r\n <mat-icon *ngIf=\"infoMessage\" (click)=\"onInfoClick($event)\" matTooltip=\"Info\" matTooltipPosition=\"above\" style=\"color: steelblue;font-size: 15px;margin-left: 5px;margin-top: 8px;\">info</mat-icon>\r\n\r\n</div> -->\r\n\r\n", styles: [".suffix-icons{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }] }); }
11085
11386
  }
11086
11387
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CheckComponent, decorators: [{
11087
11388
  type: Component,
@@ -12585,7 +12886,7 @@ class CardsComponent {
12585
12886
  return this.displayedButtons?.find(x => x.name === name) || null;
12586
12887
  }
12587
12888
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CardsComponent, deps: [{ token: ButtonService }, { token: ConditionService }], target: i0.ɵɵFactoryTarget.Component }); }
12588
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: CardsComponent, isStandalone: false, selector: "spa-cards", inputs: { config: "config", dataSource: "dataSource", displayedButtons: "displayedButtons", smallScreen: "smallScreen" }, outputs: { actionClick: "actionClick", columnClick: "columnClick", showBannerEvent: "showBannerEvent" }, ngImport: i0, template: "<div class=\"cards-container\" [style.grid-template-columns]=\"'repeat(' + getColumnCount() + ', 1fr)'\">\r\n <mat-card *ngFor=\"let row of dataSource\" [class]=\"getElevationClass()\" class=\"card-item\">\r\n <mat-card-header>\r\n <mat-card-title>{{getTitleValue(row)}}</mat-card-title>\r\n <mat-card-subtitle *ngIf=\"getSubtitleValue(row)\">{{getSubtitleValue(row)}}</mat-card-subtitle>\r\n </mat-card-header>\r\n \r\n <mat-card-content>\r\n <div class=\"content-item\" *ngFor=\"let content of getContentValues(row)\">\r\n <span class=\"label\">{{content.label}}:</span>\r\n <span class=\"value\">{{content.value}}</span>\r\n </div>\r\n </mat-card-content>\r\n \r\n <mat-card-actions align=\"end\">\r\n <ng-container *ngFor=\"let button of displayedButtons\">\r\n <button *ngIf=\"!config.flatButtons && testVisible(row, button.name)\" \r\n mat-mini-fab\r\n [matTooltip]=\"button.tip ?? button.name | titlecase\"\r\n matTooltipPosition=\"above\"\r\n [ngStyle]=\"{'background-color': getButtonColor(button, row)}\"\r\n [disabled]=\"testDisabled(row, button.name)\"\r\n (click)=\"onActionClick(button.name, row)\">\r\n <mat-icon>{{getIcon(button.name)}}</mat-icon>\r\n </button>\r\n \r\n <button *ngIf=\"config.flatButtons && testVisible(row, button.name)\"\r\n mat-icon-button \r\n [matTooltip]=\"button.tip ?? button.name | titlecase\"\r\n matTooltipPosition=\"above\"\r\n [disabled]=\"testDisabled(row, button.name)\"\r\n (click)=\"onActionClick(button.name, row)\">\r\n <mat-icon [ngStyle]=\"{'color': getButtonColor(button, row)}\">{{getIcon(button.name)}}</mat-icon>\r\n </button>\r\n </ng-container>\r\n </mat-card-actions>\r\n </mat-card>\r\n </div>", styles: [".cards-container{display:grid;gap:20px;padding:20px}.card-item{height:100%;transition:transform .2s}.card-item:hover{transform:translateY(-5px)}.content-item{margin:8px 0;display:grid;grid-template-columns:minmax(33%,1fr) minmax(67%,2fr);gap:8px;align-items:baseline}.label{font-weight:500;color:#000000b3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.value{text-align:left;word-break:break-word}mat-card-actions{padding:16px;display:flex;gap:8px}.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}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i3.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.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: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i18.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i18.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i18.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i18.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }] }); }
12889
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: CardsComponent, isStandalone: false, selector: "spa-cards", inputs: { config: "config", dataSource: "dataSource", displayedButtons: "displayedButtons", smallScreen: "smallScreen" }, outputs: { actionClick: "actionClick", columnClick: "columnClick", showBannerEvent: "showBannerEvent" }, ngImport: i0, template: "<div class=\"cards-container\" [style.grid-template-columns]=\"'repeat(' + getColumnCount() + ', 1fr)'\">\r\n <mat-card *ngFor=\"let row of dataSource\" [class]=\"getElevationClass()\" class=\"card-item\">\r\n <mat-card-header>\r\n <mat-card-title>{{getTitleValue(row)}}</mat-card-title>\r\n <mat-card-subtitle *ngIf=\"getSubtitleValue(row)\">{{getSubtitleValue(row)}}</mat-card-subtitle>\r\n </mat-card-header>\r\n \r\n <mat-card-content>\r\n <div class=\"content-item\" *ngFor=\"let content of getContentValues(row)\">\r\n <span class=\"label\">{{content.label}}:</span>\r\n <span class=\"value\">{{content.value}}</span>\r\n </div>\r\n </mat-card-content>\r\n \r\n <mat-card-actions align=\"end\">\r\n <ng-container *ngFor=\"let button of displayedButtons\">\r\n <button *ngIf=\"!config.flatButtons && testVisible(row, button.name)\" \r\n mat-mini-fab\r\n [matTooltip]=\"button.tip ?? button.name | titlecase\"\r\n matTooltipPosition=\"above\"\r\n [ngStyle]=\"{'background-color': getButtonColor(button, row)}\"\r\n [disabled]=\"testDisabled(row, button.name)\"\r\n (click)=\"onActionClick(button.name, row)\">\r\n <mat-icon>{{getIcon(button.name)}}</mat-icon>\r\n </button>\r\n \r\n <button *ngIf=\"config.flatButtons && testVisible(row, button.name)\"\r\n mat-icon-button \r\n [matTooltip]=\"button.tip ?? button.name | titlecase\"\r\n matTooltipPosition=\"above\"\r\n [disabled]=\"testDisabled(row, button.name)\"\r\n (click)=\"onActionClick(button.name, row)\">\r\n <mat-icon [ngStyle]=\"{'color': getButtonColor(button, row)}\">{{getIcon(button.name)}}</mat-icon>\r\n </button>\r\n </ng-container>\r\n </mat-card-actions>\r\n </mat-card>\r\n </div>", styles: [".cards-container{display:grid;gap:20px;padding:20px}.card-item{height:100%;transition:transform .2s}.card-item:hover{transform:translateY(-5px)}.content-item{margin:8px 0;display:grid;grid-template-columns:minmax(33%,1fr) minmax(67%,2fr);gap:8px;align-items:baseline}.label{font-weight:500;color:#000000b3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.value{text-align:left;word-break:break-word}mat-card-actions{padding:16px;display:flex;gap:8px}.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}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i3.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.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: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }] }); }
12589
12890
  }
12590
12891
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CardsComponent, decorators: [{
12591
12892
  type: Component,
@@ -12810,11 +13111,7 @@ class GroupsComponent {
12810
13111
  getVisibleButtons(item) {
12811
13112
  if (!this.displayedButtons)
12812
13113
  return [];
12813
- return this.displayedButtons.filter(button => {
12814
- if (!button.visible)
12815
- return true;
12816
- return button.visible(item);
12817
- });
13114
+ return this.displayedButtons.filter(button => Core.isItemVisible(button, item)); // Changed: unified visible/hidden (boolean | condition)
12818
13115
  }
12819
13116
  // Get button icon using ButtonService to apply defaults for standard buttons
12820
13117
  getButtonIcon(button) {
@@ -12847,7 +13144,7 @@ class GroupsComponent {
12847
13144
  });
12848
13145
  }
12849
13146
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: GroupsComponent, deps: [{ token: ConditionService }, { token: ButtonService }, { token: DataServiceLib }, { token: i2.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component }); }
12850
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: GroupsComponent, isStandalone: false, selector: "spa-groups", inputs: { config: "config", dataSource: "dataSource", displayedButtons: "displayedButtons" }, outputs: { actionClick: "actionClick" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"groups-filter\" *ngIf=\"config?.groupConfig?.filterEnabled !== false\">\r\n <mat-form-field appearance=\"outline\" class=\"filter-field\">\r\n <mat-icon matPrefix>search</mat-icon>\r\n <input matInput placeholder=\"Filter\" [(ngModel)]=\"filterText\" (ngModelChange)=\"applyFilter()\">\r\n <button *ngIf=\"filterText\" mat-icon-button matSuffix (click)=\"filterText = ''; applyFilter()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n</div>\r\n\r\n<div class=\"groups-container\" cdkDropListGroup>\r\n\r\n <mat-card *ngFor=\"let group of groupedData\" class=\"group-card\">\r\n\r\n <div class=\"group-header\">\r\n <div class=\"header-left\">\r\n <mat-icon *ngIf=\"group.icon\" [style.color]=\"group.color\" class=\"group-icon\">{{group.icon}}</mat-icon>\r\n <label class=\"group-title\">{{group.displayName}}</label>\r\n <label *ngIf=\"config.groupConfig.showGroupCount !== false\" class=\"group-count\">({{group.items.length}})</label>\r\n </div>\r\n\r\n <div class=\"header-right\">\r\n <button\r\n *ngFor=\"let button of getVisibleHeaderButtons(group)\"\r\n mat-icon-button\r\n [matTooltip]=\"getHeaderButtonTooltip(button)\"\r\n matTooltipPosition=\"above\"\r\n [style.color]=\"getHeaderButtonColor(button, group)\"\r\n [disabled]=\"isHeaderButtonDisabled(button, group)\"\r\n (click)=\"headerButtonClicked(button, group)\">\r\n <mat-icon>{{getHeaderButtonIcon(button)}}</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <hr class=\"group-divider\" />\r\n\r\n <!-- Empty state: show when no items and drag is disabled -->\r\n <div *ngIf=\"group.items.length === 0 && !config.groupConfig.dragEnabled\" class=\"empty-state\">\r\n <label>{{config.groupConfig.emptyGroupMessage ?? 'Empty'}}</label>\r\n </div>\r\n\r\n <!-- Changed: Replaced mat-chip-set with flex container of mat-stroked-button for cleaner button style -->\r\n <div\r\n *ngIf=\"group.items.length > 0 || config.groupConfig.dragEnabled\"\r\n cdkDropList\r\n [cdkDropListData]=\"group.items\"\r\n [cdkDropListDisabled]=\"!config.groupConfig.dragEnabled\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n (cdkDropListEntered)=\"onDropListEntered($event)\"\r\n (cdkDropListExited)=\"onDropListExited($event)\"\r\n [class.drop-highlight]=\"group === highlightedGroup\"\r\n class=\"drop-list items-container\">\r\n\r\n <!-- Empty state inside drop list when drag is enabled -->\r\n <div *ngIf=\"group.items.length === 0\" class=\"empty-state drop-empty\">\r\n <label>{{config.groupConfig.emptyGroupMessage ?? 'Empty'}}</label>\r\n </div>\r\n\r\n <button\r\n mat-stroked-button\r\n *ngFor=\"let item of group.items\"\r\n cdkDrag [cdkDragData]=\"item\"\r\n [cdkDragDisabled]=\"!config.groupConfig.dragEnabled\"\r\n [matMenuTriggerFor]=\"config.groupConfig.contextMenuEnabled !== false ? itemMenu : null\"\r\n [matMenuTriggerData]=\"{item: item, group: group}\"\r\n class=\"item-button\">\r\n\r\n <mat-icon\r\n *ngIf=\"getItemIcon(item)\"\r\n [style.color]=\"getItemIconColor(item)\"\r\n [matTooltip]=\"getItemIconTip(item)\"\r\n matTooltipPosition=\"above\"\r\n class=\"item-icon\">\r\n {{getItemIcon(item)}}\r\n </mat-icon>\r\n\r\n {{getItemText(item)}}\r\n\r\n <ng-container *ngFor=\"let additionalIcon of getVisibleAdditionalIcons(item)\">\r\n <mat-icon\r\n [style.color]=\"additionalIcon.color\"\r\n [matTooltip]=\"getIconTooltip(additionalIcon, item)\"\r\n matTooltipPosition=\"above\"\r\n class=\"item-additional-icon\">\r\n {{additionalIcon.name}}\r\n </mat-icon>\r\n </ng-container>\r\n\r\n </button>\r\n </div>\r\n\r\n </mat-card>\r\n\r\n</div>\r\n\r\n<mat-menu #itemMenu=\"matMenu\">\r\n <ng-template matMenuContent let-item=\"item\" let-group=\"group\">\r\n <button\r\n *ngFor=\"let button of getVisibleButtons(item)\"\r\n mat-menu-item\r\n [disabled]=\"isButtonDisabled(button, item)\"\r\n (click)=\"itemActionClicked(button.name, item, group)\">\r\n <mat-icon [style.color]=\"getButtonIconColor(button, item)\">{{getButtonIcon(button)}}</mat-icon>\r\n <span>{{button.display ?? button.tip ?? (button.name | titlecase)}}</span>\r\n </button>\r\n </ng-template>\r\n</mat-menu>\r\n", styles: [".groups-filter{display:flex;justify-content:flex-end;margin-bottom:8px;padding-right:10px}.filter-field{width:300px;font-size:13px}.filter-field ::ng-deep .mat-mdc-form-field-infix{padding-top:8px!important;padding-bottom:8px!important;min-height:36px}.groups-container{display:flex;flex-direction:column;gap:20px;padding-right:10px;margin-left:0}.group-card{width:100%;margin-bottom:0;padding:16px}.group-header{display:flex;justify-content:space-between;align-items:center;padding:5px 0}.header-left{display:flex;align-items:center;gap:5px}.header-right{display:flex;align-items:center;gap:4px}.group-icon{padding-top:5px;margin-right:5px;font-size:24px;width:24px;height:24px}.group-title{font-size:24px;font-weight:300}.group-count{font-size:12px;font-weight:300;margin-left:5px}.group-divider{margin-top:5px;margin-bottom:10px}.empty-state{display:flex;justify-content:center;align-items:center;padding:10px}.empty-state label{color:#757575;font-style:italic}.items-container{display:flex;flex-wrap:wrap;gap:10px;padding:8px 0;align-items:center}.item-button{cursor:pointer;font-weight:400;letter-spacing:.01em;transition:box-shadow .2s ease,background-color .2s ease}.item-button:hover{box-shadow:0 1px 4px #00000026;background-color:#00000005}.item-icon{font-size:18px;width:18px;height:18px;margin-right:4px;vertical-align:middle}.item-additional-icon{font-size:16px;width:16px;height:16px;margin-left:6px;vertical-align:middle;opacity:.85}button[mat-icon-button]{width:32px;height:32px}button[mat-icon-button] mat-icon{font-size:18px;margin-top:-3px}.drop-list{min-height:40px}.drop-empty{padding:5px 10px}.cdk-drag-preview{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;opacity:.9;border-radius:4px}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.drop-highlight{border:2px dashed #90caf9;border-radius:8px;background:#90caf90d}.cdk-drop-list-dragging .item-button:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$4.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i4$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i3.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: i3.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: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i14$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i14$1.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i14$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }] }); }
13147
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: GroupsComponent, isStandalone: false, selector: "spa-groups", inputs: { config: "config", dataSource: "dataSource", displayedButtons: "displayedButtons" }, outputs: { actionClick: "actionClick" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"groups-filter\" *ngIf=\"config?.groupConfig?.filterEnabled !== false\">\r\n <mat-form-field appearance=\"outline\" class=\"filter-field\">\r\n <mat-icon matPrefix>search</mat-icon>\r\n <input matInput placeholder=\"Filter\" [(ngModel)]=\"filterText\" (ngModelChange)=\"applyFilter()\">\r\n <button *ngIf=\"filterText\" mat-icon-button matSuffix (click)=\"filterText = ''; applyFilter()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n</div>\r\n\r\n<div class=\"groups-container\" cdkDropListGroup>\r\n\r\n <mat-card *ngFor=\"let group of groupedData\" class=\"group-card\">\r\n\r\n <div class=\"group-header\">\r\n <div class=\"header-left\">\r\n <mat-icon *ngIf=\"group.icon\" [style.color]=\"group.color\" class=\"group-icon\">{{group.icon}}</mat-icon>\r\n <label class=\"group-title\">{{group.displayName}}</label>\r\n <label *ngIf=\"config.groupConfig.showGroupCount !== false\" class=\"group-count\">({{group.items.length}})</label>\r\n </div>\r\n\r\n <div class=\"header-right\">\r\n <button\r\n *ngFor=\"let button of getVisibleHeaderButtons(group)\"\r\n mat-icon-button\r\n [matTooltip]=\"getHeaderButtonTooltip(button)\"\r\n matTooltipPosition=\"above\"\r\n [style.color]=\"getHeaderButtonColor(button, group)\"\r\n [disabled]=\"isHeaderButtonDisabled(button, group)\"\r\n (click)=\"headerButtonClicked(button, group)\">\r\n <mat-icon>{{getHeaderButtonIcon(button)}}</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <hr class=\"group-divider\" />\r\n\r\n <!-- Empty state: show when no items and drag is disabled -->\r\n <div *ngIf=\"group.items.length === 0 && !config.groupConfig.dragEnabled\" class=\"empty-state\">\r\n <label>{{config.groupConfig.emptyGroupMessage ?? 'Empty'}}</label>\r\n </div>\r\n\r\n <!-- Changed: Replaced mat-chip-set with flex container of mat-stroked-button for cleaner button style -->\r\n <div\r\n *ngIf=\"group.items.length > 0 || config.groupConfig.dragEnabled\"\r\n cdkDropList\r\n [cdkDropListData]=\"group.items\"\r\n [cdkDropListDisabled]=\"!config.groupConfig.dragEnabled\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n (cdkDropListEntered)=\"onDropListEntered($event)\"\r\n (cdkDropListExited)=\"onDropListExited($event)\"\r\n [class.drop-highlight]=\"group === highlightedGroup\"\r\n class=\"drop-list items-container\">\r\n\r\n <!-- Empty state inside drop list when drag is enabled -->\r\n <div *ngIf=\"group.items.length === 0\" class=\"empty-state drop-empty\">\r\n <label>{{config.groupConfig.emptyGroupMessage ?? 'Empty'}}</label>\r\n </div>\r\n\r\n <button\r\n mat-stroked-button\r\n *ngFor=\"let item of group.items\"\r\n cdkDrag [cdkDragData]=\"item\"\r\n [cdkDragDisabled]=\"!config.groupConfig.dragEnabled\"\r\n [matMenuTriggerFor]=\"config.groupConfig.contextMenuEnabled !== false ? itemMenu : null\"\r\n [matMenuTriggerData]=\"{item: item, group: group}\"\r\n class=\"item-button\">\r\n\r\n <mat-icon\r\n *ngIf=\"getItemIcon(item)\"\r\n [style.color]=\"getItemIconColor(item)\"\r\n [matTooltip]=\"getItemIconTip(item)\"\r\n matTooltipPosition=\"above\"\r\n class=\"item-icon\">\r\n {{getItemIcon(item)}}\r\n </mat-icon>\r\n\r\n {{getItemText(item)}}\r\n\r\n <ng-container *ngFor=\"let additionalIcon of getVisibleAdditionalIcons(item)\">\r\n <mat-icon\r\n [style.color]=\"additionalIcon.color\"\r\n [matTooltip]=\"getIconTooltip(additionalIcon, item)\"\r\n matTooltipPosition=\"above\"\r\n class=\"item-additional-icon\">\r\n {{additionalIcon.name}}\r\n </mat-icon>\r\n </ng-container>\r\n\r\n </button>\r\n </div>\r\n\r\n </mat-card>\r\n\r\n</div>\r\n\r\n<mat-menu #itemMenu=\"matMenu\">\r\n <ng-template matMenuContent let-item=\"item\" let-group=\"group\">\r\n <button\r\n *ngFor=\"let button of getVisibleButtons(item)\"\r\n mat-menu-item\r\n [disabled]=\"isButtonDisabled(button, item)\"\r\n (click)=\"itemActionClicked(button.name, item, group)\">\r\n <mat-icon [style.color]=\"getButtonIconColor(button, item)\">{{getButtonIcon(button)}}</mat-icon>\r\n <span>{{button.display ?? button.tip ?? (button.name | titlecase)}}</span>\r\n </button>\r\n </ng-template>\r\n</mat-menu>\r\n", styles: [".groups-filter{display:flex;justify-content:flex-end;margin-bottom:8px;padding-right:10px}.filter-field{width:300px;font-size:13px}.filter-field ::ng-deep .mat-mdc-form-field-infix{padding-top:8px!important;padding-bottom:8px!important;min-height:36px}.groups-container{display:flex;flex-direction:column;gap:20px;padding-right:10px;margin-left:0}.group-card{width:100%;margin-bottom:0;padding:16px}.group-header{display:flex;justify-content:space-between;align-items:center;padding:5px 0}.header-left{display:flex;align-items:center;gap:5px}.header-right{display:flex;align-items:center;gap:4px}.group-icon{padding-top:5px;margin-right:5px;font-size:24px;width:24px;height:24px}.group-title{font-size:24px;font-weight:300}.group-count{font-size:12px;font-weight:300;margin-left:5px}.group-divider{margin-top:5px;margin-bottom:10px}.empty-state{display:flex;justify-content:center;align-items:center;padding:10px}.empty-state label{color:#757575;font-style:italic}.items-container{display:flex;flex-wrap:wrap;gap:10px;padding:8px 0;align-items:center}.item-button{cursor:pointer;font-weight:400;letter-spacing:.01em;transition:box-shadow .2s ease,background-color .2s ease}.item-button:hover{box-shadow:0 1px 4px #00000026;background-color:#00000005}.item-icon{font-size:18px;width:18px;height:18px;margin-right:4px;vertical-align:middle}.item-additional-icon{font-size:16px;width:16px;height:16px;margin-left:6px;vertical-align:middle;opacity:.85}button[mat-icon-button]{width:32px;height:32px}button[mat-icon-button] mat-icon{font-size:18px;margin-top:-3px}.drop-list{min-height:40px}.drop-empty{padding:5px 10px}.cdk-drag-preview{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;opacity:.9;border-radius:4px}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.drop-highlight{border:2px dashed #90caf9;border-radius:8px;background:#90caf90d}.cdk-drop-list-dragging .item-button:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$4.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i4$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i3.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: i3.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: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i14$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i14$1.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i14$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }] }); }
12851
13148
  }
12852
13149
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: GroupsComponent, decorators: [{
12853
13150
  type: Component,
@@ -14364,11 +14661,7 @@ class StepsComponent {
14364
14661
  getVisibleSteps() {
14365
14662
  if (!this.steps?.length)
14366
14663
  return [];
14367
- return this.steps.filter(step => {
14368
- if (!step.hiddenCondition)
14369
- return true;
14370
- return !step.hiddenCondition(this.data || {});
14371
- });
14664
+ return this.steps.filter(step => Core.isItemVisible(step, this.data || {})); // Changed: unified visible/hidden replaces hiddenCondition
14372
14665
  }
14373
14666
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StepsComponent, deps: [{ token: i1$3.BreakpointObserver }, { token: DataServiceLib }], target: i0.ɵɵFactoryTarget.Component }); }
14374
14667
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: StepsComponent, isStandalone: false, selector: "spa-steps", inputs: { value: "value", config: "config", data: "data" }, providers: [{
@@ -14797,6 +15090,7 @@ class NavMenuComponent {
14797
15090
  this.isExpanded = true;
14798
15091
  }
14799
15092
  this.setupService.loadCount(); // Added: Refresh setup badge on nav init (persists across F5 — no SignalR for setup)
15093
+ this.setupService.loadModules(); // Added (Setup v2): module catalog for moduleKey menu gating — loads once per shell init
14800
15094
  // Added: start recording the visited page for resumeLastRoute. The nav shell only exists while signed in,
14801
15095
  // so this one subscription covers every in-app navigation without touching individual pages.
14802
15096
  this.lastRouteService.start();
@@ -14847,6 +15141,8 @@ class NavMenuComponent {
14847
15141
  }
14848
15142
  // Added: Check if a capItem is enabled by subscription plan (featureKey gating)
14849
15143
  isFeatureAllowed(cap) {
15144
+ if (cap.moduleKey && !this.setupService.isModuleEnabled(cap.moduleKey))
15145
+ return false; // Added (Setup v2): tenant module choice hides unused menus
14850
15146
  if (!cap.featureKey)
14851
15147
  return true; // No feature restriction
14852
15148
  return this.subscriptionService.isModuleEnabled(cap.featureKey);
@@ -14935,11 +15231,11 @@ class NavMenuComponent {
14935
15231
  return !this.isMiniSidebar || this.isMiniHovered;
14936
15232
  }
14937
15233
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: NavMenuComponent, deps: [{ token: i1$2.Router }, { token: AuthService }, { token: StorageService }, { token: NotificationsService }, { token: i1$3.BreakpointObserver }, { token: DataServiceLib }, { token: i4.MatDialog }, { token: SubscriptionService }, { token: SetupService }, { token: LastRouteService }, { token: OfflineService }], target: i0.ɵɵFactoryTarget.Component }); }
14938
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: NavMenuComponent, isStandalone: false, selector: "spa-nav-menu", inputs: { appConfig: "appConfig", footer: "footer" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- ============================================================ -->\r\n<!-- TOP-MODERN (navigation == 'top-modern') -->\r\n<!-- Changed: Modernised horizontal top navigation -->\r\n<!-- ============================================================ -->\r\n<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n\r\n <nav class=\"tm-navbar\" [class.tm-scrolled]=\"topbarScrolled\">\r\n <div class=\"tm-bar\">\r\n\r\n <!-- Brand -->\r\n <div class=\"tm-brand\" (click)=\"modernNavigate('')\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" class=\"tm-logo\" alt=\"logo\" />\r\n <div class=\"tm-brand-text\">\r\n <div class=\"tm-app-name\">{{appConfig.appName}}</div>\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" class=\"tm-tenant\">{{tenantName}}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Mobile toggle -->\r\n <button class=\"tm-toggler\" type=\"button\" aria-label=\"Toggle navigation\"\r\n [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <mat-icon>{{isExpanded ? 'close' : 'menu'}}</mat-icon>\r\n </button>\r\n\r\n <!-- Menu items -->\r\n <div class=\"tm-menu\" [class.tm-menu-open]=\"isExpanded\">\r\n\r\n <ng-container *ngFor=\"let item of dataService.appConfig.capItems\">\r\n <div class=\"tm-item-wrap\"\r\n *ngIf=\"myRole[item.name] && item.showMenu && isFeatureAllowed(item)\">\r\n\r\n <!-- Simple item (no sub-items, or sub-items ignored for display) -->\r\n <button *ngIf=\"!item.capSubItems || item.ignoreSubsDisplay\"\r\n class=\"tm-item\"\r\n [class.tm-item-active]=\"isActiveRoute(item.link)\"\r\n (click)=\"modernNavigate(item.link)\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n </button>\r\n\r\n <!-- Parent item with displayed sub-items -->\r\n <ng-container *ngIf=\"item.capSubItems && !item.ignoreSubsDisplay\">\r\n <button class=\"tm-item tm-item-parent\"\r\n [class.tm-item-active]=\"isParentActive(item)\"\r\n [matMenuTriggerFor]=\"tmSubMenu\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n <!-- Changed: Caret signals this item has more items beneath it -->\r\n <mat-icon class=\"tm-item-caret\">expand_more</mat-icon>\r\n </button>\r\n\r\n <mat-menu #tmSubMenu=\"matMenu\" class=\"tm-submenu-panel\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <ng-container *ngFor=\"let sub of getSubItems(item)\">\r\n <button *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n mat-menu-item\r\n [class.tm-sub-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\">{{sub.icon}}</mat-icon>\r\n <span>{{sub.display}}</span>\r\n </button>\r\n </ng-container>\r\n </mat-menu>\r\n </ng-container>\r\n\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Right-side actions -->\r\n <div class=\"tm-actions\" [class.tm-actions-open]=\"isExpanded\">\r\n\r\n <ng-container *ngIf=\"dataService.appConfig.multitenant\">\r\n <button mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </ng-container>\r\n\r\n <span class=\"tm-divider-v\"></span>\r\n\r\n <!-- Profile -->\r\n <button mat-button class=\"tm-user-btn\" [matMenuTriggerFor]=\"tmProfileMenu\">\r\n <mat-icon class=\"tm-user-icon\">account_circle</mat-icon>\r\n <span class=\"tm-user-name\">{{loggedUserFullName}}</span>\r\n </button>\r\n\r\n <mat-menu #tmProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item (click)=\"modernNavigate('home/user/profile')\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon><span>Log Off</span>\r\n </button>\r\n </mat-menu>\r\n\r\n <!-- Sign out \u2014 Changed: aligned via flex-centred action button -->\r\n <button mat-icon-button class=\"tm-action-btn tm-signout\" (click)=\"logoff()\" matTooltip=\"Sign Out\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n </div>\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Top-modern page content \u2014 Changed: use original 'top' background (tin-bg-image), no footer bar -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Not logged in fallback for top-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}.tm-navbar{position:sticky;top:0;z-index:1030;background-color:#03a;color:#fff;box-shadow:0 2px 12px #0000001f;transition:background-color .3s ease,backdrop-filter .3s ease,box-shadow .3s ease}.tm-navbar.tm-scrolled{background-color:#0033aad9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 4px 18px #0000002e}.tm-bar{display:flex;align-items:center;flex-wrap:nowrap;min-height:60px;padding:6px 16px;gap:4px}.tm-brand{display:flex;align-items:center;gap:12px;flex-shrink:0;margin-right:18px;cursor:pointer;-webkit-user-select:none;user-select:none}.tm-logo{height:40px;width:auto;object-fit:contain}.tm-app-name{font-size:20px;font-weight:500;line-height:1.2;white-space:nowrap}.tm-tenant{font-size:12px;font-weight:400;color:#ffffffb3;line-height:1.2}.tm-toggler{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.4);border-radius:8px;color:#fff;cursor:pointer;align-items:center;justify-content:center;width:40px;height:40px}.tm-toggler mat-icon{color:#fff}.tm-menu{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 auto;min-width:0;row-gap:4px}.tm-item-wrap{display:flex;align-items:center;position:relative}.tm-item-wrap:not(:first-child):before{content:\"\";width:1px;height:18px;background:#ffffff2e;margin:0 2px;flex-shrink:0}.tm-item{position:relative;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 14px;margin:0 2px;background:transparent;border:none;border-radius:8px;color:#ffffffeb;font-size:14px;font-weight:400;letter-spacing:.2px;white-space:nowrap;cursor:pointer;transition:background .18s ease,color .18s ease}.tm-item:after{content:\"\";position:absolute;left:12px;right:12px;bottom:5px;height:1px;border-radius:1px;background:#ffffff80;transform:scaleX(0);transform-origin:center;transition:transform .25s cubic-bezier(.4,0,.2,1)}.tm-item:hover{color:#fff}.tm-item:hover:after{transform:scaleX(1)}.tm-item.tm-item-active:after{transform:scaleX(1)}.tm-item-icon{font-size:19px!important;width:19px!important;height:19px!important;display:inline-flex!important;align-items:center;justify-content:center;color:inherit!important}.tm-item-caret{font-size:18px!important;width:18px!important;height:18px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-left:-2px;margin-right:-4px;color:#ffffffb3!important;transition:transform .2s ease}.tm-item:hover .tm-item-caret,.tm-item-active .tm-item-caret{color:#fff!important}.tm-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;margin-left:auto}.tm-actions .mat-mdc-icon-button,.tm-action-btn{display:inline-flex!important;align-items:center!important;justify-content:center!important;color:#fff!important}.tm-actions mat-icon{color:#fff!important}.tm-divider-v{width:1px;height:24px;background:#ffffff38;margin:0 6px;flex-shrink:0}.tm-user-btn{display:inline-flex!important;align-items:center!important;gap:6px;height:40px;color:#fff!important;border-radius:8px;transition:background .18s ease}.tm-user-btn:hover{background:#ffffff1f}.tm-user-icon{font-size:24px!important;width:24px!important;height:24px!important;color:#fff!important}.tm-user-name{font-size:14px;font-weight:400;white-space:nowrap}::ng-deep .tm-submenu-panel .tm-sub-active{background:#0033aa14;font-weight:600;color:#03a}::ng-deep .tm-submenu-panel .tm-sub-active .mat-icon{color:#03a}@media (max-width: 991px){.tm-toggler{display:inline-flex}.tm-menu,.tm-actions{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;align-items:stretch;background:#03a;padding:8px 12px;box-shadow:0 8px 18px #0003;z-index:1029}.tm-menu.tm-menu-open{display:flex}.tm-actions.tm-actions-open{display:flex;top:100%;border-top:1px solid rgba(255,255,255,.12)}.tm-item-wrap{width:100%}.tm-item-wrap:not(:first-child):before{width:100%;height:1px;margin:2px 0}.tm-item{width:100%;justify-content:flex-start;height:44px;margin:0}.tm-item:after{inset:8px auto 8px 0;width:4px;height:auto;transform:scaleY(0);transform-origin:center}.tm-item:hover:after,.tm-item.tm-item-active:after{transform:scaleY(1)}.tm-item-caret{margin-left:auto}.tm-divider-v{display:none}.tm-user-btn{justify-content:flex-start;width:100%}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: i4$2.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "component", type: i3.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: i3.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: "component", type: i17.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i17.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i19.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i19.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i19.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "component", type: i20.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i6$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i6$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "component", type: LoaderComponent, selector: "spa-loader", inputs: ["logo"] }, { kind: "component", type: ToastComponent, selector: "spa-toast" }, { kind: "component", type: AgentComponent, selector: "spa-agent" }, { kind: "component", type: OfflineIndicatorComponent, selector: "spa-offline-indicator" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
15234
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: NavMenuComponent, isStandalone: false, selector: "spa-nav-menu", inputs: { appConfig: "appConfig", footer: "footer" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- ============================================================ -->\r\n<!-- TOP-MODERN (navigation == 'top-modern') -->\r\n<!-- Changed: Modernised horizontal top navigation -->\r\n<!-- ============================================================ -->\r\n<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n\r\n <nav class=\"tm-navbar\" [class.tm-scrolled]=\"topbarScrolled\">\r\n <div class=\"tm-bar\">\r\n\r\n <!-- Brand -->\r\n <div class=\"tm-brand\" (click)=\"modernNavigate('')\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" class=\"tm-logo\" alt=\"logo\" />\r\n <div class=\"tm-brand-text\">\r\n <div class=\"tm-app-name\">{{appConfig.appName}}</div>\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" class=\"tm-tenant\">{{tenantName}}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Mobile toggle -->\r\n <button class=\"tm-toggler\" type=\"button\" aria-label=\"Toggle navigation\"\r\n [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <mat-icon>{{isExpanded ? 'close' : 'menu'}}</mat-icon>\r\n </button>\r\n\r\n <!-- Menu items -->\r\n <div class=\"tm-menu\" [class.tm-menu-open]=\"isExpanded\">\r\n\r\n <ng-container *ngFor=\"let item of dataService.appConfig.capItems\">\r\n <div class=\"tm-item-wrap\"\r\n *ngIf=\"myRole[item.name] && item.showMenu && isFeatureAllowed(item)\">\r\n\r\n <!-- Simple item (no sub-items, or sub-items ignored for display) -->\r\n <button *ngIf=\"!item.capSubItems || item.ignoreSubsDisplay\"\r\n class=\"tm-item\"\r\n [class.tm-item-active]=\"isActiveRoute(item.link)\"\r\n (click)=\"modernNavigate(item.link)\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n </button>\r\n\r\n <!-- Parent item with displayed sub-items -->\r\n <ng-container *ngIf=\"item.capSubItems && !item.ignoreSubsDisplay\">\r\n <button class=\"tm-item tm-item-parent\"\r\n [class.tm-item-active]=\"isParentActive(item)\"\r\n [matMenuTriggerFor]=\"tmSubMenu\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n <!-- Changed: Caret signals this item has more items beneath it -->\r\n <mat-icon class=\"tm-item-caret\">expand_more</mat-icon>\r\n </button>\r\n\r\n <mat-menu #tmSubMenu=\"matMenu\" class=\"tm-submenu-panel\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <ng-container *ngFor=\"let sub of getSubItems(item)\">\r\n <button *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n mat-menu-item\r\n [class.tm-sub-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\">{{sub.icon}}</mat-icon>\r\n <span>{{sub.display}}</span>\r\n </button>\r\n </ng-container>\r\n </mat-menu>\r\n </ng-container>\r\n\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Right-side actions -->\r\n <div class=\"tm-actions\" [class.tm-actions-open]=\"isExpanded\">\r\n\r\n <ng-container *ngIf=\"dataService.appConfig.multitenant\">\r\n <button mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </ng-container>\r\n\r\n <span class=\"tm-divider-v\"></span>\r\n\r\n <!-- Profile -->\r\n <button mat-button class=\"tm-user-btn\" [matMenuTriggerFor]=\"tmProfileMenu\">\r\n <mat-icon class=\"tm-user-icon\">account_circle</mat-icon>\r\n <span class=\"tm-user-name\">{{loggedUserFullName}}</span>\r\n </button>\r\n\r\n <mat-menu #tmProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item (click)=\"modernNavigate('home/user/profile')\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon><span>Log Off</span>\r\n </button>\r\n </mat-menu>\r\n\r\n <!-- Sign out \u2014 Changed: aligned via flex-centred action button -->\r\n <button mat-icon-button class=\"tm-action-btn tm-signout\" (click)=\"logoff()\" matTooltip=\"Sign Out\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n </div>\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Top-modern page content \u2014 Changed: use original 'top' background (tin-bg-image), no footer bar -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Not logged in fallback for top-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}.tm-navbar{position:sticky;top:0;z-index:1030;background-color:#03a;color:#fff;box-shadow:0 2px 12px #0000001f;transition:background-color .3s ease,backdrop-filter .3s ease,box-shadow .3s ease}.tm-navbar.tm-scrolled{background-color:#0033aad9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 4px 18px #0000002e}.tm-bar{display:flex;align-items:center;flex-wrap:nowrap;min-height:60px;padding:6px 16px;gap:4px}.tm-brand{display:flex;align-items:center;gap:12px;flex-shrink:0;margin-right:18px;cursor:pointer;-webkit-user-select:none;user-select:none}.tm-logo{height:40px;width:auto;object-fit:contain}.tm-app-name{font-size:20px;font-weight:500;line-height:1.2;white-space:nowrap}.tm-tenant{font-size:12px;font-weight:400;color:#ffffffb3;line-height:1.2}.tm-toggler{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.4);border-radius:8px;color:#fff;cursor:pointer;align-items:center;justify-content:center;width:40px;height:40px}.tm-toggler mat-icon{color:#fff}.tm-menu{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 auto;min-width:0;row-gap:4px;justify-content:flex-end}.tm-item-wrap{display:flex;align-items:center;position:relative}.tm-item-wrap:not(:first-child):before{content:\"\";width:1px;height:18px;background:#ffffff2e;margin:0 2px;flex-shrink:0}.tm-item{position:relative;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 14px;margin:0 2px;background:transparent;border:none;border-radius:8px;color:#ffffffeb;font-size:14px;font-weight:400;letter-spacing:.2px;white-space:nowrap;cursor:pointer;transition:background .18s ease,color .18s ease}.tm-item:after{content:\"\";position:absolute;left:12px;right:12px;bottom:5px;height:1px;border-radius:1px;background:#ffffff80;transform:scaleX(0);transform-origin:center;transition:transform .25s cubic-bezier(.4,0,.2,1)}.tm-item:hover{color:#fff}.tm-item:hover:after{transform:scaleX(1)}.tm-item.tm-item-active:after{transform:scaleX(1)}.tm-item-icon{font-size:19px!important;width:19px!important;height:19px!important;display:inline-flex!important;align-items:center;justify-content:center;color:inherit!important}.tm-item-caret{font-size:18px!important;width:18px!important;height:18px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-left:-2px;margin-right:-4px;color:#ffffffb3!important;transition:transform .2s ease}.tm-item:hover .tm-item-caret,.tm-item-active .tm-item-caret{color:#fff!important}.tm-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;margin-left:auto}.tm-actions .mat-mdc-icon-button,.tm-action-btn{display:inline-flex!important;align-items:center!important;justify-content:center!important;color:#fff!important}.tm-actions mat-icon{color:#fff!important}.tm-divider-v{width:1px;height:24px;background:#ffffff38;margin:0 6px;flex-shrink:0}.tm-user-btn{display:inline-flex!important;align-items:center!important;gap:6px;height:40px;color:#fff!important;border-radius:8px;transition:background .18s ease}.tm-user-btn:hover{background:#ffffff1f}.tm-user-icon{font-size:24px!important;width:24px!important;height:24px!important;color:#fff!important}.tm-user-name{font-size:14px;font-weight:400;white-space:nowrap}::ng-deep .tm-submenu-panel .tm-sub-active{background:#0033aa14;font-weight:600;color:#03a}::ng-deep .tm-submenu-panel .tm-sub-active .mat-icon{color:#03a}@media (max-width: 991px){.tm-toggler{display:inline-flex}.tm-menu,.tm-actions{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;align-items:stretch;background:#03a;padding:8px 12px;box-shadow:0 8px 18px #0003;z-index:1029}.tm-menu.tm-menu-open{display:flex}.tm-actions.tm-actions-open{display:flex;top:100%;border-top:1px solid rgba(255,255,255,.12)}.tm-item-wrap{width:100%}.tm-item-wrap:not(:first-child):before{width:100%;height:1px;margin:2px 0}.tm-item{width:100%;justify-content:flex-start;height:44px;margin:0}.tm-item:after{inset:8px auto 8px 0;width:4px;height:auto;transform:scaleY(0);transform-origin:center}.tm-item:hover:after,.tm-item.tm-item-active:after{transform:scaleY(1)}.tm-item-caret{margin-left:auto}.tm-divider-v{display:none}.tm-user-btn{justify-content:flex-start;width:100%}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: i4$2.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "component", type: i3.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: i3.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: "component", type: i17.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i17.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i19.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i19.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i19.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "component", type: i20.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i8.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i8.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "component", type: LoaderComponent, selector: "spa-loader", inputs: ["logo"] }, { kind: "component", type: ToastComponent, selector: "spa-toast" }, { kind: "component", type: AgentComponent, selector: "spa-agent" }, { kind: "component", type: OfflineIndicatorComponent, selector: "spa-offline-indicator" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
14939
15235
  }
14940
15236
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: NavMenuComponent, decorators: [{
14941
15237
  type: Component,
14942
- args: [{ selector: 'spa-nav-menu', standalone: false, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- ============================================================ -->\r\n<!-- TOP-MODERN (navigation == 'top-modern') -->\r\n<!-- Changed: Modernised horizontal top navigation -->\r\n<!-- ============================================================ -->\r\n<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n\r\n <nav class=\"tm-navbar\" [class.tm-scrolled]=\"topbarScrolled\">\r\n <div class=\"tm-bar\">\r\n\r\n <!-- Brand -->\r\n <div class=\"tm-brand\" (click)=\"modernNavigate('')\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" class=\"tm-logo\" alt=\"logo\" />\r\n <div class=\"tm-brand-text\">\r\n <div class=\"tm-app-name\">{{appConfig.appName}}</div>\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" class=\"tm-tenant\">{{tenantName}}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Mobile toggle -->\r\n <button class=\"tm-toggler\" type=\"button\" aria-label=\"Toggle navigation\"\r\n [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <mat-icon>{{isExpanded ? 'close' : 'menu'}}</mat-icon>\r\n </button>\r\n\r\n <!-- Menu items -->\r\n <div class=\"tm-menu\" [class.tm-menu-open]=\"isExpanded\">\r\n\r\n <ng-container *ngFor=\"let item of dataService.appConfig.capItems\">\r\n <div class=\"tm-item-wrap\"\r\n *ngIf=\"myRole[item.name] && item.showMenu && isFeatureAllowed(item)\">\r\n\r\n <!-- Simple item (no sub-items, or sub-items ignored for display) -->\r\n <button *ngIf=\"!item.capSubItems || item.ignoreSubsDisplay\"\r\n class=\"tm-item\"\r\n [class.tm-item-active]=\"isActiveRoute(item.link)\"\r\n (click)=\"modernNavigate(item.link)\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n </button>\r\n\r\n <!-- Parent item with displayed sub-items -->\r\n <ng-container *ngIf=\"item.capSubItems && !item.ignoreSubsDisplay\">\r\n <button class=\"tm-item tm-item-parent\"\r\n [class.tm-item-active]=\"isParentActive(item)\"\r\n [matMenuTriggerFor]=\"tmSubMenu\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n <!-- Changed: Caret signals this item has more items beneath it -->\r\n <mat-icon class=\"tm-item-caret\">expand_more</mat-icon>\r\n </button>\r\n\r\n <mat-menu #tmSubMenu=\"matMenu\" class=\"tm-submenu-panel\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <ng-container *ngFor=\"let sub of getSubItems(item)\">\r\n <button *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n mat-menu-item\r\n [class.tm-sub-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\">{{sub.icon}}</mat-icon>\r\n <span>{{sub.display}}</span>\r\n </button>\r\n </ng-container>\r\n </mat-menu>\r\n </ng-container>\r\n\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Right-side actions -->\r\n <div class=\"tm-actions\" [class.tm-actions-open]=\"isExpanded\">\r\n\r\n <ng-container *ngIf=\"dataService.appConfig.multitenant\">\r\n <button mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </ng-container>\r\n\r\n <span class=\"tm-divider-v\"></span>\r\n\r\n <!-- Profile -->\r\n <button mat-button class=\"tm-user-btn\" [matMenuTriggerFor]=\"tmProfileMenu\">\r\n <mat-icon class=\"tm-user-icon\">account_circle</mat-icon>\r\n <span class=\"tm-user-name\">{{loggedUserFullName}}</span>\r\n </button>\r\n\r\n <mat-menu #tmProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item (click)=\"modernNavigate('home/user/profile')\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon><span>Log Off</span>\r\n </button>\r\n </mat-menu>\r\n\r\n <!-- Sign out \u2014 Changed: aligned via flex-centred action button -->\r\n <button mat-icon-button class=\"tm-action-btn tm-signout\" (click)=\"logoff()\" matTooltip=\"Sign Out\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n </div>\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Top-modern page content \u2014 Changed: use original 'top' background (tin-bg-image), no footer bar -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Not logged in fallback for top-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}.tm-navbar{position:sticky;top:0;z-index:1030;background-color:#03a;color:#fff;box-shadow:0 2px 12px #0000001f;transition:background-color .3s ease,backdrop-filter .3s ease,box-shadow .3s ease}.tm-navbar.tm-scrolled{background-color:#0033aad9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 4px 18px #0000002e}.tm-bar{display:flex;align-items:center;flex-wrap:nowrap;min-height:60px;padding:6px 16px;gap:4px}.tm-brand{display:flex;align-items:center;gap:12px;flex-shrink:0;margin-right:18px;cursor:pointer;-webkit-user-select:none;user-select:none}.tm-logo{height:40px;width:auto;object-fit:contain}.tm-app-name{font-size:20px;font-weight:500;line-height:1.2;white-space:nowrap}.tm-tenant{font-size:12px;font-weight:400;color:#ffffffb3;line-height:1.2}.tm-toggler{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.4);border-radius:8px;color:#fff;cursor:pointer;align-items:center;justify-content:center;width:40px;height:40px}.tm-toggler mat-icon{color:#fff}.tm-menu{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 auto;min-width:0;row-gap:4px}.tm-item-wrap{display:flex;align-items:center;position:relative}.tm-item-wrap:not(:first-child):before{content:\"\";width:1px;height:18px;background:#ffffff2e;margin:0 2px;flex-shrink:0}.tm-item{position:relative;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 14px;margin:0 2px;background:transparent;border:none;border-radius:8px;color:#ffffffeb;font-size:14px;font-weight:400;letter-spacing:.2px;white-space:nowrap;cursor:pointer;transition:background .18s ease,color .18s ease}.tm-item:after{content:\"\";position:absolute;left:12px;right:12px;bottom:5px;height:1px;border-radius:1px;background:#ffffff80;transform:scaleX(0);transform-origin:center;transition:transform .25s cubic-bezier(.4,0,.2,1)}.tm-item:hover{color:#fff}.tm-item:hover:after{transform:scaleX(1)}.tm-item.tm-item-active:after{transform:scaleX(1)}.tm-item-icon{font-size:19px!important;width:19px!important;height:19px!important;display:inline-flex!important;align-items:center;justify-content:center;color:inherit!important}.tm-item-caret{font-size:18px!important;width:18px!important;height:18px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-left:-2px;margin-right:-4px;color:#ffffffb3!important;transition:transform .2s ease}.tm-item:hover .tm-item-caret,.tm-item-active .tm-item-caret{color:#fff!important}.tm-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;margin-left:auto}.tm-actions .mat-mdc-icon-button,.tm-action-btn{display:inline-flex!important;align-items:center!important;justify-content:center!important;color:#fff!important}.tm-actions mat-icon{color:#fff!important}.tm-divider-v{width:1px;height:24px;background:#ffffff38;margin:0 6px;flex-shrink:0}.tm-user-btn{display:inline-flex!important;align-items:center!important;gap:6px;height:40px;color:#fff!important;border-radius:8px;transition:background .18s ease}.tm-user-btn:hover{background:#ffffff1f}.tm-user-icon{font-size:24px!important;width:24px!important;height:24px!important;color:#fff!important}.tm-user-name{font-size:14px;font-weight:400;white-space:nowrap}::ng-deep .tm-submenu-panel .tm-sub-active{background:#0033aa14;font-weight:600;color:#03a}::ng-deep .tm-submenu-panel .tm-sub-active .mat-icon{color:#03a}@media (max-width: 991px){.tm-toggler{display:inline-flex}.tm-menu,.tm-actions{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;align-items:stretch;background:#03a;padding:8px 12px;box-shadow:0 8px 18px #0003;z-index:1029}.tm-menu.tm-menu-open{display:flex}.tm-actions.tm-actions-open{display:flex;top:100%;border-top:1px solid rgba(255,255,255,.12)}.tm-item-wrap{width:100%}.tm-item-wrap:not(:first-child):before{width:100%;height:1px;margin:2px 0}.tm-item{width:100%;justify-content:flex-start;height:44px;margin:0}.tm-item:after{inset:8px auto 8px 0;width:4px;height:auto;transform:scaleY(0);transform-origin:center}.tm-item:hover:after,.tm-item.tm-item-active:after{transform:scaleY(1)}.tm-item-caret{margin-left:auto}.tm-divider-v{display:none}.tm-user-btn{justify-content:flex-start;width:100%}}\n"] }]
15238
+ args: [{ selector: 'spa-nav-menu', standalone: false, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- ============================================================ -->\r\n<!-- TOP-MODERN (navigation == 'top-modern') -->\r\n<!-- Changed: Modernised horizontal top navigation -->\r\n<!-- ============================================================ -->\r\n<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n\r\n <nav class=\"tm-navbar\" [class.tm-scrolled]=\"topbarScrolled\">\r\n <div class=\"tm-bar\">\r\n\r\n <!-- Brand -->\r\n <div class=\"tm-brand\" (click)=\"modernNavigate('')\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" class=\"tm-logo\" alt=\"logo\" />\r\n <div class=\"tm-brand-text\">\r\n <div class=\"tm-app-name\">{{appConfig.appName}}</div>\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" class=\"tm-tenant\">{{tenantName}}</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Mobile toggle -->\r\n <button class=\"tm-toggler\" type=\"button\" aria-label=\"Toggle navigation\"\r\n [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <mat-icon>{{isExpanded ? 'close' : 'menu'}}</mat-icon>\r\n </button>\r\n\r\n <!-- Menu items -->\r\n <div class=\"tm-menu\" [class.tm-menu-open]=\"isExpanded\">\r\n\r\n <ng-container *ngFor=\"let item of dataService.appConfig.capItems\">\r\n <div class=\"tm-item-wrap\"\r\n *ngIf=\"myRole[item.name] && item.showMenu && isFeatureAllowed(item)\">\r\n\r\n <!-- Simple item (no sub-items, or sub-items ignored for display) -->\r\n <button *ngIf=\"!item.capSubItems || item.ignoreSubsDisplay\"\r\n class=\"tm-item\"\r\n [class.tm-item-active]=\"isActiveRoute(item.link)\"\r\n (click)=\"modernNavigate(item.link)\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n </button>\r\n\r\n <!-- Parent item with displayed sub-items -->\r\n <ng-container *ngIf=\"item.capSubItems && !item.ignoreSubsDisplay\">\r\n <button class=\"tm-item tm-item-parent\"\r\n [class.tm-item-active]=\"isParentActive(item)\"\r\n [matMenuTriggerFor]=\"tmSubMenu\">\r\n <mat-icon *ngIf=\"item.icon && item.icon != 'navigate_next'\" class=\"tm-item-icon\">{{item.icon}}</mat-icon>\r\n <span class=\"tm-item-text\">{{item.display}}</span>\r\n <!-- Changed: Caret signals this item has more items beneath it -->\r\n <mat-icon class=\"tm-item-caret\">expand_more</mat-icon>\r\n </button>\r\n\r\n <mat-menu #tmSubMenu=\"matMenu\" class=\"tm-submenu-panel\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <ng-container *ngFor=\"let sub of getSubItems(item)\">\r\n <button *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n mat-menu-item\r\n [class.tm-sub-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\">{{sub.icon}}</mat-icon>\r\n <span>{{sub.display}}</span>\r\n </button>\r\n </ng-container>\r\n </mat-menu>\r\n </ng-container>\r\n\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Right-side actions -->\r\n <div class=\"tm-actions\" [class.tm-actions-open]=\"isExpanded\">\r\n\r\n <ng-container *ngIf=\"dataService.appConfig.multitenant\">\r\n <button mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button class=\"tm-action-btn\" (click)=\"modernNavigate('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </ng-container>\r\n\r\n <span class=\"tm-divider-v\"></span>\r\n\r\n <!-- Profile -->\r\n <button mat-button class=\"tm-user-btn\" [matMenuTriggerFor]=\"tmProfileMenu\">\r\n <mat-icon class=\"tm-user-icon\">account_circle</mat-icon>\r\n <span class=\"tm-user-name\">{{loggedUserFullName}}</span>\r\n </button>\r\n\r\n <mat-menu #tmProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item (click)=\"modernNavigate('home/user/profile')\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon><span>Log Off</span>\r\n </button>\r\n </mat-menu>\r\n\r\n <!-- Sign out \u2014 Changed: aligned via flex-centred action button -->\r\n <button mat-icon-button class=\"tm-action-btn tm-signout\" (click)=\"logoff()\" matTooltip=\"Sign Out\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n </div>\r\n\r\n </div>\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Top-modern page content \u2014 Changed: use original 'top' background (tin-bg-image), no footer bar -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'top-modern'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Not logged in fallback for top-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'top-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n<!-- SIDE -->\r\n<mat-toolbar class=\"tin-bg-image-toolbar\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\" style=\"padding: 0px 8px;\">\r\n\r\n <button mat-icon-button (click)=\"toggle()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <img [src]=\"dataService.appConfig.logo\" style=\"height: 50px;\" />\r\n\r\n <div style=\"padding-left: 10px; \">\r\n\r\n <div style=\"font-size: 22px; font-weight: 400;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <!-- <div style=\"font-size: 20px; height: 25px; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div> -->\r\n\r\n <!-- <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <span class=\"toolbar-item-spacer\"></span>\r\n\r\n <!-- buttons -->\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n\r\n <!-- <label style=\"font-size: 14px;\">Hi, {{loggedUserFullName}}</label> -->\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <label style=\"font-size: 14px;margin-right: 20px;\">{{tenantName}}</label>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n\r\n </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"setupService.enabled && ((setupCount$ | async) || 0) > 0 && !smallScreen\" mat-icon-button (click)=\"redirectTo('home/setup')\" matTooltip=\"Getting Started\"> <!-- Added: Setup readiness badge \u2014 hidden when complete or unconfigured -->\r\n <mat-icon [matBadge]=\"setupCount$ | async\" matBadgeColor=\"warn\" matBadgeSize=\"small\">rocket_launch</mat-icon>\r\n </button>\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"redirectTo('home/workflow/notifications')\" matTooltip=\"Notifications\">\r\n <mat-icon [matBadge]=\"notificationCount$ | async\" [matBadgeHidden]=\"(notificationCount$ | async) === 0\" matBadgeColor=\"warn\" matBadgeSize=\"small\">notifications</mat-icon>\r\n </button>\r\n <spa-offline-indicator *ngIf=\"!smallScreen\"></spa-offline-indicator> <!-- Changed: TinSync connection + pending-sync indicator -->\r\n </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}.tm-navbar{position:sticky;top:0;z-index:1030;background-color:#03a;color:#fff;box-shadow:0 2px 12px #0000001f;transition:background-color .3s ease,backdrop-filter .3s ease,box-shadow .3s ease}.tm-navbar.tm-scrolled{background-color:#0033aad9;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 4px 18px #0000002e}.tm-bar{display:flex;align-items:center;flex-wrap:nowrap;min-height:60px;padding:6px 16px;gap:4px}.tm-brand{display:flex;align-items:center;gap:12px;flex-shrink:0;margin-right:18px;cursor:pointer;-webkit-user-select:none;user-select:none}.tm-logo{height:40px;width:auto;object-fit:contain}.tm-app-name{font-size:20px;font-weight:500;line-height:1.2;white-space:nowrap}.tm-tenant{font-size:12px;font-weight:400;color:#ffffffb3;line-height:1.2}.tm-toggler{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.4);border-radius:8px;color:#fff;cursor:pointer;align-items:center;justify-content:center;width:40px;height:40px}.tm-toggler mat-icon{color:#fff}.tm-menu{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 auto;min-width:0;row-gap:4px;justify-content:flex-end}.tm-item-wrap{display:flex;align-items:center;position:relative}.tm-item-wrap:not(:first-child):before{content:\"\";width:1px;height:18px;background:#ffffff2e;margin:0 2px;flex-shrink:0}.tm-item{position:relative;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 14px;margin:0 2px;background:transparent;border:none;border-radius:8px;color:#ffffffeb;font-size:14px;font-weight:400;letter-spacing:.2px;white-space:nowrap;cursor:pointer;transition:background .18s ease,color .18s ease}.tm-item:after{content:\"\";position:absolute;left:12px;right:12px;bottom:5px;height:1px;border-radius:1px;background:#ffffff80;transform:scaleX(0);transform-origin:center;transition:transform .25s cubic-bezier(.4,0,.2,1)}.tm-item:hover{color:#fff}.tm-item:hover:after{transform:scaleX(1)}.tm-item.tm-item-active:after{transform:scaleX(1)}.tm-item-icon{font-size:19px!important;width:19px!important;height:19px!important;display:inline-flex!important;align-items:center;justify-content:center;color:inherit!important}.tm-item-caret{font-size:18px!important;width:18px!important;height:18px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-left:-2px;margin-right:-4px;color:#ffffffb3!important;transition:transform .2s ease}.tm-item:hover .tm-item-caret,.tm-item-active .tm-item-caret{color:#fff!important}.tm-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;margin-left:auto}.tm-actions .mat-mdc-icon-button,.tm-action-btn{display:inline-flex!important;align-items:center!important;justify-content:center!important;color:#fff!important}.tm-actions mat-icon{color:#fff!important}.tm-divider-v{width:1px;height:24px;background:#ffffff38;margin:0 6px;flex-shrink:0}.tm-user-btn{display:inline-flex!important;align-items:center!important;gap:6px;height:40px;color:#fff!important;border-radius:8px;transition:background .18s ease}.tm-user-btn:hover{background:#ffffff1f}.tm-user-icon{font-size:24px!important;width:24px!important;height:24px!important;color:#fff!important}.tm-user-name{font-size:14px;font-weight:400;white-space:nowrap}::ng-deep .tm-submenu-panel .tm-sub-active{background:#0033aa14;font-weight:600;color:#03a}::ng-deep .tm-submenu-panel .tm-sub-active .mat-icon{color:#03a}@media (max-width: 991px){.tm-toggler{display:inline-flex}.tm-menu,.tm-actions{display:none;position:absolute;left:0;right:0;top:100%;flex-direction:column;align-items:stretch;background:#03a;padding:8px 12px;box-shadow:0 8px 18px #0003;z-index:1029}.tm-menu.tm-menu-open{display:flex}.tm-actions.tm-actions-open{display:flex;top:100%;border-top:1px solid rgba(255,255,255,.12)}.tm-item-wrap{width:100%}.tm-item-wrap:not(:first-child):before{width:100%;height:1px;margin:2px 0}.tm-item{width:100%;justify-content:flex-start;height:44px;margin:0}.tm-item:after{inset:8px auto 8px 0;width:4px;height:auto;transform:scaleY(0);transform-origin:center}.tm-item:hover:after,.tm-item.tm-item-active:after{transform:scaleY(1)}.tm-item-caret{margin-left:auto}.tm-divider-v{display:none}.tm-user-btn{justify-content:flex-start;width:100%}}\n"] }]
14943
15239
  }], ctorParameters: () => [{ type: i1$2.Router }, { type: AuthService }, { type: StorageService }, { type: NotificationsService }, { type: i1$3.BreakpointObserver }, { type: DataServiceLib }, { type: i4.MatDialog }, { type: SubscriptionService }, { type: SetupService }, { type: LastRouteService }, { type: OfflineService }], propDecorators: { onWindowScroll: [{
14944
15240
  type: HostListener,
14945
15241
  args: ['window:scroll']
@@ -15395,6 +15691,9 @@ class FormComponent {
15395
15691
  refreshVisibleFields() {
15396
15692
  this.visibleFields = this.getVisibleFields();
15397
15693
  }
15694
+ get selectTemplate() {
15695
+ return this.dynamicSelectTemplate ?? this.defaultDynamicSelectTemplate;
15696
+ }
15398
15697
  // Changed: Resolve {placeholder} in loadAction URLs from form data — e.g. 'pigs/list/bypen-{fromPenID}' → 'pigs/list/bypen-5'
15399
15698
  resolveLoadAction(field) {
15400
15699
  if (!field.loadAction?.url || !field.loadAction.url.includes('{'))
@@ -15471,6 +15770,13 @@ class FormComponent {
15471
15770
  testRequired(field) {
15472
15771
  return Core.testRequired(this.config, this.data, field);
15473
15772
  }
15773
+ // Readonly resolver for composite subfields projected into the dynamic select template: the
15774
+ // template calls testReadOnly(subfield) once, but composite members must also inherit the
15775
+ // parent composite's readonly state (parity with the inline subfield types, which bind
15776
+ // testReadOnly(field) || testReadOnly(subfield)).
15777
+ compositeReadOnly(parent) {
15778
+ return (subfield) => this.testReadOnly(parent) || this.testReadOnly(subfield);
15779
+ }
15474
15780
  toggleSection(field) {
15475
15781
  if (field.type === 'section') {
15476
15782
  field.collapsed = !field.collapsed;
@@ -15624,14 +15930,17 @@ class FormComponent {
15624
15930
  processForm() {
15625
15931
  }
15626
15932
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FormComponent, deps: [{ token: MessageService }, { token: DataServiceLib }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Component }); }
15627
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FormComponent, isStandalone: false, selector: "spa-form", inputs: { files: "files", data: "data", config: "config" }, outputs: { buttonClick: "buttonClick", inputChange: "inputChange" }, queries: [{ propertyName: "dynamicSelectTemplate", first: true, predicate: ["dynamicSelect"], descendants: true }], ngImport: i0, template: "\r\n\r\n\r\n<div class=\"tin-form-container\" >\r\n <div [ngClass]=\"[multiColumn ? 'tin-grid' : 'tin-col', config.notesConfig ? 'width-75' : 'width-100']\" class=\"form-main-content\">\r\n\r\n <div *ngIf=\"!hasAccess\" class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n \r\n <div [ngClass]=\"field.span || field.type =='section' || field.type =='file' || field.type =='file-view' || field.type =='editor' ? 'span-col' : ''\" *ngFor=\"let field of visibleFields\"><!-- TS-11: bind cached visibleFields instead of getVisibleFields() per CD -->\r\n \r\n <ng-container>\r\n \r\n <ng-container [ngSwitch]=\"field.type\" class=\"highlight\">\r\n \r\n <div *ngSwitchCase=\"'section'\" class=\"title d-flex align-items-center\" (click)=\"toggleSection(field)\" style=\"cursor: pointer;\">\r\n <label style=\"font-size: larger;margin-right: 10px;\">{{field.alias ?? field.name | camelToWords}}</label>\r\n <mat-icon *ngIf=\"field.infoMessage\" (click)=\"onInfoClick($event, field.infoMessage)\" style=\"color: steelblue; font-size: 14px;\">info</mat-icon>\r\n <!-- <button mat-icon-button class=\"info-icon-button\" matTooltip=\"Info\" matTooltipPosition=\"above\">\r\n \r\n </button> -->\r\n <mat-icon *ngIf=\"hasSectionFields(field.name)\">{{shouldSectionCollapse(field) ? 'expand_more' : 'expand_less'}}</mat-icon>\r\n </div>\r\n \r\n <ng-container *ngSwitchCase=\"'file'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode !='view'\">\r\n <spa-attach [message]=\"field.alias ?? 'Drag and Drop files here'\" [(files)]=\"files\" [fileOptions]=\"field.fileOptions\"></spa-attach>\r\n </div>\r\n </ng-container>\r\n \r\n <ng-container *ngSwitchCase=\"'file-view'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode && config.mode !='create'\">\r\n <spa-viewer [fileAction]=\"field.loadAction\" [path]=\"field.path\" [folderName]=\"data[field.keyField]\" ></spa-viewer>\r\n </div>\r\n </ng-container>\r\n \r\n <spa-html *ngSwitchCase=\"'html'\" [display]=\"field.alias | camelToWords\" [value]=\"data[field.name]\" [maxHeight]=\"field.maxHeight\"></spa-html>\r\n \r\n <label *ngSwitchCase=\"'blank'\"></label>\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-label *ngSwitchCase=\"'label'\" [display]=\"field.alias ?? field.name | camelToWords\" [value]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [format]=\"field.format ?? 'text'\" [suffix]=\"field.suffix\" [size]=\"field.size\"></spa-label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [infoMessage]=\"field.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [min]=\"field?.min\" [max]=\"field?.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [min]=\"field.min\" [max]=\"field.max\" [infoMessage]=\"field.infoMessage\" ></spa-datetime>\r\n \r\n <spa-email *ngSwitchCase=\"'email'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-email>\r\n \r\n <spa-text-mask *ngSwitchCase=\"'text-mask'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\"></spa-text-mask>\r\n \r\n \r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"dynamicSelectTemplate; context: {\r\n $implicit: field,\r\n field: field,\r\n data: data,\r\n testReadOnly: testReadOnly.bind(this),\r\n testRequired: testRequired.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n \r\n <spa-select-multi *ngSwitchCase=\"'select-multi'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [selectAll]=\"field.selectAll\">\r\n </spa-select-multi>\r\n \r\n <spa-text-multi *ngSwitchCase=\"'text-multi'\" [strict]=\"field.strict\" [display]=\"field.alias ?? field.name | camelToWords\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\"></spa-text-multi>\r\n \r\n \r\n \r\n <ng-container *ngSwitchCase=\"'composite'\">\r\n <div class=\"composite-field-container\">\r\n <div class=\"composite-field-group\">\r\n <ng-container *ngFor=\"let subfield of getVisibleSubfields(field)\">\r\n <ng-container [ngSwitch]=\"subfield.type\">\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [infoMessage]=\"subfield.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [infoMessage]=\"subfield.infoMessage\" ></spa-datetime>\r\n \r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"dynamicSelectTemplate; context: {\r\n $implicit: field,\r\n field: field,\r\n data: data,\r\n testReadOnly: testReadOnly.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [options]=\"subfield.options\" [optionDisplay]=\"subfield.optionDisplay ?? 'name'\" [optionValue]=\"subfield.optionValue ?? 'value'\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [loadAction]=\"resolveLoadAction(subfield)\" [regex]=\"subfield.regex\" [field]=\"subfield\" [data]=\"data\" [detailsConfig]=\"subfield.detailsConfig\" [masterField]=\"subfield.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [rows]=\"subfield.rows\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [editorConfig]=\"subfield.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text>\r\n \r\n \r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n \r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [regex]=\"field.regex\" [field]=\"field\" [data]=\"data\" [detailsConfig]=\"field.detailsConfig\" [masterField]=\"field.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [rows]=\"field.rows\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [editorConfig]=\"field.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text>\r\n \r\n </ng-container>\r\n \r\n </ng-container>\r\n \r\n </div>\r\n \r\n \r\n <div class=\"span-col-center\" *ngIf=\"config.button\">\r\n <button mat-raised-button color=\"primary\" (click)=\"buttonClicked()\" cdkFocusInitial>{{buttonDisplay}}</button>\r\n </div>\r\n \r\n \r\n </div>\r\n <div class=\"notes-section\" *ngIf=\"config.notesConfig\">\r\n <spa-notes\r\n [title]=\"config.notesConfig.title || 'Notes'\"\r\n [notes]=\"config.notesConfig.notes || []\"\r\n [loadAction]=\"config.notesConfig.loadAction\"\r\n [loadIDField]=\"config.notesConfig.loadIDField\"\r\n [data]=\"data\"\r\n [nameField]=\"config.notesConfig.nameField || 'createdByName'\"\r\n [dateField]=\"config.notesConfig.dateField || 'createdDate'\"\r\n [commentField]=\"config.notesConfig.commentField || 'details'\">\r\n </spa-notes>\r\n </div>\r\n</div>\r\n\r\n", styles: [".title{margin-top:.5em;margin-bottom:.5em;font-size:larger;font-weight:300;color:#0b447e}.composite-field-group{display:flex;flex-direction:row;flex-wrap:wrap;gap:12px}.tin-form-container{display:flex;flex-direction:row;width:100%;gap:16px}.width-100{width:100%!important}.width-75{width:70%!important}.notes-section{width:400px;border-left:1px solid #e0e0e0}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: i3.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: "component", type: TextComponent, selector: "spa-text", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "format", "type", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextMaskComponent, selector: "spa-text-mask", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "regex", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextAreaComponent, selector: "spa-text-area", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "rows", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextSingleComponent, selector: "spa-text-single", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction", "required", "min", "max", "regex", "suffix", "infoMessage", "field", "data", "detailsConfig", "masterField"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: DateComponent, selector: "spa-date", inputs: ["required", "min", "max", "readonly", "hint", "value", "display", "placeholder", "width", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: DatetimeComponent, selector: "spa-datetime", inputs: ["display", "value", "readonly", "width", "min", "max", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: LabelComponent, selector: "spa-label", inputs: ["display", "value", "format", "suffix", "size"] }, { kind: "component", type: MoneyComponent, selector: "spa-money", inputs: ["readonly", "hint", "display", "placeholder", "value", "width", "currency", "required", "min", "max", "infoMessage", "copyContent", "clearContent", "suffix"], outputs: ["valueChange", "leave", "enterPress", "infoClick"] }, { kind: "component", type: AttachComponent, selector: "spa-attach", inputs: ["fileOptions", "message", "files", "enableUpload"], outputs: ["filesChange", "upload"] }, { kind: "component", type: NumberComponent, selector: "spa-number", inputs: ["readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "step", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange", "leave", "enterPress", "infoClick"] }, { kind: "component", type: ViewerComponent, selector: "spa-viewer", inputs: ["fileAction", "path", "folderName", "fileNames", "removable", "display", "title"], outputs: ["remove"] }, { kind: "component", type: EmailComponent, selector: "spa-email", inputs: ["display", "value", "readonly", "required", "hint", "suffix", "infoMessage", "copyContent", "clearContent", "options", "optionValue"], outputs: ["valueChange"] }, { kind: "component", type: TextMultiComponent, selector: "spa-text-multi", inputs: ["display", "value", "readonly", "required", "hint", "strict", "suffix", "infoMessage", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: SelectMultiComponent, selector: "spa-select-multi", inputs: ["display", "value", "readonly", "required", "hint", "options", "optionDisplay", "optionValue", "infoMessage", "copyContent", "clearContent", "nullable", "placeholder", "width", "suffix", "loadAction", "selectAll"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: HtmlComponent, selector: "spa-html", inputs: ["value", "maxHeight", "display"] }, { kind: "component", type: EditorComponent, selector: "spa-editor", inputs: ["display", "value", "readonly", "required", "hint", "infoMessage", "placeholder", "width", "height", "minHeight", "defaultFontName", "editorConfig"], outputs: ["valueChange"] }, { kind: "component", type: NotesComponent, selector: "spa-notes", inputs: ["title", "notes", "loadAction", "loadIDField", "data", "nameField", "dateField", "commentField"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
15933
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FormComponent, isStandalone: false, selector: "spa-form", inputs: { files: "files", data: "data", config: "config" }, outputs: { buttonClick: "buttonClick", inputChange: "inputChange" }, queries: [{ propertyName: "dynamicSelectTemplate", first: true, predicate: ["dynamicSelect"], descendants: true }], viewQueries: [{ propertyName: "defaultDynamicSelectTemplate", first: true, predicate: ["defaultDynamicSelect"], descendants: true, static: true }], ngImport: i0, template: "\r\n\r\n\r\n<div class=\"tin-form-container\" >\r\n <div [ngClass]=\"[multiColumn ? 'tin-grid' : 'tin-col', config.notesConfig ? 'width-75' : 'width-100']\" class=\"form-main-content\">\r\n\r\n <div *ngIf=\"!hasAccess\" class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n \r\n <div [ngClass]=\"field.span || field.type =='section' || field.type =='file' || field.type =='file-view' || field.type =='editor' ? 'span-col' : ''\" *ngFor=\"let field of visibleFields\"><!-- TS-11: bind cached visibleFields instead of getVisibleFields() per CD -->\r\n \r\n <ng-container>\r\n \r\n <ng-container [ngSwitch]=\"field.type\" class=\"highlight\">\r\n \r\n <div *ngSwitchCase=\"'section'\" class=\"title d-flex align-items-center\" (click)=\"toggleSection(field)\" style=\"cursor: pointer;\">\r\n <label style=\"font-size: larger;margin-right: 10px;\">{{field.alias ?? field.name | camelToWords}}</label>\r\n <mat-icon *ngIf=\"field.infoMessage\" (click)=\"onInfoClick($event, field.infoMessage)\" style=\"color: steelblue; font-size: 14px;\">info</mat-icon>\r\n <!-- <button mat-icon-button class=\"info-icon-button\" matTooltip=\"Info\" matTooltipPosition=\"above\">\r\n \r\n </button> -->\r\n <mat-icon *ngIf=\"hasSectionFields(field.name)\">{{shouldSectionCollapse(field) ? 'expand_more' : 'expand_less'}}</mat-icon>\r\n </div>\r\n \r\n <ng-container *ngSwitchCase=\"'file'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode !='view'\">\r\n <spa-attach [message]=\"field.alias ?? 'Drag and Drop files here'\" [(files)]=\"files\" [fileOptions]=\"field.fileOptions\"></spa-attach>\r\n </div>\r\n </ng-container>\r\n \r\n <ng-container *ngSwitchCase=\"'file-view'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode && config.mode !='create'\">\r\n <spa-viewer [fileAction]=\"field.loadAction\" [path]=\"field.path\" [folderName]=\"data[field.keyField]\" ></spa-viewer>\r\n </div>\r\n </ng-container>\r\n \r\n <spa-html *ngSwitchCase=\"'html'\" [display]=\"field.alias | camelToWords\" [value]=\"data[field.name]\" [maxHeight]=\"field.maxHeight\"></spa-html>\r\n \r\n <label *ngSwitchCase=\"'blank'\"></label>\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-label *ngSwitchCase=\"'label'\" [display]=\"field.alias ?? field.name | camelToWords\" [value]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [format]=\"field.format ?? 'text'\" [suffix]=\"field.suffix\" [size]=\"field.size\"></spa-label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [infoMessage]=\"field.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [min]=\"field?.min\" [max]=\"field?.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [min]=\"field.min\" [max]=\"field.max\" [infoMessage]=\"field.infoMessage\" ></spa-datetime>\r\n \r\n <spa-email *ngSwitchCase=\"'email'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-email>\r\n \r\n <spa-text-mask *ngSwitchCase=\"'text-mask'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\"></spa-text-mask>\r\n \r\n \r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"selectTemplate; context: {\r\n $implicit: field,\r\n field: field,\r\n data: data,\r\n testReadOnly: testReadOnly.bind(this),\r\n testRequired: testRequired.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n \r\n <spa-select-multi *ngSwitchCase=\"'select-multi'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [selectAll]=\"field.selectAll\">\r\n </spa-select-multi>\r\n \r\n <spa-text-multi *ngSwitchCase=\"'text-multi'\" [strict]=\"field.strict\" [display]=\"field.alias ?? field.name | camelToWords\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\"></spa-text-multi>\r\n \r\n \r\n \r\n <ng-container *ngSwitchCase=\"'composite'\">\r\n <div class=\"composite-field-container\">\r\n <div class=\"composite-field-group\">\r\n <ng-container *ngFor=\"let subfield of getVisibleSubfields(field)\">\r\n <ng-container [ngSwitch]=\"subfield.type\">\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [infoMessage]=\"subfield.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [infoMessage]=\"subfield.infoMessage\" ></spa-datetime>\r\n \r\n <!-- Fixed: composite select subfields projected the PARENT composite field into the\r\n dynamic select template (wrong options/value binding) and omitted testRequired,\r\n so the template's testRequired(field) call threw and killed the whole dialog.\r\n Context now mirrors the top-level select outlet, with the SUBFIELD, and readonly\r\n cascades composite parent || subfield like every other subfield type. -->\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"selectTemplate; context: {\r\n $implicit: subfield,\r\n field: subfield,\r\n data: data,\r\n testReadOnly: compositeReadOnly(field),\r\n testRequired: testRequired.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [options]=\"subfield.options\" [optionDisplay]=\"subfield.optionDisplay ?? 'name'\" [optionValue]=\"subfield.optionValue ?? 'value'\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [loadAction]=\"resolveLoadAction(subfield)\" [regex]=\"subfield.regex\" [field]=\"subfield\" [data]=\"data\" [detailsConfig]=\"subfield.detailsConfig\" [masterField]=\"subfield.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [rows]=\"subfield.rows\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [editorConfig]=\"subfield.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text>\r\n \r\n \r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n \r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [regex]=\"field.regex\" [field]=\"field\" [data]=\"data\" [detailsConfig]=\"field.detailsConfig\" [masterField]=\"field.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [rows]=\"field.rows\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [editorConfig]=\"field.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text>\r\n \r\n </ng-container>\r\n \r\n </ng-container>\r\n \r\n </div>\r\n \r\n \r\n <div class=\"span-col-center\" *ngIf=\"config.button\">\r\n <button mat-raised-button color=\"primary\" (click)=\"buttonClicked()\" cdkFocusInitial>{{buttonDisplay}}</button>\r\n </div>\r\n \r\n \r\n </div>\r\n <!-- Fixed: built-in fallback select template \u2014 spa-form used consumers' projected #dynamicSelect\r\n (only detailsDialog provides one), so selects silently rendered NOTHING when spa-form was used\r\n directly. The projected template (if any) still wins; this default carries the same bindings. -->\r\n <ng-template #defaultDynamicSelect 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\r\n <div class=\"notes-section\" *ngIf=\"config.notesConfig\">\r\n <spa-notes\r\n [title]=\"config.notesConfig.title || 'Notes'\"\r\n [notes]=\"config.notesConfig.notes || []\"\r\n [loadAction]=\"config.notesConfig.loadAction\"\r\n [loadIDField]=\"config.notesConfig.loadIDField\"\r\n [data]=\"data\"\r\n [nameField]=\"config.notesConfig.nameField || 'createdByName'\"\r\n [dateField]=\"config.notesConfig.dateField || 'createdDate'\"\r\n [commentField]=\"config.notesConfig.commentField || 'details'\">\r\n </spa-notes>\r\n </div>\r\n</div>\r\n\r\n", styles: [".title{margin-top:.5em;margin-bottom:.5em;font-size:larger;font-weight:300;color:#0b447e}.composite-field-group{display:flex;flex-direction:row;flex-wrap:wrap;gap:12px}.tin-form-container{display:flex;flex-direction:row;width:100%;gap:16px}.width-100{width:100%!important}.width-75{width:70%!important}.notes-section{width:400px;border-left:1px solid #e0e0e0}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: i3.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: "component", type: TextComponent, selector: "spa-text", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "format", "type", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextMaskComponent, selector: "spa-text-mask", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "regex", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextAreaComponent, selector: "spa-text-area", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "rows", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextSingleComponent, selector: "spa-text-single", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction", "required", "min", "max", "regex", "suffix", "infoMessage", "field", "data", "detailsConfig", "masterField"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: DateComponent, selector: "spa-date", inputs: ["required", "min", "max", "readonly", "hint", "value", "display", "placeholder", "width", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: DatetimeComponent, selector: "spa-datetime", inputs: ["display", "value", "readonly", "width", "min", "max", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: LabelComponent, selector: "spa-label", inputs: ["display", "value", "format", "suffix", "size"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: MoneyComponent, selector: "spa-money", inputs: ["readonly", "hint", "display", "placeholder", "value", "width", "currency", "required", "min", "max", "infoMessage", "copyContent", "clearContent", "suffix"], outputs: ["valueChange", "leave", "enterPress", "infoClick"] }, { kind: "component", type: AttachComponent, selector: "spa-attach", inputs: ["fileOptions", "message", "files", "enableUpload"], outputs: ["filesChange", "upload"] }, { kind: "component", type: NumberComponent, selector: "spa-number", inputs: ["readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "step", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange", "leave", "enterPress", "infoClick"] }, { kind: "component", type: ViewerComponent, selector: "spa-viewer", inputs: ["fileAction", "path", "folderName", "fileNames", "removable", "display", "title"], outputs: ["remove"] }, { kind: "component", type: EmailComponent, selector: "spa-email", inputs: ["display", "value", "readonly", "required", "hint", "suffix", "infoMessage", "copyContent", "clearContent", "options", "optionValue"], outputs: ["valueChange"] }, { kind: "component", type: TextMultiComponent, selector: "spa-text-multi", inputs: ["display", "value", "readonly", "required", "hint", "strict", "suffix", "infoMessage", "copyContent", "clearContent", "options", "optionDisplay", "optionValue", "loadAction"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: SelectMultiComponent, selector: "spa-select-multi", inputs: ["display", "value", "readonly", "required", "hint", "options", "optionDisplay", "optionValue", "infoMessage", "copyContent", "clearContent", "nullable", "placeholder", "width", "suffix", "loadAction", "selectAll"], outputs: ["valueChange", "hoverChange"] }, { kind: "component", type: HtmlComponent, selector: "spa-html", inputs: ["value", "maxHeight", "display"] }, { kind: "component", type: EditorComponent, selector: "spa-editor", inputs: ["display", "value", "readonly", "required", "hint", "infoMessage", "placeholder", "width", "height", "minHeight", "defaultFontName", "editorConfig"], outputs: ["valueChange"] }, { kind: "component", type: NotesComponent, selector: "spa-notes", inputs: ["title", "notes", "loadAction", "loadIDField", "data", "nameField", "dateField", "commentField"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
15628
15934
  }
15629
15935
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FormComponent, decorators: [{
15630
15936
  type: Component,
15631
- args: [{ selector: 'spa-form', standalone: false, template: "\r\n\r\n\r\n<div class=\"tin-form-container\" >\r\n <div [ngClass]=\"[multiColumn ? 'tin-grid' : 'tin-col', config.notesConfig ? 'width-75' : 'width-100']\" class=\"form-main-content\">\r\n\r\n <div *ngIf=\"!hasAccess\" class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n \r\n <div [ngClass]=\"field.span || field.type =='section' || field.type =='file' || field.type =='file-view' || field.type =='editor' ? 'span-col' : ''\" *ngFor=\"let field of visibleFields\"><!-- TS-11: bind cached visibleFields instead of getVisibleFields() per CD -->\r\n \r\n <ng-container>\r\n \r\n <ng-container [ngSwitch]=\"field.type\" class=\"highlight\">\r\n \r\n <div *ngSwitchCase=\"'section'\" class=\"title d-flex align-items-center\" (click)=\"toggleSection(field)\" style=\"cursor: pointer;\">\r\n <label style=\"font-size: larger;margin-right: 10px;\">{{field.alias ?? field.name | camelToWords}}</label>\r\n <mat-icon *ngIf=\"field.infoMessage\" (click)=\"onInfoClick($event, field.infoMessage)\" style=\"color: steelblue; font-size: 14px;\">info</mat-icon>\r\n <!-- <button mat-icon-button class=\"info-icon-button\" matTooltip=\"Info\" matTooltipPosition=\"above\">\r\n \r\n </button> -->\r\n <mat-icon *ngIf=\"hasSectionFields(field.name)\">{{shouldSectionCollapse(field) ? 'expand_more' : 'expand_less'}}</mat-icon>\r\n </div>\r\n \r\n <ng-container *ngSwitchCase=\"'file'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode !='view'\">\r\n <spa-attach [message]=\"field.alias ?? 'Drag and Drop files here'\" [(files)]=\"files\" [fileOptions]=\"field.fileOptions\"></spa-attach>\r\n </div>\r\n </ng-container>\r\n \r\n <ng-container *ngSwitchCase=\"'file-view'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode && config.mode !='create'\">\r\n <spa-viewer [fileAction]=\"field.loadAction\" [path]=\"field.path\" [folderName]=\"data[field.keyField]\" ></spa-viewer>\r\n </div>\r\n </ng-container>\r\n \r\n <spa-html *ngSwitchCase=\"'html'\" [display]=\"field.alias | camelToWords\" [value]=\"data[field.name]\" [maxHeight]=\"field.maxHeight\"></spa-html>\r\n \r\n <label *ngSwitchCase=\"'blank'\"></label>\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-label *ngSwitchCase=\"'label'\" [display]=\"field.alias ?? field.name | camelToWords\" [value]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [format]=\"field.format ?? 'text'\" [suffix]=\"field.suffix\" [size]=\"field.size\"></spa-label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [infoMessage]=\"field.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [min]=\"field?.min\" [max]=\"field?.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [min]=\"field.min\" [max]=\"field.max\" [infoMessage]=\"field.infoMessage\" ></spa-datetime>\r\n \r\n <spa-email *ngSwitchCase=\"'email'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-email>\r\n \r\n <spa-text-mask *ngSwitchCase=\"'text-mask'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\"></spa-text-mask>\r\n \r\n \r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"dynamicSelectTemplate; context: {\r\n $implicit: field,\r\n field: field,\r\n data: data,\r\n testReadOnly: testReadOnly.bind(this),\r\n testRequired: testRequired.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n \r\n <spa-select-multi *ngSwitchCase=\"'select-multi'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [selectAll]=\"field.selectAll\">\r\n </spa-select-multi>\r\n \r\n <spa-text-multi *ngSwitchCase=\"'text-multi'\" [strict]=\"field.strict\" [display]=\"field.alias ?? field.name | camelToWords\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\"></spa-text-multi>\r\n \r\n \r\n \r\n <ng-container *ngSwitchCase=\"'composite'\">\r\n <div class=\"composite-field-container\">\r\n <div class=\"composite-field-group\">\r\n <ng-container *ngFor=\"let subfield of getVisibleSubfields(field)\">\r\n <ng-container [ngSwitch]=\"subfield.type\">\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [infoMessage]=\"subfield.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [infoMessage]=\"subfield.infoMessage\" ></spa-datetime>\r\n \r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"dynamicSelectTemplate; context: {\r\n $implicit: field,\r\n field: field,\r\n data: data,\r\n testReadOnly: testReadOnly.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [options]=\"subfield.options\" [optionDisplay]=\"subfield.optionDisplay ?? 'name'\" [optionValue]=\"subfield.optionValue ?? 'value'\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [loadAction]=\"resolveLoadAction(subfield)\" [regex]=\"subfield.regex\" [field]=\"subfield\" [data]=\"data\" [detailsConfig]=\"subfield.detailsConfig\" [masterField]=\"subfield.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [rows]=\"subfield.rows\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [editorConfig]=\"subfield.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text>\r\n \r\n \r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n \r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [regex]=\"field.regex\" [field]=\"field\" [data]=\"data\" [detailsConfig]=\"field.detailsConfig\" [masterField]=\"field.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [rows]=\"field.rows\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [editorConfig]=\"field.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text>\r\n \r\n </ng-container>\r\n \r\n </ng-container>\r\n \r\n </div>\r\n \r\n \r\n <div class=\"span-col-center\" *ngIf=\"config.button\">\r\n <button mat-raised-button color=\"primary\" (click)=\"buttonClicked()\" cdkFocusInitial>{{buttonDisplay}}</button>\r\n </div>\r\n \r\n \r\n </div>\r\n <div class=\"notes-section\" *ngIf=\"config.notesConfig\">\r\n <spa-notes\r\n [title]=\"config.notesConfig.title || 'Notes'\"\r\n [notes]=\"config.notesConfig.notes || []\"\r\n [loadAction]=\"config.notesConfig.loadAction\"\r\n [loadIDField]=\"config.notesConfig.loadIDField\"\r\n [data]=\"data\"\r\n [nameField]=\"config.notesConfig.nameField || 'createdByName'\"\r\n [dateField]=\"config.notesConfig.dateField || 'createdDate'\"\r\n [commentField]=\"config.notesConfig.commentField || 'details'\">\r\n </spa-notes>\r\n </div>\r\n</div>\r\n\r\n", styles: [".title{margin-top:.5em;margin-bottom:.5em;font-size:larger;font-weight:300;color:#0b447e}.composite-field-group{display:flex;flex-direction:row;flex-wrap:wrap;gap:12px}.tin-form-container{display:flex;flex-direction:row;width:100%;gap:16px}.width-100{width:100%!important}.width-75{width:70%!important}.notes-section{width:400px;border-left:1px solid #e0e0e0}\n"] }]
15937
+ args: [{ selector: 'spa-form', standalone: false, template: "\r\n\r\n\r\n<div class=\"tin-form-container\" >\r\n <div [ngClass]=\"[multiColumn ? 'tin-grid' : 'tin-col', config.notesConfig ? 'width-75' : 'width-100']\" class=\"form-main-content\">\r\n\r\n <div *ngIf=\"!hasAccess\" class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n \r\n <div [ngClass]=\"field.span || field.type =='section' || field.type =='file' || field.type =='file-view' || field.type =='editor' ? 'span-col' : ''\" *ngFor=\"let field of visibleFields\"><!-- TS-11: bind cached visibleFields instead of getVisibleFields() per CD -->\r\n \r\n <ng-container>\r\n \r\n <ng-container [ngSwitch]=\"field.type\" class=\"highlight\">\r\n \r\n <div *ngSwitchCase=\"'section'\" class=\"title d-flex align-items-center\" (click)=\"toggleSection(field)\" style=\"cursor: pointer;\">\r\n <label style=\"font-size: larger;margin-right: 10px;\">{{field.alias ?? field.name | camelToWords}}</label>\r\n <mat-icon *ngIf=\"field.infoMessage\" (click)=\"onInfoClick($event, field.infoMessage)\" style=\"color: steelblue; font-size: 14px;\">info</mat-icon>\r\n <!-- <button mat-icon-button class=\"info-icon-button\" matTooltip=\"Info\" matTooltipPosition=\"above\">\r\n \r\n </button> -->\r\n <mat-icon *ngIf=\"hasSectionFields(field.name)\">{{shouldSectionCollapse(field) ? 'expand_more' : 'expand_less'}}</mat-icon>\r\n </div>\r\n \r\n <ng-container *ngSwitchCase=\"'file'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode !='view'\">\r\n <spa-attach [message]=\"field.alias ?? 'Drag and Drop files here'\" [(files)]=\"files\" [fileOptions]=\"field.fileOptions\"></spa-attach>\r\n </div>\r\n </ng-container>\r\n \r\n <ng-container *ngSwitchCase=\"'file-view'\">\r\n <div class=\"mt-1 mb-2\" *ngIf=\"config.mode && config.mode !='create'\">\r\n <spa-viewer [fileAction]=\"field.loadAction\" [path]=\"field.path\" [folderName]=\"data[field.keyField]\" ></spa-viewer>\r\n </div>\r\n </ng-container>\r\n \r\n <spa-html *ngSwitchCase=\"'html'\" [display]=\"field.alias | camelToWords\" [value]=\"data[field.name]\" [maxHeight]=\"field.maxHeight\"></spa-html>\r\n \r\n <label *ngSwitchCase=\"'blank'\"></label>\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-label *ngSwitchCase=\"'label'\" [display]=\"field.alias ?? field.name | camelToWords\" [value]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [format]=\"field.format ?? 'text'\" [suffix]=\"field.suffix\" [size]=\"field.size\"></spa-label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [infoMessage]=\"field.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [min]=\"field?.min\" [max]=\"field?.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [readonly]=\"testReadOnly(field)\" [min]=\"field.min\" [max]=\"field.max\" [infoMessage]=\"field.infoMessage\" ></spa-datetime>\r\n \r\n <spa-email *ngSwitchCase=\"'email'\" [display]=\"field.alias ?? field.name | camelToWords\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-email>\r\n \r\n <spa-text-mask *ngSwitchCase=\"'text-mask'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\"></spa-text-mask>\r\n \r\n \r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"selectTemplate; context: {\r\n $implicit: field,\r\n field: field,\r\n data: data,\r\n testReadOnly: testReadOnly.bind(this),\r\n testRequired: testRequired.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n \r\n <spa-select-multi *ngSwitchCase=\"'select-multi'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field, data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [selectAll]=\"field.selectAll\">\r\n </spa-select-multi>\r\n \r\n <spa-text-multi *ngSwitchCase=\"'text-multi'\" [strict]=\"field.strict\" [display]=\"field.alias ?? field.name | camelToWords\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\"></spa-text-multi>\r\n \r\n \r\n \r\n <ng-container *ngSwitchCase=\"'composite'\">\r\n <div class=\"composite-field-container\">\r\n <div class=\"composite-field-group\">\r\n <ng-container *ngFor=\"let subfield of getVisibleSubfields(field)\">\r\n <ng-container [ngSwitch]=\"subfield.type\">\r\n \r\n <label *ngSwitchCase=\"'string'\" [ngStyle]=\"{'font-size':field.size ?? '14px'}\" >{{data[field.name] ?? field.alias ?? field.name}} {{field.suffix ?? ''}}</label>\r\n \r\n <spa-number *ngSwitchCase=\"'number'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\"></spa-number>\r\n \r\n <spa-money *ngSwitchCase=\"'money'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\"></spa-money>\r\n \r\n <spa-check *ngSwitchCase=\"'checkbox'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [infoMessage]=\"subfield.infoMessage\" ></spa-check>\r\n \r\n <spa-date *ngSwitchCase=\"'date'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" ></spa-date>\r\n \r\n <spa-datetime *ngSwitchCase=\"'datetime'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [infoMessage]=\"subfield.infoMessage\" ></spa-datetime>\r\n \r\n <!-- Fixed: composite select subfields projected the PARENT composite field into the\r\n dynamic select template (wrong options/value binding) and omitted testRequired,\r\n so the template's testRequired(field) call threw and killed the whole dialog.\r\n Context now mirrors the top-level select outlet, with the SUBFIELD, and readonly\r\n cascades composite parent || subfield like every other subfield type. -->\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <ng-container *ngTemplateOutlet=\"selectTemplate; context: {\r\n $implicit: subfield,\r\n field: subfield,\r\n data: data,\r\n testReadOnly: compositeReadOnly(field),\r\n testRequired: testRequired.bind(this),\r\n selectChanged: selectChanged.bind(this)\r\n }\">\r\n </ng-container>\r\n </ng-container>\r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [options]=\"subfield.options\" [optionDisplay]=\"subfield.optionDisplay ?? 'name'\" [optionValue]=\"subfield.optionValue ?? 'value'\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [loadAction]=\"resolveLoadAction(subfield)\" [regex]=\"subfield.regex\" [field]=\"subfield\" [data]=\"data\" [detailsConfig]=\"subfield.detailsConfig\" [masterField]=\"subfield.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [rows]=\"subfield.rows\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [editorConfig]=\"subfield.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"subfield.alias ?? subfield.name | camelToWords\" [width]=\"subfield.width\" [(value)]=\"data[subfield.name]\" (valueChange)=\"inputChanged(subfield, $event)\" [required]=\"testRequired(subfield)\" [min]=\"subfield.min\" [max]=\"subfield.max\" [readonly]=\"testReadOnly(field) || testReadOnly(subfield)\" [hint]=\"subfield.hint\" [infoMessage]=\"subfield.infoMessage\" [suffix]=\"subfield.suffix\" [copyContent]=\"subfield.copyContent\" [clearContent]=\"subfield.clearContent\" [regex]=\"subfield.regex\"></spa-text>\r\n \r\n \r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n \r\n \r\n <spa-text-single *ngSwitchCase=\"'text-single'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [options]=\"field.options\" [optionDisplay]=\"field.optionDisplay ?? 'name'\" [optionValue]=\"field.optionValue ?? 'value'\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [loadAction]=\"resolveLoadAction(field)\" [regex]=\"field.regex\" [field]=\"field\" [data]=\"data\" [detailsConfig]=\"field.detailsConfig\" [masterField]=\"field.masterField\"></spa-text-single>\r\n\r\n <spa-text-area *ngSwitchCase=\"'text-area'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [rows]=\"field.rows\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text-area>\r\n\r\n <spa-editor *ngSwitchCase=\"'editor'\" [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [editorConfig]=\"field.editorConfig\"></spa-editor>\r\n\r\n <spa-text *ngSwitchDefault [display]=\"field.alias ?? field.name | camelToWords\" [width]=\"field.width\" [(value)]=\"data[field.name]\" (valueChange)=\"inputChanged(field,data[field.name])\" [required]=\"testRequired(field)\" [min]=\"field.min\" [max]=\"field.max\" [readonly]=\"testReadOnly(field)\" [hint]=\"field.hint\" [infoMessage]=\"field.infoMessage\" [suffix]=\"field.suffix\" [copyContent]=\"field.copyContent\" [clearContent]=\"field.clearContent\" [regex]=\"field.regex\"></spa-text>\r\n \r\n </ng-container>\r\n \r\n </ng-container>\r\n \r\n </div>\r\n \r\n \r\n <div class=\"span-col-center\" *ngIf=\"config.button\">\r\n <button mat-raised-button color=\"primary\" (click)=\"buttonClicked()\" cdkFocusInitial>{{buttonDisplay}}</button>\r\n </div>\r\n \r\n \r\n </div>\r\n <!-- Fixed: built-in fallback select template \u2014 spa-form used consumers' projected #dynamicSelect\r\n (only detailsDialog provides one), so selects silently rendered NOTHING when spa-form was used\r\n directly. The projected template (if any) still wins; this default carries the same bindings. -->\r\n <ng-template #defaultDynamicSelect 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\r\n <div class=\"notes-section\" *ngIf=\"config.notesConfig\">\r\n <spa-notes\r\n [title]=\"config.notesConfig.title || 'Notes'\"\r\n [notes]=\"config.notesConfig.notes || []\"\r\n [loadAction]=\"config.notesConfig.loadAction\"\r\n [loadIDField]=\"config.notesConfig.loadIDField\"\r\n [data]=\"data\"\r\n [nameField]=\"config.notesConfig.nameField || 'createdByName'\"\r\n [dateField]=\"config.notesConfig.dateField || 'createdDate'\"\r\n [commentField]=\"config.notesConfig.commentField || 'details'\">\r\n </spa-notes>\r\n </div>\r\n</div>\r\n\r\n", styles: [".title{margin-top:.5em;margin-bottom:.5em;font-size:larger;font-weight:300;color:#0b447e}.composite-field-group{display:flex;flex-direction:row;flex-wrap:wrap;gap:12px}.tin-form-container{display:flex;flex-direction:row;width:100%;gap:16px}.width-100{width:100%!important}.width-75{width:70%!important}.notes-section{width:400px;border-left:1px solid #e0e0e0}\n"] }]
15632
15938
  }], ctorParameters: () => [{ type: MessageService }, { type: DataServiceLib }, { type: AuthService }], propDecorators: { dynamicSelectTemplate: [{
15633
15939
  type: ContentChild,
15634
15940
  args: ['dynamicSelect']
15941
+ }], defaultDynamicSelectTemplate: [{
15942
+ type: ViewChild,
15943
+ args: ['defaultDynamicSelect', { static: true }]
15635
15944
  }], files: [{
15636
15945
  type: Input
15637
15946
  }], data: [{
@@ -15665,6 +15974,12 @@ class TabsComponent {
15665
15974
  this.tabCounts = initialized.tabCounts;
15666
15975
  this.tableReloads = initialized.tableReloads;
15667
15976
  this.updateVisibleTabs(); // Changed: Cache visible tabs on init
15977
+ // Added: targeted sibling refresh — the host pushes a tab index (into tableConfigs) to reload that tab's
15978
+ // table and count badge (e.g. a custom catalog tab submitting a request refreshes the My Requests tab).
15979
+ this.reloadTab?.subscribe((index) => {
15980
+ this.tableReloads[index]?.next(true);
15981
+ this.refreshTabCount(index);
15982
+ });
15668
15983
  }
15669
15984
  // Changed: Recalculate visible tabs when inputs change
15670
15985
  ngOnChanges(changes) {
@@ -15721,15 +16036,17 @@ class TabsComponent {
15721
16036
  return this.reload || this.tableReloads[index];
15722
16037
  }
15723
16038
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TabsComponent, deps: [{ token: TabService }], target: i0.ɵɵFactoryTarget.Component }); }
15724
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TabsComponent, isStandalone: false, selector: "spa-tabs", inputs: { tableConfigs: "tableConfigs", reload: "reload", parentDetails: "parentDetails", localMode: "localMode", nestingLevel: "nestingLevel" }, outputs: { formRefresh: "formRefresh", actionSuccess: "actionSuccess" }, usesOnChanges: true, ngImport: i0, template: "<mat-tab-group (selectedTabChange)=\"onTabChange($event)\" [selectedIndex]=\"selectedTabIndex\">\r\n\r\n <!-- Changed: Use cached visibleTabs property to prevent infinite change detection loop -->\r\n <ng-container *ngFor=\"let tab of visibleTabs; let i = index\">\r\n <mat-tab><!-- TS-11: visibleTabs is already filtered via getVisibleTabs; dropped per-tab isTabVisible() call per CD -->\r\n\r\n <!-- Tab label with optional count badge -->\r\n <ng-template matTabLabel>\r\n <span\r\n [matBadge]=\"shouldShowBadge(tab.originalIndex) ? getTabCount(tab.originalIndex) : null\"\r\n [matBadgeHidden]=\"!shouldShowBadge(tab.originalIndex)\"\r\n matBadgeOverlap=\"false\">\r\n {{getTabTitle(tab.config)}}\r\n </span>\r\n </ng-template>\r\n\r\n <!-- Tab content with lazy-loaded table -->\r\n <div *ngIf=\"shouldLoadTabData(i)\" class=\"tab-content\">\r\n <spa-table\r\n [config]=\"tab.config\"\r\n [reload]=\"getReloadSubject(tab.originalIndex)\"\r\n [inTab]=\"true\"\r\n [activeTab]=\"selectedTabIndex === i\"\r\n [nestingLevel]=\"nestingLevel\"\r\n [localMode]=\"localMode\"\r\n [parentDetails]=\"parentDetails\"\r\n (actionSuccess)=\"onTableActionSuccess(tab.originalIndex, $event)\">\r\n </spa-table><!-- Changed: localMode + parentDetails passed through for one-step create local tables -->\r\n </div>\r\n\r\n <!-- Placeholder for non-loaded tabs -->\r\n <div *ngIf=\"!shouldLoadTabData(i)\" class=\"tab-placeholder\">\r\n <!-- Empty placeholder - content will load when tab is activated -->\r\n </div>\r\n\r\n </mat-tab>\r\n </ng-container>\r\n\r\n</mat-tab-group>", styles: [":host{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-group{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-body-wrapper{flex:1}.tab-content{padding-top:16px;overflow-x:auto}.tab-placeholder{min-height:100px;display:flex;align-items:center;justify-content:center}.badge{background-color:#2196f3;color:#fff;border-radius:12px;padding:2px 8px;margin-left:8px;font-size:12px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4$2.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i5$2.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i5$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i5$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: TableComponent, selector: "spa-table", inputs: ["data", "tileData", "config", "localMode", "parentDetails", "reload", "activeTab", "inTab", "nestingLevel"], outputs: ["dataLoad", "actionSuccess", "refreshClick", "searchClick", "createClick", "actionClick", "inputChange", "actionResponse"] }] }); }
16039
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TabsComponent, isStandalone: false, selector: "spa-tabs", inputs: { tableConfigs: "tableConfigs", reload: "reload", reloadTab: "reloadTab", parentDetails: "parentDetails", localMode: "localMode", nestingLevel: "nestingLevel" }, outputs: { formRefresh: "formRefresh", actionSuccess: "actionSuccess" }, usesOnChanges: true, ngImport: i0, template: "<mat-tab-group (selectedTabChange)=\"onTabChange($event)\" [selectedIndex]=\"selectedTabIndex\">\r\n\r\n <!-- Changed: Use cached visibleTabs property to prevent infinite change detection loop -->\r\n <ng-container *ngFor=\"let tab of visibleTabs; let i = index\">\r\n <mat-tab><!-- TS-11: visibleTabs is already filtered via getVisibleTabs; dropped per-tab isTabVisible() call per CD -->\r\n\r\n <!-- Tab label with optional count badge -->\r\n <ng-template matTabLabel>\r\n <span\r\n [matBadge]=\"shouldShowBadge(tab.originalIndex) ? getTabCount(tab.originalIndex) : null\"\r\n [matBadgeHidden]=\"!shouldShowBadge(tab.originalIndex)\"\r\n matBadgeOverlap=\"false\">\r\n {{getTabTitle(tab.config)}}\r\n </span>\r\n </ng-template>\r\n\r\n <!-- Tab content: custom template when provided (tabTemplate), otherwise the lazy-loaded table.\r\n Templates keep the same lazy semantics \u2014 rendered only once the tab has been activated. -->\r\n <div *ngIf=\"shouldLoadTabData(i) && tab.config.tabTemplate\" class=\"tab-content\">\r\n <ng-container [ngTemplateOutlet]=\"tab.config.tabTemplate\"></ng-container>\r\n </div>\r\n\r\n <div *ngIf=\"shouldLoadTabData(i) && !tab.config.tabTemplate\" class=\"tab-content\">\r\n <spa-table\r\n [config]=\"tab.config\"\r\n [reload]=\"getReloadSubject(tab.originalIndex)\"\r\n [inTab]=\"true\"\r\n [activeTab]=\"selectedTabIndex === i\"\r\n [nestingLevel]=\"nestingLevel\"\r\n [localMode]=\"localMode\"\r\n [parentDetails]=\"parentDetails\"\r\n (actionSuccess)=\"onTableActionSuccess(tab.originalIndex, $event)\">\r\n </spa-table><!-- Changed: localMode + parentDetails passed through for one-step create local tables -->\r\n </div>\r\n\r\n <!-- Placeholder for non-loaded tabs -->\r\n <div *ngIf=\"!shouldLoadTabData(i)\" class=\"tab-placeholder\">\r\n <!-- Empty placeholder - content will load when tab is activated -->\r\n </div>\r\n\r\n </mat-tab>\r\n </ng-container>\r\n\r\n</mat-tab-group>", styles: [":host{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-group{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-body-wrapper{flex:1}.tab-content{padding-top:16px;overflow-x:auto}.tab-placeholder{min-height:100px;display:flex;align-items:center;justify-content:center}.badge{background-color:#2196f3;color:#fff;border-radius:12px;padding:2px 8px;margin-left:8px;font-size:12px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i4$2.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i5$2.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i5$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i5$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: TableComponent, selector: "spa-table", inputs: ["data", "tileData", "config", "localMode", "parentDetails", "reload", "activeTab", "inTab", "nestingLevel"], outputs: ["dataLoad", "actionSuccess", "refreshClick", "searchClick", "createClick", "actionClick", "inputChange", "actionResponse"] }] }); }
15725
16040
  }
15726
16041
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TabsComponent, decorators: [{
15727
16042
  type: Component,
15728
- args: [{ selector: 'spa-tabs', standalone: false, template: "<mat-tab-group (selectedTabChange)=\"onTabChange($event)\" [selectedIndex]=\"selectedTabIndex\">\r\n\r\n <!-- Changed: Use cached visibleTabs property to prevent infinite change detection loop -->\r\n <ng-container *ngFor=\"let tab of visibleTabs; let i = index\">\r\n <mat-tab><!-- TS-11: visibleTabs is already filtered via getVisibleTabs; dropped per-tab isTabVisible() call per CD -->\r\n\r\n <!-- Tab label with optional count badge -->\r\n <ng-template matTabLabel>\r\n <span\r\n [matBadge]=\"shouldShowBadge(tab.originalIndex) ? getTabCount(tab.originalIndex) : null\"\r\n [matBadgeHidden]=\"!shouldShowBadge(tab.originalIndex)\"\r\n matBadgeOverlap=\"false\">\r\n {{getTabTitle(tab.config)}}\r\n </span>\r\n </ng-template>\r\n\r\n <!-- Tab content with lazy-loaded table -->\r\n <div *ngIf=\"shouldLoadTabData(i)\" class=\"tab-content\">\r\n <spa-table\r\n [config]=\"tab.config\"\r\n [reload]=\"getReloadSubject(tab.originalIndex)\"\r\n [inTab]=\"true\"\r\n [activeTab]=\"selectedTabIndex === i\"\r\n [nestingLevel]=\"nestingLevel\"\r\n [localMode]=\"localMode\"\r\n [parentDetails]=\"parentDetails\"\r\n (actionSuccess)=\"onTableActionSuccess(tab.originalIndex, $event)\">\r\n </spa-table><!-- Changed: localMode + parentDetails passed through for one-step create local tables -->\r\n </div>\r\n\r\n <!-- Placeholder for non-loaded tabs -->\r\n <div *ngIf=\"!shouldLoadTabData(i)\" class=\"tab-placeholder\">\r\n <!-- Empty placeholder - content will load when tab is activated -->\r\n </div>\r\n\r\n </mat-tab>\r\n </ng-container>\r\n\r\n</mat-tab-group>", styles: [":host{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-group{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-body-wrapper{flex:1}.tab-content{padding-top:16px;overflow-x:auto}.tab-placeholder{min-height:100px;display:flex;align-items:center;justify-content:center}.badge{background-color:#2196f3;color:#fff;border-radius:12px;padding:2px 8px;margin-left:8px;font-size:12px}\n"] }]
16043
+ args: [{ selector: 'spa-tabs', standalone: false, template: "<mat-tab-group (selectedTabChange)=\"onTabChange($event)\" [selectedIndex]=\"selectedTabIndex\">\r\n\r\n <!-- Changed: Use cached visibleTabs property to prevent infinite change detection loop -->\r\n <ng-container *ngFor=\"let tab of visibleTabs; let i = index\">\r\n <mat-tab><!-- TS-11: visibleTabs is already filtered via getVisibleTabs; dropped per-tab isTabVisible() call per CD -->\r\n\r\n <!-- Tab label with optional count badge -->\r\n <ng-template matTabLabel>\r\n <span\r\n [matBadge]=\"shouldShowBadge(tab.originalIndex) ? getTabCount(tab.originalIndex) : null\"\r\n [matBadgeHidden]=\"!shouldShowBadge(tab.originalIndex)\"\r\n matBadgeOverlap=\"false\">\r\n {{getTabTitle(tab.config)}}\r\n </span>\r\n </ng-template>\r\n\r\n <!-- Tab content: custom template when provided (tabTemplate), otherwise the lazy-loaded table.\r\n Templates keep the same lazy semantics \u2014 rendered only once the tab has been activated. -->\r\n <div *ngIf=\"shouldLoadTabData(i) && tab.config.tabTemplate\" class=\"tab-content\">\r\n <ng-container [ngTemplateOutlet]=\"tab.config.tabTemplate\"></ng-container>\r\n </div>\r\n\r\n <div *ngIf=\"shouldLoadTabData(i) && !tab.config.tabTemplate\" class=\"tab-content\">\r\n <spa-table\r\n [config]=\"tab.config\"\r\n [reload]=\"getReloadSubject(tab.originalIndex)\"\r\n [inTab]=\"true\"\r\n [activeTab]=\"selectedTabIndex === i\"\r\n [nestingLevel]=\"nestingLevel\"\r\n [localMode]=\"localMode\"\r\n [parentDetails]=\"parentDetails\"\r\n (actionSuccess)=\"onTableActionSuccess(tab.originalIndex, $event)\">\r\n </spa-table><!-- Changed: localMode + parentDetails passed through for one-step create local tables -->\r\n </div>\r\n\r\n <!-- Placeholder for non-loaded tabs -->\r\n <div *ngIf=\"!shouldLoadTabData(i)\" class=\"tab-placeholder\">\r\n <!-- Empty placeholder - content will load when tab is activated -->\r\n </div>\r\n\r\n </mat-tab>\r\n </ng-container>\r\n\r\n</mat-tab-group>", styles: [":host{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-group{flex:1;display:flex;flex-direction:column}:host ::ng-deep .mat-mdc-tab-body-wrapper{flex:1}.tab-content{padding-top:16px;overflow-x:auto}.tab-placeholder{min-height:100px;display:flex;align-items:center;justify-content:center}.badge{background-color:#2196f3;color:#fff;border-radius:12px;padding:2px 8px;margin-left:8px;font-size:12px}\n"] }]
15729
16044
  }], ctorParameters: () => [{ type: TabService }], propDecorators: { tableConfigs: [{
15730
16045
  type: Input
15731
16046
  }], reload: [{
15732
16047
  type: Input
16048
+ }], reloadTab: [{
16049
+ type: Input
15733
16050
  }], parentDetails: [{
15734
16051
  type: Input
15735
16052
  }], localMode: [{
@@ -15747,13 +16064,13 @@ class StatusesComponent {
15747
16064
  isComponentVisible() {
15748
16065
  if (!this.config || !this.data)
15749
16066
  return false;
15750
- return this.config.hiddenCondition ? !this.config.hiddenCondition(this.data) : true;
16067
+ return Core.isItemVisible(this.config, this.data); // Changed: unified visible/hidden replaces hiddenCondition
15751
16068
  }
15752
16069
  // Check if individual status item should be visible
15753
16070
  isItemVisible(item) {
15754
16071
  if (!this.data)
15755
16072
  return false;
15756
- return item.hiddenCondition ? !item.hiddenCondition(this.data) : true;
16073
+ return Core.isItemVisible(item, this.data); // Changed: unified visible/hidden replaces hiddenCondition
15757
16074
  }
15758
16075
  // Get the icon based on status value and state conditions
15759
16076
  getIcon(item) {
@@ -16274,7 +16591,7 @@ class DetailsDialog {
16274
16591
  }
16275
16592
  }
16276
16593
  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: i4.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
16277
- 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: "<!-- Changed: dialog-has-tables mirrors the service's hasTables (tableConfigs?.length)\r\n which decides height '90%' vs 'auto' \u2014 used to scope scroll behaviour per case -->\r\n<div class=\"dialog-container\" [class.dialog-has-tables]=\"detailsConfig.tableConfigs?.length > 0\">\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 <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <!-- TS-16: bind precomputed editButtonVM instead of testVisible/testDisabled (which cloned objects) per CD -->\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && editButtonVM.visible\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"editButtonVM.disabled\"><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: Use unified spa-tabs with nestingLevel control \u2014 tabs hidden when nestingLevel >= 2 -->\r\n <spa-tabs\r\n *ngIf=\"showTabs && tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n [localMode]=\"formConfig?.mode === 'create'\"\r\n [nestingLevel]=\"nestingLevel + 1\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\"\r\n (actionSuccess)=\"actionPerformed = true\">\r\n </spa-tabs><!-- Changed: nested-tab table actions flag actionPerformed so refreshOnCloseIfActioned refreshes the parent on close -->\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 <!-- TS-16: bind precomputed extraButtonVMs instead of testVisible/testDisabled/getButtonColor (each cloning objects) per CD -->\r\n <ng-container *ngFor=\"let vm of extraButtonVMs\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && vm.visible\" mat-stroked-button [disabled]=\"isProcessing || vm.disabled\" [ngStyle]=\"{'color': vm.color}\" (click)=\"custom(vm.button)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"vm.button.icon\" [ngStyle]=\"{'color': vm.color}\">{{vm.button.icon.name}}</mat-icon>\r\n {{vm.button.display ?? vm.button.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}tr.mat-mdc-row{transition:background-color .15s ease}tr.mat-mdc-row:hover{background-color:#2196f305}tr.mat-mdc-row.row-editing,tr.mat-mdc-row.row-editing:hover{background-color:#ffa00014}.inline-save{color:#2e7d32!important}.inline-cancel{color:#c62828!important}.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%;max-height:100%;min-height:0}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column;min-height:0}.dialog-container:not(.dialog-has-tables){height:auto}.dialog-container:not(.dialog-has-tables) .dialog-scroll-content{flex:1 1 auto}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}.paged-filter-hint{display:flex;align-items:center;gap:8px;margin:4px 0 8px;padding:6px 12px;border-radius:4px;background-color:#fff8e1;color:#795548;font-size:13px}.paged-filter-hint mat-icon{font-size:18px;width:18px;height:18px;color:#ffa000}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i3.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: i3.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: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { 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: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
16594
+ 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: "<!-- Changed: dialog-has-tables mirrors the service's hasTables (tableConfigs?.length)\r\n which decides height '90%' vs 'auto' \u2014 used to scope scroll behaviour per case -->\r\n<div class=\"dialog-container\" [class.dialog-has-tables]=\"detailsConfig.tableConfigs?.length > 0\">\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 <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <!-- TS-16: bind precomputed editButtonVM instead of testVisible/testDisabled (which cloned objects) per CD -->\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && editButtonVM.visible\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"editButtonVM.disabled\"><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: Use unified spa-tabs with nestingLevel control \u2014 tabs hidden when nestingLevel >= 2 -->\r\n <spa-tabs\r\n *ngIf=\"showTabs && tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n [localMode]=\"formConfig?.mode === 'create'\"\r\n [nestingLevel]=\"nestingLevel + 1\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\"\r\n (actionSuccess)=\"actionPerformed = true\">\r\n </spa-tabs><!-- Changed: nested-tab table actions flag actionPerformed so refreshOnCloseIfActioned refreshes the parent on close -->\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 <!-- TS-16: bind precomputed extraButtonVMs instead of testVisible/testDisabled/getButtonColor (each cloning objects) per CD -->\r\n <ng-container *ngFor=\"let vm of extraButtonVMs\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && vm.visible\" mat-stroked-button [disabled]=\"isProcessing || vm.disabled\" [ngStyle]=\"{'color': vm.color}\" (click)=\"custom(vm.button)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"vm.button.icon\" [ngStyle]=\"{'color': vm.color}\">{{vm.button.icon.name}}</mat-icon>\r\n {{vm.button.display ?? vm.button.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}tr.mat-mdc-row{transition:background-color .15s ease}tr.mat-mdc-row:hover{background-color:#2196f305}tr.mat-mdc-row.row-editing,tr.mat-mdc-row.row-editing:hover{background-color:#ffa00014}.inline-save{color:#2e7d32!important}.inline-cancel{color:#c62828!important}.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%;max-height:100%;min-height:0}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column;min-height:0}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column;min-height:0}.dialog-container:not(.dialog-has-tables){height:auto}.dialog-container:not(.dialog-has-tables) .dialog-scroll-content{flex:1 1 auto}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}.paged-filter-hint{display:flex;align-items:center;gap:8px;margin:4px 0 8px;padding:6px 12px;border-radius:4px;background-color:#fff8e1;color:#795548;font-size:13px}.paged-filter-hint mat-icon{font-size:18px;width:18px;height:18px;color:#ffa000}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i3.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: i3.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: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { 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: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
16278
16595
  }
16279
16596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, decorators: [{
16280
16597
  type: Component,
@@ -16481,12 +16798,7 @@ class TitleActionsComponent {
16481
16798
  }
16482
16799
  // Check if action should be hidden
16483
16800
  isHidden(action) {
16484
- if (action.hidden)
16485
- return true;
16486
- if (action.hiddenCondition) {
16487
- return action.hiddenCondition(this.actionValues);
16488
- }
16489
- return false;
16801
+ return !Core.isItemVisible(action, this.actionValues); // Changed: unified visible/hidden replaces hidden + hiddenCondition
16490
16802
  }
16491
16803
  // Check if action should be readonly
16492
16804
  isReadonly(action) {
@@ -17126,14 +17438,10 @@ class ChartsComponent {
17126
17438
  }
17127
17439
  // Check if a chart should be hidden
17128
17440
  isHidden(chart) {
17129
- if (chart.hidden)
17130
- return true;
17131
- if (chart.hiddenCondition)
17132
- return chart.hiddenCondition(this.data);
17133
- return false;
17441
+ return !Core.isItemVisible(chart, this.data); // Changed: unified visible/hidden replaces hidden + hiddenCondition
17134
17442
  }
17135
17443
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ChartsComponent, deps: [{ token: DataServiceLib }, { token: MessageService }], target: i0.ɵɵFactoryTarget.Component }); }
17136
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ChartsComponent, isStandalone: false, selector: "spa-charts", inputs: { config: "config", data: "data", reload: "reload" }, ngImport: i0, template: "<div class=\"charts-grid\" [style.--columns]=\"config?.columns\">\r\n <ng-container *ngFor=\"let chart of config?.charts\">\r\n <mat-card *ngIf=\"!isHidden(chart)\" class=\"chart-card\">\r\n\r\n <!-- Header -->\r\n <div class=\"chart-header\" *ngIf=\"chart.title\">\r\n <h4 class=\"chart-title\">{{ chart.title }}</h4>\r\n <p class=\"chart-subtitle\" *ngIf=\"chart.subtitle\">{{ chart.subtitle }}</p>\r\n </div>\r\n\r\n <!-- Chart Area -->\r\n <div class=\"chart-body\" [style.height]=\"chart.height ?? '300px'\">\r\n <canvas baseChart\r\n [type]=\"chart.type\"\r\n [data]=\"getChartData(chart)\"\r\n [options]=\"getChartOptions(chart)\"\r\n [plugins]=\"getPlugins(chart)\">\r\n </canvas>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"chart-footer\" *ngIf=\"chart.footer\">\r\n <mat-divider></mat-divider>\r\n <div class=\"footer-content\">\r\n <mat-icon *ngIf=\"chart.footerIcon\">{{ chart.footerIcon }}</mat-icon>\r\n <span>{{ chart.footer }}</span>\r\n </div>\r\n </div>\r\n\r\n </mat-card>\r\n </ng-container>\r\n</div>\r\n", styles: [".charts-grid{display:grid;grid-template-columns:repeat(var(--columns, auto-fit),minmax(380px,1fr));gap:10px;padding:12px 0;margin-left:-5px;overflow:hidden}@media (max-width: 768px){.charts-grid{grid-template-columns:1fr;margin-left:0}}.chart-card{padding:20px;display:flex;flex-direction:column;border-radius:12px;transition:box-shadow .3s ease;min-width:0;overflow:hidden}.chart-card:hover{box-shadow:0 6px 20px #00000014}.chart-header{margin-bottom:8px}.chart-title{font-size:16px;font-weight:500;margin:0;color:#333}.chart-subtitle{font-size:13px;color:#9a9a9a;margin:4px 0 0}.chart-body{flex:1;position:relative;padding:8px 4px;min-width:0}.chart-footer{padding-top:12px}.footer-content{display:flex;align-items:center;gap:8px;padding:8px 0 4px;color:#9a9a9a;font-size:13px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i9.BaseChartDirective, selector: "canvas[baseChart]", inputs: ["type", "legend", "data", "options", "plugins", "labels", "datasets"], outputs: ["chartClick", "chartHover"], exportAs: ["base-chart"] }] }); }
17444
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ChartsComponent, isStandalone: false, selector: "spa-charts", inputs: { config: "config", data: "data", reload: "reload" }, ngImport: i0, template: "<div class=\"charts-grid\" [style.--columns]=\"config?.columns\">\r\n <ng-container *ngFor=\"let chart of config?.charts\">\r\n <mat-card *ngIf=\"!isHidden(chart)\" class=\"chart-card\">\r\n\r\n <!-- Header -->\r\n <div class=\"chart-header\" *ngIf=\"chart.title\">\r\n <h4 class=\"chart-title\">{{ chart.title }}</h4>\r\n <p class=\"chart-subtitle\" *ngIf=\"chart.subtitle\">{{ chart.subtitle }}</p>\r\n </div>\r\n\r\n <!-- Chart Area -->\r\n <div class=\"chart-body\" [style.height]=\"chart.height ?? '300px'\">\r\n <canvas baseChart\r\n [type]=\"chart.type\"\r\n [data]=\"getChartData(chart)\"\r\n [options]=\"getChartOptions(chart)\"\r\n [plugins]=\"getPlugins(chart)\">\r\n </canvas>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"chart-footer\" *ngIf=\"chart.footer\">\r\n <mat-divider></mat-divider>\r\n <div class=\"footer-content\">\r\n <mat-icon *ngIf=\"chart.footerIcon\">{{ chart.footerIcon }}</mat-icon>\r\n <span>{{ chart.footer }}</span>\r\n </div>\r\n </div>\r\n\r\n </mat-card>\r\n </ng-container>\r\n</div>\r\n", styles: [".charts-grid{display:grid;grid-template-columns:repeat(var(--columns, auto-fit),minmax(380px,1fr));gap:10px;padding:12px 0;margin-left:-5px;overflow:hidden}@media (max-width: 768px){.charts-grid{grid-template-columns:1fr;margin-left:0}}.chart-card{padding:20px;display:flex;flex-direction:column;border-radius:12px;transition:box-shadow .3s ease;min-width:0;overflow:hidden}.chart-card:hover{box-shadow:0 6px 20px #00000014}.chart-header{margin-bottom:8px}.chart-title{font-size:16px;font-weight:500;margin:0;color:#333}.chart-subtitle{font-size:13px;color:#9a9a9a;margin:4px 0 0}.chart-body{flex:1;position:relative;padding:8px 4px;min-width:0}.chart-footer{padding-top:12px}.footer-content{display:flex;align-items:center;gap:8px;padding:8px 0 4px;color:#9a9a9a;font-size:13px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i9.BaseChartDirective, selector: "canvas[baseChart]", inputs: ["type", "legend", "data", "options", "plugins", "labels", "datasets"], outputs: ["chartClick", "chartHover"], exportAs: ["base-chart"] }] }); }
17137
17445
  }
17138
17446
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ChartsComponent, decorators: [{
17139
17447
  type: Component,
@@ -17452,7 +17760,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
17452
17760
  }] });
17453
17761
 
17454
17762
  class LoginComponent {
17455
- constructor(httpService, storageService, router, messageService, dataService, authService, logService, route, notificationsService, signalRService, msalService, dialog, setupService, lastRouteService) {
17763
+ constructor(httpService, storageService, router, messageService, dataService, authService, logService, route, notificationsService, signalRService, msalService, dialog, setupService, lastRouteService, runtimeConfig, socialAuthConfig) {
17456
17764
  this.httpService = httpService;
17457
17765
  this.storageService = storageService;
17458
17766
  this.router = router;
@@ -17467,6 +17775,8 @@ class LoginComponent {
17467
17775
  this.dialog = dialog;
17468
17776
  this.setupService = setupService;
17469
17777
  this.lastRouteService = lastRouteService;
17778
+ this.runtimeConfig = runtimeConfig;
17779
+ this.socialAuthConfig = socialAuthConfig;
17470
17780
  this.rememberMe = false; // Changed: Added for "Keep me signed in" checkbox
17471
17781
  this.style = "default";
17472
17782
  this.email = "";
@@ -17475,7 +17785,7 @@ class LoginComponent {
17475
17785
  this.hide = true;
17476
17786
  this.isProcessing = false;
17477
17787
  this.appConfig = new AppConfig();
17478
- } // Changed: Added SignalRService injection // Changed: Added MatDialog injection // Added: SetupService for setup badge // Added: LastRouteService for resumeLastRoute
17788
+ } // Changed: Added SignalRService injection // Changed: Added MatDialog injection // Added: SetupService for setup badge // Added: LastRouteService for resumeLastRoute // Added: runtime + social configs for SSO diagnostics
17479
17789
  // Added: where a successful login/resume should land. An explicit ?redirectTo= always wins (it means the
17480
17790
  // user was bounced out of a specific page); otherwise resumeLastRoute lands them on their last page.
17481
17791
  async resumeTarget() {
@@ -17505,6 +17815,11 @@ class LoginComponent {
17505
17815
  });
17506
17816
  }
17507
17817
  loginWithMS() {
17818
+ // MSAL v3 throws uninitialized_public_client_application unless initialize() has
17819
+ // completed; it is idempotent, so awaiting here is safe even when already done.
17820
+ this.msalService.instance.initialize().then(() => this.doMsalLoginPopup(), (error) => { console.error('MSAL initialize error:', error); this.messageService.toast("Microsoft login failed. Please try again."); });
17821
+ }
17822
+ doMsalLoginPopup() {
17508
17823
  this.msalService.loginPopup({
17509
17824
  scopes: ["openid", "profile", "email"],
17510
17825
  // prompt: "select_account"
@@ -17532,8 +17847,33 @@ class LoginComponent {
17532
17847
  recoverAccount() {
17533
17848
  this.router.navigate(["recover-account"]);
17534
17849
  }
17850
+ // Added: SSO troubleshooting — when a sign-in method is enabled (appConfig) but its
17851
+ // configuration is missing or incomplete, say so in the console instead of failing silently.
17852
+ logSsoDiagnostics() {
17853
+ if (this.appConfig.microsoftAuth) {
17854
+ if (!this.runtimeConfig.microsoftAuth) {
17855
+ console.error('[tin-spa] appConfig.microsoftAuth is TRUE (the login page shows the Microsoft button) but the runtime has Microsoft SSO disabled. ' +
17856
+ 'Set "MicrosoftAuth": true in assets/appsettings.json (or microsoftAuth: true in provideTinSpaRuntime) — clicking the button will fail until then.');
17857
+ }
17858
+ // Missing/empty ClientId or Authority is reported by tinSpaMsalInstanceFactory the moment
17859
+ // this page injects MsalService, so it is not repeated here.
17860
+ }
17861
+ if (this.appConfig.googleAuth) {
17862
+ const google = this.socialAuthConfig?.providers?.find(p => p.id === GoogleLoginProvider.PROVIDER_ID);
17863
+ const clientId = google?.provider?.clientId;
17864
+ if (!google) {
17865
+ console.error('[tin-spa] appConfig.googleAuth is TRUE (the login page shows Google sign-in) but no Google provider is registered in SocialAuthServiceConfig. ' +
17866
+ 'Google sign-in will not work — check GoogleClientID in assets/appsettings.json.');
17867
+ }
17868
+ else if (typeof clientId === 'string' && !clientId.trim()) {
17869
+ console.error('[tin-spa] appConfig.googleAuth is TRUE but the registered Google provider has an EMPTY client id. ' +
17870
+ 'Google sign-in will not work — set GoogleClientID in assets/appsettings.json.');
17871
+ }
17872
+ }
17873
+ }
17535
17874
  // Changed: Extracted from ngOnInit — runs only when no session could be restored
17536
17875
  setupForm() {
17876
+ this.logSsoDiagnostics(); // Added: surface SSO misconfiguration in the browser console
17537
17877
  if (this.route.snapshot.queryParams["sso"] == "msal" && this.appConfig.microsoftAuth) {
17538
17878
  this.loginWithMS();
17539
17879
  }
@@ -17632,13 +17972,21 @@ class LoginComponent {
17632
17972
  this.dialog.open(PrivacyDialogComponent, { width: '600px', maxHeight: '80vh' });
17633
17973
  }
17634
17974
  }
17635
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LoginComponent, deps: [{ token: HttpService }, { token: StorageService }, { token: i1$2.Router }, { token: MessageService }, { token: DataServiceLib }, { token: AuthService }, { token: LogService }, { token: i1$2.ActivatedRoute }, { token: NotificationsService }, { token: SignalRService }, { token: i10.MsalService }, { token: i4.MatDialog }, { token: SetupService }, { token: LastRouteService }], target: i0.ɵɵFactoryTarget.Component }); }
17636
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: LoginComponent, isStandalone: false, selector: "spa-login", ngImport: i0, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n </div>\r\n\r\n </mat-card-content>\r\n\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n\r\n <!-- Changed: Widths removed \u2014 all elements aligned to one column via .container CSS -->\r\n <div class=\"button\" *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n <button id=\"btnLogin\" mat-flat-button [disabled]=\"isProcessing\" (click)=\"login()\" color=\"primary\">Login</button>\r\n </div>\r\n\r\n <!-- Changed: Show signup button whenever selfService is enabled, not just for local/AD auth -->\r\n <div class=\"button\" *ngIf=\"appConfig.selfService\" >\r\n <button id=\"btnSignup\" mat-stroked-button color=\"primary\" (click)=\"signup()\">Create an account</button>\r\n </div>\r\n\r\n <div class=\"divider\" *ngIf=\"appConfig.googleAuth || appConfig.microsoftAuth\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Changed: Match modern Google button \u2014 pill shape + width aligned to the column -->\r\n <div class=\"button\" *ngIf=\"appConfig.googleAuth\">\r\n <asl-google-signin-button type='standard' width=\"350\" size='medium' shape=\"pill\" logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" style=\"margin-top: 1em\" (click)=\"recoverAccount()\">Forgot your password ?</a>\r\n\r\n\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"style=='modern'\" class=\"modern-login\">\r\n <mat-card class=\"login-card\">\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Welcome text -->\r\n <div class=\"header-section\"> <!-- Changed: Use custom div instead of mat-card-header -->\r\n <h2 class=\"login-title\">{{appConfig.loginTitle ?? appConfig.appName}}</h2> <!-- Changed: Use h2 for title -->\r\n <p class=\"login-subtitle\" *ngIf=\"appConfig.loginMessage\">{{appConfig.loginMessage}}</p> <!-- Changed: Use p for subtitle -->\r\n </div>\r\n\r\n <mat-card-content>\r\n <div class=\"login-form\">\r\n <!-- Username -->\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" [appearance]=\"'outline'\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <!-- Password -->\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" [appearance]=\"'outline'\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px; margin-bottom: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n <!-- Login Button -->\r\n <div class=\"button-container\">\r\n <button id=\"btnLogin\" mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"login()\">\r\n Login\r\n </button>\r\n </div>\r\n\r\n <!-- Divider -->\r\n <div class=\"divider\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Social Login -->\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <!-- Microsoft Login -->\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n <!-- Links -->\r\n <div class=\"links-container mb-5\">\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" color=\"primary\" (click)=\"recoverAccount()\">\r\n Forgot password?\r\n </a>\r\n\r\n <div *ngIf=\"appConfig.selfService\" class=\"signup-container\">\r\n <span>Don't have an account?</span>\r\n <a mat-button id=\"btnSignup\" color=\"primary\" (click)=\"signup()\">Sign up</a>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-footer>\r\n <div class=\"terms-container\">\r\n <mat-divider></mat-divider>\r\n <p class=\"terms-text\">\r\n By continuing, you acknowledge that you accept our\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openTerms()\">Terms and Conditions</a>\r\n and\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openPrivacy()\">Privacy Policy</a>.\r\n </p>\r\n </div>\r\n </mat-card-footer>\r\n </mat-card>\r\n</div>\r\n\r\n\r\n\r\n", styles: [".login-page{position:absolute;inset:0;overflow:auto}.background{min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.buttons{display:flex;flex-direction:row;justify-content:space-evenly}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.container mat-card-content,.container mat-card-actions{width:350px;max-width:100%;box-sizing:border-box;padding-left:0;padding-right:0;margin-left:auto;margin-right:auto}.container mat-card-actions{align-items:stretch}.container .button{width:100%}.container .button button{width:100%!important}.container .tin-input ::ng-deep mat-form-field{width:100%!important;margin-right:0!important}.container .button asl-google-signin-button{display:flex;justify-content:center;width:100%}.container mat-card{padding-top:16px;padding-bottom:44px;border-radius:16px}.container mat-card-content{padding-top:0;padding-bottom:0}.container mat-card-actions .button:first-of-type,.container mat-card-actions .divider:first-of-type{margin-top:20px}.modern-login{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.login-card{width:100%;max-width:400px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.login-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.login-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.mat-card-header{text-align:center;justify-content:center;margin-bottom:24px}.mat-card-title{margin:0;font-size:24px;font-weight:400}.mat-card-subtitle{margin:8px 0 0}.login-form{display:flex;flex-direction:column}.button-container button{width:100%;margin-top:10px;border-radius:24px;height:40px}.divider{position:relative;text-align:center;margin:10px 0}.divider span{background:transparent;padding:0 16px;color:#666;font-size:14px;position:relative}.social-login{display:flex;justify-content:center}.modern-login .button button{width:100%;border-radius:24px;height:40px;margin-top:10px}.links-container{text-align:center;margin-top:16px}.signup-container{margin-top:16px;color:#0009}.signup-container span{margin-right:8px}.terms-container{margin-top:24px;padding:16px}.terms-text{color:#0009;font-size:12px;text-align:center;margin:16px 0 0;line-height:1.5}.terms-link{padding:0 4px;min-width:auto;line-height:inherit;height:auto}\n"], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i18.MatCardContent, selector: "mat-card-content" }, { kind: "directive", type: i18.MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: i18.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i18.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: TextComponent, selector: "spa-text", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "format", "type", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextMaskComponent, selector: "spa-text-mask", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "regex", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "directive", type: i2$1.GoogleSigninButtonDirective, selector: "asl-google-signin-button", inputs: ["type", "size", "text", "shape", "theme", "logo_alignment", "width", "locale"] }] }); }
17975
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LoginComponent, deps: [{ token: HttpService }, { token: StorageService }, { token: i1$2.Router }, { token: MessageService }, { token: DataServiceLib }, { token: AuthService }, { token: LogService }, { token: i1$2.ActivatedRoute }, { token: NotificationsService }, { token: SignalRService }, { token: i10.MsalService }, { token: i4.MatDialog }, { token: SetupService }, { token: LastRouteService }, { token: TIN_SPA_RUNTIME_CONFIG }, { token: 'SocialAuthServiceConfig', optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
17976
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: LoginComponent, isStandalone: false, selector: "spa-login", ngImport: i0, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n </div>\r\n\r\n </mat-card-content>\r\n\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n\r\n <!-- Changed: Widths removed \u2014 all elements aligned to one column via .container CSS -->\r\n <div class=\"button\" *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n <button id=\"btnLogin\" mat-flat-button [disabled]=\"isProcessing\" (click)=\"login()\" color=\"primary\">Login</button>\r\n </div>\r\n\r\n <!-- Changed: Show signup button whenever selfService is enabled, not just for local/AD auth -->\r\n <div class=\"button\" *ngIf=\"appConfig.selfService\" >\r\n <button id=\"btnSignup\" mat-stroked-button color=\"primary\" (click)=\"signup()\">Create an account</button>\r\n </div>\r\n\r\n <div class=\"divider\" *ngIf=\"appConfig.googleAuth || appConfig.microsoftAuth\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Changed: Match modern Google button \u2014 pill shape + width aligned to the column -->\r\n <div class=\"button\" *ngIf=\"appConfig.googleAuth\">\r\n <asl-google-signin-button type='standard' width=\"350\" size='medium' shape=\"pill\" logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" style=\"margin-top: 1em\" (click)=\"recoverAccount()\">Forgot your password ?</a>\r\n\r\n\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"style=='modern'\" class=\"modern-login\">\r\n <mat-card class=\"login-card\">\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Welcome text -->\r\n <div class=\"header-section\"> <!-- Changed: Use custom div instead of mat-card-header -->\r\n <h2 class=\"login-title\">{{appConfig.loginTitle ?? appConfig.appName}}</h2> <!-- Changed: Use h2 for title -->\r\n <p class=\"login-subtitle\" *ngIf=\"appConfig.loginMessage\">{{appConfig.loginMessage}}</p> <!-- Changed: Use p for subtitle -->\r\n </div>\r\n\r\n <mat-card-content>\r\n <div class=\"login-form\">\r\n <!-- Username -->\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" [appearance]=\"'outline'\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <!-- Password -->\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" [appearance]=\"'outline'\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px; margin-bottom: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n <!-- Login Button -->\r\n <div class=\"button-container\">\r\n <button id=\"btnLogin\" mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"login()\">\r\n Login\r\n </button>\r\n </div>\r\n\r\n <!-- Divider -->\r\n <div class=\"divider\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Social Login -->\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <!-- Microsoft Login -->\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n <!-- Links -->\r\n <div class=\"links-container mb-5\">\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" color=\"primary\" (click)=\"recoverAccount()\">\r\n Forgot password?\r\n </a>\r\n\r\n <div *ngIf=\"appConfig.selfService\" class=\"signup-container\">\r\n <span>Don't have an account?</span>\r\n <a mat-button id=\"btnSignup\" color=\"primary\" (click)=\"signup()\">Sign up</a>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-footer>\r\n <div class=\"terms-container\">\r\n <mat-divider></mat-divider>\r\n <p class=\"terms-text\">\r\n By continuing, you acknowledge that you accept our\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openTerms()\">Terms and Conditions</a>\r\n and\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openPrivacy()\">Privacy Policy</a>.\r\n </p>\r\n </div>\r\n </mat-card-footer>\r\n </mat-card>\r\n</div>\r\n\r\n\r\n\r\n", styles: [".login-page{position:absolute;inset:0;overflow:auto}.background{min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.buttons{display:flex;flex-direction:row;justify-content:space-evenly}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.container mat-card-content,.container mat-card-actions{width:350px;max-width:100%;box-sizing:border-box;padding-left:0;padding-right:0;margin-left:auto;margin-right:auto}.container mat-card-actions{align-items:stretch}.container .button{width:100%}.container .button button{width:100%!important}.container .tin-input ::ng-deep mat-form-field{width:100%!important;margin-right:0!important}.container .button asl-google-signin-button{display:flex;justify-content:center;width:100%}.container mat-card{padding-top:16px;padding-bottom:44px;border-radius:16px}.container mat-card-content{padding-top:0;padding-bottom:0}.container mat-card-actions .button:first-of-type,.container mat-card-actions .divider:first-of-type{margin-top:20px}.modern-login{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.login-card{width:100%;max-width:400px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.login-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.login-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.mat-card-header{text-align:center;justify-content:center;margin-bottom:24px}.mat-card-title{margin:0;font-size:24px;font-weight:400}.mat-card-subtitle{margin:8px 0 0}.login-form{display:flex;flex-direction:column}.button-container button{width:100%;margin-top:10px;border-radius:24px;height:40px}.divider{position:relative;text-align:center;margin:10px 0}.divider span{background:transparent;padding:0 16px;color:#666;font-size:14px;position:relative}.social-login{display:flex;justify-content:center}.modern-login .button button{width:100%;border-radius:24px;height:40px;margin-top:10px}.links-container{text-align:center;margin-top:16px}.signup-container{margin-top:16px;color:#0009}.signup-container span{margin-right:8px}.terms-container{margin-top:24px;padding:16px}.terms-text{color:#0009;font-size:12px;text-align:center;margin:16px 0 0;line-height:1.5}.terms-link{padding:0 4px;min-width:auto;line-height:inherit;height:auto}\n"], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "directive", type: i6$1.MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: TextComponent, selector: "spa-text", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "format", "type", "width", "copyContent", "clearContent", "required", "min", "max", "regex", "suffix", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "component", type: TextMaskComponent, selector: "spa-text-mask", inputs: ["appearance", "readonly", "hint", "display", "placeholder", "value", "width", "required", "min", "max", "regex", "infoMessage"], outputs: ["valueChange", "leave", "enterPress"] }, { kind: "directive", type: i2$1.GoogleSigninButtonDirective, selector: "asl-google-signin-button", inputs: ["type", "size", "text", "shape", "theme", "logo_alignment", "width", "locale"] }] }); }
17637
17977
  }
17638
17978
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LoginComponent, decorators: [{
17639
17979
  type: Component,
17640
17980
  args: [{ selector: "spa-login", standalone: false, template: "\r\n <div *ngIf=\"style=='default'\" class=\"login-page background tin-bg-login\">\r\n\r\n <div class=\"container\" >\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3 \" style=\"width:400px; \">\r\n\r\n <mat-card-header style=\"margin-bottom: 30px;margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 40px;margin-bottom: 10px; margin-top: 20px; font-weight: 300\">{{appConfig.appName}}</mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n\r\n <div class=\"tin-input mt-2\">\r\n\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n </div>\r\n\r\n </mat-card-content>\r\n\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n\r\n <!-- Changed: Widths removed \u2014 all elements aligned to one column via .container CSS -->\r\n <div class=\"button\" *ngIf=\"appConfig.localAuth || appConfig.ADAuth\">\r\n <button id=\"btnLogin\" mat-flat-button [disabled]=\"isProcessing\" (click)=\"login()\" color=\"primary\">Login</button>\r\n </div>\r\n\r\n <!-- Changed: Show signup button whenever selfService is enabled, not just for local/AD auth -->\r\n <div class=\"button\" *ngIf=\"appConfig.selfService\" >\r\n <button id=\"btnSignup\" mat-stroked-button color=\"primary\" (click)=\"signup()\">Create an account</button>\r\n </div>\r\n\r\n <div class=\"divider\" *ngIf=\"appConfig.googleAuth || appConfig.microsoftAuth\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Changed: Match modern Google button \u2014 pill shape + width aligned to the column -->\r\n <div class=\"button\" *ngIf=\"appConfig.googleAuth\">\r\n <asl-google-signin-button type='standard' width=\"350\" size='medium' shape=\"pill\" logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" style=\"margin-top: 1em\" (click)=\"recoverAccount()\">Forgot your password ?</a>\r\n\r\n\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"style=='modern'\" class=\"modern-login\">\r\n <mat-card class=\"login-card\">\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Welcome text -->\r\n <div class=\"header-section\"> <!-- Changed: Use custom div instead of mat-card-header -->\r\n <h2 class=\"login-title\">{{appConfig.loginTitle ?? appConfig.appName}}</h2> <!-- Changed: Use h2 for title -->\r\n <p class=\"login-subtitle\" *ngIf=\"appConfig.loginMessage\">{{appConfig.loginMessage}}</p> <!-- Changed: Use p for subtitle -->\r\n </div>\r\n\r\n <mat-card-content>\r\n <div class=\"login-form\">\r\n <!-- Username -->\r\n <spa-text id=\"txtuserName\" display=\"Username\" [(value)]=\"user.userName\" [appearance]=\"'outline'\" style=\"margin-bottom: 20px;\"></spa-text>\r\n\r\n <!-- Password -->\r\n <spa-text-mask id=\"txtPassword\" display=\"Password\" [(value)]=\"user.password\" [appearance]=\"'outline'\" (enterPress)=\"login()\"></spa-text-mask>\r\n\r\n <!-- Changed: \"Keep me signed in\" now opt-in via appConfig.keepSignedIn -->\r\n <mat-checkbox *ngIf=\"appConfig.keepSignedIn\" [(ngModel)]=\"rememberMe\" color=\"primary\" style=\"margin-top: 10px; margin-bottom: 10px;\">Keep me signed in</mat-checkbox>\r\n\r\n <!-- Login Button -->\r\n <div class=\"button-container\">\r\n <button id=\"btnLogin\" mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"login()\">\r\n Login\r\n </button>\r\n </div>\r\n\r\n <!-- Divider -->\r\n <div class=\"divider\">\r\n <span>OR</span>\r\n </div>\r\n\r\n <!-- Social Login -->\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <!-- Microsoft Login -->\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage}}</button>\r\n </div>\r\n\r\n <!-- Links -->\r\n <div class=\"links-container mb-5\">\r\n <a *ngIf=\"appConfig.selfService\" mat-button id=\"lnkRecover\" color=\"primary\" (click)=\"recoverAccount()\">\r\n Forgot password?\r\n </a>\r\n\r\n <div *ngIf=\"appConfig.selfService\" class=\"signup-container\">\r\n <span>Don't have an account?</span>\r\n <a mat-button id=\"btnSignup\" color=\"primary\" (click)=\"signup()\">Sign up</a>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-footer>\r\n <div class=\"terms-container\">\r\n <mat-divider></mat-divider>\r\n <p class=\"terms-text\">\r\n By continuing, you acknowledge that you accept our\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openTerms()\">Terms and Conditions</a>\r\n and\r\n <a mat-button color=\"primary\" class=\"terms-link\" (click)=\"openPrivacy()\">Privacy Policy</a>.\r\n </p>\r\n </div>\r\n </mat-card-footer>\r\n </mat-card>\r\n</div>\r\n\r\n\r\n\r\n", styles: [".login-page{position:absolute;inset:0;overflow:auto}.background{min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.buttons{display:flex;flex-direction:row;justify-content:space-evenly}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.container mat-card-content,.container mat-card-actions{width:350px;max-width:100%;box-sizing:border-box;padding-left:0;padding-right:0;margin-left:auto;margin-right:auto}.container mat-card-actions{align-items:stretch}.container .button{width:100%}.container .button button{width:100%!important}.container .tin-input ::ng-deep mat-form-field{width:100%!important;margin-right:0!important}.container .button asl-google-signin-button{display:flex;justify-content:center;width:100%}.container mat-card{padding-top:16px;padding-bottom:44px;border-radius:16px}.container mat-card-content{padding-top:0;padding-bottom:0}.container mat-card-actions .button:first-of-type,.container mat-card-actions .divider:first-of-type{margin-top:20px}.modern-login{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.login-card{width:100%;max-width:400px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.login-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.login-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.mat-card-header{text-align:center;justify-content:center;margin-bottom:24px}.mat-card-title{margin:0;font-size:24px;font-weight:400}.mat-card-subtitle{margin:8px 0 0}.login-form{display:flex;flex-direction:column}.button-container button{width:100%;margin-top:10px;border-radius:24px;height:40px}.divider{position:relative;text-align:center;margin:10px 0}.divider span{background:transparent;padding:0 16px;color:#666;font-size:14px;position:relative}.social-login{display:flex;justify-content:center}.modern-login .button button{width:100%;border-radius:24px;height:40px;margin-top:10px}.links-container{text-align:center;margin-top:16px}.signup-container{margin-top:16px;color:#0009}.signup-container span{margin-right:8px}.terms-container{margin-top:24px;padding:16px}.terms-text{color:#0009;font-size:12px;text-align:center;margin:16px 0 0;line-height:1.5}.terms-link{padding:0 4px;min-width:auto;line-height:inherit;height:auto}\n"] }]
17641
- }], ctorParameters: () => [{ type: HttpService }, { type: StorageService }, { type: i1$2.Router }, { type: MessageService }, { type: DataServiceLib }, { type: AuthService }, { type: LogService }, { type: i1$2.ActivatedRoute }, { type: NotificationsService }, { type: SignalRService }, { type: i10.MsalService }, { type: i4.MatDialog }, { type: SetupService }, { type: LastRouteService }] });
17981
+ }], ctorParameters: () => [{ type: HttpService }, { type: StorageService }, { type: i1$2.Router }, { type: MessageService }, { type: DataServiceLib }, { type: AuthService }, { type: LogService }, { type: i1$2.ActivatedRoute }, { type: NotificationsService }, { type: SignalRService }, { type: i10.MsalService }, { type: i4.MatDialog }, { type: SetupService }, { type: LastRouteService }, { type: undefined, decorators: [{
17982
+ type: Inject,
17983
+ args: [TIN_SPA_RUNTIME_CONFIG]
17984
+ }] }, { type: undefined, decorators: [{
17985
+ type: Optional
17986
+ }, {
17987
+ type: Inject,
17988
+ args: ['SocialAuthServiceConfig']
17989
+ }] }] });
17642
17990
 
17643
17991
  // Rewritten: Step-based signup flow for Google/MSAL OAuth users
17644
17992
  class SignupComponent {
@@ -17717,6 +18065,11 @@ class SignupComponent {
17717
18065
  }
17718
18066
  // Login with Microsoft from signup page
17719
18067
  loginWithMS() {
18068
+ // MSAL v3 throws uninitialized_public_client_application unless initialize() has
18069
+ // completed; it is idempotent, so awaiting here is safe even when already done.
18070
+ this.msalService.instance.initialize().then(() => this.doMsalLoginPopup(), (error) => { console.error('MSAL initialize error:', error); this.messageService.toast("Microsoft login failed. Please try again."); });
18071
+ }
18072
+ doMsalLoginPopup() {
17720
18073
  this.msalService.loginPopup({
17721
18074
  scopes: ["openid", "profile", "email"],
17722
18075
  }).subscribe({
@@ -17818,7 +18171,7 @@ class SignupComponent {
17818
18171
  }
17819
18172
  }
17820
18173
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignupComponent, deps: [{ token: HttpService }, { token: MessageService }, { token: DataServiceLib }, { token: AuthService }, { token: StorageService }, { token: i1$2.Router }, { token: i10.MsalService }, { token: NotificationsService }, { token: SetupService }, { token: i4.MatDialog }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
17821
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SignupComponent, isStandalone: false, selector: "spa-signup", ngImport: i0, template: "<!-- Modern Style -->\r\n<div *ngIf=\"style=='modern'\" class=\"modern-signup\">\r\n <mat-card class=\"signup-card\">\r\n\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Provider Step -->\r\n <div *ngIf=\"step === 'provider'\">\r\n <div class=\"header-section\">\r\n <h2 class=\"signup-title\">Create your account</h2>\r\n <p class=\"signup-subtitle\">Get started with {{appConfig.appName}}</p>\r\n </div>\r\n\r\n <div class=\"provider-buttons\">\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage ?? 'Continue with Microsoft'}}</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"links-container\">\r\n <div class=\"login-link-container\">\r\n <span>Already have an account?</span>\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Log in</a>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Step (New Org) -->\r\n <div *ngIf=\"step === 'form'\">\r\n <div class=\"header-section\">\r\n <h2 class=\"signup-title\">Almost there</h2>\r\n <p class=\"signup-subtitle\">Confirm your details to get started</p>\r\n </div>\r\n\r\n <div class=\"signup-form\">\r\n <div class=\"name-row\">\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n </div>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Email</mat-label>\r\n <input matInput [value]=\"signupData.email\" readonly>\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Organisation Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.orgName\">\r\n </mat-form-field>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms and Conditions</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n\r\n <div class=\"button-container\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Create Account</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"links-container\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Invitations Step (Join Existing Org) -->\r\n <div *ngIf=\"step === 'invitations'\">\r\n <div class=\"header-section\">\r\n <mat-icon class=\"invite-icon\" color=\"primary\">mail</mat-icon>\r\n <h2 class=\"signup-title\">You've been invited!</h2>\r\n <p class=\"signup-subtitle\">Accept an invitation to get started</p>\r\n </div>\r\n\r\n <div class=\"signup-form\">\r\n <div class=\"invitation-list\">\r\n <mat-card *ngFor=\"let inv of signupData.invitations\" class=\"invitation-card\" [class.accepted]=\"inv.accepted\" (click)=\"toggleInvitation(inv)\">\r\n <div class=\"invitation-content\">\r\n <mat-icon [color]=\"inv.accepted ? 'primary' : ''\">{{inv.accepted ? 'check_circle' : 'circle'}}</mat-icon>\r\n <span class=\"invitation-org-name\">{{inv.tenantName}}</span>\r\n </div>\r\n </mat-card>\r\n </div>\r\n\r\n <div class=\"name-row\">\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n </div>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms and Conditions</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n\r\n <div class=\"button-container\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Complete Signup</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"links-container\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Complete Step -->\r\n <div *ngIf=\"step === 'complete'\" class=\"complete-section\">\r\n <mat-icon class=\"complete-icon\">check_circle</mat-icon>\r\n <h2 class=\"signup-title\">You're all set!</h2>\r\n <p class=\"signup-subtitle\">Welcome to {{appConfig.appName}}</p>\r\n <div class=\"button-container\">\r\n <button mat-flat-button color=\"primary\" (click)=\"goToDashboard()\">Go to Dashboard</button>\r\n </div>\r\n </div>\r\n\r\n </mat-card>\r\n</div>\r\n\r\n\r\n<!-- Default Style -->\r\n<div *ngIf=\"style=='default'\" class=\"default-signup background tin-bg-login\">\r\n <div class=\"container\">\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3\" style=\"width:400px;\">\r\n\r\n <!-- Provider Step -->\r\n <div *ngIf=\"step === 'provider'\">\r\n <mat-card-header style=\"margin-bottom: 30px; margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 28px; margin-bottom: 10px; margin-top: 20px; font-weight: 300\">Create Account</mat-card-title>\r\n <mat-card-subtitle>Get started with {{appConfig.appName}}</mat-card-subtitle>\r\n </mat-card-header>\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"button\">\r\n <asl-google-signin-button type='standard' width=\"320px\" size='medium' logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage ?? 'Continue with Microsoft'}}</button>\r\n </div>\r\n\r\n <div class=\"button\" style=\"margin-top: 20px;\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Already have an account? Log in</a>\r\n </div>\r\n </mat-card-actions>\r\n </div>\r\n\r\n <!-- Form Step (New Org) -->\r\n <div *ngIf=\"step === 'form'\">\r\n <mat-card-header style=\"margin-bottom: 20px; margin-top: 20px;\">\r\n <mat-card-title style=\"font-size: 28px; margin-bottom: 10px; font-weight: 300\">Almost there</mat-card-title>\r\n <mat-card-subtitle>Confirm your details to get started</mat-card-subtitle>\r\n </mat-card-header>\r\n\r\n <mat-card-content>\r\n <div class=\"signup-form-default\">\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Email</mat-label>\r\n <input matInput [value]=\"signupData.email\" readonly>\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Organisation Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.orgName\">\r\n </mat-form-field>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n <div class=\"button\">\r\n <button mat-flat-button color=\"primary\" style=\"width: 350px;\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Create Account</span>\r\n </button>\r\n </div>\r\n <div class=\"button\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </mat-card-actions>\r\n </div>\r\n\r\n <!-- Invitations Step -->\r\n <div *ngIf=\"step === 'invitations'\">\r\n <mat-card-header style=\"margin-bottom: 20px; margin-top: 20px;\">\r\n <mat-card-title style=\"font-size: 28px; margin-bottom: 10px; font-weight: 300\">You've been invited!</mat-card-title>\r\n <mat-card-subtitle>Accept an invitation to get started</mat-card-subtitle>\r\n </mat-card-header>\r\n\r\n <mat-card-content>\r\n <div class=\"signup-form-default\">\r\n <div class=\"invitation-list\">\r\n <mat-card *ngFor=\"let inv of signupData.invitations\" class=\"invitation-card\" [class.accepted]=\"inv.accepted\" (click)=\"toggleInvitation(inv)\">\r\n <div class=\"invitation-content\">\r\n <mat-icon [color]=\"inv.accepted ? 'primary' : ''\">{{inv.accepted ? 'check_circle' : 'circle'}}</mat-icon>\r\n <span class=\"invitation-org-name\">{{inv.tenantName}}</span>\r\n </div>\r\n </mat-card>\r\n </div>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n <div class=\"button\">\r\n <button mat-flat-button color=\"primary\" style=\"width: 350px;\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Complete Signup</span>\r\n </button>\r\n </div>\r\n <div class=\"button\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </mat-card-actions>\r\n </div>\r\n\r\n <!-- Complete Step -->\r\n <div *ngIf=\"step === 'complete'\" class=\"complete-section-default\">\r\n <mat-icon class=\"complete-icon-default\">check_circle</mat-icon>\r\n <h3 style=\"font-weight: 300; font-size: 28px;\">You're all set!</h3>\r\n <p style=\"color: rgba(0,0,0,0.6);\">Welcome to {{appConfig.appName}}</p>\r\n <div class=\"button\" style=\"margin-bottom: 20px;\">\r\n <button mat-flat-button color=\"primary\" style=\"width: 350px;\" (click)=\"goToDashboard()\">Go to Dashboard</button>\r\n </div>\r\n </div>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n</div>\r\n", styles: [".modern-signup{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.signup-card{width:100%;max-width:440px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.signup-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.signup-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.signup-form{display:flex;flex-direction:column}.name-row{display:flex;gap:12px}.name-field{flex:1}.full-width{width:100%}.button-container{margin:16px 0}.button-container button{width:100%;border-radius:24px;height:40px}.provider-buttons{display:flex;flex-direction:column;align-items:center;gap:10px}.provider-buttons .button button{width:320px;border-radius:24px;height:40px}.social-login{display:flex;justify-content:center}.links-container{text-align:center;margin-top:16px}.login-link-container{margin-top:16px;color:#0009}.login-link-container span{margin-right:8px}.terms-checkbox{margin:12px 0;font-size:13px}.terms-link{color:inherit;text-decoration:underline;cursor:pointer}.inline-spinner{display:inline-block}.invitation-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.invitation-card{cursor:pointer;padding:12px 16px;transition:all .2s ease;border:1px solid rgba(0,0,0,.12)}.invitation-card:hover{border-color:#0000003d}.invitation-card.accepted{border-color:var(--mdc-theme-primary, #3f51b5);background-color:#3f51b50a}.invitation-content{display:flex;align-items:center;gap:12px}.invitation-org-name{font-size:15px;font-weight:400}.invite-icon{font-size:40px;width:40px;height:40px;margin-bottom:8px}.complete-section{text-align:center;padding:40px 0}.complete-icon{font-size:64px;width:64px;height:64px;color:#4caf50;margin-bottom:16px}.default-signup{position:absolute;inset:0;overflow:auto;min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.signup-form-default{padding:0 20px;display:flex;flex-direction:column}.complete-section-default{text-align:center;padding:40px 20px}.complete-icon-default{font-size:64px;width:64px;height:64px;color:#4caf50;margin-bottom:16px}\n"], dependencies: [{ kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i18.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i18.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i18.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i18.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i19$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: i2$1.GoogleSigninButtonDirective, selector: "asl-google-signin-button", inputs: ["type", "size", "text", "shape", "theme", "logo_alignment", "width", "locale"] }] }); }
18174
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SignupComponent, isStandalone: false, selector: "spa-signup", ngImport: i0, template: "<!-- Modern Style -->\r\n<div *ngIf=\"style=='modern'\" class=\"modern-signup\">\r\n <mat-card class=\"signup-card\">\r\n\r\n <!-- Logo -->\r\n <div class=\"logo-container\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <!-- Provider Step -->\r\n <div *ngIf=\"step === 'provider'\">\r\n <div class=\"header-section\">\r\n <h2 class=\"signup-title\">Create your account</h2>\r\n <p class=\"signup-subtitle\">Get started with {{appConfig.appName}}</p>\r\n </div>\r\n\r\n <div class=\"provider-buttons\">\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"social-login\">\r\n <asl-google-signin-button type='standard' size='medium' width=\"320\" logo_alignment=\"center\" shape=\"pill\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage ?? 'Continue with Microsoft'}}</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"links-container\">\r\n <div class=\"login-link-container\">\r\n <span>Already have an account?</span>\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Log in</a>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Step (New Org) -->\r\n <div *ngIf=\"step === 'form'\">\r\n <div class=\"header-section\">\r\n <h2 class=\"signup-title\">Almost there</h2>\r\n <p class=\"signup-subtitle\">Confirm your details to get started</p>\r\n </div>\r\n\r\n <div class=\"signup-form\">\r\n <div class=\"name-row\">\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n </div>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Email</mat-label>\r\n <input matInput [value]=\"signupData.email\" readonly>\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Organisation Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.orgName\">\r\n </mat-form-field>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms and Conditions</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n\r\n <div class=\"button-container\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Create Account</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"links-container\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Invitations Step (Join Existing Org) -->\r\n <div *ngIf=\"step === 'invitations'\">\r\n <div class=\"header-section\">\r\n <mat-icon class=\"invite-icon\" color=\"primary\">mail</mat-icon>\r\n <h2 class=\"signup-title\">You've been invited!</h2>\r\n <p class=\"signup-subtitle\">Accept an invitation to get started</p>\r\n </div>\r\n\r\n <div class=\"signup-form\">\r\n <div class=\"invitation-list\">\r\n <mat-card *ngFor=\"let inv of signupData.invitations\" class=\"invitation-card\" [class.accepted]=\"inv.accepted\" (click)=\"toggleInvitation(inv)\">\r\n <div class=\"invitation-content\">\r\n <mat-icon [color]=\"inv.accepted ? 'primary' : ''\">{{inv.accepted ? 'check_circle' : 'circle'}}</mat-icon>\r\n <span class=\"invitation-org-name\">{{inv.tenantName}}</span>\r\n </div>\r\n </mat-card>\r\n </div>\r\n\r\n <div class=\"name-row\">\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"name-field\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n </div>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms and Conditions</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n\r\n <div class=\"button-container\">\r\n <button mat-flat-button color=\"primary\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Complete Signup</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"links-container\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Complete Step -->\r\n <div *ngIf=\"step === 'complete'\" class=\"complete-section\">\r\n <mat-icon class=\"complete-icon\">check_circle</mat-icon>\r\n <h2 class=\"signup-title\">You're all set!</h2>\r\n <p class=\"signup-subtitle\">Welcome to {{appConfig.appName}}</p>\r\n <div class=\"button-container\">\r\n <button mat-flat-button color=\"primary\" (click)=\"goToDashboard()\">Go to Dashboard</button>\r\n </div>\r\n </div>\r\n\r\n </mat-card>\r\n</div>\r\n\r\n\r\n<!-- Default Style -->\r\n<div *ngIf=\"style=='default'\" class=\"default-signup background tin-bg-login\">\r\n <div class=\"container\">\r\n\r\n <div class=\"logo\">\r\n <img *ngIf=\"appConfig.logoSize=='normal'\" [src]=\"appConfig.logo\" style=\"width: 100px\" />\r\n <img *ngIf=\"appConfig.logoSize=='medium'\" [src]=\"appConfig.logo\" style=\"width: 150px\" />\r\n <img *ngIf=\"appConfig.logoSize=='large'\" [src]=\"appConfig.logo\" style=\"width: 250px\" />\r\n </div>\r\n\r\n <mat-card class=\"mat-elevation-z3\" style=\"width:400px;\">\r\n\r\n <!-- Provider Step -->\r\n <div *ngIf=\"step === 'provider'\">\r\n <mat-card-header style=\"margin-bottom: 30px; margin-top: 30px;\">\r\n <mat-card-title style=\"font-size: 28px; margin-bottom: 10px; margin-top: 20px; font-weight: 300\">Create Account</mat-card-title>\r\n <mat-card-subtitle>Get started with {{appConfig.appName}}</mat-card-subtitle>\r\n </mat-card-header>\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n <div *ngIf=\"appConfig.googleAuth\" class=\"button\">\r\n <asl-google-signin-button type='standard' width=\"320px\" size='medium' logo_alignment=\"center\" style=\"text-align: center;\"></asl-google-signin-button>\r\n </div>\r\n\r\n <div class=\"button\" *ngIf=\"appConfig.microsoftAuth\">\r\n <button mat-stroked-button color=\"primary\" style=\"width: 350px;\" (click)=\"loginWithMS()\">{{appConfig.microsoftAuthMessage ?? 'Continue with Microsoft'}}</button>\r\n </div>\r\n\r\n <div class=\"button\" style=\"margin-top: 20px;\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Already have an account? Log in</a>\r\n </div>\r\n </mat-card-actions>\r\n </div>\r\n\r\n <!-- Form Step (New Org) -->\r\n <div *ngIf=\"step === 'form'\">\r\n <mat-card-header style=\"margin-bottom: 20px; margin-top: 20px;\">\r\n <mat-card-title style=\"font-size: 28px; margin-bottom: 10px; font-weight: 300\">Almost there</mat-card-title>\r\n <mat-card-subtitle>Confirm your details to get started</mat-card-subtitle>\r\n </mat-card-header>\r\n\r\n <mat-card-content>\r\n <div class=\"signup-form-default\">\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Email</mat-label>\r\n <input matInput [value]=\"signupData.email\" readonly>\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Organisation Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.orgName\">\r\n </mat-form-field>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n <div class=\"button\">\r\n <button mat-flat-button color=\"primary\" style=\"width: 350px;\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Create Account</span>\r\n </button>\r\n </div>\r\n <div class=\"button\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </mat-card-actions>\r\n </div>\r\n\r\n <!-- Invitations Step -->\r\n <div *ngIf=\"step === 'invitations'\">\r\n <mat-card-header style=\"margin-bottom: 20px; margin-top: 20px;\">\r\n <mat-card-title style=\"font-size: 28px; margin-bottom: 10px; font-weight: 300\">You've been invited!</mat-card-title>\r\n <mat-card-subtitle>Accept an invitation to get started</mat-card-subtitle>\r\n </mat-card-header>\r\n\r\n <mat-card-content>\r\n <div class=\"signup-form-default\">\r\n <div class=\"invitation-list\">\r\n <mat-card *ngFor=\"let inv of signupData.invitations\" class=\"invitation-card\" [class.accepted]=\"inv.accepted\" (click)=\"toggleInvitation(inv)\">\r\n <div class=\"invitation-content\">\r\n <mat-icon [color]=\"inv.accepted ? 'primary' : ''\">{{inv.accepted ? 'check_circle' : 'circle'}}</mat-icon>\r\n <span class=\"invitation-org-name\">{{inv.tenantName}}</span>\r\n </div>\r\n </mat-card>\r\n </div>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>First Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.firstName\">\r\n </mat-form-field>\r\n\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>Last Name</mat-label>\r\n <input matInput [(ngModel)]=\"signupData.lastName\">\r\n </mat-form-field>\r\n\r\n <mat-checkbox [(ngModel)]=\"signupData.termsAccepted\" color=\"primary\" class=\"terms-checkbox\">\r\n I agree to the\r\n <a (click)=\"openTerms(); $event.preventDefault()\" class=\"terms-link\">Terms</a>\r\n and\r\n <a (click)=\"openPrivacy(); $event.preventDefault()\" class=\"terms-link\">Privacy Policy</a>\r\n </mat-checkbox>\r\n </div>\r\n </mat-card-content>\r\n\r\n <mat-card-actions style=\"margin-bottom: 10px;\">\r\n <div class=\"button\">\r\n <button mat-flat-button color=\"primary\" style=\"width: 350px;\" [disabled]=\"isProcessing\" (click)=\"submit()\">\r\n <mat-spinner *ngIf=\"isProcessing\" diameter=\"20\" class=\"inline-spinner\"></mat-spinner>\r\n <span *ngIf=\"!isProcessing\">Complete Signup</span>\r\n </button>\r\n </div>\r\n <div class=\"button\">\r\n <a mat-button color=\"primary\" (click)=\"goToLogin()\">Back to Login</a>\r\n </div>\r\n </mat-card-actions>\r\n </div>\r\n\r\n <!-- Complete Step -->\r\n <div *ngIf=\"step === 'complete'\" class=\"complete-section-default\">\r\n <mat-icon class=\"complete-icon-default\">check_circle</mat-icon>\r\n <h3 style=\"font-weight: 300; font-size: 28px;\">You're all set!</h3>\r\n <p style=\"color: rgba(0,0,0,0.6);\">Welcome to {{appConfig.appName}}</p>\r\n <div class=\"button\" style=\"margin-bottom: 20px;\">\r\n <button mat-flat-button color=\"primary\" style=\"width: 350px;\" (click)=\"goToDashboard()\">Go to Dashboard</button>\r\n </div>\r\n </div>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n</div>\r\n", styles: [".modern-signup{min-height:100vh;display:flex;align-items:center;justify-content:center;background-color:#000}.signup-card{width:100%;max-width:440px;padding:24px}.logo-container{text-align:center;margin-bottom:24px}.header-section{text-align:center;margin-bottom:32px}.signup-title{margin:0 0 8px;font-size:32px;font-weight:300;letter-spacing:.5px;color:#000000de;text-align:center}.signup-subtitle{margin:0;font-size:14px;font-weight:300;color:#0000008a;letter-spacing:.25px;text-align:center}.signup-form{display:flex;flex-direction:column}.name-row{display:flex;gap:12px}.name-field{flex:1}.full-width{width:100%}.button-container{margin:16px 0}.button-container button{width:100%;border-radius:24px;height:40px}.provider-buttons{display:flex;flex-direction:column;align-items:center;gap:10px}.provider-buttons .button button{width:320px;border-radius:24px;height:40px}.social-login{display:flex;justify-content:center}.links-container{text-align:center;margin-top:16px}.login-link-container{margin-top:16px;color:#0009}.login-link-container span{margin-right:8px}.terms-checkbox{margin:12px 0;font-size:13px}.terms-link{color:inherit;text-decoration:underline;cursor:pointer}.inline-spinner{display:inline-block}.invitation-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.invitation-card{cursor:pointer;padding:12px 16px;transition:all .2s ease;border:1px solid rgba(0,0,0,.12)}.invitation-card:hover{border-color:#0000003d}.invitation-card.accepted{border-color:var(--mdc-theme-primary, #3f51b5);background-color:#3f51b50a}.invitation-content{display:flex;align-items:center;gap:12px}.invitation-org-name{font-size:15px;font-weight:400}.invite-icon{font-size:40px;width:40px;height:40px;margin-bottom:8px}.complete-section{text-align:center;padding:40px 0}.complete-icon{font-size:64px;width:64px;height:64px;color:#4caf50;margin-bottom:16px}.default-signup{position:absolute;inset:0;overflow:auto;min-height:100%}.container{display:flex;flex-direction:column;align-items:center;height:100vh}.logo{margin-top:3em;margin-bottom:1em}.container mat-card-header{text-align:center;justify-content:center}.container mat-card-title{text-align:center}.container mat-card-actions{display:flex;flex-direction:column;align-items:center;justify-content:center}.button{display:flex;flex-direction:row;justify-content:center;margin-top:10px}.signup-form-default{padding:0 20px;display:flex;flex-direction:column}.complete-section-default{text-align:center;padding:40px 20px}.complete-icon-default{font-size:64px;width:64px;height:64px;color:#4caf50;margin-bottom:16px}\n"], dependencies: [{ kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i19$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: i2$1.GoogleSigninButtonDirective, selector: "asl-google-signin-button", inputs: ["type", "size", "text", "shape", "theme", "logo_alignment", "width", "locale"] }] }); }
17822
18175
  }
17823
18176
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignupComponent, decorators: [{
17824
18177
  type: Component,
@@ -18171,7 +18524,7 @@ class LogsComponent {
18171
18524
  { name: 'userFullName', alias: 'Name' },
18172
18525
  { name: 'source', },
18173
18526
  { name: 'details', maxLength: 150 },
18174
- { name: 'tenantName', alias: 'Tenant', hiddenCondition: x => !this.dataService.appConfig.multitenant },
18527
+ { name: 'tenantName', alias: 'Tenant', hidden: x => !this.dataService.appConfig.multitenant },
18175
18528
  ],
18176
18529
  loadAction: { url: 'logs/all/x' },
18177
18530
  buttons: [
@@ -18211,11 +18564,11 @@ class UsersComponent {
18211
18564
  },
18212
18565
  {
18213
18566
  name: 'password', type: 'text-mask', required: true, // Changed: password -> text-mask
18214
- hiddenCondition: x => x.authType !== 'local', span: true,
18567
+ hidden: x => x.authType !== 'local', span: true,
18215
18568
  },
18216
18569
  {
18217
18570
  name: 'confirmPassword', type: 'text-mask', required: true, // Changed: password -> text-mask
18218
- hiddenCondition: x => x.authType !== 'local', span: true
18571
+ hidden: x => x.authType !== 'local', span: true
18219
18572
  },
18220
18573
  { name: 'email', type: 'text', required: false, span: true },
18221
18574
  { name: 'roleID', type: 'select', alias: 'Role', required: true, span: true, defaultFirstValue: true },
@@ -18246,8 +18599,8 @@ class UsersComponent {
18246
18599
  },
18247
18600
  { name: 'firstName', type: 'text', required: true, readonlyCondition: x => x.authType !== 'local' },
18248
18601
  { name: 'lastName', type: 'text', required: true, readonlyCondition: x => x.authType !== 'local' },
18249
- { name: 'password', type: 'text-mask', required: true, hiddenCondition: x => x.authType !== 'local', span: true }, // Changed: password -> text-mask
18250
- { name: 'confirmPassword', type: 'text-mask', required: true, hiddenCondition: x => x.authType !== 'local', span: true }, // Changed: password -> text-mask
18602
+ { name: 'password', type: 'text-mask', required: true, hidden: x => x.authType !== 'local', span: true }, // Changed: password -> text-mask
18603
+ { name: 'confirmPassword', type: 'text-mask', required: true, hidden: x => x.authType !== 'local', span: true }, // Changed: password -> text-mask
18251
18604
  { name: 'email', type: 'text', required: false, span: true },
18252
18605
  { name: 'roleID', type: 'select', alias: 'Role', required: true, span: true, defaultFirstValue: true },
18253
18606
  ]
@@ -18491,7 +18844,7 @@ class RolesComponent {
18491
18844
  });
18492
18845
  }
18493
18846
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RolesComponent, deps: [{ token: HttpService }, { token: i1$2.Router }, { token: AuthService }, { token: DataServiceLib }, { token: DialogService }, { token: i4.MatDialog }, { token: MessageService }], target: i0.ɵɵFactoryTarget.Component }); }
18494
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RolesComponent, isStandalone: false, selector: "spa-roles", ngImport: i0, template: "<h4> Roles </h4>\r\n<hr />\r\n\r\n<div class=\"container-fluid mb-5\">\r\n\r\n <div class=\"d-flex justify-content-between mb-2\">\r\n\r\n <div >\r\n <button id=\"btnNewRole\" mat-raised-button color=\"primary\" (click)=\"addRole()\">New Role</button>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-end\">\r\n <button id=\"btnRefresh\" mat-icon-button color=\"primary\" (click)=\"refresh()\" matTooltip=\"refresh data\" matTooltipPosition=\"right\"><mat-icon >refresh</mat-icon></button>\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <div class=\"row mt-2 mb-1\" *ngFor=\"let role of roles\">\r\n\r\n <mat-card class=\"mat-elevation-z8\" style=\"width:100%\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n\r\n <label style=\"font-size: 16px;\">{{role.roleName}}</label>\r\n\r\n <button mat-icon-button color=\"primary\" matTooltip=\"Rename Role\" (click)=\"renameRole(role)\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <hr style=\"margin-top: 0px;\">\r\n\r\n <div class=\"tin-row\" style=\" font-size:12px;\">\r\n\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capItem of appConfig.capItems\">\r\n\r\n <!-- Main item-->\r\n <mat-checkbox *ngIf=\"capItem.isBool || capItem.capSubItems\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capItem.name]\" (ngModelChange)=\"onCapItemChange(capItem, $event, role)\">\r\n {{capItem.display}}\r\n <span *ngIf=\"!role[capItem.name] && hasSubItemsAccess(capItem, role)\" class=\"asterisk\" style=\"color: red;\">*</span>\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capItem.isBool && !capItem.capSubItems\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capItem.display\"\r\n [(value)]=\"role[capItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n\r\n <ng-container *ngIf=\"capItem.capSubItems && role[capItem.name]\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubItem of capItem.capSubItems\">\r\n\r\n\r\n <!-- Sub Item -->\r\n <mat-checkbox *ngIf=\"capSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubItem.name]\">\r\n {{capSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubItem.display\"\r\n [(value)]=\"role[capSubItem.name]\"\r\n width=\"150px\"\r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n <ng-container *ngIf=\"capSubItem.capSubItems\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubSubItem of capSubItem.capSubItems\">\r\n\r\n <!-- Sub Sub Items -->\r\n <mat-checkbox *ngIf=\"capSubSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubSubItem.name]\">\r\n {{capSubSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubSubItem.display\"\r\n [(value)]=\"role[capSubSubItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <mat-card-actions>\r\n\r\n <button mat-raised-button color=\"primary\" (click)=\"updateRole(role)\" style=\"margin-right:10px;\">\r\n <mat-icon>done_all</mat-icon>\r\n Update\r\n </button>\r\n\r\n <button mat-raised-button (click)=\"deleteRole(role)\" style=\"margin-right:10px\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n\r\n <hr style=\"margin-top: 50px;\" />\r\n\r\n\r\n</div>\r\n\r\n", styles: [""], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i3.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: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }] }); }
18847
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RolesComponent, isStandalone: false, selector: "spa-roles", ngImport: i0, template: "<h4> Roles </h4>\r\n<hr />\r\n\r\n<div class=\"container-fluid mb-5\">\r\n\r\n <div class=\"d-flex justify-content-between mb-2\">\r\n\r\n <div >\r\n <button id=\"btnNewRole\" mat-raised-button color=\"primary\" (click)=\"addRole()\">New Role</button>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-end\">\r\n <button id=\"btnRefresh\" mat-icon-button color=\"primary\" (click)=\"refresh()\" matTooltip=\"refresh data\" matTooltipPosition=\"right\"><mat-icon >refresh</mat-icon></button>\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <div class=\"row mt-2 mb-1\" *ngFor=\"let role of roles\">\r\n\r\n <mat-card class=\"mat-elevation-z8\" style=\"width:100%\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n\r\n <label style=\"font-size: 16px;\">{{role.roleName}}</label>\r\n\r\n <button mat-icon-button color=\"primary\" matTooltip=\"Rename Role\" (click)=\"renameRole(role)\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <hr style=\"margin-top: 0px;\">\r\n\r\n <div class=\"tin-row\" style=\" font-size:12px;\">\r\n\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capItem of appConfig.capItems\">\r\n\r\n <!-- Main item-->\r\n <mat-checkbox *ngIf=\"capItem.isBool || capItem.capSubItems\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capItem.name]\" (ngModelChange)=\"onCapItemChange(capItem, $event, role)\">\r\n {{capItem.display}}\r\n <span *ngIf=\"!role[capItem.name] && hasSubItemsAccess(capItem, role)\" class=\"asterisk\" style=\"color: red;\">*</span>\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capItem.isBool && !capItem.capSubItems\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capItem.display\"\r\n [(value)]=\"role[capItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n\r\n <ng-container *ngIf=\"capItem.capSubItems && role[capItem.name]\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubItem of capItem.capSubItems\">\r\n\r\n\r\n <!-- Sub Item -->\r\n <mat-checkbox *ngIf=\"capSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubItem.name]\">\r\n {{capSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubItem.display\"\r\n [(value)]=\"role[capSubItem.name]\"\r\n width=\"150px\"\r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n <ng-container *ngIf=\"capSubItem.capSubItems\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubSubItem of capSubItem.capSubItems\">\r\n\r\n <!-- Sub Sub Items -->\r\n <mat-checkbox *ngIf=\"capSubSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubSubItem.name]\">\r\n {{capSubSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubSubItem.display\"\r\n [(value)]=\"role[capSubSubItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <mat-card-actions>\r\n\r\n <button mat-raised-button color=\"primary\" (click)=\"updateRole(role)\" style=\"margin-right:10px;\">\r\n <mat-icon>done_all</mat-icon>\r\n Update\r\n </button>\r\n\r\n <button mat-raised-button (click)=\"deleteRole(role)\" style=\"margin-right:10px\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n\r\n <hr style=\"margin-top: 50px;\" />\r\n\r\n\r\n</div>\r\n\r\n", styles: [""], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: i3.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: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }] }); }
18495
18848
  }
18496
18849
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RolesComponent, decorators: [{
18497
18850
  type: Component,
@@ -18833,7 +19186,7 @@ class PreferencesComponent {
18833
19186
  });
18834
19187
  }
18835
19188
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PreferencesComponent, deps: [{ token: DataServiceLib }, { token: MessageService }], target: i0.ɵɵFactoryTarget.Component }); }
18836
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: PreferencesComponent, isStandalone: false, selector: "spa-preferences", ngImport: i0, template: "<h4>Preferences</h4>\r\n<hr>\r\n\r\n<mat-card>\r\n <mat-card-content>\r\n <form (ngSubmit)=\"onSubmit()\">\r\n\r\n <div class=\"tin-col mt-3\">\r\n\r\n <div class=\"header-title\">General</div>\r\n <hr class=\"header-line\">\r\n <div class=\"ml-4 header\">\r\n <spa-select display=\"Default Currency\" [(value)]=\"preference.baseCurrencyID\" optionDisplay=\"name\" optionValue=\"value\" [loadAction]=\"{url: 'currencies/list'}\"></spa-select>\r\n </div>\r\n\r\n <div class=\"header-title\">Accounting</div>\r\n <hr class=\"header-line\">\r\n <div class=\"ml-4 header\">\r\n <spa-select display=\"First Month of Year\" [(value)]=\"preference.firstMonthOfYear\" optionDisplay=\"name\" optionValue=\"value\" [options]=\"monthOptions\"></spa-select>\r\n <spa-select display=\"First Day of Month\" [(value)]=\"preference.firstDayOfMonth\" optionDisplay=\"name\" optionValue=\"value\" [options]=\"dayOptions\"></spa-select>\r\n </div>\r\n\r\n </div>\r\n\r\n <mat-card-actions>\r\n <button mat-raised-button color=\"primary\" type=\"submit\">Save Preferences</button>\r\n </mat-card-actions>\r\n </form>\r\n </mat-card-content>\r\n</mat-card>\r\n", styles: [".header-title{font-size:24px;font-weight:300}.header-line{margin-top:5px;margin-bottom:5px}.header{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:16px;margin-bottom:30px}\n"], dependencies: [{ kind: "directive", type: i2$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i3.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: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i18.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }] }); }
19189
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: PreferencesComponent, isStandalone: false, selector: "spa-preferences", ngImport: i0, template: "<h4>Preferences</h4>\r\n<hr>\r\n\r\n<mat-card>\r\n <mat-card-content>\r\n <form (ngSubmit)=\"onSubmit()\">\r\n\r\n <div class=\"tin-col mt-3\">\r\n\r\n <div class=\"header-title\">General</div>\r\n <hr class=\"header-line\">\r\n <div class=\"ml-4 header\">\r\n <spa-select display=\"Default Currency\" [(value)]=\"preference.baseCurrencyID\" optionDisplay=\"name\" optionValue=\"value\" [loadAction]=\"{url: 'currencies/list'}\"></spa-select>\r\n </div>\r\n\r\n <div class=\"header-title\">Accounting</div>\r\n <hr class=\"header-line\">\r\n <div class=\"ml-4 header\">\r\n <spa-select display=\"First Month of Year\" [(value)]=\"preference.firstMonthOfYear\" optionDisplay=\"name\" optionValue=\"value\" [options]=\"monthOptions\"></spa-select>\r\n <spa-select display=\"First Day of Month\" [(value)]=\"preference.firstDayOfMonth\" optionDisplay=\"name\" optionValue=\"value\" [options]=\"dayOptions\"></spa-select>\r\n </div>\r\n\r\n </div>\r\n\r\n <mat-card-actions>\r\n <button mat-raised-button color=\"primary\" type=\"submit\">Save Preferences</button>\r\n </mat-card-actions>\r\n </form>\r\n </mat-card-content>\r\n</mat-card>\r\n", styles: [".header-title{font-size:24px;font-weight:300}.header-line{margin-top:5px;margin-bottom:5px}.header{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:16px;margin-bottom:30px}\n"], dependencies: [{ kind: "directive", type: i2$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i3.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: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }] }); }
18837
19190
  }
18838
19191
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PreferencesComponent, decorators: [{
18839
19192
  type: Component,
@@ -18983,7 +19336,7 @@ class AccountsComponent {
18983
19336
  <spa-tiles [config]="accountTileConfig"></spa-tiles>
18984
19337
 
18985
19338
  <spa-tabs [tableConfigs]="accountTabConfigs"></spa-tabs>
18986
- `, isInline: true, dependencies: [{ kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
19339
+ `, isInline: true, dependencies: [{ kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
18987
19340
  }
18988
19341
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountsComponent, decorators: [{
18989
19342
  type: Component,
@@ -19037,7 +19390,7 @@ class AggregatesComponent {
19037
19390
  <hr>
19038
19391
 
19039
19392
  <spa-tabs [tableConfigs]="aggregateTabConfigs"></spa-tabs>
19040
- `, isInline: true, dependencies: [{ kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
19393
+ `, isInline: true, dependencies: [{ kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
19041
19394
  }
19042
19395
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AggregatesComponent, decorators: [{
19043
19396
  type: Component,
@@ -19129,7 +19482,7 @@ class AgingComponent {
19129
19482
  </div>
19130
19483
 
19131
19484
  <spa-tabs [tableConfigs]="agingTableConfigs"></spa-tabs>
19132
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
19485
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
19133
19486
  }
19134
19487
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgingComponent, decorators: [{
19135
19488
  type: Component,
@@ -19331,7 +19684,7 @@ class PurchasingService {
19331
19684
  compact: true,
19332
19685
  messages: [
19333
19686
  { message: 'Please capture the products purchased and click complete to proceed', type: 'success',
19334
- showCondition: (x) => x.status === PurchaseStatus.Draft || x.status === PurchaseStatus.Confirmed // Changed: InventoryReceiptStatus → PurchaseStatus, Receiving → Confirmed
19687
+ visible: (x) => x.status === PurchaseStatus.Draft || x.status === PurchaseStatus.Confirmed // Changed: InventoryReceiptStatus → PurchaseStatus, Receiving → Confirmed
19335
19688
  }
19336
19689
  ]
19337
19690
  },
@@ -19351,20 +19704,20 @@ class PurchasingService {
19351
19704
  { name: 'currencyID', type: 'select', alias: 'Currency', section: 'purchaseInfo', loadAction: { url: 'currencies/list/x' }, defaultFirstValue: true, infoMessage: 'Currency for this purchase (defaults to base currency)' }, // Changed: receiptInfo → purchaseInfo
19352
19705
  { name: 'paymentType', type: 'select', required: true, alias: 'Payment Method', section: 'purchaseInfo', defaultFirstValue: true, // Changed: receiptInfo → purchaseInfo
19353
19706
  options: this.paymentTypeOptions, infoMessage: 'Payment method used for this purchase',
19354
- hiddenCondition: x => x.timing === 1,
19707
+ hidden: x => x.timing === 1,
19355
19708
  requiredCondition: x => x.timing === 0
19356
19709
  },
19357
19710
  { name: 'purchaseNumber', type: 'text', alias: 'Purchase Number', readonly: true, hideOnCreate: true, section: 'purchaseInfo', infoMessage: 'Auto-generated unique purchase identifier' }, // Changed: receiptNumber → purchaseNumber
19358
19711
  { name: 'poNumber', type: 'text', alias: 'PO Number', hideOnCreate: true, section: 'purchaseInfo', infoMessage: 'Linked purchase order if receiving against a PO', // Changed: receiptInfo → purchaseInfo
19359
- hiddenCondition: (row) => !row.purchaseOrderID,
19712
+ hidden: (row) => !row.purchaseOrderID,
19360
19713
  },
19361
19714
  { name: 'multipleProducts', type: 'checkbox', alias: 'Multiple Products', defaultValue: false, hideOnExists: true, infoMessage: 'Check this box if you want to add multiple products to this purchase' }, // Changed: receipt → purchase
19362
19715
  { name: 'quickPurchaseItem', type: 'section', alias: 'Quick Purchase Item',
19363
- hiddenCondition: x => x.multipleProducts === true || x.purchaseID // Changed: inventoryReceiptID → purchaseID
19716
+ hidden: x => x.multipleProducts === true || x.purchaseID // Changed: inventoryReceiptID → purchaseID
19364
19717
  },
19365
19718
  { name: 'productID', type: 'select', required: true, alias: 'Product', section: 'quickPurchaseItem', span: true,
19366
19719
  loadAction: { url: 'products/list/x' },
19367
- hiddenCondition: x => x.multipleProducts === true,
19720
+ hidden: x => x.multipleProducts === true,
19368
19721
  onSelectChange: (selectedProductId, formData, option) => {
19369
19722
  if (option && option.costPrice) {
19370
19723
  formData.unitCost = option.costPrice;
@@ -19372,15 +19725,15 @@ class PurchasingService {
19372
19725
  },
19373
19726
  detailsConfig: this.inventoryService.productDetailsConfig, infoMessage: 'Product being purchased' },
19374
19727
  { name: 'quantity', type: 'number', required: true, alias: 'Quantity', section: 'quickPurchaseItem',
19375
- hiddenCondition: x => x.multipleProducts === true, infoMessage: 'Number of units purchased'
19728
+ hidden: x => x.multipleProducts === true, infoMessage: 'Number of units purchased'
19376
19729
  },
19377
19730
  { name: 'unitCost', type: 'money', required: true, alias: 'Unit Cost', section: 'quickPurchaseItem',
19378
- hiddenCondition: x => x.multipleProducts === true, infoMessage: 'Cost per unit'
19731
+ hidden: x => x.multipleProducts === true, infoMessage: 'Cost per unit'
19379
19732
  },
19380
19733
  { name: 'totals', type: 'section', alias: 'Totals', hideOnCreate: true, collapsed: true },
19381
19734
  { name: 'totalAmount', type: 'money', alias: 'Total Amount', readonly: true, section: 'totals', infoMessage: 'Total value of all items' },
19382
- { name: 'dueDate', type: 'date', alias: 'Due Date', readonly: true, section: 'totals', hiddenCondition: (x) => x.timing !== 1, infoMessage: 'Payment due date (auto-set to purchase date + 30 days)' }, // Changed: receipt date → purchase date
19383
- { name: 'outstandingAmount', type: 'money', alias: 'Outstanding', readonly: true, section: 'totals', hiddenCondition: (x) => x.timing !== 1, infoMessage: 'Amount still owed to supplier' },
19735
+ { name: 'dueDate', type: 'date', alias: 'Due Date', readonly: true, section: 'totals', hidden: (x) => x.timing !== 1, infoMessage: 'Payment due date (auto-set to purchase date + 30 days)' }, // Changed: receipt date → purchase date
19736
+ { name: 'outstandingAmount', type: 'money', alias: 'Outstanding', readonly: true, section: 'totals', hidden: (x) => x.timing !== 1, infoMessage: 'Amount still owed to supplier' },
19384
19737
  { name: 'additionalInfo', type: 'section', alias: 'Additional Information', collapsed: true },
19385
19738
  { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'additionalInfo', infoMessage: 'Additional notes about this purchase' }, // Changed: receipt → purchase
19386
19739
  { name: 'supplierInvoiceNumber', type: 'text', alias: 'Supplier Invoice #', section: 'additionalInfo', infoMessage: 'Invoice number from supplier' },
@@ -19442,7 +19795,7 @@ class PurchasingService {
19442
19795
  ]
19443
19796
  },
19444
19797
  { name: 'totalAmount', type: 'money', alias: 'Total Value' },
19445
- { name: 'outstandingAmount', type: 'money', alias: 'Outstanding', hiddenCondition: (x) => x?.timing !== 1 }
19798
+ { name: 'outstandingAmount', type: 'money', alias: 'Outstanding', hidden: (x) => x?.timing !== 1 }
19446
19799
  ],
19447
19800
  buttons: [
19448
19801
  { name: 'view', icon: { name: 'launch' }, dialog: true, detailsConfig: this.purchaseDetailsConfig }, // Changed: inventoryReceiptDetailsConfig → purchaseDetailsConfig
@@ -19569,7 +19922,7 @@ class PurchasingService {
19569
19922
  };
19570
19923
  this.supplierPaymentsTableConfig = {
19571
19924
  tabTitle: 'Payments',
19572
- hideTabCondition: (x) => x.timing !== 1, // Changed: Hide Payments tab for Immediate purchases (only show for Deferred/Credit)
19925
+ hidden: (x) => x.timing !== 1, // Changed: Hide Payments tab for Immediate purchases (only show for Deferred/Credit)
19573
19926
  showFilter: false,
19574
19927
  elevation: 'none',
19575
19928
  flatButtons: true,
@@ -19688,7 +20041,7 @@ class SupplierAgingComponent {
19688
20041
  </div>
19689
20042
 
19690
20043
  <spa-tabs [tableConfigs]="agingTableConfigs"></spa-tabs>
19691
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
20044
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
19692
20045
  }
19693
20046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SupplierAgingComponent, decorators: [{
19694
20047
  type: Component,
@@ -20176,7 +20529,7 @@ class StatementComponent {
20176
20529
  </mat-card>
20177
20530
  </div>
20178
20531
  </ng-template>
20179
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.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: "component", type: i5$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i5$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i7$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
20532
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.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: "component", type: i5$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i5$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: i7$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
20180
20533
  }
20181
20534
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StatementComponent, decorators: [{
20182
20535
  type: Component,
@@ -20367,7 +20720,7 @@ class ReportsComponent {
20367
20720
  </div>
20368
20721
 
20369
20722
  <spa-tabs [tableConfigs]="reportTabConfigs"></spa-tabs>
20370
- `, isInline: true, dependencies: [{ kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i3.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: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
20723
+ `, isInline: true, dependencies: [{ kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i3.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: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
20371
20724
  }
20372
20725
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReportsComponent, decorators: [{
20373
20726
  type: Component,
@@ -20541,7 +20894,7 @@ class BudgetVsActualComponent {
20541
20894
  <spa-tiles *ngIf="summaryTileConfig.loadAction" [config]="summaryTileConfig"></spa-tiles>
20542
20895
 
20543
20896
  <spa-tabs *ngIf="reportTabConfigs.length > 0" [tableConfigs]="reportTabConfigs"></spa-tabs>
20544
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }] }); }
20897
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }] }); }
20545
20898
  }
20546
20899
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BudgetVsActualComponent, decorators: [{
20547
20900
  type: Component,
@@ -20936,7 +21289,7 @@ class VatReturnComponent {
20936
21289
  </mat-card>
20937
21290
  </mat-tab>
20938
21291
  </mat-tab-group>
20939
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.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: "component", type: i5$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i5$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
21292
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.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: "component", type: i5$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i5$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
20940
21293
  }
20941
21294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: VatReturnComponent, decorators: [{
20942
21295
  type: Component,
@@ -21588,7 +21941,7 @@ class ReconcileDialogComponent {
21588
21941
  <button mat-stroked-button (click)="match()" [disabled]="!selectedLine || !selectedEntry">Match Selected</button>
21589
21942
  <button mat-flat-button color="primary" (click)="complete()" [disabled]="!rec || rec.matchedCount != rec.lineCount">Complete Reconciliation</button>
21590
21943
  </mat-dialog-actions>
21591
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
21944
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.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: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
21592
21945
  }
21593
21946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReconcileDialogComponent, decorators: [{
21594
21947
  type: Component,
@@ -22367,7 +22720,7 @@ class SalesService {
22367
22720
  sticky: true,
22368
22721
  messages: [
22369
22722
  { message: 'Please add sale items and click complete to finalise the sale', type: 'success',
22370
- showCondition: (x) => x.paymentStatus === 0
22723
+ visible: (x) => x.paymentStatus === 0
22371
22724
  }
22372
22725
  ]
22373
22726
  },
@@ -22379,7 +22732,7 @@ class SalesService {
22379
22732
  { name: 'customerID', type: 'select', required: true, alias: 'Customer', section: 'saleInfo', loadAction: { url: 'customers/list/x' }, detailsConfig: this.dataService.customerDetailsConfig },
22380
22733
  { name: 'timing', type: 'select', required: true, alias: 'Transaction Type', section: 'saleInfo', loadAction: { url: 'sales/list/timing' }, defaultFirstValue: true, infoMessage: 'Cash Sale (pay now) or Credit Sale (pay later)' },
22381
22734
  { name: 'currencyID', type: 'select', alias: 'Currency', section: 'saleInfo', loadAction: { url: 'currencies/list/x' }, defaultFirstValue: true, infoMessage: 'Currency for this sale (defaults to base currency)' }, // Changed: Added currency select for multi-currency sales
22382
- { name: 'paymentMethod', type: 'select', required: true, alias: 'Payment Method', section: 'saleInfo', defaultFirstValue: true, infoMessage: 'Method of payment used for this sale', requiredCondition: x => x.timing === 0, hiddenCondition: x => x.timing === 1,
22735
+ { name: 'paymentMethod', type: 'select', required: true, alias: 'Payment Method', section: 'saleInfo', defaultFirstValue: true, infoMessage: 'Method of payment used for this sale', requiredCondition: x => x.timing === 0, hidden: x => x.timing === 1,
22383
22736
  options: [
22384
22737
  { name: 'Cash', value: 0 },
22385
22738
  { name: 'Bank Transfer', value: 1 },
@@ -22390,16 +22743,16 @@ class SalesService {
22390
22743
  },
22391
22744
  { name: 'multipleProducts', type: 'checkbox', alias: 'Multiple Products', defaultValue: false, hideOnExists: true, infoMessage: 'Check this box if you want to add multiple products to this sale' },
22392
22745
  { name: 'saleType', type: 'select', required: true, alias: 'Sale Type', section: 'saleInfo', options: [{ name: 'Quick Sale', value: 0 }, { name: 'From Order', value: 1 }], defaultFirstValue: true, hideOnCreate: true, infoMessage: 'Select if this is a quick sale or created from an order' },
22393
- { name: 'quickSaleItem', type: 'section', alias: 'Quick Sale Item', hiddenCondition: x => x.multipleProducts === true || x.saleID },
22394
- { name: 'productID', type: 'select', required: true, alias: 'Product', section: 'quickSaleItem', span: true, loadAction: { url: 'products/list/x' }, hiddenCondition: x => x.multipleProducts === true,
22746
+ { name: 'quickSaleItem', type: 'section', alias: 'Quick Sale Item', hidden: x => x.multipleProducts === true || x.saleID },
22747
+ { name: 'productID', type: 'select', required: true, alias: 'Product', section: 'quickSaleItem', span: true, loadAction: { url: 'products/list/x' }, hidden: x => x.multipleProducts === true,
22395
22748
  onSelectChange: (selectedProductId, formData, option) => {
22396
22749
  if (option && option.sellingPrice) {
22397
22750
  formData.unitPrice = option.sellingPrice;
22398
22751
  }
22399
22752
  }
22400
22753
  },
22401
- { name: 'quantity', type: 'number', required: true, alias: 'Quantity', section: 'quickSaleItem', defaultValue: 1, hiddenCondition: x => x.multipleProducts === true, infoMessage: 'Quantity of the product' },
22402
- { name: 'unitPrice', type: 'money', required: true, alias: 'Unit Price', section: 'quickSaleItem', hiddenCondition: x => x.multipleProducts === true, infoMessage: 'Price per unit' },
22754
+ { name: 'quantity', type: 'number', required: true, alias: 'Quantity', section: 'quickSaleItem', defaultValue: 1, hidden: x => x.multipleProducts === true, infoMessage: 'Quantity of the product' },
22755
+ { name: 'unitPrice', type: 'money', required: true, alias: 'Unit Price', section: 'quickSaleItem', hidden: x => x.multipleProducts === true, infoMessage: 'Price per unit' },
22403
22756
  { name: 'totals', type: 'section', alias: 'Totals', collapsed: true, hideOnCreate: true },
22404
22757
  { name: 'subTotal', type: 'money', alias: 'Sub Total', readonly: true, section: 'totals', infoMessage: 'Total before tax and discounts' },
22405
22758
  { name: 'taxAmount', type: 'money', alias: 'Tax Amount', section: 'totals', infoMessage: 'Tax amount applied to the sale' },
@@ -22407,7 +22760,7 @@ class SalesService {
22407
22760
  { name: 'totalAmount', type: 'money', alias: 'Total Amount', readonly: true, section: 'totals', infoMessage: 'Final amount after tax and discounts' },
22408
22761
  { name: 'additionalInfo', type: 'section', alias: 'Additional Information', collapsed: true },
22409
22762
  { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'additionalInfo', infoMessage: 'Additional notes or comments about this sale' },
22410
- { name: 'paymentReference', type: 'text', alias: 'Payment Reference', section: 'additionalInfo', infoMessage: 'Transaction reference number or payment receipt', hiddenCondition: x => x.timing === 1 },
22763
+ { name: 'paymentReference', type: 'text', alias: 'Payment Reference', section: 'additionalInfo', infoMessage: 'Transaction reference number or payment receipt', hidden: x => x.timing === 1 },
22411
22764
  ],
22412
22765
  loadAction: { url: 'sales/id' },
22413
22766
  heroField: 'saleID'
@@ -22454,7 +22807,7 @@ class SalesService {
22454
22807
  { name: 'reference', type: 'text', alias: 'Reference' }
22455
22808
  ],
22456
22809
  loadAction: { url: 'invoicepayments/x/x' }, loadCriteria: 'invoice', loadIDField: 'invoiceID',
22457
- hideTabCondition: (x) => x.timing !== 1
22810
+ hidden: (x) => x.timing !== 1
22458
22811
  };
22459
22812
  this.saleDetailsConfig = {
22460
22813
  formConfig: this.saleFormConfig,
@@ -23965,7 +24318,7 @@ class ProductionService {
23965
24318
  sticky: true,
23966
24319
  messages: [
23967
24320
  { message: 'Click Complete to process production and consume materials', type: 'success', // Changed: content to message
23968
- showCondition: (x) => x.statusName === 'Draft'
24321
+ visible: (x) => x.statusName === 'Draft'
23969
24322
  }
23970
24323
  ]
23971
24324
  },
@@ -25024,7 +25377,7 @@ class SubscriptionPageComponent {
25024
25377
  </mat-card>
25025
25378
  </div>
25026
25379
  </div>
25027
- `, isInline: true, styles: [".subscription-container{padding:16px;max-width:1200px}.page-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}.section-title{margin:24px 0 16px;color:#555}.current-plan-card{margin-bottom:24px}.plan-avatar{font-size:40px;width:40px;height:40px;color:#1976d2}.plan-details{display:flex;gap:32px;flex-wrap:wrap;padding:8px 0}.detail-item{display:flex;flex-direction:column}.detail-label{font-size:12px;color:#888;text-transform:uppercase}.detail-value{font-size:16px;font-weight:500}.status-badge{padding:2px 10px;border-radius:12px;font-size:12px;font-weight:500}.status-0{background:#e3f2fd;color:#1565c0}.status-1{background:#e8f5e9;color:#2e7d32}.status-2{background:#fff3e0;color:#e65100}.status-3{background:#fce4ec;color:#c62828}.status-4{background:#f5f5f5;color:#616161}.no-plan-message{display:flex;align-items:center;gap:8px;color:#666;margin:0}.plans-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px}.plan-card{display:flex;flex-direction:column}.plan-card.current{border:2px solid #1976d2}.plan-price{text-align:center;padding:16px 0}.price-amount{font-size:32px;font-weight:700;color:#1976d2}.price-period{font-size:14px;color:#888}.plan-limits{display:flex;gap:16px;justify-content:center;padding-bottom:12px}.limit-item{display:flex;align-items:center;gap:4px;font-size:13px;color:#666}.limit-item mat-icon{font-size:18px;width:18px;height:18px}.feature-list{padding:12px 0}.feature-item{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:14px}.feature-check{color:#4caf50;font-size:18px;width:18px;height:18px}.feature-limit{font-size:12px;color:#999}.no-features{display:flex;align-items:center;gap:4px;color:#999;font-size:13px}mat-card-actions{margin-top:auto}mat-card-actions button{width:100%}.overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.confirm-dialog{max-width:440px;width:90%}.price-info{font-size:16px}.trial-info{display:flex;align-items:center;gap:4px;color:#1976d2;font-size:14px}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i18.MatCardAvatar, selector: "[mat-card-avatar], [matCardAvatar]" }, { kind: "directive", type: i18.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i18.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i18.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i18.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
25380
+ `, isInline: true, styles: [".subscription-container{padding:16px;max-width:1200px}.page-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}.section-title{margin:24px 0 16px;color:#555}.current-plan-card{margin-bottom:24px}.plan-avatar{font-size:40px;width:40px;height:40px;color:#1976d2}.plan-details{display:flex;gap:32px;flex-wrap:wrap;padding:8px 0}.detail-item{display:flex;flex-direction:column}.detail-label{font-size:12px;color:#888;text-transform:uppercase}.detail-value{font-size:16px;font-weight:500}.status-badge{padding:2px 10px;border-radius:12px;font-size:12px;font-weight:500}.status-0{background:#e3f2fd;color:#1565c0}.status-1{background:#e8f5e9;color:#2e7d32}.status-2{background:#fff3e0;color:#e65100}.status-3{background:#fce4ec;color:#c62828}.status-4{background:#f5f5f5;color:#616161}.no-plan-message{display:flex;align-items:center;gap:8px;color:#666;margin:0}.plans-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px}.plan-card{display:flex;flex-direction:column}.plan-card.current{border:2px solid #1976d2}.plan-price{text-align:center;padding:16px 0}.price-amount{font-size:32px;font-weight:700;color:#1976d2}.price-period{font-size:14px;color:#888}.plan-limits{display:flex;gap:16px;justify-content:center;padding-bottom:12px}.limit-item{display:flex;align-items:center;gap:4px;font-size:13px;color:#666}.limit-item mat-icon{font-size:18px;width:18px;height:18px}.feature-list{padding:12px 0}.feature-item{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:14px}.feature-check{color:#4caf50;font-size:18px;width:18px;height:18px}.feature-limit{font-size:12px;color:#999}.no-features{display:flex;align-items:center;gap:4px;color:#999;font-size:13px}mat-card-actions{margin-top:auto}mat-card-actions button{width:100%}.overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.confirm-dialog{max-width:440px;width:90%}.price-info{font-size:16px}.trial-info{display:flex;align-items:center;gap:4px;color:#1976d2;font-size:14px}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardAvatar, selector: "[mat-card-avatar], [matCardAvatar]" }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i17.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
25028
25381
  }
25029
25382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SubscriptionPageComponent, decorators: [{
25030
25383
  type: Component,
@@ -25473,7 +25826,7 @@ class BillingPageComponent {
25473
25826
  </mat-card>
25474
25827
  </div>
25475
25828
  </div>
25476
- `, isInline: true, styles: [".billing-container{padding:16px;max-width:1200px}.page-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}.summary-card{margin-bottom:24px}.summary-row{display:flex;gap:32px;flex-wrap:wrap}.summary-item{display:flex;flex-direction:column}.summary-label{font-size:12px;color:#888;text-transform:uppercase}.summary-value{font-size:16px;font-weight:500}.status-badge{padding:2px 10px;border-radius:12px;font-size:12px;font-weight:500}.status-0{background:#e3f2fd;color:#1565c0}.status-1{background:#e8f5e9;color:#2e7d32}.status-2{background:#fff3e0;color:#e65100}.status-3{background:#fce4ec;color:#c62828}.status-4{background:#f5f5f5;color:#616161}.invoices-card{margin-bottom:24px}.table-container{overflow-x:auto}.invoice-table,.transaction-table{width:100%;border-collapse:collapse}.invoice-table th,.invoice-table td,.transaction-table th,.transaction-table td{padding:10px 12px;text-align:left;border-bottom:1px solid #e0e0e0;font-size:14px}.invoice-table th,.transaction-table th{font-weight:500;color:#666;font-size:12px;text-transform:uppercase}.invoice-table tbody tr:hover{background:#f5f5f5}.invoice-status{padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}.inv-status-0{background:#fff3e0;color:#e65100}.inv-status-1{background:#e8f5e9;color:#2e7d32}.inv-status-2{background:#fce4ec;color:#c62828}.inv-status-3{background:#f3e5f5;color:#6a1b9a}.txn-status{padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}.txn-status-0{background:#e3f2fd;color:#1565c0}.txn-status-1{background:#fff3e0;color:#e65100}.txn-status-2{background:#e8f5e9;color:#2e7d32}.txn-status-3{background:#fce4ec;color:#c62828}.txn-status-4{background:#f5f5f5;color:#616161}.empty-state,.loading-state{display:flex;flex-direction:column;align-items:center;padding:32px;color:#999}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:8px}.overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.detail-dialog{max-width:640px;width:90%;max-height:80vh;overflow-y:auto}.payment-dialog{max-width:440px;width:90%}.close-btn{position:absolute;top:8px;right:8px}.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px}.detail-field{display:flex;flex-direction:column}.field-label{font-size:12px;color:#888;text-transform:uppercase}.field-value{font-size:14px;font-weight:500}.transactions-section{margin-top:16px}.transactions-section h5{margin:0 0 8px;color:#555}.no-transactions{color:#999;font-size:13px}.payment-summary{margin-bottom:16px}.payment-summary p{margin:4px 0;font-size:15px}.payment-state{display:flex;flex-direction:column;align-items:center;padding:24px;text-align:center}.result-icon{font-size:48px;width:48px;height:48px;margin-bottom:8px}.success-icon{color:#4caf50}.redirect-icon{color:#1976d2}.error-icon{color:#f44336}.payment-ref{font-size:12px;color:#888}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: i3.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: "component", type: i18.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i18.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i18.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i18.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i18.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i19$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
25829
+ `, isInline: true, styles: [".billing-container{padding:16px;max-width:1200px}.page-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}.summary-card{margin-bottom:24px}.summary-row{display:flex;gap:32px;flex-wrap:wrap}.summary-item{display:flex;flex-direction:column}.summary-label{font-size:12px;color:#888;text-transform:uppercase}.summary-value{font-size:16px;font-weight:500}.status-badge{padding:2px 10px;border-radius:12px;font-size:12px;font-weight:500}.status-0{background:#e3f2fd;color:#1565c0}.status-1{background:#e8f5e9;color:#2e7d32}.status-2{background:#fff3e0;color:#e65100}.status-3{background:#fce4ec;color:#c62828}.status-4{background:#f5f5f5;color:#616161}.invoices-card{margin-bottom:24px}.table-container{overflow-x:auto}.invoice-table,.transaction-table{width:100%;border-collapse:collapse}.invoice-table th,.invoice-table td,.transaction-table th,.transaction-table td{padding:10px 12px;text-align:left;border-bottom:1px solid #e0e0e0;font-size:14px}.invoice-table th,.transaction-table th{font-weight:500;color:#666;font-size:12px;text-transform:uppercase}.invoice-table tbody tr:hover{background:#f5f5f5}.invoice-status{padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}.inv-status-0{background:#fff3e0;color:#e65100}.inv-status-1{background:#e8f5e9;color:#2e7d32}.inv-status-2{background:#fce4ec;color:#c62828}.inv-status-3{background:#f3e5f5;color:#6a1b9a}.txn-status{padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}.txn-status-0{background:#e3f2fd;color:#1565c0}.txn-status-1{background:#fff3e0;color:#e65100}.txn-status-2{background:#e8f5e9;color:#2e7d32}.txn-status-3{background:#fce4ec;color:#c62828}.txn-status-4{background:#f5f5f5;color:#616161}.empty-state,.loading-state{display:flex;flex-direction:column;align-items:center;padding:32px;color:#999}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:8px}.overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.detail-dialog{max-width:640px;width:90%;max-height:80vh;overflow-y:auto}.payment-dialog{max-width:440px;width:90%}.close-btn{position:absolute;top:8px;right:8px}.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px}.detail-field{display:flex;flex-direction:column}.field-label{font-size:12px;color:#888;text-transform:uppercase}.field-value{font-size:14px;font-weight:500}.transactions-section{margin-top:16px}.transactions-section h5{margin:0 0 8px;color:#555}.no-transactions{color:#999;font-size:13px}.payment-summary{margin-bottom:16px}.payment-summary p{margin:4px 0;font-size:15px}.payment-state{display:flex;flex-direction:column;align-items:center;padding:24px;text-align:center}.result-icon{font-size:48px;width:48px;height:48px;margin-bottom:8px}.success-icon{color:#4caf50}.redirect-icon{color:#1976d2}.error-icon{color:#f44336}.payment-ref{font-size:12px;color:#888}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.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: i3.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: "component", type: i6$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$1.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i6$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i6$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i6$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i19$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: i7$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }] }); }
25477
25830
  }
25478
25831
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BillingPageComponent, decorators: [{
25479
25832
  type: Component,
@@ -25821,7 +26174,7 @@ class ApprovalsConfigComponent {
25821
26174
  { name: 'notify', type: 'checkbox', defaultValue: true, },
25822
26175
  {
25823
26176
  name: 'channels', type: 'select', alias: 'Notification Channels', span: false, defaultFirstValue: true, required: true,
25824
- hiddenCondition: x => x.notify === false,
26177
+ hidden: x => x.notify === false,
25825
26178
  options: this.dataService.notificationOptions,
25826
26179
  }
25827
26180
  ]
@@ -25923,8 +26276,8 @@ class NotificationsComponent {
25923
26276
  },
25924
26277
  { name: 'durationHours', type: 'number', required: true, defaultValue: 24, hint: 'How long to keep this notification active' },
25925
26278
  { name: 'notifyAllUsers', type: 'checkbox', alias: 'Notify All Users', span: false },
25926
- { name: 'usernames', type: 'text-multi', alias: 'Recipients', copyContent: true, clearContent: true, span: true, hiddenCondition: x => x.notifyAllUsers, loadAction: { url: 'user/list/x' } }, // Changed: multi-text -> text-multi
25927
- { name: 'roleIDs', type: 'select-multi', strict: true, copyContent: true, alias: 'Roles', span: false, hiddenCondition: x => x.notifyAllUsers, loadAction: { url: 'role/list/x' } }, // Changed: multi-select -> select-multi
26279
+ { name: 'usernames', type: 'text-multi', alias: 'Recipients', copyContent: true, clearContent: true, span: true, hidden: x => x.notifyAllUsers, loadAction: { url: 'user/list/x' } }, // Changed: multi-text -> text-multi
26280
+ { name: 'roleIDs', type: 'select-multi', strict: true, copyContent: true, alias: 'Roles', span: false, hidden: x => x.notifyAllUsers, loadAction: { url: 'role/list/x' } }, // Changed: multi-select -> select-multi
25928
26281
  {
25929
26282
  name: 'channels', type: 'select', alias: 'Notification Channels', span: false, defaultFirstValue: true, required: true,
25930
26283
  options: this.dataService.notificationOptions,
@@ -26050,9 +26403,9 @@ class NotificationsConfigComponent {
26050
26403
  security: { allow: [this.dataService.capNotificationsConfig] },
26051
26404
  fields: [
26052
26405
  { name: 'operationType', type: 'select', required: true, alias: 'Operation Type', loadAction: { url: 'notificationconfigs/meta/x' }, optionDisplay: 'name', optionValue: 'value' },
26053
- { name: 'customAction', type: 'text', required: true, alias: 'Custom Action', hiddenCondition: x => x.operationType !== 4 }, // 4 = Custom
26406
+ { name: 'customAction', type: 'text', required: true, alias: 'Custom Action', hidden: x => x.operationType !== 4 }, // 4 = Custom
26054
26407
  { name: 'enabled', type: 'checkbox', alias: 'Enable Notifications', defaultValue: true },
26055
- { name: 'roleIDs', type: 'select-multi', alias: 'Notify Roles', required: true, loadAction: { url: 'role/list/x' }, }, //hiddenCondition: x => !x.enabled // Changed: multi-select -> select-multi
26408
+ { name: 'roleIDs', type: 'select-multi', alias: 'Notify Roles', required: true, loadAction: { url: 'role/list/x' }, }, //hidden: x => !x.enabled // Changed: multi-select -> select-multi
26056
26409
  {
26057
26410
  name: 'channels', type: 'select', alias: 'Notification Channels', span: false, defaultFirstValue: true, required: true,
26058
26411
  options: this.dataService.notificationOptions,
@@ -26832,8 +27185,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
26832
27185
  class SetupModule {
26833
27186
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SetupModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
26834
27187
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: SetupModule, declarations: [SetupGuideComponent], imports: [CommonModule,
27188
+ FormsModule,
26835
27189
  SpaMatModule] }); }
26836
27190
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SetupModule, imports: [CommonModule,
27191
+ FormsModule,
26837
27192
  SpaMatModule] }); }
26838
27193
  }
26839
27194
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SetupModule, decorators: [{
@@ -26844,6 +27199,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
26844
27199
  ],
26845
27200
  imports: [
26846
27201
  CommonModule,
27202
+ FormsModule,
26847
27203
  SpaMatModule
26848
27204
  ]
26849
27205
  }]
@@ -26952,5 +27308,5 @@ const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://w
26952
27308
  * Generated bundle index. Do not edit.
26953
27309
  */
26954
27310
 
26955
- export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CreditNotesComponent as AccountingCreditNotesComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, VatReturnComponent as AccountingVatReturnComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, AnalyticsService, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BankReconciliationComponent, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CreditNoteStatus, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EditorComponent, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FiscalPeriodStatus, FiscalPeriodsComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, ImportDialogComponent, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, JournalEntryDialogComponent, LabelComponent, LastRouteService, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OfflineIndicatorComponent, OfflineService, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PaginationConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, QuoteStatus, QuotesComponent, ReceiptDialogComponent, ReceiptStatus, ReceiptsComponent, ReconcileDialogComponent, RecoverAccountComponent, Register, RevenueScheduleDialogComponent, RevenueSchedulesComponent, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SetupGuideComponent, SetupService, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatementImportDialogComponent, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, SyncComponent, TIN_SPA_RUNTIME_CONFIG, TabService, TableComponent, TableConfig, TabsComponent, TasksComponent, TenancyModule, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, WorkflowModule, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, provideTinSpaRuntime, tinSpaLocationStrategyFactory, tinSpaMsalInstanceFactory, viewerDialog };
27311
+ export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CreditNotesComponent as AccountingCreditNotesComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, VatReturnComponent as AccountingVatReturnComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, AnalyticsService, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BankReconciliationComponent, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CreditNoteStatus, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EditorComponent, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FiscalPeriodStatus, FiscalPeriodsComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, ImportDialogComponent, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, JournalEntryDialogComponent, LabelComponent, LastRouteService, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OfflineIndicatorComponent, OfflineService, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PaginationConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, QuoteStatus, QuotesComponent, ReceiptDialogComponent, ReceiptStatus, ReceiptsComponent, ReconcileDialogComponent, RecoverAccountComponent, Register, RevenueScheduleDialogComponent, RevenueSchedulesComponent, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SetupGuideComponent, SetupService, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatementImportDialogComponent, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, SyncComponent, TIN_SPA_RUNTIME_CONFIG, TabService, TableComponent, TableConfig, TabsComponent, TasksComponent, TenancyModule, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, WorkflowModule, authGuard, dialogOptions, featureGuard, getTinSpaAppSettings, loadTinSpaAppSettings, loginConfig, messageDialog, provideTinSpaRuntime, tinSpaLocationStrategyFactory, tinSpaMsalInstanceFactory, tinSpaRuntimeConfigFactory, viewerDialog };
26956
27312
  //# sourceMappingURL=tin-spa.mjs.map