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.
- package/fesm2022/tin-spa.mjs +524 -168
- package/fesm2022/tin-spa.mjs.map +1 -1
- package/index.d.ts +140 -26
- package/package.json +1 -1
package/fesm2022/tin-spa.mjs
CHANGED
|
@@ -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
|
|
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
|
|
47
|
+
import * as i6$1 from '@angular/material/card';
|
|
48
48
|
import { MatCardModule } from '@angular/material/card';
|
|
49
|
-
import * as
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1408
|
-
//
|
|
1409
|
-
//
|
|
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
|
|
1430
|
-
|
|
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 &&
|
|
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
|
-
:
|
|
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
|
-
|
|
1450
|
-
|
|
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
|
-
|
|
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',
|
|
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',
|
|
5028
|
-
{ name: 'includeInBankTotal', type: 'checkbox', alias: 'Include in Bank Total',
|
|
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
|
-
|
|
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
|
-
|
|
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' },
|
|
5151
|
-
{ name: 'isTaxInclusive', type: 'checkbox', alias: 'Price includes Tax',
|
|
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',
|
|
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,
|
|
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
|
-
|
|
6079
|
+
hidden: (x) => x.frequency !== 3 },
|
|
5899
6080
|
{ name: 'dayOfWeek', type: 'select', alias: 'Day of Week', nullable: true,
|
|
5900
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
7731
|
-
{ name: 'isGoodCondition', type: 'checkbox', alias: 'Good Condition',
|
|
7732
|
-
{ name: 'conditionNotes', type: 'text', alias: 'Condition Notes', span: true,
|
|
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' },
|
|
7774
|
-
{ name: 'saleID', type: 'select', alias: 'Sale', section: 'sourceInfo', loadAction: { url: 'sales/list/x' },
|
|
7775
|
-
{ name: 'supplierID', type: 'select', alias: 'Supplier', section: 'sourceInfo', loadAction: { url: 'suppliers/list/x' },
|
|
7776
|
-
{ name: 'purchaseID', type: 'select', alias: 'Purchase', section: 'sourceInfo', loadAction: { url: 'purchases/list/x' },
|
|
7777
|
-
{ name: 'requisitionID', type: 'select', alias: 'Requisition', section: 'sourceInfo', loadAction: { url: 'requisitions/list/x' },
|
|
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
|
|
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 (
|
|
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 =>
|
|
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
|
|
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
|
-
|
|
8946
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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> \r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label> \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:#
|
|
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> \r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label> \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> \r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label> \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:#
|
|
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> \r\n <mat-label style=\"font-weight:bold; text-align: center;\" [ngStyle]=\"{'color':tile.color }\">{{data?.[tile.name] ?? 0}}</mat-label> \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:
|
|
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:
|
|
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> {{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;\">© {{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 © {{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> {{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;\">© {{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 © {{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> {{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;\">© {{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 © {{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> {{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;\">© {{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 © {{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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
18250
|
-
{ name: 'confirmPassword', type: 'text-mask', required: true,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
19383
|
-
{ name: 'outstandingAmount', type: 'money', alias: 'Outstanding', readonly: true, section: 'totals',
|
|
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',
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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',
|
|
22394
|
-
{ name: 'productID', type: 'select', required: true, alias: 'Product', section: 'quickSaleItem', span: true, loadAction: { url: 'products/list/x' },
|
|
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,
|
|
22402
|
-
{ name: 'unitPrice', type: 'money', required: true, alias: 'Unit Price', section: 'quickSaleItem',
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
25927
|
-
{ name: 'roleIDs', type: 'select-multi', strict: true, copyContent: true, alias: 'Roles', span: false,
|
|
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',
|
|
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' }, }, //
|
|
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
|