@flusys/ng-layout 1.0.0-beta → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,11 @@
1
- import * as i1 from '@angular/common';
2
- import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
3
1
  import * as i0 from '@angular/core';
4
- import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, Component, ChangeDetectionStrategy, InjectionToken, DestroyRef, ElementRef, viewChild, input, Renderer2, ViewChild } from '@angular/core';
5
- import * as i2 from '@angular/forms';
2
+ import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, ChangeDetectionStrategy, Component, afterNextRender, InjectionToken, DestroyRef, ElementRef, viewChild, input, Renderer2 } from '@angular/core';
3
+ import { isPlatformBrowser, NgClass, DOCUMENT as DOCUMENT$1 } from '@angular/common';
4
+ import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled } from '@flusys/ng-core';
5
+ import * as i2$2 from '@flusys/ng-shared';
6
+ import { evaluateLogicNode, PermissionValidatorService, AngularModule, IconComponent } from '@flusys/ng-shared';
7
+ import { Subject, fromEvent, filter as filter$1 } from 'rxjs';
8
+ import * as i1 from '@angular/forms';
6
9
  import { FormsModule } from '@angular/forms';
7
10
  import * as i1$2 from '@angular/router';
8
11
  import { Router, RouterModule, NavigationEnd } from '@angular/router';
@@ -10,13 +13,8 @@ import { updatePreset, updateSurfacePalette, $t, definePreset } from '@primeuix/
10
13
  import Aura from '@primeuix/themes/aura';
11
14
  import Lara from '@primeuix/themes/lara';
12
15
  import Nora from '@primeuix/themes/nora';
13
- import { PrimeNG } from 'primeng/config';
14
- import * as i3 from 'primeng/selectbutton';
16
+ import * as i2 from 'primeng/selectbutton';
15
17
  import { SelectButtonModule } from 'primeng/selectbutton';
16
- import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled } from '@flusys/ng-core';
17
- import * as i2$2 from '@flusys/ng-shared';
18
- import { evaluateLogicNode, PermissionValidatorService, PlatformService, AngularModule, IconComponent } from '@flusys/ng-shared';
19
- import { Subject, fromEvent, filter as filter$1 } from 'rxjs';
20
18
  import * as i1$1 from 'primeng/button';
21
19
  import { ButtonModule } from 'primeng/button';
22
20
  import * as i2$1 from 'primeng/styleclass';
@@ -204,94 +202,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
204
202
  }]
205
203
  }] });
206
204
 
207
- /**
208
- * Service managing layout configuration and state.
209
- * Provides signals for reactive layout updates.
210
- */
205
+ /** Layout configuration and state management service */
211
206
  class LayoutService {
212
207
  document = inject(DOCUMENT);
213
208
  platformId = inject(PLATFORM_ID);
214
209
  isBrowser = isPlatformBrowser(this.platformId);
215
210
  persistence = inject(LayoutPersistenceService);
216
211
  appConfig = inject(APP_CONFIG, { optional: true });
217
- defaultConfig = {
212
+ DEFAULT_CONFIG = {
218
213
  preset: 'Aura',
219
214
  primary: 'emerald',
220
215
  surface: null,
221
216
  darkTheme: false,
222
217
  menuMode: 'static',
223
218
  };
224
- // Load persisted config merged with defaults
225
- initialConfig = (() => {
226
- const persisted = this.persistence.load();
227
- return persisted
228
- ? { ...this.defaultConfig, ...persisted }
229
- : this.defaultConfig;
230
- })();
231
- defaultState = {
219
+ DEFAULT_STATE = {
232
220
  staticMenuDesktopInactive: false,
233
221
  overlayMenuActive: false,
234
222
  configSidebarVisible: false,
235
223
  staticMenuMobileActive: false,
236
224
  menuHoverActive: false,
237
225
  };
238
- // Signals
239
- layoutConfig = signal(this.initialConfig, ...(ngDevMode ? [{ debugName: "layoutConfig" }] : []));
240
- layoutState = signal(this.defaultState, ...(ngDevMode ? [{ debugName: "layoutState" }] : []));
241
- transitionComplete = signal(false, ...(ngDevMode ? [{ debugName: "transitionComplete" }] : []));
242
- // User Profile Signals
243
- userProfile = signal(null, ...(ngDevMode ? [{ debugName: "userProfile" }] : []));
244
- companyProfile = signal(null, ...(ngDevMode ? [{ debugName: "companyProfile" }] : []));
245
- appName = signal(this.appConfig?.appName ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "appName" }] : []));
246
- // Author/Brand Signals
247
- authorName = signal(this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name, ...(ngDevMode ? [{ debugName: "authorName" }] : []));
248
- authorUrl = signal(this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url, ...(ngDevMode ? [{ debugName: "authorUrl" }] : []));
249
- // App Launcher Signals
250
- _rawApps = signal([], ...(ngDevMode ? [{ debugName: "_rawApps" }] : []));
251
- /**
252
- * Filtered launcher apps based on user permissions.
253
- * Automatically recomputes when raw apps or permissions change.
254
- */
255
- apps = computed(() => {
256
- const raw = this._rawApps();
257
- const permission = this.permissionValidator.permissions();
258
- return filterAppsByPermissions(raw, permission);
259
- }, ...(ngDevMode ? [{ debugName: "apps" }] : []));
260
- // Menu Signals
226
+ _layoutConfig = signal({
227
+ ...this.DEFAULT_CONFIG,
228
+ ...this.persistence.load(),
229
+ }, ...(ngDevMode ? [{ debugName: "_layoutConfig" }] : []));
230
+ _layoutState = signal(this.DEFAULT_STATE, ...(ngDevMode ? [{ debugName: "_layoutState" }] : []));
231
+ _transitionComplete = signal(false, ...(ngDevMode ? [{ debugName: "_transitionComplete" }] : []));
232
+ layoutConfig = this._layoutConfig.asReadonly();
233
+ layoutState = this._layoutState.asReadonly();
234
+ transitionComplete = this._transitionComplete.asReadonly();
235
+ _userProfile = signal(null, ...(ngDevMode ? [{ debugName: "_userProfile" }] : []));
236
+ _companyProfile = signal(null, ...(ngDevMode ? [{ debugName: "_companyProfile" }] : []));
261
237
  _rawMenu = signal([], ...(ngDevMode ? [{ debugName: "_rawMenu" }] : []));
238
+ _rawApps = signal([], ...(ngDevMode ? [{ debugName: "_rawApps" }] : []));
262
239
  permissionValidator = inject(PermissionValidatorService);
263
- /**
264
- * Filtered menu items based on user permissions.
265
- * Automatically recomputes when raw menu or permission checker changes.
266
- * Role checker is optional - if not set, role-based permissions will be denied.
267
- */
268
- menu = computed(() => {
269
- const raw = this._rawMenu(); // Track permission changes
270
- const permission = this.permissionValidator.permissions();
271
- return filterMenuByPermissions(raw, permission);
272
- }, ...(ngDevMode ? [{ debugName: "menu" }] : []));
240
+ userProfile = this._userProfile.asReadonly();
241
+ companyProfile = this._companyProfile.asReadonly();
242
+ // Static app info from config
243
+ appName = this.appConfig?.appName ?? DEFAULT_APP_NAME;
244
+ authorName = this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name;
245
+ authorUrl = this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url;
246
+ // Permission-filtered menu and apps
247
+ menu = computed(() => filterMenuByPermissions(this._rawMenu(), this.permissionValidator.permissions()), ...(ngDevMode ? [{ debugName: "menu" }] : []));
248
+ apps = computed(() => filterAppsByPermissions(this._rawApps(), this.permissionValidator.permissions()), ...(ngDevMode ? [{ debugName: "apps" }] : []));
249
+ hasApps = computed(() => this.apps().length > 0, ...(ngDevMode ? [{ debugName: "hasApps" }] : []));
273
250
  // Computed signals - Layout
274
- isSidebarActive = computed(() => this.layoutState().overlayMenuActive ||
275
- this.layoutState().staticMenuMobileActive, ...(ngDevMode ? [{ debugName: "isSidebarActive" }] : []));
276
- isDarkTheme = computed(() => this.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
277
- getPrimary = computed(() => this.layoutConfig().primary, ...(ngDevMode ? [{ debugName: "getPrimary" }] : []));
278
- getSurface = computed(() => this.layoutConfig().surface, ...(ngDevMode ? [{ debugName: "getSurface" }] : []));
279
- isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay', ...(ngDevMode ? [{ debugName: "isOverlay" }] : []));
280
- // Computed signals - User Profile
281
- userName = computed(() => this.userProfile()?.name ?? 'User', ...(ngDevMode ? [{ debugName: "userName" }] : []));
282
- userEmail = computed(() => this.userProfile()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
283
- userProfilePictureUrl = computed(() => this.userProfile()?.profilePictureUrl ?? null, ...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : []));
251
+ isSidebarActive = computed(() => this._layoutState().overlayMenuActive ||
252
+ this._layoutState().staticMenuMobileActive, ...(ngDevMode ? [{ debugName: "isSidebarActive" }] : []));
253
+ isDarkTheme = computed(() => this._layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
254
+ getPrimary = computed(() => this._layoutConfig().primary, ...(ngDevMode ? [{ debugName: "getPrimary" }] : []));
255
+ getSurface = computed(() => this._layoutConfig().surface, ...(ngDevMode ? [{ debugName: "getSurface" }] : []));
256
+ isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', ...(ngDevMode ? [{ debugName: "isOverlay" }] : []));
257
+ // User profile computed signals
258
+ userName = computed(() => this._userProfile()?.name ?? 'User', ...(ngDevMode ? [{ debugName: "userName" }] : []));
259
+ userEmail = computed(() => this._userProfile()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
260
+ userProfilePictureUrl = computed(() => this._userProfile()?.profilePictureUrl ?? null, ...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : []));
261
+ companyLogoUrl = computed(() => this._companyProfile()?.logoUrl ?? null, ...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : []));
262
+ isAuthenticated = computed(() => !!this._userProfile(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
284
263
  companyName = computed(() => {
285
- // If company feature is disabled, always show app name
286
- if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig)) {
287
- return this.appName();
288
- }
289
- return this.companyProfile()?.name ?? this.appName();
264
+ if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig))
265
+ return this.appName;
266
+ return this._companyProfile()?.name ?? this.appName;
290
267
  }, ...(ngDevMode ? [{ debugName: "companyName" }] : []));
291
- companyLogoUrl = computed(() => this.companyProfile()?.logoUrl ?? null, ...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : []));
292
- isAuthenticated = computed(() => !!this.userProfile(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
293
- // Computed signals - App Launcher
294
- hasApps = computed(() => this.apps().length > 0, ...(ngDevMode ? [{ debugName: "hasApps" }] : []));
295
268
  // RxJS Subjects for event communication
296
269
  configUpdate = new Subject();
297
270
  overlayOpen = new Subject();
@@ -304,79 +277,56 @@ class LayoutService {
304
277
  initialized = false;
305
278
  constructor() {
306
279
  effect(() => {
307
- const config = this.layoutConfig();
308
- if (config) {
309
- this.onConfigUpdate();
310
- }
311
- });
312
- effect(() => {
313
- const config = this.layoutConfig();
314
- if (!this.initialized || !config) {
315
- this.initialized = true;
316
- return;
317
- }
318
- this.handleDarkModeTransition(config);
319
- });
320
- // Auto-save configuration changes to localStorage
321
- effect(() => {
322
- const config = this.layoutConfig();
323
- if (config && this.initialized) {
280
+ const config = this._layoutConfig();
281
+ this.configUpdate.next(config);
282
+ if (this.initialized) {
283
+ this.handleDarkModeTransition(config);
324
284
  this.persistence.save(config);
325
285
  }
286
+ this.initialized = true;
326
287
  });
327
288
  }
289
+ toggleDarkMode(config) {
290
+ const isDark = (config ?? this._layoutConfig()).darkTheme;
291
+ this.document.documentElement.classList.toggle('app-dark', isDark);
292
+ }
328
293
  handleDarkModeTransition(config) {
329
- if (this.document.startViewTransition) {
330
- this.startViewTransition(config);
294
+ const doc = this.document;
295
+ if ('startViewTransition' in doc && typeof doc.startViewTransition === 'function') {
296
+ doc
297
+ .startViewTransition(() => this.toggleDarkMode(config))
298
+ .ready.then(() => this.onTransitionEnd())
299
+ .catch(() => { });
331
300
  }
332
301
  else {
333
302
  this.toggleDarkMode(config);
334
303
  this.onTransitionEnd();
335
304
  }
336
305
  }
337
- startViewTransition(config) {
338
- const transition = this.document.startViewTransition(() => {
339
- this.toggleDarkMode(config);
340
- });
341
- transition.ready.then(() => this.onTransitionEnd()).catch(() => { });
342
- }
343
- toggleDarkMode(config) {
344
- const _config = config || this.layoutConfig();
345
- if (_config.darkTheme) {
346
- this.document.documentElement.classList.add('app-dark');
347
- }
348
- else {
349
- this.document.documentElement.classList.remove('app-dark');
350
- }
351
- }
352
306
  onTransitionEnd() {
353
- this.transitionComplete.set(true);
354
- setTimeout(() => this.transitionComplete.set(false));
307
+ this._transitionComplete.set(true);
308
+ setTimeout(() => this._transitionComplete.set(false));
355
309
  }
356
310
  onMenuToggle() {
311
+ const state = this._layoutState();
357
312
  if (this.isOverlay()) {
358
- this.layoutState.update((prev) => ({
359
- ...prev,
360
- overlayMenuActive: !this.layoutState().overlayMenuActive,
361
- }));
362
- if (this.layoutState().overlayMenuActive) {
313
+ const newOverlayActive = !state.overlayMenuActive;
314
+ this._layoutState.update((prev) => ({ ...prev, overlayMenuActive: newOverlayActive }));
315
+ if (newOverlayActive)
363
316
  this.overlayOpen.next();
364
- }
317
+ return;
365
318
  }
366
319
  if (this.isDesktop()) {
367
- this.layoutState.update((prev) => ({
320
+ this._layoutState.update((prev) => ({
368
321
  ...prev,
369
- staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive,
322
+ staticMenuDesktopInactive: !state.staticMenuDesktopInactive,
370
323
  }));
371
324
  }
372
325
  else {
373
- this.layoutState.update((prev) => ({
374
- ...prev,
375
- staticMenuMobileActive: !this.layoutState().staticMenuMobileActive,
376
- }));
377
- if (this.layoutState().staticMenuMobileActive) {
326
+ const newMobileActive = !state.staticMenuMobileActive;
327
+ this._layoutState.update((prev) => ({ ...prev, staticMenuMobileActive: newMobileActive }));
328
+ if (newMobileActive)
378
329
  this.overlayOpen.next();
379
- }
380
330
  }
381
331
  }
382
332
  isDesktop() {
@@ -385,65 +335,36 @@ class LayoutService {
385
335
  isMobile() {
386
336
  return !this.isDesktop();
387
337
  }
388
- onConfigUpdate() {
389
- this.configUpdate.next(this.layoutConfig());
390
- }
391
338
  onMenuStateChange(event) {
392
339
  this.menuSource.next(event);
393
340
  }
394
341
  reset() {
395
342
  this.resetSource.next(true);
396
343
  }
397
- // ==========================================================================
398
- // User Profile Methods
399
- // ==========================================================================
400
- /**
401
- * Set the current user profile for display in layout.
402
- * Called by auth integration to sync user data.
403
- */
344
+ // Config & state updates
345
+ updateLayoutConfig(config) {
346
+ this._layoutConfig.update((prev) => ({ ...prev, ...config }));
347
+ }
348
+ updateLayoutState(state) {
349
+ this._layoutState.update((prev) => ({ ...prev, ...state }));
350
+ }
351
+ // Profile setters
404
352
  setUserProfile(profile) {
405
- this.userProfile.set(profile);
353
+ this._userProfile.set(profile);
406
354
  }
407
- /**
408
- * Set the current company profile for display in layout.
409
- * Called by auth integration to sync company data.
410
- */
411
355
  setCompanyProfile(profile) {
412
- this.companyProfile.set(profile);
356
+ this._companyProfile.set(profile);
413
357
  }
414
- // ==========================================================================
415
- // Menu Methods
416
- // ==========================================================================
417
- /**
418
- * Set the raw menu items (unfiltered).
419
- * Menu will be automatically filtered based on permission checker.
420
- * Called by app initialization to set menu from IAM or other source.
421
- */
358
+ // Menu & apps
422
359
  setMenu(items) {
423
360
  this._rawMenu.set(items);
424
361
  }
425
- /**
426
- * Clear menu and permission/role checkers.
427
- * Called on logout.
428
- */
429
362
  clearMenu() {
430
363
  this._rawMenu.set([]);
431
364
  }
432
- // ==========================================================================
433
- // App Launcher Methods
434
- // ==========================================================================
435
- /**
436
- * Set launcher apps for display in header.
437
- * Apps will be automatically filtered based on user permissions.
438
- * If empty after filtering, the app launcher button is hidden.
439
- */
440
365
  setApps(apps) {
441
366
  this._rawApps.set(apps);
442
367
  }
443
- /**
444
- * Clear launcher apps.
445
- * Called on logout.
446
- */
447
368
  clearApps() {
448
369
  this._rawApps.set([]);
449
370
  }
@@ -452,10 +373,32 @@ class LayoutService {
452
373
  }
453
374
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutService, decorators: [{
454
375
  type: Injectable,
376
+ args: [{ providedIn: 'root' }]
377
+ }], ctorParameters: () => [] });
378
+
379
+ class AppFooter {
380
+ layoutService = inject(LayoutService);
381
+ // Footer shows product branding (appName), not user's company
382
+ appName = this.layoutService.appName;
383
+ authorName = this.layoutService.authorName;
384
+ authorUrl = this.layoutService.authorUrl;
385
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
386
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppFooter, isStandalone: true, selector: "app-footer", ngImport: i0, template: `<div class="layout-footer">
387
+ {{ appName }} by
388
+ <a [href]="authorUrl" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName }}</a>
389
+ </div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
390
+ }
391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFooter, decorators: [{
392
+ type: Component,
455
393
  args: [{
456
- providedIn: 'root',
394
+ selector: 'app-footer',
395
+ changeDetection: ChangeDetectionStrategy.OnPush,
396
+ template: `<div class="layout-footer">
397
+ {{ appName }} by
398
+ <a [href]="authorUrl" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName }}</a>
399
+ </div>`,
457
400
  }]
458
- }], ctorParameters: () => [] });
401
+ }] });
459
402
 
460
403
  const presets = {
461
404
  Aura,
@@ -464,19 +407,19 @@ const presets = {
464
407
  };
465
408
  class AppConfigurator {
466
409
  router = inject(Router);
467
- primeng = inject(PrimeNG);
468
410
  layoutService = inject(LayoutService);
469
- platformService = inject(PlatformService);
470
411
  presets = Object.keys(presets);
471
412
  showMenuModeButton = signal(!this.router.url.includes('auth'), ...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : []));
472
413
  menuModeOptions = [
473
414
  { label: 'Static', value: 'static' },
474
415
  { label: 'Overlay', value: 'overlay' },
475
416
  ];
476
- ngOnInit() {
477
- if (!this.platformService.isServer) {
478
- this.onPresetChange(this.layoutService.layoutConfig().preset);
479
- }
417
+ constructor() {
418
+ // Use afterNextRender for browser-only initialization (replaces isServer check)
419
+ afterNextRender(() => {
420
+ const preset = this.layoutService.layoutConfig().preset ?? 'Aura';
421
+ this.onPresetChange(preset);
422
+ });
480
423
  }
481
424
  surfaces = [
482
425
  {
@@ -782,16 +725,10 @@ class AppConfigurator {
782
725
  }
783
726
  updateColors(event, type, color) {
784
727
  if (type === 'primary') {
785
- this.layoutService.layoutConfig.update((state) => ({
786
- ...state,
787
- primary: color.name,
788
- }));
728
+ this.layoutService.updateLayoutConfig({ primary: color.name });
789
729
  }
790
730
  else if (type === 'surface') {
791
- this.layoutService.layoutConfig.update((state) => ({
792
- ...state,
793
- surface: color.name,
794
- }));
731
+ this.layoutService.updateLayoutConfig({ surface: color.name });
795
732
  }
796
733
  this.applyTheme(type, color);
797
734
  event.stopPropagation();
@@ -805,12 +742,12 @@ class AppConfigurator {
805
742
  }
806
743
  }
807
744
  onPresetChange(event) {
808
- this.layoutService.layoutConfig.update((state) => ({
809
- ...state,
810
- preset: event,
811
- }));
745
+ this.layoutService.updateLayoutConfig({ preset: event });
812
746
  const preset = presets[event];
813
- const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette;
747
+ // Use selected surface or default (slate for light, zinc for dark)
748
+ const selectedSurface = this.selectedSurfaceColor();
749
+ const defaultSurface = this.layoutService.layoutConfig().darkTheme ? 'zinc' : 'slate';
750
+ const surfacePalette = this.surfaces.find((s) => s.name === (selectedSurface || defaultSurface))?.palette;
814
751
  $t()
815
752
  .preset(preset)
816
753
  .preset(this.getPresetExt())
@@ -818,13 +755,10 @@ class AppConfigurator {
818
755
  .use({ useDefaultOptions: true });
819
756
  }
820
757
  onMenuModeChange(event) {
821
- this.layoutService.layoutConfig.update((prev) => ({
822
- ...prev,
823
- menuMode: event,
824
- }));
758
+ this.layoutService.updateLayoutConfig({ menuMode: event });
825
759
  }
826
760
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
827
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppConfigurator, isStandalone: true, selector: "app-configurator", host: { styleAttribute: "background-color: var(--surface-overlay)", classAttribute: "hidden absolute top-[3.25rem] right-0 w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]" }, ngImport: i0, template: `
761
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppConfigurator, isStandalone: true, selector: "app-configurator", host: { styleAttribute: "background-color: var(--surface-overlay)", classAttribute: "hidden absolute top-[3.25rem] right-0 w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]" }, ngImport: i0, template: `
828
762
  <div class="flex flex-col gap-4">
829
763
  <div>
830
764
  <span class="text-sm text-muted-color font-semibold">Primary</span>
@@ -891,14 +825,14 @@ class AppConfigurator {
891
825
  </div>
892
826
  }
893
827
  </div>
894
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i3.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }] });
828
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i2.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
895
829
  }
896
830
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppConfigurator, decorators: [{
897
831
  type: Component,
898
832
  args: [{
899
833
  selector: 'app-configurator',
900
- standalone: true,
901
- imports: [CommonModule, FormsModule, SelectButtonModule],
834
+ changeDetection: ChangeDetectionStrategy.OnPush,
835
+ imports: [NgClass, FormsModule, SelectButtonModule],
902
836
  template: `
903
837
  <div class="flex flex-col gap-4">
904
838
  <div>
@@ -968,36 +902,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
968
902
  </div>
969
903
  `,
970
904
  host: {
971
- class: 'hidden absolute top-[3.25rem] right-0 w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]',
905
+ class: 'hidden absolute top-[3.25rem] right-0 w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]',
972
906
  style: 'background-color: var(--surface-overlay)',
973
907
  },
974
908
  }]
975
- }] });
909
+ }], ctorParameters: () => [] });
976
910
 
977
911
  class AppFloatingConfigurator {
978
- LayoutService = inject(LayoutService);
979
- isDarkTheme = computed(() => this.LayoutService.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
912
+ layoutService = inject(LayoutService);
913
+ isDarkTheme = computed(() => this.layoutService.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
980
914
  toggleDarkMode() {
981
- this.LayoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme }));
915
+ const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
916
+ this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
982
917
  }
983
918
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
984
919
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppFloatingConfigurator, isStandalone: true, selector: "app-floating-configurator", ngImport: i0, template: `
985
- <div class="fixed flex flex-col md:flex-row gap-4 top-8 right-0 md:right-8 z-99">
920
+ <div class="fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 right-2 md:right-8 z-50">
986
921
  <p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
987
922
  <div class="relative">
988
923
  <p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
989
924
  <app-configurator />
990
925
  </div>
991
926
  </div>
992
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }] });
927
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
993
928
  }
994
929
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
995
930
  type: Component,
996
931
  args: [{
997
932
  selector: 'app-floating-configurator',
933
+ changeDetection: ChangeDetectionStrategy.OnPush,
998
934
  imports: [ButtonModule, StyleClassModule, AppConfigurator],
999
935
  template: `
1000
- <div class="fixed flex flex-col md:flex-row gap-4 top-8 right-0 md:right-8 z-99">
936
+ <div class="fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 right-2 md:right-8 z-50">
1001
937
  <p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
1002
938
  <div class="relative">
1003
939
  <p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
@@ -1008,34 +944,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1008
944
  }]
1009
945
  }] });
1010
946
 
1011
- class AppFooter {
1012
- layoutService = inject(LayoutService);
1013
- // Footer shows product branding (appName), not user's company
1014
- appName = this.layoutService.appName;
1015
- authorName = this.layoutService.authorName;
1016
- authorUrl = this.layoutService.authorUrl;
1017
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
1018
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppFooter, isStandalone: true, selector: "app-footer", ngImport: i0, template: `<div class="layout-footer">
1019
- {{ appName() }} by
1020
- <a [href]="authorUrl()" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName() }}</a>
1021
- </div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1022
- }
1023
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFooter, decorators: [{
1024
- type: Component,
1025
- args: [{
1026
- standalone: true,
1027
- selector: 'app-footer',
1028
- changeDetection: ChangeDetectionStrategy.OnPush,
1029
- template: `<div class="layout-footer">
1030
- {{ appName() }} by
1031
- <a [href]="authorUrl()" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName() }}</a>
1032
- </div>`,
1033
- }]
1034
- }] });
1035
-
1036
947
  const LAYOUT_AUTH_STATE = new InjectionToken('LAYOUT_AUTH_STATE');
1037
948
  const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
1038
949
 
950
+ /**
951
+ * View Transitions API type definitions
952
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
953
+ */
954
+
1039
955
  /** Company/branch switcher displayed in top bar */
1040
956
  class AppCompanyBranchSelector {
1041
957
  destroyRef = inject(DestroyRef);
@@ -1044,26 +960,34 @@ class AppCompanyBranchSelector {
1044
960
  messageService = inject(MessageService);
1045
961
  document = inject(DOCUMENT$1);
1046
962
  elementRef = inject(ElementRef);
1047
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
963
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
964
+ isActive = this._isActive.asReadonly();
1048
965
  constructor() {
1049
966
  fromEvent(this.document, 'click')
1050
967
  .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1051
968
  .subscribe((event) => {
1052
969
  const target = event.target;
1053
970
  if (!this.elementRef.nativeElement.contains(target)) {
1054
- this.isActive.set(false);
971
+ this._isActive.set(false);
1055
972
  }
1056
973
  });
1057
974
  }
1058
975
  currentCompanyName = computed(() => this.authState?.currentCompanyInfo()?.name ?? 'No Company', ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
1059
976
  currentBranchName = computed(() => this.authState?.currentBranchInfo()?.name ?? null, ...(ngDevMode ? [{ debugName: "currentBranchName" }] : []));
1060
- companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
1061
- branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : []));
1062
- selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
1063
- selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
1064
- isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingCompanies" }] : []));
1065
- isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingBranches" }] : []));
1066
- isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "isSwitching" }] : []));
977
+ _companies = signal([], ...(ngDevMode ? [{ debugName: "_companies" }] : []));
978
+ companies = this._companies.asReadonly();
979
+ _branches = signal([], ...(ngDevMode ? [{ debugName: "_branches" }] : []));
980
+ branches = this._branches.asReadonly();
981
+ _selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : []));
982
+ selectedCompanyId = this._selectedCompanyId.asReadonly();
983
+ _selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : []));
984
+ selectedBranchId = this._selectedBranchId.asReadonly();
985
+ _isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : []));
986
+ isLoadingCompanies = this._isLoadingCompanies.asReadonly();
987
+ _isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : []));
988
+ isLoadingBranches = this._isLoadingBranches.asReadonly();
989
+ _isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "_isSwitching" }] : []));
990
+ isSwitching = this._isSwitching.asReadonly();
1067
991
  canSwitch = computed(() => {
1068
992
  const selectedCompany = this.selectedCompanyId();
1069
993
  if (!selectedCompany)
@@ -1074,7 +998,7 @@ class AppCompanyBranchSelector {
1074
998
  return selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId;
1075
999
  }, ...(ngDevMode ? [{ debugName: "canSwitch" }] : []));
1076
1000
  onPanelToggle() {
1077
- this.isActive.update((v) => !v);
1001
+ this._isActive.update((v) => !v);
1078
1002
  if (this.isActive() && this.companies().length === 0) {
1079
1003
  this.loadCompanies();
1080
1004
  }
@@ -1082,61 +1006,53 @@ class AppCompanyBranchSelector {
1082
1006
  loadCompanies() {
1083
1007
  if (!this.authApi)
1084
1008
  return;
1085
- this.isLoadingCompanies.set(true);
1009
+ this._isLoadingCompanies.set(true);
1086
1010
  this.authApi
1087
1011
  .getUserCompanies()
1088
1012
  .pipe(takeUntilDestroyed(this.destroyRef))
1089
1013
  .subscribe({
1090
1014
  next: (companies) => {
1091
- this.companies.set(companies);
1092
- this.isLoadingCompanies.set(false);
1015
+ this._companies.set(companies);
1016
+ this._isLoadingCompanies.set(false);
1093
1017
  },
1094
- error: (err) => {
1095
- this.isLoadingCompanies.set(false);
1096
- this.messageService.add({
1097
- severity: 'error',
1098
- summary: 'Error',
1099
- detail: err?.message || 'Failed to load companies',
1100
- });
1018
+ error: () => {
1019
+ // Error toast handled by global interceptor
1020
+ this._isLoadingCompanies.set(false);
1101
1021
  },
1102
1022
  });
1103
1023
  }
1104
1024
  onCompanyChange(companyId) {
1105
- this.selectedCompanyId.set(companyId);
1025
+ this._selectedCompanyId.set(companyId);
1106
1026
  if (!companyId) {
1107
- this.branches.set([]);
1108
- this.selectedBranchId.set(null);
1027
+ this._branches.set([]);
1028
+ this._selectedBranchId.set(null);
1109
1029
  return;
1110
1030
  }
1111
1031
  this.loadBranches(companyId);
1112
1032
  }
1113
1033
  onBranchChange(branchId) {
1114
- this.selectedBranchId.set(branchId);
1034
+ this._selectedBranchId.set(branchId);
1115
1035
  }
1116
1036
  loadBranches(companyId) {
1117
1037
  if (!this.authApi)
1118
1038
  return;
1119
- this.isLoadingBranches.set(true);
1120
- this.selectedBranchId.set(null);
1039
+ this._isLoadingBranches.set(true);
1040
+ this._selectedBranchId.set(null);
1121
1041
  this.authApi
1122
1042
  .getCompanyBranches(companyId)
1123
1043
  .pipe(takeUntilDestroyed(this.destroyRef))
1124
1044
  .subscribe({
1125
1045
  next: (branches) => {
1126
- this.branches.set(branches);
1127
- this.isLoadingBranches.set(false);
1046
+ this._branches.set(branches);
1047
+ this._isLoadingBranches.set(false);
1128
1048
  // Auto-select if only one branch available
1129
1049
  if (branches.length === 1) {
1130
- this.selectedBranchId.set(branches[0].id);
1050
+ this._selectedBranchId.set(branches[0].id);
1131
1051
  }
1132
1052
  },
1133
- error: (err) => {
1134
- this.isLoadingBranches.set(false);
1135
- this.messageService.add({
1136
- severity: 'error',
1137
- summary: 'Error',
1138
- detail: err?.message || 'Failed to load branches',
1139
- });
1053
+ error: () => {
1054
+ // Error toast handled by global interceptor
1055
+ this._isLoadingBranches.set(false);
1140
1056
  },
1141
1057
  });
1142
1058
  }
@@ -1153,19 +1069,15 @@ class AppCompanyBranchSelector {
1153
1069
  });
1154
1070
  return;
1155
1071
  }
1156
- this.isSwitching.set(true);
1072
+ this._isSwitching.set(true);
1157
1073
  this.authApi
1158
1074
  .switchCompany(selectedCompany, selectedBranch || '')
1159
1075
  .pipe(takeUntilDestroyed(this.destroyRef))
1160
1076
  .subscribe({
1161
1077
  next: () => { },
1162
- error: (err) => {
1163
- this.isSwitching.set(false);
1164
- this.messageService.add({
1165
- severity: 'error',
1166
- summary: 'Error',
1167
- detail: err?.message || 'Failed to switch company',
1168
- });
1078
+ error: () => {
1079
+ // Error toast handled by global interceptor
1080
+ this._isSwitching.set(false);
1169
1081
  },
1170
1082
  });
1171
1083
  }
@@ -1190,10 +1102,10 @@ class AppCompanyBranchSelector {
1190
1102
  }
1191
1103
  </button>
1192
1104
  <div
1193
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1105
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]"
1194
1106
  style="background-color: var(--surface-overlay)"
1195
1107
  >
1196
- <div class="flex flex-col gap-4 min-w-[280px]">
1108
+ <div class="flex flex-col gap-4">
1197
1109
  <span class="text-sm text-muted-color font-semibold"
1198
1110
  >Switch Company & Branch</span
1199
1111
  >
@@ -1243,13 +1155,13 @@ class AppCompanyBranchSelector {
1243
1155
  </div>
1244
1156
  </div>
1245
1157
  </div>
1246
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] });
1158
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1247
1159
  }
1248
1160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
1249
1161
  type: Component,
1250
1162
  args: [{
1251
1163
  selector: 'app-company-branch-selector',
1252
- standalone: true,
1164
+ changeDetection: ChangeDetectionStrategy.OnPush,
1253
1165
  imports: [AngularModule, StyleClassModule, ButtonModule, SelectModule],
1254
1166
  host: { class: 'relative' },
1255
1167
  template: `
@@ -1272,10 +1184,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1272
1184
  }
1273
1185
  </button>
1274
1186
  <div
1275
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1187
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]"
1276
1188
  style="background-color: var(--surface-overlay)"
1277
1189
  >
1278
- <div class="flex flex-col gap-4 min-w-[280px]">
1190
+ <div class="flex flex-col gap-4">
1279
1191
  <span class="text-sm text-muted-color font-semibold"
1280
1192
  >Switch Company & Branch</span
1281
1193
  >
@@ -1334,19 +1246,20 @@ class AppLauncher {
1334
1246
  document = inject(DOCUMENT$1);
1335
1247
  elementRef = inject(ElementRef);
1336
1248
  layoutService = inject(LayoutService);
1337
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1249
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1250
+ isActive = this._isActive.asReadonly();
1338
1251
  constructor() {
1339
1252
  fromEvent(this.document, 'click')
1340
- .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1253
+ .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this._isActive()))
1341
1254
  .subscribe((event) => {
1342
1255
  const target = event.target;
1343
1256
  if (!this.elementRef.nativeElement.contains(target)) {
1344
- this.isActive.set(false);
1257
+ this._isActive.set(false);
1345
1258
  }
1346
1259
  });
1347
1260
  }
1348
1261
  togglePanel() {
1349
- this.isActive.update((v) => !v);
1262
+ this._isActive.update((v) => !v);
1350
1263
  }
1351
1264
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLauncher, deps: [], target: i0.ɵɵFactoryTarget.Component });
1352
1265
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppLauncher, isStandalone: true, selector: "app-launcher", host: { classAttribute: "relative" }, ngImport: i0, template: `
@@ -1366,15 +1279,15 @@ class AppLauncher {
1366
1279
  <span>Apps</span>
1367
1280
  </button>
1368
1281
  <div
1369
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1282
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1370
1283
  style="background-color: var(--surface-overlay)"
1371
1284
  >
1372
- <div class="flex flex-col gap-2 min-w-[240px]">
1285
+ <div class="flex flex-col gap-2">
1373
1286
  <span class="text-sm text-muted-color font-semibold">Applications</span>
1374
- <div class="grid grid-cols-3 gap-2">
1287
+ <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
1375
1288
  @for (app of layoutService.apps(); track app.id) {
1376
1289
  <a
1377
- class="group flex flex-col items-center w-20 p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1290
+ class="group flex flex-col items-center p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1378
1291
  [href]="app.url"
1379
1292
  target="_blank"
1380
1293
  rel="noopener noreferrer"
@@ -1389,7 +1302,7 @@ class AppLauncher {
1389
1302
  />
1390
1303
  </div>
1391
1304
  <span
1392
- class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200"
1305
+ class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200 truncate max-w-full"
1393
1306
  >{{ app.name }}</span
1394
1307
  >
1395
1308
  </a>
@@ -1397,13 +1310,13 @@ class AppLauncher {
1397
1310
  </div>
1398
1311
  </div>
1399
1312
  </div>
1400
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }] });
1313
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1401
1314
  }
1402
1315
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLauncher, decorators: [{
1403
1316
  type: Component,
1404
1317
  args: [{
1405
1318
  selector: 'app-launcher',
1406
- standalone: true,
1319
+ changeDetection: ChangeDetectionStrategy.OnPush,
1407
1320
  imports: [StyleClassModule, IconComponent],
1408
1321
  host: { class: 'relative' },
1409
1322
  template: `
@@ -1423,15 +1336,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1423
1336
  <span>Apps</span>
1424
1337
  </button>
1425
1338
  <div
1426
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1339
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1427
1340
  style="background-color: var(--surface-overlay)"
1428
1341
  >
1429
- <div class="flex flex-col gap-2 min-w-[240px]">
1342
+ <div class="flex flex-col gap-2">
1430
1343
  <span class="text-sm text-muted-color font-semibold">Applications</span>
1431
- <div class="grid grid-cols-3 gap-2">
1344
+ <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
1432
1345
  @for (app of layoutService.apps(); track app.id) {
1433
1346
  <a
1434
- class="group flex flex-col items-center w-20 p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1347
+ class="group flex flex-col items-center p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1435
1348
  [href]="app.url"
1436
1349
  target="_blank"
1437
1350
  rel="noopener noreferrer"
@@ -1446,7 +1359,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1446
1359
  />
1447
1360
  </div>
1448
1361
  <span
1449
- class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200"
1362
+ class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200 truncate max-w-full"
1450
1363
  >{{ app.name }}</span
1451
1364
  >
1452
1365
  </a>
@@ -1462,26 +1375,28 @@ class AppProfile {
1462
1375
  destroyRef = inject(DestroyRef);
1463
1376
  authState = inject(LAYOUT_AUTH_STATE, { optional: true });
1464
1377
  authApi = inject(LAYOUT_AUTH_API, { optional: true });
1378
+ layoutService = inject(LayoutService);
1465
1379
  messageService = inject(MessageService);
1466
1380
  document = inject(DOCUMENT$1);
1467
1381
  elementRef = inject(ElementRef);
1468
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1382
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1383
+ isActive = this._isActive.asReadonly();
1469
1384
  constructor() {
1470
1385
  fromEvent(this.document, 'click')
1471
1386
  .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1472
1387
  .subscribe((event) => {
1473
1388
  const target = event.target;
1474
1389
  if (!this.elementRef.nativeElement.contains(target)) {
1475
- this.isActive.set(false);
1390
+ this._isActive.set(false);
1476
1391
  }
1477
1392
  });
1478
1393
  }
1479
1394
  togglePanel() {
1480
- this.isActive.update((v) => !v);
1395
+ this._isActive.update((v) => !v);
1481
1396
  }
1482
1397
  userName = computed(() => this.authState?.loginUserData()?.name ?? 'Guest', ...(ngDevMode ? [{ debugName: "userName" }] : []));
1483
1398
  userEmail = computed(() => this.authState?.loginUserData()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
1484
- profilePicture = computed(() => this.authState?.loginUserData()?.profilePicture?.url ?? '', ...(ngDevMode ? [{ debugName: "profilePicture" }] : []));
1399
+ profilePicture = computed(() => this.layoutService.userProfilePictureUrl() ?? '', ...(ngDevMode ? [{ debugName: "profilePicture" }] : []));
1485
1400
  logout() {
1486
1401
  if (!this.authApi)
1487
1402
  return;
@@ -1497,12 +1412,8 @@ class AppProfile {
1497
1412
  });
1498
1413
  this.authApi?.navigateLogin(false);
1499
1414
  },
1500
- error: (err) => {
1501
- this.messageService.add({
1502
- severity: 'error',
1503
- summary: 'Error',
1504
- detail: err?.message || 'Failed to logout',
1505
- });
1415
+ error: () => {
1416
+ // Error toast handled by global interceptor
1506
1417
  },
1507
1418
  });
1508
1419
  }
@@ -1544,10 +1455,10 @@ class AppProfile {
1544
1455
  <span>Profile</span>
1545
1456
  </button>
1546
1457
  <div
1547
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1458
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1548
1459
  style="background-color: var(--surface-overlay)"
1549
1460
  >
1550
- <div class="flex flex-col gap-3 min-w-[240px]">
1461
+ <div class="flex flex-col gap-3">
1551
1462
  <!-- User Info -->
1552
1463
  <a
1553
1464
  class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline hover:bg-emphasis transition-all duration-200"
@@ -1566,9 +1477,9 @@ class AppProfile {
1566
1477
  <i class="pi pi-user text-xl text-primary-contrast"></i>
1567
1478
  </div>
1568
1479
  }
1569
- <div class="flex flex-col">
1570
- <span class="font-semibold text-color">{{ userName() }}</span>
1571
- <span class="text-sm text-muted-color">{{ userEmail() }}</span>
1480
+ <div class="flex flex-col min-w-0 flex-1">
1481
+ <span class="font-semibold text-color truncate">{{ userName() }}</span>
1482
+ <span class="text-sm text-muted-color truncate">{{ userEmail() }}</span>
1572
1483
  </div>
1573
1484
  </a>
1574
1485
 
@@ -1594,13 +1505,13 @@ class AppProfile {
1594
1505
  </div>
1595
1506
  </div>
1596
1507
  </div>
1597
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$2.IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }] });
1508
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$2.IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1598
1509
  }
1599
1510
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppProfile, decorators: [{
1600
1511
  type: Component,
1601
1512
  args: [{
1602
1513
  selector: 'app-profile',
1603
- standalone: true,
1514
+ changeDetection: ChangeDetectionStrategy.OnPush,
1604
1515
  imports: [AngularModule, StyleClassModule],
1605
1516
  host: { class: 'relative' },
1606
1517
  template: `
@@ -1620,10 +1531,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1620
1531
  <span>Profile</span>
1621
1532
  </button>
1622
1533
  <div
1623
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1534
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1624
1535
  style="background-color: var(--surface-overlay)"
1625
1536
  >
1626
- <div class="flex flex-col gap-3 min-w-[240px]">
1537
+ <div class="flex flex-col gap-3">
1627
1538
  <!-- User Info -->
1628
1539
  <a
1629
1540
  class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline hover:bg-emphasis transition-all duration-200"
@@ -1642,9 +1553,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1642
1553
  <i class="pi pi-user text-xl text-primary-contrast"></i>
1643
1554
  </div>
1644
1555
  }
1645
- <div class="flex flex-col">
1646
- <span class="font-semibold text-color">{{ userName() }}</span>
1647
- <span class="text-sm text-muted-color">{{ userEmail() }}</span>
1556
+ <div class="flex flex-col min-w-0 flex-1">
1557
+ <span class="font-semibold text-color truncate">{{ userName() }}</span>
1558
+ <span class="text-sm text-muted-color truncate">{{ userEmail() }}</span>
1648
1559
  </div>
1649
1560
  </a>
1650
1561
 
@@ -1688,16 +1599,13 @@ class AppTopbar {
1688
1599
  this.handleOutsideClick(event);
1689
1600
  });
1690
1601
  }
1691
- // Use layoutService.companyName which falls back to appName
1692
1602
  companyName = this.layoutService.companyName;
1693
1603
  enableCompanyFeature = computed(() => {
1694
1604
  return isCompanyFeatureEnabled(this.appConfig);
1695
1605
  }, ...(ngDevMode ? [{ debugName: "enableCompanyFeature" }] : []));
1696
1606
  toggleDarkMode() {
1697
- this.layoutService.layoutConfig.update((state) => ({
1698
- ...state,
1699
- darkTheme: !state.darkTheme,
1700
- }));
1607
+ const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
1608
+ this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
1701
1609
  }
1702
1610
  togglePanel(panel) {
1703
1611
  this.activePanel.update((current) => (current === panel ? null : panel));
@@ -1731,11 +1639,9 @@ class AppTopbar {
1731
1639
  (click)="toggleDarkMode()"
1732
1640
  >
1733
1641
  <i
1734
- [ngClass]="{
1735
- 'pi ': true,
1736
- 'pi-moon': layoutService.isDarkTheme(),
1737
- 'pi-sun': !layoutService.isDarkTheme(),
1738
- }"
1642
+ class="pi"
1643
+ [class.pi-moon]="layoutService.isDarkTheme()"
1644
+ [class.pi-sun]="!layoutService.isDarkTheme()"
1739
1645
  ></i>
1740
1646
  </button>
1741
1647
  <div class="relative" #configContainer>
@@ -1780,16 +1686,15 @@ class AppTopbar {
1780
1686
  </div>
1781
1687
  </div>
1782
1688
  </div>
1783
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }, { kind: "component", type: AppProfile, selector: "app-profile" }, { kind: "component", type: AppCompanyBranchSelector, selector: "app-company-branch-selector" }, { kind: "component", type: AppLauncher, selector: "app-launcher" }] });
1689
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }, { kind: "component", type: AppProfile, selector: "app-profile" }, { kind: "component", type: AppCompanyBranchSelector, selector: "app-company-branch-selector" }, { kind: "component", type: AppLauncher, selector: "app-launcher" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1784
1690
  }
1785
1691
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppTopbar, decorators: [{
1786
1692
  type: Component,
1787
1693
  args: [{
1788
1694
  selector: 'app-topbar',
1789
- standalone: true,
1695
+ changeDetection: ChangeDetectionStrategy.OnPush,
1790
1696
  imports: [
1791
1697
  RouterModule,
1792
- CommonModule,
1793
1698
  StyleClassModule,
1794
1699
  AppConfigurator,
1795
1700
  AppProfile,
@@ -1817,11 +1722,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1817
1722
  (click)="toggleDarkMode()"
1818
1723
  >
1819
1724
  <i
1820
- [ngClass]="{
1821
- 'pi ': true,
1822
- 'pi-moon': layoutService.isDarkTheme(),
1823
- 'pi-sun': !layoutService.isDarkTheme(),
1824
- }"
1725
+ class="pi"
1726
+ [class.pi-moon]="layoutService.isDarkTheme()"
1727
+ [class.pi-sun]="!layoutService.isDarkTheme()"
1825
1728
  ></i>
1826
1729
  </button>
1827
1730
  <div class="relative" #configContainer>
@@ -1878,10 +1781,10 @@ class AppMenuitem {
1878
1781
  // Injected services
1879
1782
  router = inject(Router);
1880
1783
  layoutService = inject(LayoutService);
1881
- authState = inject(LAYOUT_AUTH_STATE, { optional: true });
1882
1784
  destroyRef = inject(DestroyRef);
1883
- // State signals
1884
- active = signal(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
1785
+ // State signals - private writable + public readonly pattern
1786
+ _active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
1787
+ active = this._active.asReadonly();
1885
1788
  // Computed signals
1886
1789
  key = computed(() => {
1887
1790
  const parent = this.parentKey();
@@ -1910,19 +1813,19 @@ class AppMenuitem {
1910
1813
  Promise.resolve(null).then(() => {
1911
1814
  const currentKey = this.key();
1912
1815
  if (value.routeEvent) {
1913
- this.active.set(value.key === currentKey ||
1816
+ this._active.set(value.key === currentKey ||
1914
1817
  value.key?.startsWith(currentKey + '-'));
1915
1818
  }
1916
1819
  else if (value.key !== currentKey &&
1917
1820
  !value.key?.startsWith(currentKey + '-')) {
1918
- this.active.set(false);
1821
+ this._active.set(false);
1919
1822
  }
1920
1823
  });
1921
1824
  });
1922
1825
  // Subscribe to menu reset
1923
1826
  this.layoutService.resetSource$
1924
1827
  .pipe(takeUntilDestroyed(this.destroyRef))
1925
- .subscribe(() => this.active.set(false));
1828
+ .subscribe(() => this._active.set(false));
1926
1829
  // Subscribe to navigation events
1927
1830
  this.router.events
1928
1831
  .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
@@ -1961,7 +1864,7 @@ class AppMenuitem {
1961
1864
  }
1962
1865
  itemClick() {
1963
1866
  if (this.item().children) {
1964
- this.active.update((prev) => !prev);
1867
+ this._active.update((prev) => !prev);
1965
1868
  }
1966
1869
  this.layoutService.onMenuStateChange({ key: this.key() });
1967
1870
  }
@@ -2022,11 +1925,11 @@ class AppMenuitem {
2022
1925
  </ul>
2023
1926
  }
2024
1927
  </ng-container>
2025
- `, isInline: true, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i2$3.Ripple, selector: "[pRipple]" }] });
1928
+ `, isInline: true, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i2$3.Ripple, selector: "[pRipple]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2026
1929
  }
2027
1930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenuitem, decorators: [{
2028
1931
  type: Component,
2029
- args: [{ selector: '[app-menuitem]', standalone: true, imports: [IconComponent, RouterModule, RippleModule], template: `
1932
+ args: [{ selector: '[app-menuitem]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent, RouterModule, RippleModule], template: `
2030
1933
  <ng-container>
2031
1934
  @if (item().children?.length) {
2032
1935
  <a (click)="itemClick()" tabindex="0" pRipple>
@@ -2112,13 +2015,13 @@ class AppMenu {
2112
2015
  <li app-menuitem [item]="item" [index]="i"></li>
2113
2016
  }
2114
2017
  </ul>
2115
- </div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
2018
+ </div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2116
2019
  }
2117
2020
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenu, decorators: [{
2118
2021
  type: Component,
2119
2022
  args: [{
2120
2023
  selector: 'app-menu',
2121
- standalone: true,
2024
+ changeDetection: ChangeDetectionStrategy.OnPush,
2122
2025
  imports: [AppMenuitem, RouterModule],
2123
2026
  template: `<div class="layout-menu">
2124
2027
  <ul>
@@ -2140,13 +2043,13 @@ class AppSidebar {
2140
2043
  <div class="layout-sidebar">
2141
2044
  <app-menu />
2142
2045
  </div>
2143
- `, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
2046
+ `, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2144
2047
  }
2145
2048
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppSidebar, decorators: [{
2146
2049
  type: Component,
2147
2050
  args: [{
2148
2051
  selector: 'app-sidebar',
2149
- standalone: true,
2052
+ changeDetection: ChangeDetectionStrategy.OnPush,
2150
2053
  imports: [AppMenu],
2151
2054
  template: `
2152
2055
  <div class="layout-sidebar">
@@ -2163,8 +2066,6 @@ class AppLayout {
2163
2066
  renderer = inject(Renderer2);
2164
2067
  router = inject(Router);
2165
2068
  menuOutsideClickListener = null;
2166
- appSidebar;
2167
- appTopBar;
2168
2069
  constructor() {
2169
2070
  this.layoutService.overlayOpen$
2170
2071
  .pipe(takeUntilDestroyed(this.destroyRef))
@@ -2194,12 +2095,11 @@ class AppLayout {
2194
2095
  topbarEl?.contains(eventTarget));
2195
2096
  }
2196
2097
  hideMenu() {
2197
- this.layoutService.layoutState.update((prev) => ({
2198
- ...prev,
2098
+ this.layoutService.updateLayoutState({
2199
2099
  overlayMenuActive: false,
2200
2100
  staticMenuMobileActive: false,
2201
2101
  menuHoverActive: false,
2202
- }));
2102
+ });
2203
2103
  if (this.menuOutsideClickListener) {
2204
2104
  this.menuOutsideClickListener();
2205
2105
  this.menuOutsideClickListener = null;
@@ -2212,7 +2112,7 @@ class AppLayout {
2212
2112
  unblockBodyScroll() {
2213
2113
  this.document.body.classList.remove('blocked-scroll');
2214
2114
  }
2215
- get containerClass() {
2115
+ containerClass = computed(() => {
2216
2116
  const config = this.layoutService.layoutConfig();
2217
2117
  const state = this.layoutService.layoutState();
2218
2118
  return {
@@ -2222,10 +2122,10 @@ class AppLayout {
2222
2122
  'layout-overlay-active': state.overlayMenuActive,
2223
2123
  'layout-mobile-active': state.staticMenuMobileActive,
2224
2124
  };
2225
- }
2125
+ }, ...(ngDevMode ? [{ debugName: "containerClass" }] : []));
2226
2126
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
2227
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppLayout, isStandalone: true, selector: "app-layout", viewQueries: [{ propertyName: "appSidebar", first: true, predicate: AppSidebar, descendants: true }, { propertyName: "appTopBar", first: true, predicate: AppTopbar, descendants: true }], ngImport: i0, template: `
2228
- <div class="layout-wrapper" [ngClass]="containerClass">
2127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
2128
+ <div class="layout-wrapper" [ngClass]="containerClass()">
2229
2129
  <app-topbar></app-topbar>
2230
2130
  <app-sidebar></app-sidebar>
2231
2131
  <div class="layout-main-container">
@@ -2236,16 +2136,16 @@ class AppLayout {
2236
2136
  </div>
2237
2137
  <div class="layout-mask animate-fadein"></div>
2238
2138
  </div>
2239
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppFooter, selector: "app-footer" }] });
2139
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppFooter, selector: "app-footer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2240
2140
  }
2241
2141
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLayout, decorators: [{
2242
2142
  type: Component,
2243
2143
  args: [{
2244
2144
  selector: 'app-layout',
2245
- standalone: true,
2246
- imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter],
2145
+ changeDetection: ChangeDetectionStrategy.OnPush,
2146
+ imports: [NgClass, AppTopbar, AppSidebar, RouterModule, AppFooter],
2247
2147
  template: `
2248
- <div class="layout-wrapper" [ngClass]="containerClass">
2148
+ <div class="layout-wrapper" [ngClass]="containerClass()">
2249
2149
  <app-topbar></app-topbar>
2250
2150
  <app-sidebar></app-sidebar>
2251
2151
  <div class="layout-main-container">
@@ -2258,50 +2158,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2258
2158
  </div>
2259
2159
  `,
2260
2160
  }]
2261
- }], ctorParameters: () => [], propDecorators: { appSidebar: [{
2262
- type: ViewChild,
2263
- args: [AppSidebar]
2264
- }], appTopBar: [{
2265
- type: ViewChild,
2266
- args: [AppTopbar]
2267
- }] } });
2161
+ }], ctorParameters: () => [] });
2268
2162
 
2269
- const GreenTheme = definePreset(Material, {
2163
+ const NavyBlueTheme = definePreset(Material, {
2270
2164
  semantic: {
2271
2165
  colorScheme: {
2272
2166
  light: {
2273
2167
  primary: {
2274
- color: '#01712c',
2275
- inverseColor: '#119744',
2276
- hoverColor: '#119744',
2277
- activeColor: '#119744',
2168
+ color: '#3535cd',
2169
+ inverseColor: '#0707a9',
2170
+ hoverColor: '#0707a9',
2171
+ activeColor: '#0707a9',
2278
2172
  },
2279
2173
  highlight: {
2280
2174
  background: '#e2e8f0',
2281
2175
  focusBackground: '#e2e8f0',
2282
- color: '#01712c',
2283
- focusColor: '#01712c',
2176
+ color: '#3535cd',
2177
+ focusColor: '#3535cd',
2284
2178
  },
2285
2179
  },
2286
2180
  },
2287
2181
  },
2288
2182
  });
2289
2183
 
2290
- const NavyBlueTheme = definePreset(Material, {
2184
+ const GreenTheme = definePreset(Material, {
2291
2185
  semantic: {
2292
2186
  colorScheme: {
2293
2187
  light: {
2294
2188
  primary: {
2295
- color: '#3535cd',
2296
- inverseColor: '#0707a9',
2297
- hoverColor: '#0707a9',
2298
- activeColor: '#0707a9',
2189
+ color: '#01712c',
2190
+ inverseColor: '#119744',
2191
+ hoverColor: '#119744',
2192
+ activeColor: '#119744',
2299
2193
  },
2300
2194
  highlight: {
2301
2195
  background: '#e2e8f0',
2302
2196
  focusBackground: '#e2e8f0',
2303
- color: '#3535cd',
2304
- focusColor: '#3535cd',
2197
+ color: '#01712c',
2198
+ focusColor: '#01712c',
2305
2199
  },
2306
2200
  },
2307
2201
  },