tin-spa 20.6.3 → 20.6.5

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.
@@ -11,11 +11,11 @@ import { MatIconModule } from '@angular/material/icon';
11
11
  import * as i3 from '@angular/material/form-field';
12
12
  import { MatFormFieldModule, MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
13
13
  import { of, BehaviorSubject, Subject, tap, timeout, finalize, share, Observable, throwError, interval } from 'rxjs';
14
- import { mergeMap, filter, startWith, map, finalize as finalize$1, catchError, take, switchMap } from 'rxjs/operators';
14
+ import { mergeMap, filter, startWith, map, catchError, finalize as finalize$1, take, switchMap } from 'rxjs/operators';
15
15
  import * as i2$1 from '@angular/material/snack-bar';
16
16
  import { MatSnackBarModule } from '@angular/material/snack-bar';
17
17
  import * as i1$2 from '@angular/router';
18
- import { Router, RouterModule, NavigationEnd } from '@angular/router';
18
+ import { NavigationEnd, Router, RouterModule } from '@angular/router';
19
19
  import * as i2$2 from '@abacritt/angularx-social-login';
20
20
  import { SocialLoginModule } from '@abacritt/angularx-social-login';
21
21
  import * as i1$1 from '@angular/common/http';
@@ -1517,6 +1517,9 @@ class SignalRService {
1517
1517
  }
1518
1518
  // Changed: Single startConnection method handles both notification and entity change listeners
1519
1519
  startConnection(hubUrl, token) {
1520
+ // Changed: Allow disabling real-time via localStorage flag (used by E2E tests)
1521
+ if (localStorage.getItem('disableRealTime') === 'true')
1522
+ return;
1520
1523
  if (this.hubConnection && this.hubConnection.state !== signalR.HubConnectionState.Disconnected)
1521
1524
  return;
1522
1525
  if (this.hubConnection)
@@ -1992,8 +1995,10 @@ class DataServiceLib {
1992
1995
  this.capAging = new CapItem; // Added: Capability for aging report
1993
1996
  this.capTaxRates = new CapItem; // Changed: Capability for tax rates management
1994
1997
  this.capStandingOrders = new CapItem; // Changed: Capability for standing orders management
1995
- this.capFixedAssets = new CapItem; // Added: Capability for fixed assets management
1996
- this.capFixedAssetCategories = new CapItem; // Added: Capability for fixed asset categories management
1998
+ this.capFixedAssetsModule = new CapItem; // Changed: Top-level Fixed Assets module
1999
+ this.capFixedAssetsDashboard = new CapItem; // Changed: Fixed Assets dashboard
2000
+ this.capFixedAssets = new CapItem; // Changed: Asset Register (renamed from Fixed Assets)
2001
+ this.capFixedAssetCategories = new CapItem; // Changed: Depreciation Categories
1997
2002
  this.capCurrencies = new CapItem; // Changed: Capability for currencies management
1998
2003
  this.capTransactionTypes = new CapItem;
1999
2004
  this.capTransactions = new CapItem;
@@ -2936,7 +2941,7 @@ class DataServiceLib {
2936
2941
  this.capAccounting.name = "cap27";
2937
2942
  this.capAccounting.display = "Accounting";
2938
2943
  this.capAccounting.icon = "account_balance";
2939
- this.capAccounting.capSubItems = [this.capAccountingDashboard, this.capAccounts, this.capTransactions, this.capAggregates, this.capReports, this.capTransactionTypes, this.capTaxRates, this.capCurrencies, this.capStandingOrders, this.capFixedAssets, this.capFixedAssetCategories]; // Changed: Added Dashboard and Currencies to Accounting submenu
2944
+ this.capAccounting.capSubItems = [this.capAccountingDashboard, this.capAccounts, this.capTransactions, this.capAggregates, this.capReports, this.capTransactionTypes, this.capTaxRates, this.capCurrencies, this.capStandingOrders]; // Changed: Removed Fixed Assets now own module
2940
2945
  this.capAccountingDashboard.name = "cap27"; // Changed: Reuses module cap number for dashboard visibility
2941
2946
  this.capAccountingDashboard.display = "Dashboard";
2942
2947
  this.capAccountingDashboard.link = "home/accounting/dashboard";
@@ -2966,13 +2971,22 @@ class DataServiceLib {
2966
2971
  this.capStandingOrders.display = "Standing Orders";
2967
2972
  this.capStandingOrders.link = "home/accounting/standing-orders";
2968
2973
  this.capStandingOrders.icon = "schedule";
2969
- this.capFixedAssets.name = "cap70"; // Added: Fixed assets capability
2970
- this.capFixedAssets.display = "Fixed Assets";
2971
- this.capFixedAssets.link = "home/accounting/fixed-assets";
2974
+ // Changed: Fixed Assets is now its own top-level module
2975
+ this.capFixedAssetsModule.name = "cap70";
2976
+ this.capFixedAssetsModule.display = "Fixed Assets";
2977
+ this.capFixedAssetsModule.icon = "precision_manufacturing";
2978
+ this.capFixedAssetsModule.capSubItems = [this.capFixedAssetsDashboard, this.capFixedAssets, this.capFixedAssetCategories];
2979
+ this.capFixedAssetsDashboard.name = "cap70"; // Reuses module cap number for dashboard visibility
2980
+ this.capFixedAssetsDashboard.display = "Dashboard";
2981
+ this.capFixedAssetsDashboard.link = "home/fixed-assets/dashboard";
2982
+ this.capFixedAssetsDashboard.icon = "dashboard";
2983
+ this.capFixedAssets.name = "cap70";
2984
+ this.capFixedAssets.display = "Asset Register";
2985
+ this.capFixedAssets.link = "home/fixed-assets/register"; // Changed: Updated route
2972
2986
  this.capFixedAssets.icon = "precision_manufacturing";
2973
- this.capFixedAssetCategories.name = "cap71"; // Added: Fixed asset categories capability
2974
- this.capFixedAssetCategories.display = "Asset Categories";
2975
- this.capFixedAssetCategories.link = "home/accounting/fixed-asset-categories";
2987
+ this.capFixedAssetCategories.name = "cap71";
2988
+ this.capFixedAssetCategories.display = "Depreciation Categories"; // Changed: Renamed from Categories
2989
+ this.capFixedAssetCategories.link = "home/fixed-assets/depreciation-categories"; // Changed: Updated route
2976
2990
  this.capFixedAssetCategories.icon = "category";
2977
2991
  this.capTransactionTypes.name = "cap28";
2978
2992
  this.capTransactionTypes.display = "Transaction Types";
@@ -4618,153 +4632,7 @@ class AccountingService {
4618
4632
  ],
4619
4633
  loadAction: { url: 'accounts/aggregates/balancesheet' }
4620
4634
  };
4621
- //--------------------------Fixed Asset Categories-------------------------
4622
- // Category form config for create/edit dialog
4623
- this.categoryFormConfig = {
4624
- title: 'Asset Category',
4625
- includeAudit: true,
4626
- heroField: 'fixedAssetCategoryID',
4627
- fields: [
4628
- { name: 'name', type: 'text', required: true },
4629
- { name: 'description', type: 'text' },
4630
- { name: 'defaultDepreciationMethod', alias: 'Depreciation Method', type: 'select', required: true, loadAction: { url: 'fixedassetcategories/list/depreciation-methods' }, defaultValue: 0 },
4631
- { name: 'defaultUsefulLifeMonths', alias: 'Useful Life (Months)', type: 'number', required: true, defaultValue: 60 },
4632
- { name: 'defaultResidualValuePercent', alias: 'Residual Value %', type: 'number', required: true, defaultValue: 10 },
4633
- ],
4634
- };
4635
- this.categoryCreateButton = { name: 'create', display: 'Create Category', dialog: true, action: { url: 'fixedassetcategories?action=create', method: 'post' } };
4636
- this.categoryEditButton = { name: 'edit', display: 'Edit', icon: { name: 'edit' }, dialog: true, action: { url: 'fixedassetcategories?action=edit', method: 'post' } };
4637
- this.categoryDetailsConfig = {
4638
- formConfig: this.categoryFormConfig,
4639
- tableConfigs: [],
4640
- heroField: 'fixedAssetCategoryID',
4641
- buttons: [this.categoryCreateButton, this.categoryEditButton]
4642
- };
4643
- // Category table for listing all asset categories
4644
- this.categoryTableConfig = {
4645
- showFilter: true,
4646
- minColumns: ['name', 'defaultDepreciationMethodName', 'assetCount'],
4647
- columns: [
4648
- { name: 'name', type: 'button', detailsConfig: this.categoryDetailsConfig },
4649
- { name: 'defaultDepreciationMethodName', alias: 'Method', type: 'text' },
4650
- { name: 'defaultUsefulLifeMonths', alias: 'Useful Life (Mo)', type: 'number' },
4651
- { name: 'defaultResidualValuePercent', alias: 'Residual %', type: 'number' },
4652
- { name: 'assetCount', alias: 'Assets', type: 'number' },
4653
- { name: 'updatedByDetails', alias: 'Last Updated', type: 'text' },
4654
- ],
4655
- buttons: [
4656
- { name: 'create', display: 'Create Category', dialog: true, detailsConfig: this.categoryDetailsConfig, action: { url: 'fixedassetcategories?action=create', method: 'post' } }, // Changed: Added missing display text
4657
- { name: 'delete', display: 'Delete', icon: { name: 'delete', color: 'red' }, action: { url: 'fixedassetcategories?action=delete', method: 'post' }, confirm: { message: 'Delete this category?' } },
4658
- ],
4659
- loadAction: { url: 'fixedassetcategories/all/x' },
4660
- // realTime: true, // Disabled: testing realtime on transactions table only
4661
- entityName: 'FixedAssetCategory'
4662
- };
4663
- //--------------------------Fixed Assets-------------------------
4664
- // Fixed asset form config for create/edit dialog
4665
- this.assetFormConfig = {
4666
- title: 'Fixed Asset',
4667
- includeAudit: true,
4668
- heroField: 'fixedAssetID',
4669
- fields: [
4670
- { name: 'assetNumber', alias: 'Asset #', type: 'label', readonly: true, hideOnCreate: true },
4671
- { name: 'name', type: 'text', required: true },
4672
- { name: 'description', type: 'text' },
4673
- { name: 'fixedAssetCategoryID', alias: 'Category', type: 'select', required: true, loadAction: { url: 'fixedassetcategories/list/x' } },
4674
- { name: 'acquisitionDate', alias: 'Acquisition Date', type: 'date', required: true },
4675
- { name: 'acquisitionCost', alias: 'Cost', type: 'money', required: true },
4676
- { name: 'residualValue', alias: 'Residual Value', type: 'money', required: true, defaultValue: 0 },
4677
- { name: 'usefulLifeMonths', alias: 'Useful Life (Months)', type: 'number', required: true },
4678
- { name: 'depreciationMethod', alias: 'Depreciation Method', type: 'select', required: true, loadAction: { url: 'fixedassets/list/depreciation-methods' }, defaultValue: 0 },
4679
- { name: 'location', type: 'text' },
4680
- { name: 'serialNumber', alias: 'Serial Number', type: 'text' },
4681
- { name: 'assetTag', alias: 'Asset Tag', type: 'text' },
4682
- { name: 'statusName', alias: 'Status', type: 'label', readonly: true, hideOnCreate: true },
4683
- { name: 'netBookValueDisplay', alias: 'Net Book Value', type: 'label', readonly: true, hideOnCreate: true },
4684
- { name: 'accumulatedDepreciation', alias: 'Accum. Depreciation', type: 'label', readonly: true, hideOnCreate: true },
4685
- ],
4686
- loadAction: { url: 'fixedassets/id' },
4687
- };
4688
- // Depreciation entries nested table for asset details dialog
4689
- this.depreciationEntriesTableConfig = {
4690
- tabTitle: 'Depreciation History',
4691
- showFilter: false,
4692
- columns: [
4693
- { name: 'periodDate', alias: 'Period', type: 'date' },
4694
- { name: 'amountDisplay', alias: 'Amount', type: 'text' },
4695
- { name: 'accumulatedAmountDisplay', alias: 'Accumulated', type: 'text' },
4696
- { name: 'netBookValueDisplay', alias: 'NBV', type: 'text' },
4697
- { name: 'entryDate', alias: 'Posted', type: 'date' },
4698
- ],
4699
- buttons: [],
4700
- loadAction: { url: 'depreciationentries/{fixedAssetID}/x' }, loadCriteria: 'asset', loadIDField: 'fixedAssetID',
4701
- };
4702
- // Asset action buttons
4703
- this.assetCreateButton = { name: 'create', display: 'Create Asset', dialog: true, action: { url: 'fixedassets?action=create', method: 'post' } };
4704
- this.assetEditButton = { name: 'edit', display: 'Edit', icon: { name: 'edit' }, dialog: true, action: { url: 'fixedassets?action=edit', method: 'post' }, visible: x => x.status == AssetStatus.Draft };
4705
- this.assetActivateButton = { name: 'activate', display: 'Activate', icon: { name: 'check_circle', color: 'green' }, action: { url: 'fixedassets?action=activate', method: 'post' }, confirm: { message: 'Activate this asset? This will post the acquisition journal entry.' }, visible: x => x.status == AssetStatus.Draft };
4706
- this.assetDepreciateButton = { name: 'depreciate', display: 'Depreciate', icon: { name: 'trending_down', color: 'orange' }, action: { url: 'fixedassets?action=depreciate', method: 'post' }, confirm: { message: 'Post depreciation for current period?' }, visible: x => x.status == AssetStatus.Active };
4707
- // Dispose form for the dispose dialog
4708
- this.disposeFormConfig = {
4709
- title: 'Dispose Asset',
4710
- fixedTitle: true,
4711
- fields: [
4712
- { name: 'disposalType', alias: 'Disposal Type', type: 'select', required: true, loadAction: { url: 'fixedassets/list/disposal-types' }, defaultValue: 0 },
4713
- { name: 'disposalAmount', alias: 'Sale Amount', type: 'money', defaultValue: 0, hiddenCondition: x => x.disposalType !== 0 },
4714
- ]
4715
- };
4716
- 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 };
4717
- // Asset details dialog with depreciation history and action buttons
4718
- this.assetDetailsConfig = {
4719
- formConfig: this.assetFormConfig,
4720
- tableConfigs: [this.depreciationEntriesTableConfig],
4721
- heroField: 'fixedAssetID',
4722
- buttons: [this.assetCreateButton, this.assetEditButton, this.assetActivateButton, this.assetDepreciateButton, this.assetDisposeButton]
4723
- };
4724
- // Asset summary tiles
4725
- this.assetTileConfig = {
4726
- clickable: true,
4727
- tiles: [
4728
- { name: 'draft', alias: 'Draft', color: '#FFC107', icon: 'edit_note', action: { url: 'fixedassets/draft/x' } }, // Changed: Added icon
4729
- { name: 'active', alias: 'Active', color: '#4CAF50', icon: 'check_circle', action: { url: 'fixedassets/active/x' } }, // Changed: Added icon
4730
- { name: 'fullyDepreciated', alias: 'Fully Depreciated', color: '#2196F3', icon: 'trending_down' }, // Changed: Added icon
4731
- { name: 'disposed', alias: 'Disposed', color: '#9E9E9E', icon: 'delete_forever' }, // Changed: Added icon
4732
- ],
4733
- loadAction: { url: 'fixedassets/summary/x' }
4734
- };
4735
- // Main fixed assets table
4736
- this.assetsTableConfig = {
4737
- showFilter: true,
4738
- minColumns: ['assetNumber', 'name', 'statusName'],
4739
- columns: [
4740
- { name: 'assetNumber', alias: 'Asset #', type: 'button', detailsConfig: this.assetDetailsConfig },
4741
- { name: 'name', type: 'text' },
4742
- { name: 'categoryName', alias: 'Category', type: 'text' },
4743
- { name: 'acquisitionDate', alias: 'Acquired', type: 'date' },
4744
- { name: 'acquisitionCostDisplay', alias: 'Cost', type: 'text' },
4745
- { name: 'netBookValueDisplay', alias: 'NBV', type: 'text' },
4746
- { name: 'depreciationMethodName', alias: 'Method', type: 'text' },
4747
- { name: 'statusName', alias: 'Status', type: 'chip',
4748
- colors: [
4749
- { name: '#FFCC80', condition: x => x.status == AssetStatus.Draft },
4750
- { name: '#A5D6A7', condition: x => x.status == AssetStatus.Active },
4751
- { name: '#90CAF9', condition: x => x.status == AssetStatus.FullyDepreciated },
4752
- { name: '#BDBDBD', condition: x => x.status == AssetStatus.Disposed },
4753
- ]
4754
- },
4755
- { name: 'updatedByDetails', alias: 'Last Updated', type: 'text' },
4756
- ],
4757
- buttons: [
4758
- { name: 'create', display: 'Create Asset', dialog: true, detailsConfig: this.assetDetailsConfig, action: { url: 'fixedassets?action=create', method: 'post' } }, // Changed: Added missing display text
4759
- this.assetActivateButton,
4760
- this.assetDepreciateButton,
4761
- { name: 'delete', display: 'Delete', icon: { name: 'delete', color: 'red' }, action: { url: 'fixedassets?action=delete', method: 'post' }, confirm: { message: 'Delete this asset?' }, visible: x => x.status == AssetStatus.Draft },
4762
- ],
4763
- loadAction: { url: 'fixedassets/all/x' },
4764
- tileConfig: this.assetTileConfig,
4765
- // realTime: true, // Disabled: testing realtime on transactions table only
4766
- entityName: 'FixedAsset'
4767
- };
4635
+ // Changed: Fixed Assets configs moved to AssetsService (assets.service.ts)
4768
4636
  //--------------------------Budgets-------------------------
4769
4637
  // Budget form configuration
4770
4638
  this.budgetFormConfig = {
@@ -4926,6 +4794,160 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
4926
4794
  }]
4927
4795
  }] });
4928
4796
 
4797
+ class AssetsService {
4798
+ constructor() {
4799
+ //--------------------------Depreciation Categories-------------------------
4800
+ this.categoryFormConfig = {
4801
+ title: 'Depreciation Category',
4802
+ includeAudit: true,
4803
+ heroField: 'depreciationCategoryID',
4804
+ fields: [
4805
+ { name: 'identity', type: 'section' },
4806
+ { name: 'name', type: 'text', section: 'identity', required: true },
4807
+ { name: 'description', type: 'text', section: 'identity' },
4808
+ { name: 'defaults', type: 'section', alias: 'Depreciation Defaults' },
4809
+ { name: 'defaultDepreciationMethod', alias: 'Depreciation Method', type: 'select', section: 'defaults', required: true, loadAction: { url: 'depreciationcategories/list/depreciation-methods' }, defaultValue: 0 },
4810
+ { name: 'defaultUsefulLifeMonths', alias: 'Useful Life (Months)', type: 'number', section: 'defaults', required: true, defaultValue: 60 },
4811
+ { name: 'defaultResidualValuePercent', alias: 'Residual Value %', type: 'number', section: 'defaults', required: true, defaultValue: 10 },
4812
+ ],
4813
+ };
4814
+ this.categoryCreateButton = { name: 'create', display: 'Create Category', dialog: true, action: { url: 'depreciationcategories?action=create', method: 'post' } };
4815
+ this.categoryEditButton = { name: 'edit', display: 'Edit', icon: { name: 'edit' }, dialog: true, action: { url: 'depreciationcategories?action=edit', method: 'post' } };
4816
+ this.categoryDetailsConfig = {
4817
+ formConfig: this.categoryFormConfig,
4818
+ tableConfigs: [],
4819
+ heroField: 'depreciationCategoryID',
4820
+ buttons: [this.categoryCreateButton, this.categoryEditButton]
4821
+ };
4822
+ this.categoryTableConfig = {
4823
+ showFilter: true,
4824
+ minColumns: ['name', 'defaultDepreciationMethodName', 'assetCount'],
4825
+ columns: [
4826
+ { name: 'name', type: 'button', detailsConfig: this.categoryDetailsConfig },
4827
+ { name: 'defaultDepreciationMethodName', alias: 'Method', type: 'text' },
4828
+ { name: 'defaultUsefulLifeMonths', alias: 'Useful Life (Mo)', type: 'number' },
4829
+ { name: 'defaultResidualValuePercent', alias: 'Residual %', type: 'number' },
4830
+ { name: 'assetCount', alias: 'Assets', type: 'number' },
4831
+ ],
4832
+ buttons: [
4833
+ { name: 'create', display: 'Create Category', dialog: true, detailsConfig: this.categoryDetailsConfig, action: { url: 'depreciationcategories?action=create', method: 'post' } },
4834
+ { name: 'delete', display: 'Delete', icon: { name: 'delete', color: 'red' }, action: { url: 'depreciationcategories?action=delete', method: 'post' }, confirm: { message: 'Delete this category?' } },
4835
+ ],
4836
+ loadAction: { url: 'depreciationcategories/all/x' },
4837
+ entityName: 'DepreciationCategory'
4838
+ };
4839
+ //--------------------------Fixed Assets-------------------------
4840
+ this.assetFormConfig = {
4841
+ title: 'Fixed Asset',
4842
+ includeAudit: true,
4843
+ heroField: 'fixedAssetID',
4844
+ fields: [
4845
+ { name: 'identity', type: 'section' },
4846
+ { name: 'assetNumber', alias: 'Asset #', type: 'text', section: 'identity', readonly: true, hideOnCreate: true },
4847
+ { name: 'name', type: 'text', section: 'identity', required: true },
4848
+ { name: 'depreciationCategoryID', alias: 'Category', type: 'select', section: 'identity', required: true, loadAction: { url: 'depreciationcategories/list/x' }, detailsConfig: this.categoryDetailsConfig },
4849
+ { name: 'description', type: 'text', section: 'identity' },
4850
+ { name: 'acquisition', type: 'section' },
4851
+ { name: 'acquisitionDate', alias: 'Acquisition Date', type: 'date', section: 'acquisition', required: true },
4852
+ { name: 'acquisitionCost', alias: 'Cost', type: 'money', section: 'acquisition', required: true },
4853
+ { name: 'depreciation', type: 'section', collapsedCondition: (x) => x.fixedAssetID > 0, hideOnCreate: true },
4854
+ { name: 'depreciationMethod', alias: 'Depreciation Method', type: 'select', section: 'depreciation', hideOnCreate: true, loadAction: { url: 'fixedassets/list/depreciation-methods' } },
4855
+ { name: 'usefulLifeMonths', alias: 'Useful Life (Months)', type: 'number', section: 'depreciation', hideOnCreate: true },
4856
+ { name: 'residualValue', alias: 'Residual Value', type: 'money', section: 'depreciation', hideOnCreate: true },
4857
+ { name: 'tracking', type: 'section', collapsedCondition: (x) => x.fixedAssetID > 0 },
4858
+ { name: 'location', type: 'text', section: 'tracking' },
4859
+ { name: 'serialNumber', alias: 'Serial Number', type: 'text', section: 'tracking' },
4860
+ { name: 'assetTag', alias: 'Asset Tag', type: 'text', section: 'tracking' },
4861
+ { name: 'valuation', type: 'section', collapsedCondition: (x) => x.fixedAssetID > 0, hideOnCreate: true },
4862
+ { name: 'statusName', alias: 'Status', type: 'text', section: 'valuation', readonly: true, hideOnCreate: true },
4863
+ { name: 'netBookValueDisplay', alias: 'Net Book Value', type: 'text', section: 'valuation', readonly: true, hideOnCreate: true },
4864
+ { name: 'accumulatedDepreciation', alias: 'Accum. Depreciation', type: 'text', section: 'valuation', readonly: true, hideOnCreate: true },
4865
+ ],
4866
+ loadAction: { url: 'fixedassets/id' },
4867
+ };
4868
+ this.depreciationEntriesTableConfig = {
4869
+ tabTitle: 'Depreciation History',
4870
+ showFilter: false,
4871
+ columns: [
4872
+ { name: 'periodDate', alias: 'Period', type: 'date' },
4873
+ { name: 'amountDisplay', alias: 'Amount', type: 'text' },
4874
+ { name: 'accumulatedAmountDisplay', alias: 'Accumulated', type: 'text' },
4875
+ { name: 'netBookValueDisplay', alias: 'NBV', type: 'text' },
4876
+ { name: 'entryDate', alias: 'Posted', type: 'date' },
4877
+ ],
4878
+ buttons: [],
4879
+ loadAction: { url: 'depreciationentries/{fixedAssetID}/x' }, loadCriteria: 'asset', loadIDField: 'fixedAssetID',
4880
+ };
4881
+ this.assetCreateButton = { name: 'create', display: 'Create Asset', dialog: true, action: { url: 'fixedassets?action=create', method: 'post' } };
4882
+ this.assetEditButton = { name: 'edit', display: 'Edit', icon: { name: 'edit' }, dialog: true, action: { url: 'fixedassets?action=edit', method: 'post' }, visible: x => x.status == AssetStatus.Draft };
4883
+ this.assetActivateButton = { name: 'activate', display: 'Activate', icon: { name: 'check_circle', color: 'green' }, action: { url: 'fixedassets?action=activate', method: 'post' }, confirm: { message: 'Activate this asset? This will post the acquisition journal entry.' }, visible: x => x.status == AssetStatus.Draft };
4884
+ this.assetDepreciateButton = { name: 'depreciate', display: 'Depreciate', icon: { name: 'trending_down', color: 'orange' }, action: { url: 'fixedassets?action=depreciate', method: 'post' }, confirm: { message: 'Post depreciation for current period?' }, visible: x => x.status == AssetStatus.Active };
4885
+ this.disposeFormConfig = {
4886
+ title: 'Dispose Asset',
4887
+ fixedTitle: true,
4888
+ fields: [
4889
+ { name: 'disposalType', alias: 'Disposal Type', type: 'select', required: true, loadAction: { url: 'fixedassets/list/disposal-types' }, defaultValue: 0 },
4890
+ { name: 'disposalAmount', alias: 'Sale Amount', type: 'money', defaultValue: 0, hiddenCondition: x => x.disposalType !== 0 },
4891
+ ]
4892
+ };
4893
+ 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 };
4894
+ this.assetDetailsConfig = {
4895
+ formConfig: this.assetFormConfig,
4896
+ tableConfigs: [this.depreciationEntriesTableConfig],
4897
+ heroField: 'fixedAssetID',
4898
+ buttons: [this.assetCreateButton, this.assetEditButton, this.assetActivateButton, this.assetDepreciateButton, this.assetDisposeButton]
4899
+ };
4900
+ this.assetTileConfig = {
4901
+ clickable: true,
4902
+ tiles: [
4903
+ { name: 'draft', alias: 'Draft', color: '#FFC107', icon: 'edit_note', action: { url: 'fixedassets/draft/x' } },
4904
+ { name: 'active', alias: 'Active', color: '#4CAF50', icon: 'check_circle', action: { url: 'fixedassets/active/x' } },
4905
+ { name: 'fullyDepreciated', alias: 'Fully Depreciated', color: '#2196F3', icon: 'trending_down' },
4906
+ { name: 'disposed', alias: 'Disposed', color: '#9E9E9E', icon: 'delete_forever' },
4907
+ ],
4908
+ loadAction: { url: 'fixedassets/summary/x' }
4909
+ };
4910
+ this.assetsTableConfig = {
4911
+ showFilter: true,
4912
+ minColumns: ['assetNumber', 'name', 'statusName'],
4913
+ columns: [
4914
+ { name: 'assetNumber', alias: 'Asset #', type: 'button', detailsConfig: this.assetDetailsConfig },
4915
+ { name: 'name', type: 'text' },
4916
+ { name: 'categoryName', alias: 'Category', type: 'text' },
4917
+ { name: 'acquisitionDate', alias: 'Acquired', type: 'date' },
4918
+ { name: 'acquisitionCostDisplay', alias: 'Cost', type: 'text' },
4919
+ { name: 'netBookValueDisplay', alias: 'NBV', type: 'text' },
4920
+ { name: 'depreciationMethodName', alias: 'Method', type: 'text' },
4921
+ { name: 'statusName', alias: 'Status', type: 'chip',
4922
+ colors: [
4923
+ { name: '#FFCC80', condition: x => x.status == AssetStatus.Draft },
4924
+ { name: '#A5D6A7', condition: x => x.status == AssetStatus.Active },
4925
+ { name: '#90CAF9', condition: x => x.status == AssetStatus.FullyDepreciated },
4926
+ { name: '#BDBDBD', condition: x => x.status == AssetStatus.Disposed },
4927
+ ]
4928
+ },
4929
+ ],
4930
+ buttons: [
4931
+ { name: 'create', display: 'Create Asset', dialog: true, detailsConfig: this.assetDetailsConfig, action: { url: 'fixedassets?action=create', method: 'post' } },
4932
+ this.assetActivateButton,
4933
+ this.assetDepreciateButton,
4934
+ { name: 'delete', display: 'Delete', icon: { name: 'delete', color: 'red' }, action: { url: 'fixedassets?action=delete', method: 'post' }, confirm: { message: 'Delete this asset?' }, visible: x => x.status == AssetStatus.Draft },
4935
+ ],
4936
+ loadAction: { url: 'fixedassets/all/x' },
4937
+ tileConfig: this.assetTileConfig,
4938
+ entityName: 'FixedAsset'
4939
+ };
4940
+ }
4941
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4942
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsService, providedIn: 'root' }); }
4943
+ }
4944
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsService, decorators: [{
4945
+ type: Injectable,
4946
+ args: [{
4947
+ providedIn: 'root'
4948
+ }]
4949
+ }] });
4950
+
4929
4951
  class LoansService {
4930
4952
  constructor() {
4931
4953
  // ========== LOAN PRODUCTS BUTTONS ==========
@@ -6582,6 +6604,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
6582
6604
  args: [{ providedIn: 'root' }]
6583
6605
  }], ctorParameters: () => [{ type: DataServiceLib }, { type: SignalRService }] });
6584
6606
 
6607
+ class AnalyticsService {
6608
+ constructor(router) {
6609
+ this.router = router;
6610
+ }
6611
+ // Initialize route tracking — call once from app root or module constructor
6612
+ init() {
6613
+ this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event) => {
6614
+ const url = event.urlAfterRedirects || event.url;
6615
+ // Strip hash prefix so GA sees clean paths (e.g. /home/invoices instead of #/home/invoices)
6616
+ const pagePath = url.replace(/^#/, '') || '/';
6617
+ this.sendPageView(pagePath);
6618
+ });
6619
+ }
6620
+ // Send page_view event to GA4
6621
+ sendPageView(pagePath) {
6622
+ if (typeof gtag === 'function') {
6623
+ gtag('event', 'page_view', {
6624
+ page_path: pagePath,
6625
+ page_location: window.location.origin + pagePath
6626
+ });
6627
+ }
6628
+ }
6629
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AnalyticsService, deps: [{ token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
6630
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
6631
+ }
6632
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AnalyticsService, decorators: [{
6633
+ type: Injectable,
6634
+ args: [{ providedIn: 'root' }]
6635
+ }], ctorParameters: () => [{ type: i1$2.Router }] });
6636
+
6585
6637
  // Functional guard for Angular 15+ (replaces deprecated class-based CanActivate)
6586
6638
  // Changed: Returns Promise<boolean> to properly await token refresh before allowing navigation
6587
6639
  const authGuard = async (route, state) => {
@@ -10084,11 +10136,11 @@ class NavMenuComponent {
10084
10136
  return !this.isMiniSidebar || this.isMiniHovered;
10085
10137
  }
10086
10138
  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: i1.MatDialog }, { token: SubscriptionService }], target: i0.ɵɵFactoryTarget.Component }); }
10087
- 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/admin/tenant-settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\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/admin/tenant-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=\"!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 </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/admin/tenant-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=\"!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 </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$3.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$3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$3.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: i11.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "component", type: i5.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: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i14.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i14.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i14.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i16.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i16.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i16.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "component", type: i17.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: i18.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i18.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: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
10139
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: NavMenuComponent, isStandalone: false, selector: "spa-nav-menu", inputs: { appConfig: "appConfig", footer: "footer" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\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=\"!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 </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!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 </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4$3.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$3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$3.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: i11.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "component", type: i5.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: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i14.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i14.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i14.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i16.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i16.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i16.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "component", type: i17.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: i18.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i18.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: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
10088
10140
  }
10089
10141
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: NavMenuComponent, decorators: [{
10090
10142
  type: Component,
10091
- 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/admin/tenant-settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\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/admin/tenant-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=\"!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 </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/admin/tenant-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=\"!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 </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}\n"] }]
10143
+ args: [{ selector: 'spa-nav-menu', standalone: false, template: "<header *ngIf=\"loggedin && dataService.appConfig.navigation == 'top'\">\r\n\r\n <!-- Changed: Removed mb-3 class to eliminate gap between toolbar and content -->\r\n <nav class=\"toolbar navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow\" style=\"padding-right: 10px;\">\r\n\r\n\r\n <div class=\"container-fluid\" style=\"padding-right: 0px;\">\r\n\r\n <img *ngIf=\"appConfig.logo!=''\" [src]=\"appConfig.logo\" style=\"height: 50px; margin-right: 2em\" />\r\n\r\n <div>\r\n <!-- <div style=\"font-size: 20px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px;\">\r\n {{tenantName}}\r\n </div> -->\r\n\r\n <div *ngIf=\"!dataService.appConfig.multitenant\" style=\"font-size: 22px;\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"font-size: 20px; ; font-weight: 400;\" [ngStyle]=\"{'margin-top': dataService.appConfig.multitenant ? '12px' : ''}\">\r\n {{appConfig.appName}}\r\n </div>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant && tenantName\" style=\"font-size: 12px; margin-bottom: 5px;\">\r\n {{tenantName}}\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n\r\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" aria-label=\"Toggle navigation\" [attr.aria-expanded]=\"isExpanded\" (click)=\"toggle()\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n\r\n <div *ngIf=\"myRole\" class=\" navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse stack-top\" style=\"margin-right: 0px;\" [ngClass]=\"{ show: isExpanded, navitems: isExpanded }\" >\r\n\r\n <button mat-icon-button (click)=\"logoff()\" > <mat-icon>logout</mat-icon> </button>\r\n\r\n <div *ngIf=\"dataService.appConfig.multitenant\">\r\n\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" > <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon> </button>\r\n\r\n <!-- Removed: Support icon \u2014 replaced by floating assistant chat widget -->\r\n </div>\r\n\r\n\r\n <button id=\"btnUser\" mat-button [matMenuTriggerFor]=\"profileMenu\" ><mat-icon style=\"font-size: 24px;\">account_circle</mat-icon> &nbsp;{{loggedUserFullName}}</button>\r\n\r\n <mat-menu #profileMenu=\"matMenu\">\r\n <button id=\"btnProfile\" mat-menu-item (click)=\"redirectTo('home/user/profile')\" >Profile</button>\r\n <button id=\"btnLogOff\" mat-menu-item (click)=\"logoff()\">Log Off</button>\r\n </mat-menu>\r\n\r\n <div *ngFor=\"let item of reversedCapItems\">\r\n\r\n <!-- Menu Item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && !item.capSubItems && item.showMenu && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items ignored \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button (click)=\"redirectTo(item.link)\">{{item.display}}</button>\r\n\r\n <!-- Menu Item with Sub items to display \u2014 Added: isFeatureAllowed check -->\r\n <button id=\"btnMenu\" *ngIf=\"myRole[item.name] && item.capSubItems && item.showMenu && !item.ignoreSubsDisplay && isFeatureAllowed(item)\" mat-button [matMenuTriggerFor]=\"adminMenu\">{{item.display}}</button>\r\n\r\n\r\n <!-- Sub Menu Items \u2014 Added: isFeatureAllowed check on sub-items -->\r\n <mat-menu #adminMenu=\"matMenu\">\r\n\r\n <div *ngFor=\"let subItem of item.capSubItems\">\r\n\r\n <button *ngIf=\"myRole[subItem.name] && subItem.showMenu && isFeatureAllowed(subItem)\" mat-menu-item (click)=\"redirectTo(subItem.link)\">{{subItem.display}}</button>\r\n\r\n </div>\r\n\r\n </mat-menu>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n </div>\r\n\r\n </nav>\r\n\r\n</header>\r\n\r\n<!-- Changed: Removed top/bottom padding to eliminate gaps, but kept left/right padding for content spacing -->\r\n<div class=\"container-fluid tin-bg-image\" *ngIf=\"dataService.appConfig.navigation == 'top'\" style=\"padding: 12px 12px; margin: 0;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\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=\"!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 </div>\r\n\r\n\r\n\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"userAccountMenu\"><mat-icon>account_circle</mat-icon></button>\r\n <label style=\"font-size: 14px;\">{{loggedUserFullName}}</label>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n\r\n\r\n <!-- my account menu -->\r\n <mat-menu #userAccountMenu [overlapTrigger]=\"false\" yPosition=\"below\">\r\n\r\n\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n\r\n <!-- Removed: Help menu item \u2014 replaced by floating assistant chat widget -->\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n\r\n </mat-menu>\r\n\r\n</mat-toolbar>\r\n\r\n\r\n\r\n\r\n<mat-sidenav-container class=\"app-container\" [hasBackdrop]=\"smallScreen\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n\r\n <mat-sidenav #sidenav [mode]=\"smallScreen ? 'over' : 'side'\" [class.mat-elevation-z4]=\"true\" [opened]=\"isExpanded\" class=\"app-sidenav side-color\" style=\"height: 100%;\"\r\n [ngStyle]=\"{'width': dataService.appConfig.navWidth}\">\r\n <mat-nav-list >\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\" >\r\n\r\n <!-- Menu item \u2014 Added: isFeatureAllowed check for plan-based gating -->\r\n <mat-list-item [routerLink]=\"cap.link\" *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.capSubItems && cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\" style=\"height: 40px;font-size: 15px;\"\r\n (click)=\"smallScreen ? toggle() : null\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon}}</mat-icon>{{cap.display}}\r\n </mat-list-item>\r\n\r\n <!-- Menu With Sub items \u2014 Added: isFeatureAllowed check -->\r\n <mat-expansion-panel class=\"side-color\" [class.mat-elevation-z0]=\"true\" *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <mat-expansion-panel-header style=\"height: 40px;padding-left: 15px;\">\r\n <mat-icon [ngStyle]=\"{'color': cap.color}\" style=\"margin-right: 5px;\">{{cap.icon != 'navigate_next' ? cap.icon : 'fiber_manual_record' }}</mat-icon>{{cap.display}}\r\n </mat-expansion-panel-header>\r\n\r\n <!-- Sub items - Changed: Use ng-container to avoid blank spaces for hidden items -->\r\n <mat-nav-list>\r\n <ng-container *ngFor=\"let capSub of getSubItems(cap)\">\r\n <mat-list-item [routerLink]=\"capSub.link\" style=\"height: 30px; font-size: 15px; padding-left: 4px; padding-right: 10px; margin-bottom: 5px;\" (click)=\"smallScreen ? toggle() : null\" *ngIf=\"myRole[capSub.name] && capSub.showMenu && isFeatureAllowed(capSub)\" [matTooltip]=\"capSub.display\" matTooltipPosition=\"right\">\r\n <mat-icon [ngStyle]=\"{'color': capSub.color}\" style=\"margin-right: 5px;\">{{capSub.icon}}</mat-icon>{{capSub.display}}\r\n </mat-list-item>\r\n </ng-container>\r\n </mat-nav-list>\r\n\r\n </mat-expansion-panel>\r\n\r\n </ng-container>\r\n\r\n </mat-nav-list>\r\n </mat-sidenav>\r\n\r\n\r\n\r\n <mat-sidenav-content class=\"tin-bg-image\" style=\"padding: 0px 12px;\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <hr style=\"margin-top: 0px;\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </mat-sidenav-content>\r\n\r\n</mat-sidenav-container>\r\n\r\n\r\n<!-- footer -->\r\n<div class=\"tin-center\" *ngIf=\"loggedin && dataService.appConfig.navigation == 'side'\">\r\n <label style=\"text-align: center; font-size: 12px;\">&copy; {{nowDate | date : 'yyyy'}} <a color=\"primary\" class=\"terms-link\" [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openTerms()\">Terms</a> | <a color=\"primary\" class=\"terms-link\" style=\"cursor: pointer;\" (click)=\"openPrivacy()\">Privacy Policy</a></label>\r\n</div>\r\n\r\n\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n\r\n<!-- SIDE-MODERN -->\r\n\r\n<!-- Changed: Side-modern navigation layout -->\r\n<div class=\"sm-layout\"\r\n *ngIf=\"loggedin && dataService.appConfig.navigation == 'side-modern'\"\r\n [class.sm-mini]=\"isMiniSidebar && !isMiniHovered\"\r\n [class.sm-mini-hovered]=\"isMiniSidebar && isMiniHovered\"\r\n [class.sm-mobile-open]=\"smallScreen && isExpanded\">\r\n\r\n <!-- Sidebar -->\r\n <aside class=\"sm-sidebar\"\r\n (mouseenter)=\"onMiniMouseEnter()\"\r\n (mouseleave)=\"onMiniMouseLeave()\">\r\n\r\n <!-- Background layers -->\r\n <div class=\"sm-sidebar-bg\">\r\n <div class=\"sm-sidebar-bg-image\" *ngIf=\"appConfig.navImage\" [ngStyle]=\"{'background-image': 'url(' + appConfig.navImage + ')'}\"></div>\r\n <div class=\"sm-sidebar-bg-overlay\" [ngStyle]=\"{'background-color': appConfig.navColor}\"></div>\r\n </div>\r\n\r\n <!-- Sidebar content -->\r\n <div class=\"sm-sidebar-content\">\r\n\r\n <!-- Brand -->\r\n <div class=\"sm-brand\">\r\n <img *ngIf=\"appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" />\r\n <span class=\"sm-brand-name\">{{appConfig.appName}}</span>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Profile -->\r\n <div class=\"sm-profile\">\r\n <mat-icon class=\"sm-profile-icon\">account_circle</mat-icon>\r\n <div class=\"sm-profile-info\">\r\n <div class=\"sm-profile-name\">{{loggedUserFullName}}</div>\r\n <div class=\"sm-profile-role\">{{tenantName || 'User'}}</div>\r\n </div>\r\n </div>\r\n\r\n <mat-divider></mat-divider>\r\n\r\n <!-- Scrollable menu -->\r\n <div class=\"sm-menu-scroll\">\r\n\r\n <ng-container *ngFor=\"let cap of dataService.appConfig.capItems\">\r\n\r\n <!-- Simple menu item (no sub-items or ignoring sub display) \u2014 Added: isFeatureAllowed check -->\r\n <div *ngIf=\"myRole[cap.name] && cap.showMenu && (!cap.capSubItems || cap.ignoreSubsDisplay) && isFeatureAllowed(cap)\"\r\n class=\"sm-menu-item\"\r\n [class.sm-active]=\"isActiveRoute(cap.link)\"\r\n (click)=\"modernNavigate(cap.link)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n </div>\r\n\r\n <!-- Parent menu item with sub-items \u2014 Added: isFeatureAllowed check -->\r\n <ng-container *ngIf=\"myRole[cap.name] && cap.showMenu && cap.capSubItems && !cap.ignoreSubsDisplay && isFeatureAllowed(cap)\">\r\n\r\n <!-- Parent item (toggles sub-menu) -->\r\n <div class=\"sm-menu-item\"\r\n [class.sm-active]=\"isParentActive(cap) && !isMenuOpen(cap.name)\"\r\n (click)=\"toggleModernMenu(cap.name)\">\r\n <mat-icon class=\"sm-menu-icon\">{{cap.icon != 'navigate_next' ? cap.icon : 'dashboard'}}</mat-icon>\r\n <span class=\"sm-menu-text\">{{cap.display}}</span>\r\n <mat-icon class=\"sm-caret\" [class.sm-caret-open]=\"isMenuOpen(cap.name)\">expand_more</mat-icon>\r\n </div>\r\n\r\n <!-- Sub-menu container (animated) -->\r\n <div class=\"sm-submenu\" [class.sm-submenu-open]=\"isMenuOpen(cap.name)\">\r\n <ng-container *ngFor=\"let sub of getSubItems(cap)\">\r\n <div *ngIf=\"myRole[sub.name] && sub.showMenu && isFeatureAllowed(sub)\"\r\n class=\"sm-submenu-item\"\r\n [class.sm-active]=\"isActiveRoute(sub.link)\"\r\n (click)=\"modernNavigate(sub.link)\">\r\n <mat-icon *ngIf=\"sub.icon && sub.icon != 'navigate_next'\" class=\"sm-sub-icon\">{{sub.icon}}</mat-icon>\r\n <span *ngIf=\"!sub.icon || sub.icon == 'navigate_next'\" class=\"sm-initials\">{{getInitials(sub.display)}}</span>\r\n <span class=\"sm-menu-text\">{{sub.display}}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n </aside>\r\n\r\n <!-- Mobile backdrop -->\r\n <div class=\"sm-backdrop\" (click)=\"isExpanded = false\"></div>\r\n\r\n <!-- Main content -->\r\n <div class=\"sm-main\">\r\n\r\n <!-- Top bar - Changed: Added scroll class for frosted glass effect -->\r\n <div class=\"sm-topbar\" [class.sm-topbar-scrolled]=\"topbarScrolled\">\r\n <button mat-icon-button (click)=\"smallScreen ? toggle() : toggleMiniSidebar()\" matTooltip=\"Menu\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n\r\n <!-- Changed: Mobile branding - show logo + app name when sidebar is hidden on small screens -->\r\n <img *ngIf=\"smallScreen && appConfig.logo\" [src]=\"appConfig.logo\" alt=\"logo\" class=\"sm-topbar-logo\" />\r\n <span *ngIf=\"smallScreen\" class=\"sm-topbar-brand\">{{appConfig.appName}}</span>\r\n\r\n <span class=\"sm-topbar-spacer\"></span>\r\n\r\n <!-- Multitenant buttons -->\r\n <div *ngIf=\"dataService.appConfig.multitenant\" style=\"display: flex; align-items: center;\">\r\n <button mat-icon-button (click)=\"redirectTo('home/tenancy/settings')\" matTooltip=\"Organisation Settings\">\r\n <mat-icon fontSet=\"material-icons-round\">apartment</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{tenantName}}</span>\r\n\r\n <!-- Changed: Support/help icon removed \u2014 replaced by floating agent chat widget -->\r\n\r\n <button *ngIf=\"!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 </div>\r\n\r\n <!-- Profile menu -->\r\n <button mat-icon-button matTooltip=\"My Account\" [matMenuTriggerFor]=\"smProfileMenu\">\r\n <mat-icon>account_circle</mat-icon>\r\n </button>\r\n <span class=\"sm-topbar-label\">{{loggedUserFullName}}</span>\r\n\r\n <mat-menu #smProfileMenu=\"matMenu\" [overlapTrigger]=\"false\" yPosition=\"below\">\r\n <button mat-menu-item routerLink=\"home/user/profile\">\r\n <mat-icon>person</mat-icon><span>Profile</span>\r\n </button>\r\n <!-- Changed: Help menu item removed \u2014 replaced by floating agent chat widget -->\r\n <mat-divider></mat-divider>\r\n <button mat-menu-item (click)=\"logoff()\">\r\n <mat-icon>logout</mat-icon>Logout\r\n </button>\r\n </mat-menu>\r\n\r\n <button *ngIf=\"!smallScreen\" mat-icon-button (click)=\"logoff()\" matTooltip=\"Signout\">\r\n <mat-icon>logout</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Page content - Changed: Replaced tin-bg-image with sm-content modern texture -->\r\n <div class=\"sm-content\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"sm-footer\">\r\n &copy; {{nowDate | date : 'yyyy'}} <a [href]=\"appConfig.siteUrl\" target=\"_blank\">{{footer}}</a> | <a (click)=\"openTerms()\">Terms</a> | <a (click)=\"openPrivacy()\">Privacy Policy</a>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Not logged in fallback for side-modern -->\r\n<div class=\"tin-bg-image\" *ngIf=\"!loggedin && dataService.appConfig.navigation == 'side-modern'\">\r\n <router-outlet></router-outlet>\r\n <spa-loader [logo]=\"this.dataService.appConfig.logo\"></spa-loader>\r\n</div>\r\n\r\n<!-- Changed: Cascading toast notifications for real-time entity changes \u2014 visible in all layouts -->\r\n<spa-toast *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-toast>\r\n\r\n<!-- Changed: Floating agent chat widget \u2014 renamed from spa-assistant -->\r\n<spa-agent *ngIf=\"loggedin && dataService.appConfig.multitenant\"></spa-agent>", styles: ["a.navbar-brand{white-space:normal;text-align:center;word-break:break-all}html{font-size:14px}.box-shadow{box-shadow:0 .25rem .75rem #0000000d}.toolbar-item-spacer{flex:1 1 auto}.toolbar{height:60px;display:flex;align-items:center;background-color:#03a;color:#fff;margin-bottom:0!important}.toolbar button,.toolbar .mat-mdc-button,.toolbar .mat-mdc-icon-button{color:#fff!important}.toolbar mat-icon{color:#fff!important}.stack-top{z-index:9;margin:20px}.navitems{background-color:#03a}.app-container{height:90%;margin:0}.app-sidenav{width:200px;border:1px solid rgb(192,190,199)}.side-color{background-color:#e6f4ff}.app-sidenav mat-list-item{display:flex!important;align-items:center!important}.app-sidenav mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}.app-sidenav mat-expansion-panel-header mat-icon{display:inline-flex!important;align-items:center!important;vertical-align:middle!important}::ng-deep .app-sidenav .mat-expansion-panel-body{padding-bottom:5px!important;padding-right:5px!important}::ng-deep .app-sidenav .mdc-list{padding-bottom:0!important}.sm-layout{display:flex;min-height:100vh;position:relative}.sm-sidebar{position:fixed;top:0;left:0;bottom:0;width:260px;z-index:1030;overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}.sm-sidebar-bg{position:absolute;inset:0;z-index:0}.sm-sidebar-bg-image{position:absolute;inset:0;background-size:cover;background-position:center}.sm-sidebar-bg-overlay{position:absolute;inset:0}.sm-sidebar-content{position:relative;z-index:1;display:flex;flex-direction:column;height:100%;color:#fff}.sm-brand{display:flex;align-items:center;padding:18px 15px 10px;min-height:60px;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-brand img{height:34px;width:34px;object-fit:contain;margin-right:12px;flex-shrink:0}.sm-brand-name{font-size:16px;font-weight:500;letter-spacing:.5px;color:#fff;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-profile{display:flex;align-items:center;padding:12px 15px;white-space:nowrap;overflow:hidden}.sm-profile-icon{font-size:34px!important;width:34px!important;height:34px!important;margin-right:12px;flex-shrink:0;color:#fffc}.sm-profile-info{overflow:hidden;transition:opacity .2s ease}.sm-profile-name{font-size:14px;font-weight:500;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-profile-role{font-size:11px;color:#fff9;line-height:1.3;overflow:hidden;text-overflow:ellipsis}.sm-sidebar mat-divider{border-color:#ffffff26!important;margin:0 15px}.sm-menu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0}.sm-menu-scroll::-webkit-scrollbar{width:4px}.sm-menu-scroll::-webkit-scrollbar-track{background:transparent}.sm-menu-scroll::-webkit-scrollbar-thumb{background:#fff3;border-radius:2px}.sm-menu-item{display:flex;align-items:center;padding:10px 15px;margin:2px 15px;border-radius:4px;cursor:pointer;color:#fff;font-size:13px;font-weight:400;letter-spacing:.3px;transition:all .15s ease;text-decoration:none;white-space:nowrap;overflow:hidden}.sm-menu-item:hover{background:#ffffff1f}.sm-menu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-menu-item.sm-active .sm-menu-icon{color:#3c4858}.sm-menu-icon{font-size:20px!important;width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:12px;flex-shrink:0;color:#fffc;transition:color .15s ease}.sm-menu-text{flex:1;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s ease}.sm-caret{font-size:18px!important;width:18px!important;height:18px!important;transition:transform .3s cubic-bezier(.4,0,.2,1);flex-shrink:0;color:#fff9}.sm-caret.sm-caret-open{transform:rotate(180deg)}.sm-active .sm-caret{color:#3c4858}.sm-submenu{max-height:0;overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1)}.sm-submenu.sm-submenu-open{max-height:1000px}.sm-submenu-item{display:flex;align-items:center;padding:8px 15px 8px 30px;margin:1px 15px;border-radius:4px;cursor:pointer;color:#fffc;font-size:12px;font-weight:400;transition:all .15s ease;white-space:nowrap;overflow:hidden}.sm-submenu-item:hover{background:#ffffff1f;color:#fff}.sm-submenu-item.sm-active{background-color:#fff;color:#3c4858;box-shadow:0 4px 20px #00000024,0 7px 10px -5px #0003;font-weight:500}.sm-submenu-item.sm-active .sm-sub-icon{color:#3c4858}.sm-sub-icon{font-size:16px!important;width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center;margin-right:10px;flex-shrink:0;color:#fff9}.sm-initials{width:20px;height:20px;border-radius:50%;background:#ffffff26;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;margin-right:10px;flex-shrink:0;color:#fffc}.sm-active .sm-initials{background:#3c48581f;color:#3c4858}.sm-main{flex:1;min-width:0;margin-left:260px;min-height:100vh;display:flex;flex-direction:column;transition:margin-left .3s cubic-bezier(.4,0,.2,1);background-color:#eef2f7}.sm-topbar{display:flex;align-items:center;padding:8px 16px;min-height:56px;background-color:#eef2f7;background-image:radial-gradient(circle,#d5dbe3 1px,transparent 1px);background-size:16px 16px;border-bottom:1px solid rgba(0,0,0,.08);position:sticky;top:0;z-index:1020;transition:background .3s ease,backdrop-filter .3s ease}.sm-topbar-scrolled{background-color:#eef2f78c;background-image:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 3px #0000000f}.sm-topbar-spacer{flex:1 1 auto}.sm-topbar-logo{height:32px;width:32px;object-fit:contain;margin-right:8px}.sm-topbar-brand{font-size:18px;font-weight:500;margin-right:8px;white-space:nowrap}.sm-topbar-label{font-size:14px;margin-right:4px;display:inline-flex;align-items:center;align-self:center;height:40px;line-height:1}.sm-topbar .mat-mdc-icon-button{display:inline-flex!important;align-items:center!important;justify-content:center!important}.sm-content{flex:1;padding:12px;min-width:0;background-color:#e5eaf2;background-image:radial-gradient(ellipse at 50% 45%,#fffffff2,#fff6 35%,#fff0 60%),radial-gradient(circle,#bec7d4 1px,transparent 1px);background-size:100% 100%,16px 16px;min-height:calc(100vh - 104px)}.sm-footer{padding:12px 16px;text-align:center;font-size:12px;color:#999;border-top:1px solid #e0e0e0;background:#fff}.sm-footer a{color:inherit;cursor:pointer}.sm-footer a:hover{text-decoration:underline}.sm-backdrop{display:none;position:fixed;inset:0;background:#00000080;z-index:1025}.sm-layout.sm-mini .sm-sidebar{width:80px}.sm-layout.sm-mini .sm-main{margin-left:80px}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret,.sm-layout.sm-mini .sm-submenu{display:none}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 10px}.sm-layout.sm-mini .sm-brand{justify-content:center;padding:18px 0 10px}.sm-layout.sm-mini .sm-brand img{margin-right:0}.sm-layout.sm-mini .sm-profile{justify-content:center;padding:12px 0}.sm-layout.sm-mini .sm-profile-icon{margin-right:0}.sm-layout.sm-mini .sm-menu-item{justify-content:center;padding:12px 0;margin:2px 0}.sm-layout.sm-mini .sm-menu-icon{margin-right:0;font-size:22px!important}.sm-layout.sm-mini-hovered .sm-sidebar{width:260px;box-shadow:4px 0 20px #0000004d}.sm-layout.sm-mini-hovered .sm-main{margin-left:80px}.sm-layout.sm-mini-hovered .sm-brand-name,.sm-layout.sm-mini-hovered .sm-profile-info,.sm-layout.sm-mini-hovered .sm-menu-text,.sm-layout.sm-mini-hovered .sm-caret{display:initial}.sm-layout.sm-mini-hovered .sm-submenu{display:block}.sm-layout.sm-mini-hovered .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini-hovered .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini-hovered .sm-brand img{margin-right:12px}.sm-layout.sm-mini-hovered .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini-hovered .sm-profile-icon{margin-right:12px}.sm-layout.sm-mini-hovered .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini-hovered .sm-menu-icon{margin-right:12px;font-size:20px!important}@media (max-width: 600px){.sm-sidebar{transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1);width:260px!important}.sm-layout.sm-mobile-open .sm-sidebar{transform:translate(0)}.sm-layout.sm-mobile-open .sm-backdrop{display:block}.sm-main{margin-left:0!important}.sm-layout.sm-mini .sm-sidebar{width:260px!important}.sm-layout.sm-mini .sm-brand-name,.sm-layout.sm-mini .sm-profile-info,.sm-layout.sm-mini .sm-menu-text,.sm-layout.sm-mini .sm-caret{display:initial}.sm-layout.sm-mini .sm-submenu{display:block}.sm-layout.sm-mini .sm-sidebar mat-divider{margin:0 15px}.sm-layout.sm-mini .sm-menu-item{justify-content:flex-start;padding:10px 15px;margin:2px 15px}.sm-layout.sm-mini .sm-menu-icon{margin-right:12px;font-size:20px!important}.sm-layout.sm-mini .sm-brand{justify-content:flex-start;padding:18px 15px 10px}.sm-layout.sm-mini .sm-brand img{margin-right:12px}.sm-layout.sm-mini .sm-profile{justify-content:flex-start;padding:12px 15px}.sm-layout.sm-mini .sm-profile-icon{margin-right:12px}}\n"] }]
10092
10144
  }], ctorParameters: () => [{ type: i1$2.Router }, { type: AuthService }, { type: StorageService }, { type: NotificationsService }, { type: i1$3.BreakpointObserver }, { type: DataServiceLib }, { type: i1.MatDialog }, { type: SubscriptionService }], propDecorators: { onWindowScroll: [{
10093
10145
  type: HostListener,
10094
10146
  args: ['window:scroll']
@@ -10137,6 +10189,11 @@ class LoaderInterceptor {
10137
10189
  }
10138
10190
  intercept(request, next) {
10139
10191
  let requestClone = this.addToken(request);
10192
+ // Changed: Skip loader tracking for SignalR hub requests — they're background infrastructure
10193
+ // and should never show the loading spinner or block UI interactions
10194
+ if (request.url.includes('/hubs/')) {
10195
+ return next.handle(requestClone).pipe(catchError((error) => throwError(() => error)));
10196
+ }
10140
10197
  this.requests.push(requestClone);
10141
10198
  if (this.requests.length > 1) {
10142
10199
  this.logService.info("Multiple connections detected >= " + this.requests.length);
@@ -14016,7 +14073,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
14016
14073
  }] } });
14017
14074
 
14018
14075
  class TinSpaModule {
14019
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
14076
+ // Changed: Auto-initialize analytics route tracking when module loads
14077
+ constructor(analytics) {
14078
+ this.analytics = analytics;
14079
+ analytics.init();
14080
+ }
14081
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, deps: [{ token: AnalyticsService }], target: i0.ɵɵFactoryTarget.NgModule }); }
14020
14082
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, declarations: [
14021
14083
  // Changed: Removed page components now declared in domain-specific modules (accounting, inventory, sales, purchasing, hr, payroll, manufacturing, loans, general, tenancy, workflow, overview)
14022
14084
  TinSpaComponent, TextComponent, TextMaskComponent, TextAreaComponent, TextSingleComponent, CheckComponent, DateComponent, DatetimeComponent, LabelComponent, SelectComponent,
@@ -14097,7 +14159,8 @@ class TinSpaModule {
14097
14159
  LoansService,
14098
14160
  TabService, // Changed: Added TabService to providers
14099
14161
  provideCharts(withDefaultRegisterables()), // Changed: Register Chart.js with all default chart types
14100
- SubscriptionService // Added: Subscription feature-checking service
14162
+ SubscriptionService, // Added: Subscription feature-checking service
14163
+ AnalyticsService // Changed: GA4 route tracking for SPA page views
14101
14164
  ], imports: [SpaMatModule,
14102
14165
  HttpClientModule,
14103
14166
  CurrencyInputModule,
@@ -14194,10 +14257,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
14194
14257
  LoansService,
14195
14258
  TabService, // Changed: Added TabService to providers
14196
14259
  provideCharts(withDefaultRegisterables()), // Changed: Register Chart.js with all default chart types
14197
- SubscriptionService // Added: Subscription feature-checking service
14260
+ SubscriptionService, // Added: Subscription feature-checking service
14261
+ AnalyticsService // Changed: GA4 route tracking for SPA page views
14198
14262
  ],
14199
14263
  }]
14200
- }] });
14264
+ }], ctorParameters: () => [{ type: AnalyticsService }] });
14201
14265
 
14202
14266
  class LoginComponent {
14203
14267
  constructor(httpService, storageService, router, messageService, dataService, authService, logService, route, notificationsService, signalRService, msalService, dialog) {
@@ -16616,50 +16680,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16616
16680
  }]
16617
16681
  }] });
16618
16682
 
16619
- // Fixed Assets management page — full lifecycle with tiles, CRUD, activation, depreciation, and disposal
16620
- class FixedAssetsComponent {
16621
- constructor() {
16622
- this.accountingService = inject(AccountingService);
16623
- this.pageConfig = {
16624
- title: 'Fixed Assets',
16625
- tableConfig: this.accountingService.assetsTableConfig
16626
- };
16627
- }
16628
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16629
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetsComponent, isStandalone: true, selector: "spa-fixed-assets", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
16630
- }
16631
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsComponent, decorators: [{
16632
- type: Component,
16633
- args: [{
16634
- selector: 'spa-fixed-assets',
16635
- standalone: true,
16636
- imports: [TinSpaModule],
16637
- template: '<spa-page [config]="pageConfig"></spa-page>'
16638
- }]
16639
- }] });
16640
-
16641
- // Fixed Asset Categories management page — CRUD for asset categories with depreciation defaults
16642
- class FixedAssetCategoriesComponent {
16643
- constructor() {
16644
- this.accountingService = inject(AccountingService);
16645
- this.pageConfig = {
16646
- title: 'Asset Categories',
16647
- tableConfig: this.accountingService.categoryTableConfig
16648
- };
16649
- }
16650
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetCategoriesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16651
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetCategoriesComponent, isStandalone: true, selector: "spa-fixed-asset-categories", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
16652
- }
16653
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetCategoriesComponent, decorators: [{
16654
- type: Component,
16655
- args: [{
16656
- selector: 'spa-fixed-asset-categories',
16657
- standalone: true,
16658
- imports: [TinSpaModule],
16659
- template: '<spa-page [config]="pageConfig"></spa-page>'
16660
- }]
16661
- }] });
16662
-
16663
16683
  // Currency management page - CRUD for multi-currency support
16664
16684
  class CurrenciesComponent {
16665
16685
  constructor() {
@@ -16991,9 +17011,7 @@ const ACCOUNTING_ROUTES = [
16991
17011
  { path: "reports", component: ReportsComponent },
16992
17012
  { path: "tax-rates", component: TaxRatesComponent },
16993
17013
  { path: "standing-orders", component: StandingOrdersComponent },
16994
- { path: "fixed-assets", component: FixedAssetsComponent },
16995
- { path: "fixed-asset-categories", component: FixedAssetCategoriesComponent },
16996
- { path: "currencies", component: CurrenciesComponent },
17014
+ { path: "currencies", component: CurrenciesComponent }, // Changed: Removed Fixed Assets routes — moved to Assets module
16997
17015
  { path: "budgets", component: BudgetsComponent },
16998
17016
  { path: "budget-vs-actual", component: BudgetVsActualComponent },
16999
17017
  { path: "dashboard", component: AccountingDashboardComponent },
@@ -20835,6 +20853,9 @@ class ApprovalsConfigComponent {
20835
20853
  { name: 'edit', dialog: true, action: { url: 'approvallevels?action=edit', method: 'post' } }
20836
20854
  ]
20837
20855
  };
20856
+ // Changed: view + create after detailsConfig for onSuccessButton
20857
+ const approvalLevelViewButton = { name: 'view', dialog: true, detailsConfig: this.approvalLevelDetailsConfig };
20858
+ const approvalLevelCreateButton = { name: 'create', display: 'Add Level', dialog: true, onSuccessButton: approvalLevelViewButton, action: { url: 'approvallevels?action=create', method: 'post' } };
20838
20859
  this.approvalLevelsTableConfig = {
20839
20860
  tabTitle: 'Approval Levels',
20840
20861
  showFilter: true,
@@ -20849,10 +20870,10 @@ class ApprovalsConfigComponent {
20849
20870
  { name: 'rolesDisplay', type: 'text', alias: 'Roles' }
20850
20871
  ],
20851
20872
  buttons: [
20852
- { name: 'create', display: 'Add Level', dialog: true, action: { url: 'approvallevels?action=create', method: 'post' } },
20873
+ approvalLevelCreateButton, // Changed: uses onSuccessButton to auto-open view after create
20853
20874
  { name: 'up', display: 'Move Up', icon: { name: 'arrow_upward' }, action: { url: 'approvallevels?action=up', method: 'post' }, visible: x => x.level > 1 },
20854
20875
  { name: 'down', display: 'Move Down', icon: { name: 'arrow_downward' }, action: { url: 'approvallevels?action=down', method: 'post' } },
20855
- { name: 'view', dialog: true, detailsConfig: this.approvalLevelDetailsConfig },
20876
+ approvalLevelViewButton,
20856
20877
  { name: 'delete', dialog: true, action: { url: 'approvallevels?action=delete', method: 'post' } }
20857
20878
  ],
20858
20879
  formConfig: this.approvalLevelFormConfig,
@@ -20869,7 +20890,7 @@ class ApprovalsConfigComponent {
20869
20890
  // { name: 'modelName', type: 'text', required: true, alias: 'Entity Name', span: true, },
20870
20891
  { name: 'enabled', type: 'checkbox', span: true, alias: 'Enable Approvals', defaultValue: true, infoMessage: 'Enable or disable approval workflow for this entity' },
20871
20892
  // { name: 'modelName', type: 'select', required: true, span: true, alias: 'Entity', optionDisplay: 'name', optionValue: 'value', loadAction: { url: 'approvalconfigs/meta/x' }, },
20872
- { name: 'appModelID', type: 'select', required: true, span: true, alias: 'Entity', optionDisplay: 'name', optionValue: 'value', loadAction: { url: 'appmodels/list/approvable' }, },
20893
+ { name: 'appModelID', type: 'text-single', required: true, span: true, alias: 'Entity', optionDisplay: 'name', optionValue: 'value', loadAction: { url: 'appmodels/list/approvable' }, }, // Changed: select -> text-single for typeable autocomplete
20873
20894
  { name: 'createApprovalRequired', type: 'checkbox', span: true, alias: 'Create Approval' },
20874
20895
  { name: 'editApprovalRequired', type: 'checkbox', span: true, alias: 'Edit Approval' },
20875
20896
  { name: 'deleteApprovalRequired', type: 'checkbox', span: true, alias: 'Delete Approval' },
@@ -20890,6 +20911,9 @@ class ApprovalsConfigComponent {
20890
20911
  { name: 'edit', dialog: true, action: { url: 'approvalconfigs?action=edit', method: 'post' } },
20891
20912
  ]
20892
20913
  };
20914
+ // Changed: view + create after detailsConfig for onSuccessButton
20915
+ const approvalConfigViewButton = { name: 'view', dialog: true, detailsConfig: this.approvalConfigDetailsConfig };
20916
+ const approvalConfigCreateButton = { name: 'create', display: 'Create', dialog: true, onSuccessButton: approvalConfigViewButton, action: { url: 'approvalconfigs?action=create', method: 'post' } };
20893
20917
  this.approvalConfigTable = {
20894
20918
  showFilter: true,
20895
20919
  flatButtons: true,
@@ -20924,8 +20948,8 @@ class ApprovalsConfigComponent {
20924
20948
  },
20925
20949
  ],
20926
20950
  buttons: [
20927
- { name: 'create', display: 'Create', dialog: true, action: { url: 'approvalconfigs?action=create', method: 'post' } },
20928
- { name: 'view', dialog: true, detailsConfig: this.approvalConfigDetailsConfig },
20951
+ approvalConfigCreateButton, // Changed: uses onSuccessButton to auto-open view after create
20952
+ approvalConfigViewButton,
20929
20953
  { name: 'edit', dialog: true, detailsConfig: this.approvalConfigDetailsConfig },
20930
20954
  { name: 'delete', action: { url: 'approvalconfigs?action=delete', method: 'post' } },
20931
20955
  ],
@@ -21141,7 +21165,7 @@ class NotificationsConfigComponent {
21141
21165
  security: { allow: [this.dataService.capNotificationsConfig] },
21142
21166
  fields: [
21143
21167
  { name: 'enabled', type: 'checkbox', alias: 'Enable Notifications', defaultValue: true, span: true },
21144
- { name: 'appModelID', type: 'select', required: true, span: true, alias: 'Entity', loadAction: { url: 'appmodels/list/x' } },
21168
+ { name: 'appModelID', type: 'text-single', required: true, span: true, alias: 'Entity', loadAction: { url: 'appmodels/list/x' } }, // Changed: select -> text-single for typeable autocomplete
21145
21169
  // { name: 'customActions', type: 'text', span: true, alias: 'Custom Actions', infoMessage: 'Comma-separated list of actions that can trigger notifications e.g receive,disburse' }
21146
21170
  ]
21147
21171
  };
@@ -21153,6 +21177,9 @@ class NotificationsConfigComponent {
21153
21177
  { name: 'edit', dialog: true, action: { url: 'notificationconfigs?action=edit', method: 'post' } }
21154
21178
  ]
21155
21179
  };
21180
+ // Changed: view + create after detailsConfig for onSuccessButton
21181
+ const notifConfigViewButton = { name: 'view', dialog: true, detailsConfig: this.notificationConfigDetailsConfig };
21182
+ const notifConfigCreateButton = { name: 'create', display: 'Create', dialog: true, onSuccessButton: notifConfigViewButton, action: { url: 'notificationconfigs?action=create', method: 'post' } };
21156
21183
  this.notificationConfigTable = {
21157
21184
  showFilter: true,
21158
21185
  flatButtons: true,
@@ -21179,8 +21206,8 @@ class NotificationsConfigComponent {
21179
21206
  }
21180
21207
  ],
21181
21208
  buttons: [
21182
- { name: 'create', display: 'Create', dialog: true, action: { url: 'notificationconfigs?action=create', method: 'post' } },
21183
- { name: 'view', dialog: true, detailsConfig: this.notificationConfigDetailsConfig },
21209
+ notifConfigCreateButton, // Changed: uses onSuccessButton to auto-open view after create
21210
+ notifConfigViewButton,
21184
21211
  { name: 'edit', dialog: true, detailsConfig: this.notificationConfigDetailsConfig },
21185
21212
  { name: 'delete', action: { url: 'notificationconfigs?action=delete', method: 'post' } }
21186
21213
  ],
@@ -21302,6 +21329,133 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
21302
21329
  }]
21303
21330
  }] });
21304
21331
 
21332
+ // Fixed Assets module dashboard — asset performance, depreciation trends, and portfolio overview
21333
+ class FixedAssetsDashboardComponent {
21334
+ constructor() {
21335
+ this.summaryTiles = {
21336
+ tiles: [
21337
+ { name: 'draft', alias: 'Draft', color: '#FFC107', icon: 'edit_note', style: 'icon', info: 'Assets awaiting activation' },
21338
+ { name: 'active', alias: 'Active', color: '#4CAF50', icon: 'check_circle', style: 'icon', info: 'Actively depreciating assets' },
21339
+ { name: 'fullyDepreciated', alias: 'Fully Depreciated', color: '#2196F3', icon: 'task_alt', style: 'icon', info: 'Zero book value assets' },
21340
+ { name: 'disposed', alias: 'Disposed', color: '#9E9E9E', icon: 'delete_forever', style: 'icon', info: 'Sold or retired assets' },
21341
+ { name: 'totalCost', alias: 'Total Cost', color: '#ff9800', icon: 'attach_money', style: 'icon', info: 'Total acquisition cost' },
21342
+ { name: 'totalNBV', alias: 'Net Book Value', color: '#4CAF50', icon: 'trending_up', style: 'icon', info: 'Current net book value' },
21343
+ { name: 'depreciationThisMonth', alias: 'Depreciation (MTD)', color: '#f44336', icon: 'trending_down', style: 'icon', info: 'Depreciation posted this month' },
21344
+ ],
21345
+ loadAction: { url: 'fixedassets/dashboard/summary' },
21346
+ loadInit: true
21347
+ };
21348
+ this.chartTiles = {
21349
+ tiles: [
21350
+ { name: 'depreciationGauge', alias: 'Depreciation %', color: '#2196F3', chart: { type: 'doughnut', gaugeColor: '#2196F3', height: 130, dataField: 'depreciationGauge' }, footer: 'Total depreciated vs cost', footerIcon: 'pie_chart' },
21351
+ { name: 'depreciationSparkline', alias: 'Depreciation Trend', color: '#f44336', chart: { type: 'bar', colors: ['#f44336', '#ef5350', '#e57373', '#ef9a9a', '#ffcdd2', '#f44336', '#ef5350', '#e57373', '#ef9a9a', '#ffcdd2', '#f44336', '#ef5350'], height: 100, dataField: 'depreciationSparkline' }, footer: 'Last 12 months', footerIcon: 'trending_down' },
21352
+ { name: 'costByCategory', alias: 'Cost by Category', color: '#ff9800', chart: { type: 'pie', colors: ['#ff9800', '#f44336', '#9c27b0', '#2196F3', '#4CAF50', '#607d8b'], height: 130, dataField: 'costByCategory' }, footer: 'Acquisition cost split', footerIcon: 'donut_large' },
21353
+ { name: 'nbvTrend', alias: 'NBV Trend', color: '#4CAF50', chart: { type: 'line', color: '#4CAF50', height: 100, dataField: 'nbvTrend' }, footer: 'Last 6 months', footerIcon: 'show_chart' },
21354
+ ],
21355
+ loadAction: { url: 'fixedassets/dashboard/chart-tiles' },
21356
+ loadInit: true
21357
+ };
21358
+ this.chartConfig = {
21359
+ charts: [
21360
+ { name: 'depreciationTrend', title: 'Monthly Depreciation', type: 'bar', height: '300px', colors: ['#2196F3'] },
21361
+ { name: 'statusDistribution', title: 'Asset Status Distribution', type: 'doughnut', height: '300px', colors: ['#FFC107', '#4CAF50', '#2196F3', '#9E9E9E'] },
21362
+ { name: 'costByCategory', title: 'Cost by Category', type: 'bar', height: '300px', colors: ['#ff9800'] },
21363
+ { name: 'nbvVsCost', title: 'Cost vs Net Book Value', type: 'line', height: '300px', showPoints: true, showLegend: true, tension: 0.4, colors: ['#ff9800', '#4CAF50'] },
21364
+ { name: 'remainingLife', title: 'Remaining Useful Life', type: 'bar', height: '300px', colors: ['#7c4dff'] },
21365
+ { name: 'acquisitionsTimeline', title: 'Acquisitions Timeline', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#2196F3'] },
21366
+ ],
21367
+ loadAction: { url: 'fixedassets/dashboard/charts' },
21368
+ loadInit: true,
21369
+ columns: 2
21370
+ };
21371
+ }
21372
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
21373
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetsDashboardComponent, isStandalone: false, selector: "spa-fixed-assets-dashboard", ngImport: i0, template: `
21374
+ <div class="dashboard-container">
21375
+ <h4 class="dashboard-title"><mat-icon>precision_manufacturing</mat-icon> Fixed Assets Dashboard</h4>
21376
+ <spa-tiles [config]="summaryTiles"></spa-tiles>
21377
+ <div style="margin-top: 16px;"></div>
21378
+ <spa-tiles [config]="chartTiles"></spa-tiles>
21379
+ <spa-charts [config]="chartConfig"></spa-charts>
21380
+ </div>
21381
+ `, isInline: true, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"], dependencies: [{ kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: ChartsComponent, selector: "spa-charts", inputs: ["config", "data", "reload"] }] }); }
21382
+ }
21383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsDashboardComponent, decorators: [{
21384
+ type: Component,
21385
+ args: [{ selector: 'spa-fixed-assets-dashboard', template: `
21386
+ <div class="dashboard-container">
21387
+ <h4 class="dashboard-title"><mat-icon>precision_manufacturing</mat-icon> Fixed Assets Dashboard</h4>
21388
+ <spa-tiles [config]="summaryTiles"></spa-tiles>
21389
+ <div style="margin-top: 16px;"></div>
21390
+ <spa-tiles [config]="chartTiles"></spa-tiles>
21391
+ <spa-charts [config]="chartConfig"></spa-charts>
21392
+ </div>
21393
+ `, standalone: false, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"] }]
21394
+ }] });
21395
+
21396
+ // Fixed Assets management page — full lifecycle with tiles, CRUD, activation, depreciation, and disposal
21397
+ class FixedAssetsComponent {
21398
+ constructor() {
21399
+ this.assetsService = inject(AssetsService); // Changed: Use AssetsService
21400
+ this.pageConfig = {
21401
+ title: 'Fixed Assets',
21402
+ tableConfig: this.assetsService.assetsTableConfig // Changed: Use AssetsService
21403
+ };
21404
+ }
21405
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
21406
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetsComponent, isStandalone: true, selector: "spa-fixed-assets", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
21407
+ }
21408
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetsComponent, decorators: [{
21409
+ type: Component,
21410
+ args: [{
21411
+ selector: 'spa-fixed-assets',
21412
+ standalone: true,
21413
+ imports: [TinSpaModule],
21414
+ template: '<spa-page [config]="pageConfig"></spa-page>'
21415
+ }]
21416
+ }] });
21417
+
21418
+ // Depreciation Categories management page — CRUD for depreciation categories with default settings
21419
+ class FixedAssetCategoriesComponent {
21420
+ constructor() {
21421
+ this.assetsService = inject(AssetsService);
21422
+ this.pageConfig = {
21423
+ title: 'Depreciation Categories', // Changed: Renamed from Asset Categories
21424
+ tableConfig: this.assetsService.categoryTableConfig
21425
+ };
21426
+ }
21427
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetCategoriesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
21428
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FixedAssetCategoriesComponent, isStandalone: true, selector: "spa-fixed-asset-categories", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
21429
+ }
21430
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FixedAssetCategoriesComponent, decorators: [{
21431
+ type: Component,
21432
+ args: [{
21433
+ selector: 'spa-fixed-asset-categories',
21434
+ standalone: true,
21435
+ imports: [TinSpaModule],
21436
+ template: '<spa-page [config]="pageConfig"></spa-page>'
21437
+ }]
21438
+ }] });
21439
+
21440
+ // Changed: Fixed Assets routes — separated from Accounting module
21441
+ const ASSETS_ROUTES = [
21442
+ { path: "dashboard", component: FixedAssetsDashboardComponent },
21443
+ { path: "register", component: FixedAssetsComponent },
21444
+ { path: "depreciation-categories", component: FixedAssetCategoriesComponent }, // Changed: Renamed from categories
21445
+ ];
21446
+ class AssetsRoutingModule {
21447
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
21448
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: AssetsRoutingModule, imports: [i1$2.RouterModule], exports: [RouterModule] }); }
21449
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsRoutingModule, imports: [RouterModule.forChild(ASSETS_ROUTES), RouterModule] }); }
21450
+ }
21451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsRoutingModule, decorators: [{
21452
+ type: NgModule,
21453
+ args: [{
21454
+ imports: [RouterModule.forChild(ASSETS_ROUTES)],
21455
+ exports: [RouterModule]
21456
+ }]
21457
+ }] });
21458
+
21305
21459
  // All domain routes nested under their module path
21306
21460
  // Consumer apps import SpaHomeModule once — no need to declare routes per app
21307
21461
  const routes = [
@@ -21318,7 +21472,8 @@ const routes = [
21318
21472
  { path: 'general', children: GENERAL_ROUTES },
21319
21473
  { path: 'tenancy', children: TENANCY_ROUTES },
21320
21474
  { path: 'workflow', children: WORKFLOW_ROUTES },
21321
- { path: 'overview', children: OVERVIEW_ROUTES }
21475
+ { path: 'overview', children: OVERVIEW_ROUTES },
21476
+ { path: 'fixed-assets', children: ASSETS_ROUTES } // Changed: Added Fixed Assets module
21322
21477
  ];
21323
21478
  class SpaHomeRoutingModule {
21324
21479
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaHomeRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -21352,17 +21507,15 @@ class AccountingModule {
21352
21507
  // Changed: Standalone components must be imported, not declared
21353
21508
  TaxRatesComponent,
21354
21509
  StandingOrdersComponent,
21355
- FixedAssetsComponent,
21356
- FixedAssetCategoriesComponent,
21357
- CurrenciesComponent] }); }
21510
+ CurrenciesComponent // Changed: Removed Fixed Assets — moved to Assets module
21511
+ ] }); }
21358
21512
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingModule, imports: [CommonModule,
21359
21513
  SpaAdminModule,
21360
21514
  // Changed: Standalone components must be imported, not declared
21361
21515
  TaxRatesComponent,
21362
21516
  StandingOrdersComponent,
21363
- FixedAssetsComponent,
21364
- FixedAssetCategoriesComponent,
21365
- CurrenciesComponent] }); }
21517
+ CurrenciesComponent // Changed: Removed Fixed Assets — moved to Assets module
21518
+ ] }); }
21366
21519
  }
21367
21520
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingModule, decorators: [{
21368
21521
  type: NgModule,
@@ -21388,9 +21541,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
21388
21541
  // Changed: Standalone components must be imported, not declared
21389
21542
  TaxRatesComponent,
21390
21543
  StandingOrdersComponent,
21391
- FixedAssetsComponent,
21392
- FixedAssetCategoriesComponent,
21393
- CurrenciesComponent
21544
+ CurrenciesComponent // Changed: Removed Fixed Assets — moved to Assets module
21394
21545
  ]
21395
21546
  }]
21396
21547
  }] });
@@ -21697,6 +21848,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
21697
21848
  }]
21698
21849
  }] });
21699
21850
 
21851
+ // Changed: Fixed Assets module — separated from Accounting module
21852
+ class AssetsModule {
21853
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
21854
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: AssetsModule, declarations: [FixedAssetsDashboardComponent], imports: [CommonModule,
21855
+ SpaAdminModule,
21856
+ FixedAssetsComponent,
21857
+ FixedAssetCategoriesComponent] }); }
21858
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsModule, imports: [CommonModule,
21859
+ SpaAdminModule,
21860
+ FixedAssetsComponent,
21861
+ FixedAssetCategoriesComponent] }); }
21862
+ }
21863
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AssetsModule, decorators: [{
21864
+ type: NgModule,
21865
+ args: [{
21866
+ declarations: [
21867
+ FixedAssetsDashboardComponent
21868
+ ],
21869
+ imports: [
21870
+ CommonModule,
21871
+ SpaAdminModule,
21872
+ FixedAssetsComponent,
21873
+ FixedAssetCategoriesComponent
21874
+ ]
21875
+ }]
21876
+ }] });
21877
+
21700
21878
  // Single import for consumer apps — provides all domain routes and components
21701
21879
  // Usage in consumer home-routing.module.ts:
21702
21880
  // { path: '', loadChildren: () => import('tin-spa').then(m => m.SpaHomeModule) }
@@ -21716,7 +21894,9 @@ class SpaHomeModule {
21716
21894
  GeneralModule,
21717
21895
  TenancyModule,
21718
21896
  WorkflowModule,
21719
- OverviewModule] }); }
21897
+ OverviewModule,
21898
+ AssetsModule // Changed: Added Fixed Assets module
21899
+ ] }); }
21720
21900
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaHomeModule, imports: [SpaHomeRoutingModule,
21721
21901
  AdminModule,
21722
21902
  UserModule,
@@ -21731,7 +21911,9 @@ class SpaHomeModule {
21731
21911
  GeneralModule,
21732
21912
  TenancyModule,
21733
21913
  WorkflowModule,
21734
- OverviewModule] }); }
21914
+ OverviewModule,
21915
+ AssetsModule // Changed: Added Fixed Assets module
21916
+ ] }); }
21735
21917
  }
21736
21918
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaHomeModule, decorators: [{
21737
21919
  type: NgModule,
@@ -21751,7 +21933,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
21751
21933
  GeneralModule,
21752
21934
  TenancyModule,
21753
21935
  WorkflowModule,
21754
- OverviewModule
21936
+ OverviewModule,
21937
+ AssetsModule // Changed: Added Fixed Assets module
21755
21938
  ]
21756
21939
  }]
21757
21940
  }] });
@@ -21792,5 +21975,5 @@ const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://w
21792
21975
  * Generated bundle index. Do not edit.
21793
21976
  */
21794
21977
 
21795
- export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, ReceiptStatus, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, 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, viewerDialog };
21978
+ export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, AnalyticsService, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, ReceiptStatus, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, 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, viewerDialog };
21796
21979
  //# sourceMappingURL=tin-spa.mjs.map