@flusys/ng-layout 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +377 -481
- package/assets/layout/_footer.scss +7 -0
- package/assets/layout/_responsive.scss +18 -0
- package/assets/layout/_topbar.scss +159 -128
- package/assets/layout/_topbar_nav.scss +350 -0
- package/assets/layout/layout.scss +2 -1
- package/fesm2022/flusys-ng-layout.mjs +1031 -468
- package/fesm2022/flusys-ng-layout.mjs.map +1 -1
- package/package.json +3 -3
- package/types/flusys-ng-layout.d.ts +63 -7
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, Component, afterNextRender, InjectionToken,
|
|
3
|
-
import * as i2$
|
|
4
|
-
import { evaluateLogicNode, PermissionValidatorService, TranslatePipe, AngularModule,
|
|
2
|
+
import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, Component, afterNextRender, InjectionToken, input, DestroyRef, viewChild, ElementRef, Renderer2 } from '@angular/core';
|
|
3
|
+
import * as i2$3 from '@flusys/ng-shared';
|
|
4
|
+
import { evaluateLogicNode, PermissionValidatorService, TranslatePipe, IconComponent, AngularModule, PrimeModule } from '@flusys/ng-shared';
|
|
5
5
|
import { isPlatformBrowser, NgComponentOutlet, DOCUMENT as DOCUMENT$1, NgClass } from '@angular/common';
|
|
6
|
-
import { APP_CONFIG, TRANSLATE_ADAPTER, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled } from '@flusys/ng-core';
|
|
6
|
+
import { APP_CONFIG, TRANSLATE_ADAPTER, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled, isSignUpEnabled } from '@flusys/ng-core';
|
|
7
7
|
import { Subject, fromEvent, filter as filter$1 } from 'rxjs';
|
|
8
8
|
import * as i1 from '@angular/forms';
|
|
9
9
|
import { FormsModule } from '@angular/forms';
|
|
10
10
|
import * as i1$2 from '@angular/router';
|
|
11
|
-
import { Router,
|
|
11
|
+
import { Router, NavigationEnd, RouterModule } from '@angular/router';
|
|
12
12
|
import { updatePreset, updateSurfacePalette, $t, definePreset } from '@primeuix/themes';
|
|
13
13
|
import Aura from '@primeuix/themes/aura';
|
|
14
14
|
import Lara from '@primeuix/themes/lara';
|
|
@@ -21,11 +21,14 @@ import * as i2$1 from 'primeng/styleclass';
|
|
|
21
21
|
import { StyleClassModule } from 'primeng/styleclass';
|
|
22
22
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
23
23
|
import { filter } from 'rxjs/operators';
|
|
24
|
+
import * as i2$2 from 'primeng/ripple';
|
|
25
|
+
import { RippleModule } from 'primeng/ripple';
|
|
24
26
|
import { MessageService } from 'primeng/api';
|
|
25
27
|
import * as i4 from 'primeng/select';
|
|
26
28
|
import { SelectModule } from 'primeng/select';
|
|
27
|
-
import * as
|
|
28
|
-
import
|
|
29
|
+
import * as i3 from 'primeng/iconfield';
|
|
30
|
+
import * as i4$1 from 'primeng/inputicon';
|
|
31
|
+
import * as i5 from 'primeng/inputtext';
|
|
29
32
|
import Material from '@primeuix/themes/material';
|
|
30
33
|
|
|
31
34
|
/** Filter launcher apps based on user permission codes */
|
|
@@ -111,7 +114,7 @@ class LayoutPersistenceService {
|
|
|
111
114
|
platformId = inject(PLATFORM_ID);
|
|
112
115
|
isBrowser = isPlatformBrowser(this.platformId);
|
|
113
116
|
validPresets = ['Aura', 'Lara', 'Nora'];
|
|
114
|
-
validMenuModes = ['static', 'overlay'];
|
|
117
|
+
validMenuModes = ['static', 'overlay', 'topbar'];
|
|
115
118
|
/**
|
|
116
119
|
* Load configuration from localStorage.
|
|
117
120
|
* Returns null if no saved config or invalid data.
|
|
@@ -223,6 +226,7 @@ class LayoutService {
|
|
|
223
226
|
configSidebarVisible: false,
|
|
224
227
|
staticMenuMobileActive: false,
|
|
225
228
|
menuHoverActive: false,
|
|
229
|
+
topbarMenuVisible: true,
|
|
226
230
|
};
|
|
227
231
|
_layoutConfig = signal({
|
|
228
232
|
...this.DEFAULT_CONFIG,
|
|
@@ -242,6 +246,7 @@ class LayoutService {
|
|
|
242
246
|
companyProfile = this._companyProfile.asReadonly();
|
|
243
247
|
// Static app info from config
|
|
244
248
|
appName = this.appConfig?.appName ?? DEFAULT_APP_NAME;
|
|
249
|
+
appLogo = this.appConfig?.appLogo ?? '';
|
|
245
250
|
authorName = this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name;
|
|
246
251
|
authorUrl = this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url;
|
|
247
252
|
// Permission-filtered menu and apps
|
|
@@ -255,6 +260,7 @@ class LayoutService {
|
|
|
255
260
|
getPrimary = computed(() => this._layoutConfig().primary, ...(ngDevMode ? [{ debugName: "getPrimary" }] : []));
|
|
256
261
|
getSurface = computed(() => this._layoutConfig().surface, ...(ngDevMode ? [{ debugName: "getSurface" }] : []));
|
|
257
262
|
isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', ...(ngDevMode ? [{ debugName: "isOverlay" }] : []));
|
|
263
|
+
isTopbar = computed(() => this._layoutConfig().menuMode === 'topbar', ...(ngDevMode ? [{ debugName: "isTopbar" }] : []));
|
|
258
264
|
// User profile computed signals
|
|
259
265
|
userName = computed(() => this._userProfile()?.name ?? this.translateAdapter?.translate('layout.profile.guest') ?? 'Guest', ...(ngDevMode ? [{ debugName: "userName" }] : []));
|
|
260
266
|
userEmail = computed(() => this._userProfile()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
|
|
@@ -266,6 +272,7 @@ class LayoutService {
|
|
|
266
272
|
return this.appName;
|
|
267
273
|
return this._companyProfile()?.name ?? this.appName;
|
|
268
274
|
}, ...(ngDevMode ? [{ debugName: "companyName" }] : []));
|
|
275
|
+
displayLogo = computed(() => this.companyLogoUrl() ?? this.appLogo, ...(ngDevMode ? [{ debugName: "displayLogo" }] : []));
|
|
269
276
|
// RxJS Subjects for event communication
|
|
270
277
|
configUpdate = new Subject();
|
|
271
278
|
overlayOpen = new Subject();
|
|
@@ -310,6 +317,14 @@ class LayoutService {
|
|
|
310
317
|
}
|
|
311
318
|
onMenuToggle() {
|
|
312
319
|
const state = this._layoutState();
|
|
320
|
+
// Desktop topbar mode: toggle topbar nav visibility
|
|
321
|
+
if (this.isDesktop() && this.isTopbar()) {
|
|
322
|
+
this._layoutState.update((prev) => ({
|
|
323
|
+
...prev,
|
|
324
|
+
topbarMenuVisible: !prev.topbarMenuVisible,
|
|
325
|
+
}));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
313
328
|
// Mobile always uses staticMenuMobileActive regardless of menu mode
|
|
314
329
|
if (!this.isDesktop()) {
|
|
315
330
|
const newMobileActive = !state.staticMenuMobileActive;
|
|
@@ -382,13 +397,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
382
397
|
class AppFooter {
|
|
383
398
|
layoutService = inject(LayoutService);
|
|
384
399
|
appName = this.layoutService.appName;
|
|
400
|
+
displayLogo = this.layoutService.appLogo;
|
|
385
401
|
authorName = this.layoutService.authorName;
|
|
386
402
|
authorUrl = this.layoutService.authorUrl;
|
|
387
403
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
388
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
404
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppFooter, isStandalone: true, selector: "app-footer", ngImport: i0, template: `<div class="layout-footer">
|
|
405
|
+
@if (displayLogo) {
|
|
406
|
+
<img [src]="displayLogo" [alt]="appName" class="logo-image" />
|
|
407
|
+
}
|
|
408
|
+
{{ appName }} {{ 'layout.footer.by' | translate }}
|
|
409
|
+
<a
|
|
410
|
+
[href]="authorUrl"
|
|
411
|
+
target="_blank"
|
|
412
|
+
rel="noopener noreferrer"
|
|
413
|
+
class="text-primary font-bold hover:underline"
|
|
414
|
+
>{{ authorName }}</a
|
|
415
|
+
>
|
|
416
|
+
</div>`, isInline: true, dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
392
417
|
}
|
|
393
418
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppFooter, decorators: [{
|
|
394
419
|
type: Component,
|
|
@@ -396,9 +421,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
396
421
|
selector: 'app-footer',
|
|
397
422
|
imports: [TranslatePipe],
|
|
398
423
|
template: `<div class="layout-footer">
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
424
|
+
@if (displayLogo) {
|
|
425
|
+
<img [src]="displayLogo" [alt]="appName" class="logo-image" />
|
|
426
|
+
}
|
|
427
|
+
{{ appName }} {{ 'layout.footer.by' | translate }}
|
|
428
|
+
<a
|
|
429
|
+
[href]="authorUrl"
|
|
430
|
+
target="_blank"
|
|
431
|
+
rel="noopener noreferrer"
|
|
432
|
+
class="text-primary font-bold hover:underline"
|
|
433
|
+
>{{ authorName }}</a
|
|
434
|
+
>
|
|
435
|
+
</div>`,
|
|
402
436
|
}]
|
|
403
437
|
}] });
|
|
404
438
|
|
|
@@ -413,10 +447,17 @@ class AppConfigurator {
|
|
|
413
447
|
layoutService = inject(LayoutService);
|
|
414
448
|
presets = Object.keys(presets);
|
|
415
449
|
showMenuModeButton = signal(!this.router.url.includes('auth'), ...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : []));
|
|
416
|
-
menuModeOptions = computed(() =>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
450
|
+
menuModeOptions = computed(() => {
|
|
451
|
+
const options = [
|
|
452
|
+
{ label: this.translate('layout.configurator.menu.mode.static'), value: 'static' },
|
|
453
|
+
{ label: this.translate('layout.configurator.menu.mode.overlay'), value: 'overlay' },
|
|
454
|
+
];
|
|
455
|
+
// Only show topbar mode on desktop
|
|
456
|
+
if (this.layoutService.isDesktop()) {
|
|
457
|
+
options.push({ label: this.translate('layout.configurator.menu.mode.topbar'), value: 'topbar' });
|
|
458
|
+
}
|
|
459
|
+
return options;
|
|
460
|
+
}, ...(ngDevMode ? [{ debugName: "menuModeOptions" }] : []));
|
|
420
461
|
constructor() {
|
|
421
462
|
// Use afterNextRender for browser-only initialization (replaces isServer check)
|
|
422
463
|
afterNextRender(() => {
|
|
@@ -904,7 +945,9 @@ const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
|
|
|
904
945
|
const LAYOUT_NOTIFICATION_BELL = new InjectionToken('LAYOUT_NOTIFICATION_BELL');
|
|
905
946
|
const LAYOUT_LANGUAGE_SELECTOR = new InjectionToken('LAYOUT_LANGUAGE_SELECTOR');
|
|
906
947
|
const LAYOUT_LANGUAGE_SELECTOR_PANEL = new InjectionToken('LAYOUT_LANGUAGE_SELECTOR_PANEL');
|
|
948
|
+
const LAYOUT_SEARCHBAR = new InjectionToken('LAYOUT_SEARCHBAR');
|
|
907
949
|
const LAYOUT_IS_RTL = new InjectionToken('LAYOUT_IS_RTL');
|
|
950
|
+
const LAYOUT_SEARCH_ADAPTER = new InjectionToken('LAYOUT_SEARCH_ADAPTER');
|
|
908
951
|
|
|
909
952
|
class AppFloatingConfigurator {
|
|
910
953
|
layoutService = inject(LayoutService);
|
|
@@ -1008,171 +1051,493 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1008
1051
|
`, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"] }]
|
|
1009
1052
|
}] });
|
|
1010
1053
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1054
|
+
class AppMenuitem {
|
|
1055
|
+
// Signal inputs
|
|
1056
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1057
|
+
index = input.required(...(ngDevMode ? [{ debugName: "index" }] : []));
|
|
1058
|
+
parentKey = input('', ...(ngDevMode ? [{ debugName: "parentKey" }] : []));
|
|
1059
|
+
// Injected services
|
|
1060
|
+
router = inject(Router);
|
|
1061
|
+
layoutService = inject(LayoutService);
|
|
1018
1062
|
destroyRef = inject(DestroyRef);
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1063
|
+
// View references
|
|
1064
|
+
parentLink = viewChild('parentLink', ...(ngDevMode ? [{ debugName: "parentLink" }] : []));
|
|
1065
|
+
// State signals - private writable + public readonly pattern
|
|
1066
|
+
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
|
|
1067
|
+
active = this._active.asReadonly();
|
|
1068
|
+
// Computed signals
|
|
1069
|
+
key = computed(() => {
|
|
1070
|
+
const parent = this.parentKey();
|
|
1071
|
+
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
1072
|
+
}, ...(ngDevMode ? [{ debugName: "key" }] : []));
|
|
1073
|
+
routerLink = computed(() => {
|
|
1074
|
+
const menuItem = this.item();
|
|
1075
|
+
return menuItem.routerLink ?? [];
|
|
1076
|
+
}, ...(ngDevMode ? [{ debugName: "routerLink" }] : []));
|
|
1077
|
+
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
1078
|
+
routerLinkActiveOptions = computed(() => {
|
|
1079
|
+
const link = this.routerLink();
|
|
1080
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
1081
|
+
return {
|
|
1082
|
+
paths: pathsMatch,
|
|
1083
|
+
queryParams: 'ignored',
|
|
1084
|
+
matrixParams: 'ignored',
|
|
1085
|
+
fragment: 'ignored',
|
|
1086
|
+
};
|
|
1087
|
+
}, ...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : []));
|
|
1027
1088
|
constructor() {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
.
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
this.
|
|
1089
|
+
// Subscribe to menu source changes
|
|
1090
|
+
this.layoutService.menuSource$
|
|
1091
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1092
|
+
.subscribe((value) => {
|
|
1093
|
+
Promise.resolve(null).then(() => {
|
|
1094
|
+
const currentKey = this.key();
|
|
1095
|
+
if (value.routeEvent) {
|
|
1096
|
+
this._active.set(value.key === currentKey ||
|
|
1097
|
+
value.key?.startsWith(currentKey + '-'));
|
|
1098
|
+
}
|
|
1099
|
+
else if (value.key !== currentKey &&
|
|
1100
|
+
!value.key?.startsWith(currentKey + '-')) {
|
|
1101
|
+
this._active.set(false);
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
});
|
|
1105
|
+
// Subscribe to menu reset
|
|
1106
|
+
this.layoutService.resetSource$
|
|
1107
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1108
|
+
.subscribe(() => this._active.set(false));
|
|
1109
|
+
// Subscribe to navigation events
|
|
1110
|
+
this.router.events
|
|
1111
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
1112
|
+
.subscribe(() => {
|
|
1113
|
+
if (this.item().routerLink) {
|
|
1114
|
+
this.updateActiveStateFromRoute();
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
// Effect to update active state on init when item has routerLink
|
|
1118
|
+
effect(() => {
|
|
1119
|
+
const menuItem = this.item();
|
|
1120
|
+
if (menuItem.routerLink) {
|
|
1121
|
+
this.updateActiveStateFromRoute();
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
// Effect to update submenu position when active in topbar mode
|
|
1125
|
+
effect(() => {
|
|
1126
|
+
if (this.active() && this.parentLink()) {
|
|
1127
|
+
Promise.resolve().then(() => this.updateSubmenuPosition());
|
|
1034
1128
|
}
|
|
1035
1129
|
});
|
|
1036
1130
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
companies = this._companies.asReadonly();
|
|
1041
|
-
_branches = signal([], ...(ngDevMode ? [{ debugName: "_branches" }] : []));
|
|
1042
|
-
branches = this._branches.asReadonly();
|
|
1043
|
-
_selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : []));
|
|
1044
|
-
selectedCompanyId = this._selectedCompanyId.asReadonly();
|
|
1045
|
-
_selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : []));
|
|
1046
|
-
selectedBranchId = this._selectedBranchId.asReadonly();
|
|
1047
|
-
_isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : []));
|
|
1048
|
-
isLoadingCompanies = this._isLoadingCompanies.asReadonly();
|
|
1049
|
-
_isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : []));
|
|
1050
|
-
isLoadingBranches = this._isLoadingBranches.asReadonly();
|
|
1051
|
-
_isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "_isSwitching" }] : []));
|
|
1052
|
-
isSwitching = this._isSwitching.asReadonly();
|
|
1053
|
-
canSwitch = computed(() => {
|
|
1054
|
-
const selectedCompany = this.selectedCompanyId();
|
|
1055
|
-
if (!selectedCompany)
|
|
1056
|
-
return false;
|
|
1057
|
-
const currentCompanyId = this.authState?.currentCompanyInfo()?.id;
|
|
1058
|
-
const currentBranchId = this.authState?.currentBranchInfo()?.id;
|
|
1059
|
-
const selectedBranch = this.selectedBranchId();
|
|
1060
|
-
return selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId;
|
|
1061
|
-
}, ...(ngDevMode ? [{ debugName: "canSwitch" }] : []));
|
|
1062
|
-
onPanelToggle() {
|
|
1063
|
-
this._isActive.update((v) => !v);
|
|
1064
|
-
if (this.isActive() && this.companies().length === 0) {
|
|
1065
|
-
this.loadCompanies();
|
|
1131
|
+
onMouseEnter() {
|
|
1132
|
+
if (this.layoutService.isTopbar() && this.item().children) {
|
|
1133
|
+
this.updateSubmenuPosition();
|
|
1066
1134
|
}
|
|
1067
1135
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1136
|
+
updateSubmenuPosition() {
|
|
1137
|
+
const linkEl = this.parentLink()?.nativeElement;
|
|
1138
|
+
if (!linkEl)
|
|
1070
1139
|
return;
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1075
|
-
.subscribe({
|
|
1076
|
-
next: (companies) => {
|
|
1077
|
-
this._companies.set(companies);
|
|
1078
|
-
this._isLoadingCompanies.set(false);
|
|
1079
|
-
},
|
|
1080
|
-
error: () => {
|
|
1081
|
-
// Error toast handled by global interceptor
|
|
1082
|
-
this._isLoadingCompanies.set(false);
|
|
1083
|
-
},
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
onCompanyChange(companyId) {
|
|
1087
|
-
this._selectedCompanyId.set(companyId);
|
|
1088
|
-
if (!companyId) {
|
|
1089
|
-
this._branches.set([]);
|
|
1090
|
-
this._selectedBranchId.set(null);
|
|
1140
|
+
const rect = linkEl.getBoundingClientRect();
|
|
1141
|
+
const hostEl = linkEl.closest('[app-menuitem]');
|
|
1142
|
+
if (!hostEl)
|
|
1091
1143
|
return;
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1144
|
+
// Position submenu to the left of the parent link and above it
|
|
1145
|
+
const left = rect.left;
|
|
1146
|
+
const top = rect.top + 30; // 8px gap above the menu item
|
|
1147
|
+
hostEl.style.setProperty('--submenu-left', `${left}px`);
|
|
1148
|
+
hostEl.style.setProperty('--submenu-top', `${top}px`);
|
|
1097
1149
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1150
|
+
updateActiveStateFromRoute() {
|
|
1151
|
+
const link = this.routerLink();
|
|
1152
|
+
if (!link.length)
|
|
1100
1153
|
return;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
this._isLoadingBranches.set(false);
|
|
1110
|
-
// Auto-select if only one branch available
|
|
1111
|
-
if (branches.length === 1) {
|
|
1112
|
-
this._selectedBranchId.set(branches[0].id);
|
|
1113
|
-
}
|
|
1114
|
-
},
|
|
1115
|
-
error: () => {
|
|
1116
|
-
// Error toast handled by global interceptor
|
|
1117
|
-
this._isLoadingBranches.set(false);
|
|
1118
|
-
},
|
|
1154
|
+
// Use 'exact' for root path to avoid matching all routes
|
|
1155
|
+
// Use 'subset' for all other paths to match child routes
|
|
1156
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
1157
|
+
const isActive = this.router.isActive(link[0], {
|
|
1158
|
+
paths: pathsMatch,
|
|
1159
|
+
queryParams: 'ignored',
|
|
1160
|
+
matrixParams: 'ignored',
|
|
1161
|
+
fragment: 'ignored',
|
|
1119
1162
|
});
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
if (!this.authApi || !selectedCompany)
|
|
1125
|
-
return;
|
|
1126
|
-
if (this.branches().length > 0 && !selectedBranch) {
|
|
1127
|
-
this.messageService.add({
|
|
1128
|
-
severity: 'warn',
|
|
1129
|
-
summary: this.translate('layout.company.branch.selector.branch.required'),
|
|
1130
|
-
detail: this.translate('layout.company.branch.selector.please.select.branch'),
|
|
1163
|
+
if (isActive) {
|
|
1164
|
+
this.layoutService.onMenuStateChange({
|
|
1165
|
+
key: this.key(),
|
|
1166
|
+
routeEvent: true,
|
|
1131
1167
|
});
|
|
1132
|
-
return;
|
|
1133
1168
|
}
|
|
1134
|
-
this._isSwitching.set(true);
|
|
1135
|
-
this.authApi
|
|
1136
|
-
.switchCompany(selectedCompany, selectedBranch || '')
|
|
1137
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1138
|
-
.subscribe({
|
|
1139
|
-
next: () => { },
|
|
1140
|
-
error: () => {
|
|
1141
|
-
// Error toast handled by global interceptor
|
|
1142
|
-
this._isSwitching.set(false);
|
|
1143
|
-
},
|
|
1144
|
-
});
|
|
1145
1169
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1170
|
+
itemClick() {
|
|
1171
|
+
if (this.item().children && !this.layoutService.isTopbar()) {
|
|
1172
|
+
this._active.update((prev) => !prev);
|
|
1173
|
+
}
|
|
1174
|
+
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
1148
1175
|
}
|
|
1149
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type:
|
|
1150
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type:
|
|
1151
|
-
<
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1177
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenuitem, isStandalone: true, selector: "[app-menuitem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, parentKey: { classPropertyName: "parentKey", publicName: "parentKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.active-menuitem": "active()" } }, viewQueries: [{ propertyName: "parentLink", first: true, predicate: ["parentLink"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1178
|
+
<ng-container>
|
|
1179
|
+
@if (item().children?.length) {
|
|
1180
|
+
<a
|
|
1181
|
+
(click)="itemClick()"
|
|
1182
|
+
(mouseenter)="onMouseEnter()"
|
|
1183
|
+
#parentLink
|
|
1184
|
+
tabindex="0"
|
|
1185
|
+
pRipple
|
|
1186
|
+
[style.cursor]="layoutService.isTopbar() ? 'default' : 'pointer'"
|
|
1187
|
+
>
|
|
1188
|
+
@if (item().icon) {
|
|
1189
|
+
<lib-icon
|
|
1190
|
+
[icon]="item().icon!"
|
|
1191
|
+
[iconType]="item().iconType"
|
|
1192
|
+
class="layout-menuitem-icon"
|
|
1193
|
+
/>
|
|
1194
|
+
}
|
|
1195
|
+
<span class="layout-menuitem-text">{{
|
|
1196
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1197
|
+
}}</span>
|
|
1198
|
+
@if (item().children) {
|
|
1199
|
+
@if (layoutService.isTopbar()) {
|
|
1200
|
+
<i class="pi pi-fw pi-chevron-right layout-submenu-toggler"></i>
|
|
1201
|
+
} @else {
|
|
1202
|
+
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
</a>
|
|
1206
|
+
}
|
|
1207
|
+
@if (item().routerLink && !item().children?.length) {
|
|
1208
|
+
<a
|
|
1209
|
+
(click)="itemClick()"
|
|
1210
|
+
[routerLink]="routerLink()"
|
|
1211
|
+
routerLinkActive="active-route"
|
|
1212
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1213
|
+
tabindex="0"
|
|
1214
|
+
pRipple
|
|
1215
|
+
>
|
|
1216
|
+
@if (item().icon) {
|
|
1217
|
+
<lib-icon
|
|
1218
|
+
[icon]="item().icon!"
|
|
1219
|
+
[iconType]="item().iconType"
|
|
1220
|
+
class="layout-menuitem-icon"
|
|
1221
|
+
/>
|
|
1222
|
+
}
|
|
1223
|
+
<span class="layout-menuitem-text">{{
|
|
1224
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1225
|
+
}}</span>
|
|
1226
|
+
</a>
|
|
1227
|
+
}
|
|
1228
|
+
@if (item().children) {
|
|
1229
|
+
<ul
|
|
1230
|
+
(mouseenter)="onMouseEnter()"
|
|
1231
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
1232
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
1233
|
+
>
|
|
1234
|
+
@for (
|
|
1235
|
+
child of item().children;
|
|
1236
|
+
track 'menu' + child.id + '' + i;
|
|
1237
|
+
let i = $index
|
|
1238
|
+
) {
|
|
1239
|
+
<li
|
|
1240
|
+
app-menuitem
|
|
1241
|
+
[item]="child"
|
|
1242
|
+
[index]="i"
|
|
1243
|
+
[parentKey]="key()"
|
|
1244
|
+
></li>
|
|
1245
|
+
}
|
|
1246
|
+
</ul>
|
|
1247
|
+
}
|
|
1248
|
+
</ng-container>
|
|
1249
|
+
`, 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$2.Ripple, selector: "[pRipple]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1250
|
+
}
|
|
1251
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, decorators: [{
|
|
1252
|
+
type: Component,
|
|
1253
|
+
args: [{ selector: '[app-menuitem]', imports: [IconComponent, RouterModule, RippleModule, TranslatePipe], template: `
|
|
1254
|
+
<ng-container>
|
|
1255
|
+
@if (item().children?.length) {
|
|
1256
|
+
<a
|
|
1257
|
+
(click)="itemClick()"
|
|
1258
|
+
(mouseenter)="onMouseEnter()"
|
|
1259
|
+
#parentLink
|
|
1260
|
+
tabindex="0"
|
|
1261
|
+
pRipple
|
|
1262
|
+
[style.cursor]="layoutService.isTopbar() ? 'default' : 'pointer'"
|
|
1263
|
+
>
|
|
1264
|
+
@if (item().icon) {
|
|
1265
|
+
<lib-icon
|
|
1266
|
+
[icon]="item().icon!"
|
|
1267
|
+
[iconType]="item().iconType"
|
|
1268
|
+
class="layout-menuitem-icon"
|
|
1269
|
+
/>
|
|
1270
|
+
}
|
|
1271
|
+
<span class="layout-menuitem-text">{{
|
|
1272
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1273
|
+
}}</span>
|
|
1274
|
+
@if (item().children) {
|
|
1275
|
+
@if (layoutService.isTopbar()) {
|
|
1276
|
+
<i class="pi pi-fw pi-chevron-right layout-submenu-toggler"></i>
|
|
1277
|
+
} @else {
|
|
1278
|
+
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
</a>
|
|
1282
|
+
}
|
|
1283
|
+
@if (item().routerLink && !item().children?.length) {
|
|
1284
|
+
<a
|
|
1285
|
+
(click)="itemClick()"
|
|
1286
|
+
[routerLink]="routerLink()"
|
|
1287
|
+
routerLinkActive="active-route"
|
|
1288
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1289
|
+
tabindex="0"
|
|
1290
|
+
pRipple
|
|
1291
|
+
>
|
|
1292
|
+
@if (item().icon) {
|
|
1293
|
+
<lib-icon
|
|
1294
|
+
[icon]="item().icon!"
|
|
1295
|
+
[iconType]="item().iconType"
|
|
1296
|
+
class="layout-menuitem-icon"
|
|
1297
|
+
/>
|
|
1298
|
+
}
|
|
1299
|
+
<span class="layout-menuitem-text">{{
|
|
1300
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1301
|
+
}}</span>
|
|
1302
|
+
</a>
|
|
1303
|
+
}
|
|
1304
|
+
@if (item().children) {
|
|
1305
|
+
<ul
|
|
1306
|
+
(mouseenter)="onMouseEnter()"
|
|
1307
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
1308
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
1309
|
+
>
|
|
1310
|
+
@for (
|
|
1311
|
+
child of item().children;
|
|
1312
|
+
track 'menu' + child.id + '' + i;
|
|
1313
|
+
let i = $index
|
|
1314
|
+
) {
|
|
1315
|
+
<li
|
|
1316
|
+
app-menuitem
|
|
1317
|
+
[item]="child"
|
|
1318
|
+
[index]="i"
|
|
1319
|
+
[parentKey]="key()"
|
|
1320
|
+
></li>
|
|
1321
|
+
}
|
|
1322
|
+
</ul>
|
|
1323
|
+
}
|
|
1324
|
+
</ng-container>
|
|
1325
|
+
`, host: {
|
|
1326
|
+
'[class.active-menuitem]': 'active()',
|
|
1327
|
+
}, 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"] }]
|
|
1328
|
+
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], parentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKey", required: false }] }], parentLink: [{ type: i0.ViewChild, args: ['parentLink', { isSignal: true }] }] } });
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* Main menu component that displays filtered menu items.
|
|
1332
|
+
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
1333
|
+
* The filtering happens internally in LayoutService via computed signal.
|
|
1334
|
+
*/
|
|
1335
|
+
class AppMenu {
|
|
1336
|
+
layoutService = inject(LayoutService);
|
|
1337
|
+
/**
|
|
1338
|
+
* Filtered menu items from layout service.
|
|
1339
|
+
* This signal is computed in LayoutService and automatically updates when:
|
|
1340
|
+
* - Raw menu changes (setMenu called)
|
|
1341
|
+
* - Permission state changes (user permissions updated)
|
|
1342
|
+
*/
|
|
1343
|
+
menuItems = this.layoutService.menu;
|
|
1344
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1345
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
|
|
1346
|
+
<ul>
|
|
1347
|
+
@for (
|
|
1348
|
+
item of menuItems();
|
|
1349
|
+
track 'menu' + item.id + '' + i;
|
|
1350
|
+
let i = $index
|
|
1351
|
+
) {
|
|
1352
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
1353
|
+
}
|
|
1354
|
+
</ul>
|
|
1355
|
+
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
|
|
1356
|
+
}
|
|
1357
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, decorators: [{
|
|
1358
|
+
type: Component,
|
|
1359
|
+
args: [{
|
|
1360
|
+
selector: 'app-menu',
|
|
1361
|
+
imports: [AppMenuitem, RouterModule],
|
|
1362
|
+
template: `<div class="layout-menu">
|
|
1363
|
+
<ul>
|
|
1364
|
+
@for (
|
|
1365
|
+
item of menuItems();
|
|
1366
|
+
track 'menu' + item.id + '' + i;
|
|
1367
|
+
let i = $index
|
|
1368
|
+
) {
|
|
1369
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
1370
|
+
}
|
|
1371
|
+
</ul>
|
|
1372
|
+
</div>`,
|
|
1373
|
+
}]
|
|
1374
|
+
}] });
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* View Transitions API type definitions
|
|
1378
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
|
|
1379
|
+
*/
|
|
1380
|
+
|
|
1381
|
+
/** Company/branch switcher displayed in top bar */
|
|
1382
|
+
class AppCompanyBranchSelector {
|
|
1383
|
+
destroyRef = inject(DestroyRef);
|
|
1384
|
+
authState = inject(LAYOUT_AUTH_STATE, { optional: true });
|
|
1385
|
+
authApi = inject(LAYOUT_AUTH_API, { optional: true });
|
|
1386
|
+
messageService = inject(MessageService);
|
|
1387
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1388
|
+
document = inject(DOCUMENT$1);
|
|
1389
|
+
elementRef = inject(ElementRef);
|
|
1390
|
+
_isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
|
|
1391
|
+
isActive = this._isActive.asReadonly();
|
|
1392
|
+
constructor() {
|
|
1393
|
+
fromEvent(this.document, 'click')
|
|
1394
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1395
|
+
.subscribe((event) => {
|
|
1396
|
+
const target = event.target;
|
|
1397
|
+
if (!this.elementRef.nativeElement.contains(target)) {
|
|
1398
|
+
this._isActive.set(false);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
currentCompanyName = computed(() => this.authState?.currentCompanyInfo()?.name ?? this.translate('layout.company.branch.selector.no.company'), ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
|
|
1403
|
+
currentBranchName = computed(() => this.authState?.currentBranchInfo()?.name ?? null, ...(ngDevMode ? [{ debugName: "currentBranchName" }] : []));
|
|
1404
|
+
_companies = signal([], ...(ngDevMode ? [{ debugName: "_companies" }] : []));
|
|
1405
|
+
companies = this._companies.asReadonly();
|
|
1406
|
+
_branches = signal([], ...(ngDevMode ? [{ debugName: "_branches" }] : []));
|
|
1407
|
+
branches = this._branches.asReadonly();
|
|
1408
|
+
_selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : []));
|
|
1409
|
+
selectedCompanyId = this._selectedCompanyId.asReadonly();
|
|
1410
|
+
_selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : []));
|
|
1411
|
+
selectedBranchId = this._selectedBranchId.asReadonly();
|
|
1412
|
+
_isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : []));
|
|
1413
|
+
isLoadingCompanies = this._isLoadingCompanies.asReadonly();
|
|
1414
|
+
_isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : []));
|
|
1415
|
+
isLoadingBranches = this._isLoadingBranches.asReadonly();
|
|
1416
|
+
_isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "_isSwitching" }] : []));
|
|
1417
|
+
isSwitching = this._isSwitching.asReadonly();
|
|
1418
|
+
canSwitch = computed(() => {
|
|
1419
|
+
const selectedCompany = this.selectedCompanyId();
|
|
1420
|
+
if (!selectedCompany)
|
|
1421
|
+
return false;
|
|
1422
|
+
const currentCompanyId = this.authState?.currentCompanyInfo()?.id;
|
|
1423
|
+
const currentBranchId = this.authState?.currentBranchInfo()?.id;
|
|
1424
|
+
const selectedBranch = this.selectedBranchId();
|
|
1425
|
+
return selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId;
|
|
1426
|
+
}, ...(ngDevMode ? [{ debugName: "canSwitch" }] : []));
|
|
1427
|
+
onPanelToggle() {
|
|
1428
|
+
this._isActive.update((v) => !v);
|
|
1429
|
+
if (this.isActive() && this.companies().length === 0) {
|
|
1430
|
+
this.loadCompanies();
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
loadCompanies() {
|
|
1434
|
+
if (!this.authApi)
|
|
1435
|
+
return;
|
|
1436
|
+
this._isLoadingCompanies.set(true);
|
|
1437
|
+
this.authApi
|
|
1438
|
+
.getUserCompanies()
|
|
1439
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1440
|
+
.subscribe({
|
|
1441
|
+
next: (companies) => {
|
|
1442
|
+
this._companies.set(companies);
|
|
1443
|
+
this._isLoadingCompanies.set(false);
|
|
1444
|
+
},
|
|
1445
|
+
error: () => {
|
|
1446
|
+
// Error toast handled by global interceptor
|
|
1447
|
+
this._isLoadingCompanies.set(false);
|
|
1448
|
+
},
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
onCompanyChange(companyId) {
|
|
1452
|
+
this._selectedCompanyId.set(companyId);
|
|
1453
|
+
if (!companyId) {
|
|
1454
|
+
this._branches.set([]);
|
|
1455
|
+
this._selectedBranchId.set(null);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
this.loadBranches(companyId);
|
|
1459
|
+
}
|
|
1460
|
+
onBranchChange(branchId) {
|
|
1461
|
+
this._selectedBranchId.set(branchId);
|
|
1462
|
+
}
|
|
1463
|
+
loadBranches(companyId) {
|
|
1464
|
+
if (!this.authApi)
|
|
1465
|
+
return;
|
|
1466
|
+
this._isLoadingBranches.set(true);
|
|
1467
|
+
this._selectedBranchId.set(null);
|
|
1468
|
+
this.authApi
|
|
1469
|
+
.getCompanyBranches(companyId)
|
|
1470
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1471
|
+
.subscribe({
|
|
1472
|
+
next: (branches) => {
|
|
1473
|
+
this._branches.set(branches);
|
|
1474
|
+
this._isLoadingBranches.set(false);
|
|
1475
|
+
// Auto-select if only one branch available
|
|
1476
|
+
if (branches.length === 1) {
|
|
1477
|
+
this._selectedBranchId.set(branches[0].id);
|
|
1478
|
+
}
|
|
1479
|
+
},
|
|
1480
|
+
error: () => {
|
|
1481
|
+
// Error toast handled by global interceptor
|
|
1482
|
+
this._isLoadingBranches.set(false);
|
|
1483
|
+
},
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
onSwitch() {
|
|
1487
|
+
const selectedCompany = this.selectedCompanyId();
|
|
1488
|
+
const selectedBranch = this.selectedBranchId();
|
|
1489
|
+
if (!this.authApi || !selectedCompany)
|
|
1490
|
+
return;
|
|
1491
|
+
if (this.branches().length > 0 && !selectedBranch) {
|
|
1492
|
+
this.messageService.add({
|
|
1493
|
+
severity: 'warn',
|
|
1494
|
+
summary: this.translate('layout.company.branch.selector.branch.required'),
|
|
1495
|
+
detail: this.translate('layout.company.branch.selector.please.select.branch'),
|
|
1496
|
+
});
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
this._isSwitching.set(true);
|
|
1500
|
+
this.authApi
|
|
1501
|
+
.switchCompany(selectedCompany, selectedBranch || '')
|
|
1502
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1503
|
+
.subscribe({
|
|
1504
|
+
next: () => { },
|
|
1505
|
+
error: () => {
|
|
1506
|
+
// Error toast handled by global interceptor
|
|
1507
|
+
this._isSwitching.set(false);
|
|
1508
|
+
},
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
translate(key) {
|
|
1512
|
+
return this.translateAdapter?.translate(key) ?? key;
|
|
1513
|
+
}
|
|
1514
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppCompanyBranchSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1515
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppCompanyBranchSelector, isStandalone: true, selector: "app-company-branch-selector", host: { classAttribute: "relative" }, ngImport: i0, template: `
|
|
1516
|
+
<button
|
|
1517
|
+
type="button"
|
|
1518
|
+
class="layout-topbar-action"
|
|
1519
|
+
[class.layout-topbar-action-highlight]="isActive()"
|
|
1520
|
+
pStyleClass="@next"
|
|
1521
|
+
enterFromClass="hidden"
|
|
1522
|
+
enterActiveClass="animate-scalein"
|
|
1523
|
+
leaveToClass="hidden"
|
|
1524
|
+
leaveActiveClass="animate-fadeout"
|
|
1525
|
+
[hideOnOutsideClick]="true"
|
|
1526
|
+
(click)="onPanelToggle()"
|
|
1527
|
+
>
|
|
1528
|
+
<i class="pi pi-building"></i>
|
|
1529
|
+
<span>{{ currentCompanyName() }}</span>
|
|
1530
|
+
@if (currentBranchName()) {
|
|
1531
|
+
<span class="text-xs opacity-70"> | {{ currentBranchName() }}</span>
|
|
1532
|
+
}
|
|
1533
|
+
</button>
|
|
1534
|
+
<div
|
|
1535
|
+
class="hidden absolute top-[3.25rem] 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]"
|
|
1536
|
+
style="background-color: var(--surface-overlay); inset-inline-end: 0;"
|
|
1537
|
+
>
|
|
1538
|
+
<div class="flex flex-col gap-4">
|
|
1539
|
+
<span class="text-sm text-muted-color font-semibold"
|
|
1540
|
+
>{{ 'layout.company.branch.selector.title' | translate }}</span
|
|
1176
1541
|
>
|
|
1177
1542
|
|
|
1178
1543
|
<!-- Company Selector -->
|
|
@@ -1441,6 +1806,7 @@ class AppProfile {
|
|
|
1441
1806
|
layoutService = inject(LayoutService);
|
|
1442
1807
|
messageService = inject(MessageService);
|
|
1443
1808
|
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1809
|
+
appConfig = inject(APP_CONFIG, { optional: true });
|
|
1444
1810
|
document = inject(DOCUMENT$1);
|
|
1445
1811
|
elementRef = inject(ElementRef);
|
|
1446
1812
|
_isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
|
|
@@ -1461,6 +1827,7 @@ class AppProfile {
|
|
|
1461
1827
|
userName = computed(() => this.authState?.loginUserData()?.name ?? this.translate('layout.profile.guest'), ...(ngDevMode ? [{ debugName: "userName" }] : []));
|
|
1462
1828
|
userEmail = computed(() => this.authState?.loginUserData()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
|
|
1463
1829
|
profilePicture = computed(() => this.layoutService.userProfilePictureUrl() ?? '', ...(ngDevMode ? [{ debugName: "profilePicture" }] : []));
|
|
1830
|
+
signUpEnabled = computed(() => this.appConfig ? isSignUpEnabled(this.appConfig) : false, ...(ngDevMode ? [{ debugName: "signUpEnabled" }] : []));
|
|
1464
1831
|
logout() {
|
|
1465
1832
|
if (!this.authApi)
|
|
1466
1833
|
return;
|
|
@@ -1555,13 +1922,15 @@ class AppProfile {
|
|
|
1555
1922
|
|
|
1556
1923
|
<!-- Menu Items -->
|
|
1557
1924
|
<div class="flex flex-col gap-1">
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1925
|
+
@if (signUpEnabled()) {
|
|
1926
|
+
<a
|
|
1927
|
+
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1928
|
+
(click)="copySignUpLink()"
|
|
1929
|
+
>
|
|
1930
|
+
<i class="pi pi-link text-muted-color"></i>
|
|
1931
|
+
<span>{{ 'layout.profile.copy.sign.up.link' | translate }}</span>
|
|
1932
|
+
</a>
|
|
1933
|
+
}
|
|
1565
1934
|
<a
|
|
1566
1935
|
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1567
1936
|
(click)="logout()"
|
|
@@ -1572,7 +1941,7 @@ class AppProfile {
|
|
|
1572
1941
|
</div>
|
|
1573
1942
|
</div>
|
|
1574
1943
|
</div>
|
|
1575
|
-
`, 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$
|
|
1944
|
+
`, 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$3.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"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1576
1945
|
}
|
|
1577
1946
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppProfile, decorators: [{
|
|
1578
1947
|
type: Component,
|
|
@@ -1630,13 +1999,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1630
1999
|
|
|
1631
2000
|
<!-- Menu Items -->
|
|
1632
2001
|
<div class="flex flex-col gap-1">
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
2002
|
+
@if (signUpEnabled()) {
|
|
2003
|
+
<a
|
|
2004
|
+
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
2005
|
+
(click)="copySignUpLink()"
|
|
2006
|
+
>
|
|
2007
|
+
<i class="pi pi-link text-muted-color"></i>
|
|
2008
|
+
<span>{{ 'layout.profile.copy.sign.up.link' | translate }}</span>
|
|
2009
|
+
</a>
|
|
2010
|
+
}
|
|
1640
2011
|
<a
|
|
1641
2012
|
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1642
2013
|
(click)="logout()"
|
|
@@ -1651,6 +2022,356 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1651
2022
|
}]
|
|
1652
2023
|
}], ctorParameters: () => [] });
|
|
1653
2024
|
|
|
2025
|
+
class AppSearchbar {
|
|
2026
|
+
layoutService = inject(LayoutService);
|
|
2027
|
+
router = inject(Router);
|
|
2028
|
+
searchAdapter = inject(LAYOUT_SEARCH_ADAPTER, {
|
|
2029
|
+
optional: true,
|
|
2030
|
+
});
|
|
2031
|
+
destroyRef = inject(DestroyRef);
|
|
2032
|
+
document = inject(DOCUMENT$1);
|
|
2033
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
2034
|
+
suggestionsOpen = signal(false, ...(ngDevMode ? [{ debugName: "suggestionsOpen" }] : []));
|
|
2035
|
+
suggestions = signal([], ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
|
|
2036
|
+
focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
|
|
2037
|
+
isSearchExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isSearchExpanded" }] : []));
|
|
2038
|
+
isResponsive = signal(typeof window !== 'undefined' ? window.innerWidth < 768 : false, ...(ngDevMode ? [{ debugName: "isResponsive" }] : []));
|
|
2039
|
+
searchWrapper = viewChild('searchWrapper', ...(ngDevMode ? [{ debugName: "searchWrapper" }] : []));
|
|
2040
|
+
searchInput = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
|
|
2041
|
+
searchTimeout;
|
|
2042
|
+
constructor() {
|
|
2043
|
+
// Track responsive breakpoint (mobile < 768px)
|
|
2044
|
+
if (typeof window !== 'undefined') {
|
|
2045
|
+
fromEvent(window, 'resize')
|
|
2046
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2047
|
+
.subscribe(() => {
|
|
2048
|
+
this.isResponsive.set(window.innerWidth < 768);
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
// Close dropdown/search on outside click
|
|
2052
|
+
fromEvent(this.document, 'click')
|
|
2053
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2054
|
+
.subscribe((event) => {
|
|
2055
|
+
const wrapper = this.searchWrapper()?.nativeElement;
|
|
2056
|
+
const target = event.target;
|
|
2057
|
+
if (wrapper && !wrapper.contains(target)) {
|
|
2058
|
+
if (this.suggestionsOpen()) {
|
|
2059
|
+
this.closeSuggestions();
|
|
2060
|
+
}
|
|
2061
|
+
if (this.isSearchExpanded() &&
|
|
2062
|
+
(this.layoutService.isTopbar() || this.isResponsive())) {
|
|
2063
|
+
this.closeSearch();
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
// Debounced search for suggestions
|
|
2068
|
+
effect(() => {
|
|
2069
|
+
const query = this.searchTerm();
|
|
2070
|
+
clearTimeout(this.searchTimeout);
|
|
2071
|
+
if (!this.searchAdapter || !query.trim()) {
|
|
2072
|
+
this.suggestions.set([]);
|
|
2073
|
+
this.focusedIndex.set(-1);
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
this.searchTimeout = setTimeout(() => {
|
|
2077
|
+
this.searchAdapter.getSuggestions(query)
|
|
2078
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2079
|
+
.subscribe((results) => {
|
|
2080
|
+
this.suggestions.set(results);
|
|
2081
|
+
this.focusedIndex.set(-1);
|
|
2082
|
+
this.suggestionsOpen.set(results.length > 0);
|
|
2083
|
+
});
|
|
2084
|
+
}, 300);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
onFocus() {
|
|
2088
|
+
if (this.suggestions().length > 0) {
|
|
2089
|
+
this.suggestionsOpen.set(true);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
handleKeydown(event) {
|
|
2093
|
+
const items = this.suggestions();
|
|
2094
|
+
const current = this.focusedIndex();
|
|
2095
|
+
switch (event.key) {
|
|
2096
|
+
case 'ArrowDown':
|
|
2097
|
+
event.preventDefault();
|
|
2098
|
+
this.focusedIndex.set(current < items.length - 1 ? current + 1 : 0);
|
|
2099
|
+
this.suggestionsOpen.set(items.length > 0);
|
|
2100
|
+
break;
|
|
2101
|
+
case 'ArrowUp':
|
|
2102
|
+
event.preventDefault();
|
|
2103
|
+
this.focusedIndex.set(current > 0 ? current - 1 : items.length - 1);
|
|
2104
|
+
break;
|
|
2105
|
+
case 'Escape':
|
|
2106
|
+
event.preventDefault();
|
|
2107
|
+
this.closeSuggestions();
|
|
2108
|
+
break;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
handleEnter() {
|
|
2112
|
+
const current = this.focusedIndex();
|
|
2113
|
+
const items = this.suggestions();
|
|
2114
|
+
if (current >= 0 && current < items.length) {
|
|
2115
|
+
this.selectSuggestion(items[current]);
|
|
2116
|
+
}
|
|
2117
|
+
else if (items.length > 0) {
|
|
2118
|
+
this.selectSuggestion(items[0]);
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
this.onSearch();
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
selectSuggestion(item) {
|
|
2125
|
+
if (item.routerLink) {
|
|
2126
|
+
this.router.navigate(item.routerLink);
|
|
2127
|
+
}
|
|
2128
|
+
this.searchTerm.set('');
|
|
2129
|
+
this.closeSuggestions();
|
|
2130
|
+
if (this.layoutService.isTopbar() || this.isResponsive()) {
|
|
2131
|
+
this.closeSearch();
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
closeSuggestions() {
|
|
2135
|
+
this.suggestionsOpen.set(false);
|
|
2136
|
+
this.focusedIndex.set(-1);
|
|
2137
|
+
}
|
|
2138
|
+
toggleSearch() {
|
|
2139
|
+
this.isSearchExpanded.set(!this.isSearchExpanded());
|
|
2140
|
+
if (this.isSearchExpanded()) {
|
|
2141
|
+
setTimeout(() => {
|
|
2142
|
+
this.searchInput()?.nativeElement?.focus();
|
|
2143
|
+
}, 100);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
closeSearch() {
|
|
2147
|
+
this.isSearchExpanded.set(false);
|
|
2148
|
+
this.closeSuggestions();
|
|
2149
|
+
}
|
|
2150
|
+
onSearch() {
|
|
2151
|
+
// Implement global search if needed
|
|
2152
|
+
}
|
|
2153
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppSearchbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2154
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppSearchbar, isStandalone: true, selector: "app-searchbar", viewQueries: [{ propertyName: "searchWrapper", first: true, predicate: ["searchWrapper"], descendants: true, isSignal: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2155
|
+
<div
|
|
2156
|
+
class="search-container h-full"
|
|
2157
|
+
#searchWrapper
|
|
2158
|
+
[class.topbar-mode]="layoutService.isTopbar() || isResponsive()"
|
|
2159
|
+
>
|
|
2160
|
+
@if (layoutService.isTopbar() || isResponsive()) {
|
|
2161
|
+
<!-- Topbar/Mobile Icon Mode -->
|
|
2162
|
+
<button
|
|
2163
|
+
type="button"
|
|
2164
|
+
pButton
|
|
2165
|
+
pRipple
|
|
2166
|
+
class="search-toggle-btn"
|
|
2167
|
+
icon="pi pi-search"
|
|
2168
|
+
(click)="toggleSearch()"
|
|
2169
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2170
|
+
severity="secondary"
|
|
2171
|
+
text
|
|
2172
|
+
></button>
|
|
2173
|
+
|
|
2174
|
+
@if (isSearchExpanded()) {
|
|
2175
|
+
<div class="search-dropdown-panel">
|
|
2176
|
+
<p-iconfield class="search-iconfield">
|
|
2177
|
+
<p-inputicon class="pi pi-search search-icon" />
|
|
2178
|
+
<input
|
|
2179
|
+
type="text"
|
|
2180
|
+
pInputText
|
|
2181
|
+
class="search-input w-full"
|
|
2182
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2183
|
+
[(ngModel)]="searchTerm"
|
|
2184
|
+
(keyup.enter)="handleEnter()"
|
|
2185
|
+
(keydown)="handleKeydown($event)"
|
|
2186
|
+
(focus)="onFocus()"
|
|
2187
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2188
|
+
#searchInput
|
|
2189
|
+
autocomplete="off"
|
|
2190
|
+
/>
|
|
2191
|
+
</p-iconfield>
|
|
2192
|
+
|
|
2193
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2194
|
+
<div class="search-suggestions">
|
|
2195
|
+
<ul class="suggestions-list">
|
|
2196
|
+
@for (
|
|
2197
|
+
item of suggestions();
|
|
2198
|
+
track item.label;
|
|
2199
|
+
let idx = $index
|
|
2200
|
+
) {
|
|
2201
|
+
<li
|
|
2202
|
+
class="suggestion-item"
|
|
2203
|
+
[class.focused]="idx === focusedIndex()"
|
|
2204
|
+
(click)="selectSuggestion(item)"
|
|
2205
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2206
|
+
role="option"
|
|
2207
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2208
|
+
>
|
|
2209
|
+
@if (item.icon) {
|
|
2210
|
+
<i [class]="item.icon" class="suggestion-icon"></i>
|
|
2211
|
+
}
|
|
2212
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2213
|
+
</li>
|
|
2214
|
+
}
|
|
2215
|
+
</ul>
|
|
2216
|
+
</div>
|
|
2217
|
+
}
|
|
2218
|
+
</div>
|
|
2219
|
+
}
|
|
2220
|
+
} @else {
|
|
2221
|
+
<!-- Desktop Full Input Mode -->
|
|
2222
|
+
<p-iconfield class="search-iconfield">
|
|
2223
|
+
<p-inputicon class="pi pi-search search-icon" (click)="onSearch()" />
|
|
2224
|
+
<input
|
|
2225
|
+
type="text"
|
|
2226
|
+
pInputText
|
|
2227
|
+
class="search-input w-full"
|
|
2228
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2229
|
+
[(ngModel)]="searchTerm"
|
|
2230
|
+
(keyup.enter)="handleEnter()"
|
|
2231
|
+
(keydown)="handleKeydown($event)"
|
|
2232
|
+
(focus)="onFocus()"
|
|
2233
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2234
|
+
#searchInput
|
|
2235
|
+
/>
|
|
2236
|
+
</p-iconfield>
|
|
2237
|
+
|
|
2238
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2239
|
+
<div class="search-dropdown">
|
|
2240
|
+
<ul class="suggestions-list">
|
|
2241
|
+
@for (item of suggestions(); track item.label; let idx = $index) {
|
|
2242
|
+
<li
|
|
2243
|
+
class="suggestion-item"
|
|
2244
|
+
[class.focused]="idx === focusedIndex()"
|
|
2245
|
+
(click)="selectSuggestion(item)"
|
|
2246
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2247
|
+
role="option"
|
|
2248
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2249
|
+
>
|
|
2250
|
+
@if (item.icon) {
|
|
2251
|
+
<i [class]="item.icon" class="suggestion-icon"></i>
|
|
2252
|
+
}
|
|
2253
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2254
|
+
</li>
|
|
2255
|
+
}
|
|
2256
|
+
</ul>
|
|
2257
|
+
</div>
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
</div>
|
|
2261
|
+
`, isInline: true, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--border-radius);box-shadow:0 4px 20px #00000026;z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--border-radius);box-shadow:0 4px 16px #0000001f;z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:4rem;left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: 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: PrimeModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i3.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "component", type: i4$1.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "directive", type: i5.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: i2$2.Ripple, selector: "[pRipple]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2262
|
+
}
|
|
2263
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppSearchbar, decorators: [{
|
|
2264
|
+
type: Component,
|
|
2265
|
+
args: [{ selector: 'app-searchbar', imports: [AngularModule, PrimeModule, TranslatePipe], template: `
|
|
2266
|
+
<div
|
|
2267
|
+
class="search-container h-full"
|
|
2268
|
+
#searchWrapper
|
|
2269
|
+
[class.topbar-mode]="layoutService.isTopbar() || isResponsive()"
|
|
2270
|
+
>
|
|
2271
|
+
@if (layoutService.isTopbar() || isResponsive()) {
|
|
2272
|
+
<!-- Topbar/Mobile Icon Mode -->
|
|
2273
|
+
<button
|
|
2274
|
+
type="button"
|
|
2275
|
+
pButton
|
|
2276
|
+
pRipple
|
|
2277
|
+
class="search-toggle-btn"
|
|
2278
|
+
icon="pi pi-search"
|
|
2279
|
+
(click)="toggleSearch()"
|
|
2280
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2281
|
+
severity="secondary"
|
|
2282
|
+
text
|
|
2283
|
+
></button>
|
|
2284
|
+
|
|
2285
|
+
@if (isSearchExpanded()) {
|
|
2286
|
+
<div class="search-dropdown-panel">
|
|
2287
|
+
<p-iconfield class="search-iconfield">
|
|
2288
|
+
<p-inputicon class="pi pi-search search-icon" />
|
|
2289
|
+
<input
|
|
2290
|
+
type="text"
|
|
2291
|
+
pInputText
|
|
2292
|
+
class="search-input w-full"
|
|
2293
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2294
|
+
[(ngModel)]="searchTerm"
|
|
2295
|
+
(keyup.enter)="handleEnter()"
|
|
2296
|
+
(keydown)="handleKeydown($event)"
|
|
2297
|
+
(focus)="onFocus()"
|
|
2298
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2299
|
+
#searchInput
|
|
2300
|
+
autocomplete="off"
|
|
2301
|
+
/>
|
|
2302
|
+
</p-iconfield>
|
|
2303
|
+
|
|
2304
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2305
|
+
<div class="search-suggestions">
|
|
2306
|
+
<ul class="suggestions-list">
|
|
2307
|
+
@for (
|
|
2308
|
+
item of suggestions();
|
|
2309
|
+
track item.label;
|
|
2310
|
+
let idx = $index
|
|
2311
|
+
) {
|
|
2312
|
+
<li
|
|
2313
|
+
class="suggestion-item"
|
|
2314
|
+
[class.focused]="idx === focusedIndex()"
|
|
2315
|
+
(click)="selectSuggestion(item)"
|
|
2316
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2317
|
+
role="option"
|
|
2318
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2319
|
+
>
|
|
2320
|
+
@if (item.icon) {
|
|
2321
|
+
<i [class]="item.icon" class="suggestion-icon"></i>
|
|
2322
|
+
}
|
|
2323
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2324
|
+
</li>
|
|
2325
|
+
}
|
|
2326
|
+
</ul>
|
|
2327
|
+
</div>
|
|
2328
|
+
}
|
|
2329
|
+
</div>
|
|
2330
|
+
}
|
|
2331
|
+
} @else {
|
|
2332
|
+
<!-- Desktop Full Input Mode -->
|
|
2333
|
+
<p-iconfield class="search-iconfield">
|
|
2334
|
+
<p-inputicon class="pi pi-search search-icon" (click)="onSearch()" />
|
|
2335
|
+
<input
|
|
2336
|
+
type="text"
|
|
2337
|
+
pInputText
|
|
2338
|
+
class="search-input w-full"
|
|
2339
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2340
|
+
[(ngModel)]="searchTerm"
|
|
2341
|
+
(keyup.enter)="handleEnter()"
|
|
2342
|
+
(keydown)="handleKeydown($event)"
|
|
2343
|
+
(focus)="onFocus()"
|
|
2344
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2345
|
+
#searchInput
|
|
2346
|
+
/>
|
|
2347
|
+
</p-iconfield>
|
|
2348
|
+
|
|
2349
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2350
|
+
<div class="search-dropdown">
|
|
2351
|
+
<ul class="suggestions-list">
|
|
2352
|
+
@for (item of suggestions(); track item.label; let idx = $index) {
|
|
2353
|
+
<li
|
|
2354
|
+
class="suggestion-item"
|
|
2355
|
+
[class.focused]="idx === focusedIndex()"
|
|
2356
|
+
(click)="selectSuggestion(item)"
|
|
2357
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2358
|
+
role="option"
|
|
2359
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2360
|
+
>
|
|
2361
|
+
@if (item.icon) {
|
|
2362
|
+
<i [class]="item.icon" class="suggestion-icon"></i>
|
|
2363
|
+
}
|
|
2364
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2365
|
+
</li>
|
|
2366
|
+
}
|
|
2367
|
+
</ul>
|
|
2368
|
+
</div>
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
</div>
|
|
2372
|
+
`, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--border-radius);box-shadow:0 4px 20px #00000026;z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--border-radius);box-shadow:0 4px 16px #0000001f;z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:4rem;left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"] }]
|
|
2373
|
+
}], ctorParameters: () => [], propDecorators: { searchWrapper: [{ type: i0.ViewChild, args: ['searchWrapper', { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
2374
|
+
|
|
1654
2375
|
class AppTopbar {
|
|
1655
2376
|
appConfig = inject(APP_CONFIG);
|
|
1656
2377
|
document = inject(DOCUMENT$1);
|
|
@@ -1662,7 +2383,12 @@ class AppTopbar {
|
|
|
1662
2383
|
languageSelectorComponent = inject(LAYOUT_LANGUAGE_SELECTOR, {
|
|
1663
2384
|
optional: true,
|
|
1664
2385
|
});
|
|
2386
|
+
searchAdapter = inject(LAYOUT_SEARCH_ADAPTER, {
|
|
2387
|
+
optional: true,
|
|
2388
|
+
});
|
|
2389
|
+
hasSearchAdapter = computed(() => !!this.searchAdapter, ...(ngDevMode ? [{ debugName: "hasSearchAdapter" }] : []));
|
|
1665
2390
|
configContainer = viewChild('configContainer', ...(ngDevMode ? [{ debugName: "configContainer" }] : []));
|
|
2391
|
+
navContainer = viewChild('navContainer', ...(ngDevMode ? [{ debugName: "navContainer" }] : []));
|
|
1666
2392
|
activePanel = signal(null, ...(ngDevMode ? [{ debugName: "activePanel" }] : []));
|
|
1667
2393
|
constructor() {
|
|
1668
2394
|
fromEvent(this.document, 'click')
|
|
@@ -1670,8 +2396,17 @@ class AppTopbar {
|
|
|
1670
2396
|
.subscribe((event) => {
|
|
1671
2397
|
this.handleOutsideClick(event);
|
|
1672
2398
|
});
|
|
2399
|
+
effect(() => {
|
|
2400
|
+
const nav = this.navContainer();
|
|
2401
|
+
if (nav && this.layoutService.isTopbar()) {
|
|
2402
|
+
fromEvent(nav.nativeElement, 'scroll')
|
|
2403
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2404
|
+
.subscribe(() => this.recalculateHoveredSubmenu(nav.nativeElement));
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
1673
2407
|
}
|
|
1674
2408
|
companyName = this.layoutService.companyName;
|
|
2409
|
+
displayLogo = this.layoutService.displayLogo;
|
|
1675
2410
|
enableCompanyFeature = isCompanyFeatureEnabled(this.appConfig);
|
|
1676
2411
|
toggleDarkMode() {
|
|
1677
2412
|
const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
|
|
@@ -1687,22 +2422,67 @@ class AppTopbar {
|
|
|
1687
2422
|
this.activePanel.set(null);
|
|
1688
2423
|
}
|
|
1689
2424
|
}
|
|
2425
|
+
recalculateHoveredSubmenu(navEl) {
|
|
2426
|
+
const hoveredItems = navEl.querySelectorAll('li:hover');
|
|
2427
|
+
hoveredItems.forEach((li) => {
|
|
2428
|
+
const link = li.querySelector(':scope > a');
|
|
2429
|
+
if (!link)
|
|
2430
|
+
return;
|
|
2431
|
+
const rect = link.getBoundingClientRect();
|
|
2432
|
+
const top = rect.bottom + 12;
|
|
2433
|
+
const left = rect.left - 12;
|
|
2434
|
+
li.style.setProperty('--submenu-left', `${left}px`);
|
|
2435
|
+
li.style.setProperty('--submenu-top', `${top}px`);
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
1690
2438
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1691
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppTopbar, isStandalone: true, selector: "app-topbar", viewQueries: [{ propertyName: "configContainer", first: true, predicate: ["configContainer"], descendants: true, isSignal: true }], ngImport: i0, template:
|
|
1692
|
-
<div
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2439
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppTopbar, isStandalone: true, selector: "app-topbar", viewQueries: [{ propertyName: "configContainer", first: true, predicate: ["configContainer"], descendants: true, isSignal: true }, { propertyName: "navContainer", first: true, predicate: ["navContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `<div class="layout-topbar">
|
|
2440
|
+
<div
|
|
2441
|
+
class="layout-topbar-logo-container"
|
|
2442
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2443
|
+
>
|
|
2444
|
+
@if (!layoutService.isTopbar() && layoutService.isMobile()) {
|
|
2445
|
+
<button
|
|
2446
|
+
class="layout-menu-button layout-topbar-action"
|
|
2447
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2448
|
+
(click)="layoutService.onMenuToggle()"
|
|
2449
|
+
>
|
|
2450
|
+
<i class="pi pi-bars"></i>
|
|
2451
|
+
</button>
|
|
2452
|
+
}
|
|
1700
2453
|
<a class="layout-topbar-logo" routerLink="/">
|
|
2454
|
+
@if (displayLogo()) {
|
|
2455
|
+
<img [src]="displayLogo()" [alt]="companyName()" class="logo-image" />
|
|
2456
|
+
}
|
|
1701
2457
|
<span>{{ companyName() }}</span>
|
|
1702
2458
|
</a>
|
|
2459
|
+
@if (!layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2460
|
+
<button
|
|
2461
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2462
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2463
|
+
(click)="layoutService.onMenuToggle()"
|
|
2464
|
+
>
|
|
2465
|
+
<i class="pi pi-bars"></i>
|
|
2466
|
+
</button>
|
|
2467
|
+
}
|
|
1703
2468
|
</div>
|
|
1704
2469
|
|
|
2470
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2471
|
+
<div
|
|
2472
|
+
#navContainer
|
|
2473
|
+
class="layout-topbar-nav-horizontal"
|
|
2474
|
+
[class.layout-topbar-nav-hidden]="
|
|
2475
|
+
!layoutService.layoutState().topbarMenuVisible
|
|
2476
|
+
"
|
|
2477
|
+
>
|
|
2478
|
+
<app-menu />
|
|
2479
|
+
</div>
|
|
2480
|
+
}
|
|
2481
|
+
|
|
1705
2482
|
<div class="layout-topbar-actions">
|
|
2483
|
+
@if (hasSearchAdapter()) {
|
|
2484
|
+
<app-searchbar />
|
|
2485
|
+
}
|
|
1706
2486
|
<div class="layout-config-menu">
|
|
1707
2487
|
<button
|
|
1708
2488
|
type="button"
|
|
@@ -1766,7 +2546,7 @@ class AppTopbar {
|
|
|
1766
2546
|
</div>
|
|
1767
2547
|
</div>
|
|
1768
2548
|
</div>
|
|
1769
|
-
</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: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { 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" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2549
|
+
</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: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }, { kind: "component", type: AppMenu, selector: "app-menu" }, { kind: "component", type: AppProfile, selector: "app-profile" }, { kind: "component", type: AppCompanyBranchSelector, selector: "app-company-branch-selector" }, { kind: "component", type: AppLauncher, selector: "app-launcher" }, { kind: "component", type: AppSearchbar, selector: "app-searchbar" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1770
2550
|
}
|
|
1771
2551
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppTopbar, decorators: [{
|
|
1772
2552
|
type: Component,
|
|
@@ -1778,25 +2558,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1778
2558
|
NgComponentOutlet,
|
|
1779
2559
|
TranslatePipe,
|
|
1780
2560
|
AppConfigurator,
|
|
2561
|
+
AppMenu,
|
|
1781
2562
|
AppProfile,
|
|
1782
2563
|
AppCompanyBranchSelector,
|
|
1783
2564
|
AppLauncher,
|
|
2565
|
+
AppSearchbar,
|
|
1784
2566
|
],
|
|
1785
|
-
template:
|
|
1786
|
-
<div
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2567
|
+
template: `<div class="layout-topbar">
|
|
2568
|
+
<div
|
|
2569
|
+
class="layout-topbar-logo-container"
|
|
2570
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2571
|
+
>
|
|
2572
|
+
@if (!layoutService.isTopbar() && layoutService.isMobile()) {
|
|
2573
|
+
<button
|
|
2574
|
+
class="layout-menu-button layout-topbar-action"
|
|
2575
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2576
|
+
(click)="layoutService.onMenuToggle()"
|
|
2577
|
+
>
|
|
2578
|
+
<i class="pi pi-bars"></i>
|
|
2579
|
+
</button>
|
|
2580
|
+
}
|
|
1794
2581
|
<a class="layout-topbar-logo" routerLink="/">
|
|
2582
|
+
@if (displayLogo()) {
|
|
2583
|
+
<img [src]="displayLogo()" [alt]="companyName()" class="logo-image" />
|
|
2584
|
+
}
|
|
1795
2585
|
<span>{{ companyName() }}</span>
|
|
1796
2586
|
</a>
|
|
2587
|
+
@if (!layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2588
|
+
<button
|
|
2589
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2590
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2591
|
+
(click)="layoutService.onMenuToggle()"
|
|
2592
|
+
>
|
|
2593
|
+
<i class="pi pi-bars"></i>
|
|
2594
|
+
</button>
|
|
2595
|
+
}
|
|
1797
2596
|
</div>
|
|
1798
2597
|
|
|
2598
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2599
|
+
<div
|
|
2600
|
+
#navContainer
|
|
2601
|
+
class="layout-topbar-nav-horizontal"
|
|
2602
|
+
[class.layout-topbar-nav-hidden]="
|
|
2603
|
+
!layoutService.layoutState().topbarMenuVisible
|
|
2604
|
+
"
|
|
2605
|
+
>
|
|
2606
|
+
<app-menu />
|
|
2607
|
+
</div>
|
|
2608
|
+
}
|
|
2609
|
+
|
|
1799
2610
|
<div class="layout-topbar-actions">
|
|
2611
|
+
@if (hasSearchAdapter()) {
|
|
2612
|
+
<app-searchbar />
|
|
2613
|
+
}
|
|
1800
2614
|
<div class="layout-config-menu">
|
|
1801
2615
|
<button
|
|
1802
2616
|
type="button"
|
|
@@ -1862,270 +2676,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1862
2676
|
</div>
|
|
1863
2677
|
</div>`,
|
|
1864
2678
|
}]
|
|
1865
|
-
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }] } });
|
|
1866
|
-
|
|
1867
|
-
class AppMenuitem {
|
|
1868
|
-
// Signal inputs
|
|
1869
|
-
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1870
|
-
index = input.required(...(ngDevMode ? [{ debugName: "index" }] : []));
|
|
1871
|
-
parentKey = input('', ...(ngDevMode ? [{ debugName: "parentKey" }] : []));
|
|
1872
|
-
// Injected services
|
|
1873
|
-
router = inject(Router);
|
|
1874
|
-
layoutService = inject(LayoutService);
|
|
1875
|
-
destroyRef = inject(DestroyRef);
|
|
1876
|
-
// State signals - private writable + public readonly pattern
|
|
1877
|
-
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
|
|
1878
|
-
active = this._active.asReadonly();
|
|
1879
|
-
// Computed signals
|
|
1880
|
-
key = computed(() => {
|
|
1881
|
-
const parent = this.parentKey();
|
|
1882
|
-
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
1883
|
-
}, ...(ngDevMode ? [{ debugName: "key" }] : []));
|
|
1884
|
-
routerLink = computed(() => {
|
|
1885
|
-
const menuItem = this.item();
|
|
1886
|
-
return menuItem.routerLink ?? [];
|
|
1887
|
-
}, ...(ngDevMode ? [{ debugName: "routerLink" }] : []));
|
|
1888
|
-
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
1889
|
-
routerLinkActiveOptions = computed(() => {
|
|
1890
|
-
const link = this.routerLink();
|
|
1891
|
-
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
1892
|
-
return {
|
|
1893
|
-
paths: pathsMatch,
|
|
1894
|
-
queryParams: 'ignored',
|
|
1895
|
-
matrixParams: 'ignored',
|
|
1896
|
-
fragment: 'ignored',
|
|
1897
|
-
};
|
|
1898
|
-
}, ...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : []));
|
|
1899
|
-
constructor() {
|
|
1900
|
-
// Subscribe to menu source changes
|
|
1901
|
-
this.layoutService.menuSource$
|
|
1902
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1903
|
-
.subscribe((value) => {
|
|
1904
|
-
Promise.resolve(null).then(() => {
|
|
1905
|
-
const currentKey = this.key();
|
|
1906
|
-
if (value.routeEvent) {
|
|
1907
|
-
this._active.set(value.key === currentKey ||
|
|
1908
|
-
value.key?.startsWith(currentKey + '-'));
|
|
1909
|
-
}
|
|
1910
|
-
else if (value.key !== currentKey &&
|
|
1911
|
-
!value.key?.startsWith(currentKey + '-')) {
|
|
1912
|
-
this._active.set(false);
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
});
|
|
1916
|
-
// Subscribe to menu reset
|
|
1917
|
-
this.layoutService.resetSource$
|
|
1918
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1919
|
-
.subscribe(() => this._active.set(false));
|
|
1920
|
-
// Subscribe to navigation events
|
|
1921
|
-
this.router.events
|
|
1922
|
-
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
1923
|
-
.subscribe(() => {
|
|
1924
|
-
if (this.item().routerLink) {
|
|
1925
|
-
this.updateActiveStateFromRoute();
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
|
-
// Effect to update active state on init when item has routerLink
|
|
1929
|
-
effect(() => {
|
|
1930
|
-
const menuItem = this.item();
|
|
1931
|
-
if (menuItem.routerLink) {
|
|
1932
|
-
this.updateActiveStateFromRoute();
|
|
1933
|
-
}
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
|
-
updateActiveStateFromRoute() {
|
|
1937
|
-
const link = this.routerLink();
|
|
1938
|
-
if (!link.length)
|
|
1939
|
-
return;
|
|
1940
|
-
// Use 'exact' for root path to avoid matching all routes
|
|
1941
|
-
// Use 'subset' for all other paths to match child routes
|
|
1942
|
-
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
1943
|
-
const isActive = this.router.isActive(link[0], {
|
|
1944
|
-
paths: pathsMatch,
|
|
1945
|
-
queryParams: 'ignored',
|
|
1946
|
-
matrixParams: 'ignored',
|
|
1947
|
-
fragment: 'ignored',
|
|
1948
|
-
});
|
|
1949
|
-
if (isActive) {
|
|
1950
|
-
this.layoutService.onMenuStateChange({
|
|
1951
|
-
key: this.key(),
|
|
1952
|
-
routeEvent: true,
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
itemClick() {
|
|
1957
|
-
if (this.item().children) {
|
|
1958
|
-
this._active.update((prev) => !prev);
|
|
1959
|
-
}
|
|
1960
|
-
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
1961
|
-
}
|
|
1962
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1963
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenuitem, isStandalone: true, selector: "[app-menuitem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, parentKey: { classPropertyName: "parentKey", publicName: "parentKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.active-menuitem": "active()" } }, ngImport: i0, template: `
|
|
1964
|
-
<ng-container>
|
|
1965
|
-
@if (item().children?.length) {
|
|
1966
|
-
<a (click)="itemClick()" tabindex="0" pRipple>
|
|
1967
|
-
@if (item().icon) {
|
|
1968
|
-
<lib-icon
|
|
1969
|
-
[icon]="item().icon!"
|
|
1970
|
-
[iconType]="item().iconType"
|
|
1971
|
-
class="layout-menuitem-icon"
|
|
1972
|
-
/>
|
|
1973
|
-
}
|
|
1974
|
-
<span class="layout-menuitem-text">{{ item().labelKey ? (item().labelKey! | translate) : item().label }}</span>
|
|
1975
|
-
@if (item().children) {
|
|
1976
|
-
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
1977
|
-
}
|
|
1978
|
-
</a>
|
|
1979
|
-
}
|
|
1980
|
-
@if (item().routerLink && !item().children?.length) {
|
|
1981
|
-
<a
|
|
1982
|
-
(click)="itemClick()"
|
|
1983
|
-
[routerLink]="routerLink()"
|
|
1984
|
-
routerLinkActive="active-route"
|
|
1985
|
-
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1986
|
-
tabindex="0"
|
|
1987
|
-
pRipple
|
|
1988
|
-
>
|
|
1989
|
-
@if (item().icon) {
|
|
1990
|
-
<lib-icon
|
|
1991
|
-
[icon]="item().icon!"
|
|
1992
|
-
[iconType]="item().iconType"
|
|
1993
|
-
class="layout-menuitem-icon"
|
|
1994
|
-
/>
|
|
1995
|
-
}
|
|
1996
|
-
<span class="layout-menuitem-text">{{ item().labelKey ? (item().labelKey! | translate) : item().label }}</span>
|
|
1997
|
-
</a>
|
|
1998
|
-
}
|
|
1999
|
-
@if (item().children) {
|
|
2000
|
-
<ul
|
|
2001
|
-
[class.submenu-expanded]="active()"
|
|
2002
|
-
[class.submenu-collapsed]="!active()"
|
|
2003
|
-
>
|
|
2004
|
-
@for (
|
|
2005
|
-
child of item().children;
|
|
2006
|
-
track 'menu' + child.id + '' + i;
|
|
2007
|
-
let i = $index
|
|
2008
|
-
) {
|
|
2009
|
-
<li
|
|
2010
|
-
app-menuitem
|
|
2011
|
-
[item]="child"
|
|
2012
|
-
[index]="i"
|
|
2013
|
-
[parentKey]="key()"
|
|
2014
|
-
></li>
|
|
2015
|
-
}
|
|
2016
|
-
</ul>
|
|
2017
|
-
}
|
|
2018
|
-
</ng-container>
|
|
2019
|
-
`, 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]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2020
|
-
}
|
|
2021
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, decorators: [{
|
|
2022
|
-
type: Component,
|
|
2023
|
-
args: [{ selector: '[app-menuitem]', imports: [IconComponent, RouterModule, RippleModule, TranslatePipe], template: `
|
|
2024
|
-
<ng-container>
|
|
2025
|
-
@if (item().children?.length) {
|
|
2026
|
-
<a (click)="itemClick()" tabindex="0" pRipple>
|
|
2027
|
-
@if (item().icon) {
|
|
2028
|
-
<lib-icon
|
|
2029
|
-
[icon]="item().icon!"
|
|
2030
|
-
[iconType]="item().iconType"
|
|
2031
|
-
class="layout-menuitem-icon"
|
|
2032
|
-
/>
|
|
2033
|
-
}
|
|
2034
|
-
<span class="layout-menuitem-text">{{ item().labelKey ? (item().labelKey! | translate) : item().label }}</span>
|
|
2035
|
-
@if (item().children) {
|
|
2036
|
-
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
2037
|
-
}
|
|
2038
|
-
</a>
|
|
2039
|
-
}
|
|
2040
|
-
@if (item().routerLink && !item().children?.length) {
|
|
2041
|
-
<a
|
|
2042
|
-
(click)="itemClick()"
|
|
2043
|
-
[routerLink]="routerLink()"
|
|
2044
|
-
routerLinkActive="active-route"
|
|
2045
|
-
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
2046
|
-
tabindex="0"
|
|
2047
|
-
pRipple
|
|
2048
|
-
>
|
|
2049
|
-
@if (item().icon) {
|
|
2050
|
-
<lib-icon
|
|
2051
|
-
[icon]="item().icon!"
|
|
2052
|
-
[iconType]="item().iconType"
|
|
2053
|
-
class="layout-menuitem-icon"
|
|
2054
|
-
/>
|
|
2055
|
-
}
|
|
2056
|
-
<span class="layout-menuitem-text">{{ item().labelKey ? (item().labelKey! | translate) : item().label }}</span>
|
|
2057
|
-
</a>
|
|
2058
|
-
}
|
|
2059
|
-
@if (item().children) {
|
|
2060
|
-
<ul
|
|
2061
|
-
[class.submenu-expanded]="active()"
|
|
2062
|
-
[class.submenu-collapsed]="!active()"
|
|
2063
|
-
>
|
|
2064
|
-
@for (
|
|
2065
|
-
child of item().children;
|
|
2066
|
-
track 'menu' + child.id + '' + i;
|
|
2067
|
-
let i = $index
|
|
2068
|
-
) {
|
|
2069
|
-
<li
|
|
2070
|
-
app-menuitem
|
|
2071
|
-
[item]="child"
|
|
2072
|
-
[index]="i"
|
|
2073
|
-
[parentKey]="key()"
|
|
2074
|
-
></li>
|
|
2075
|
-
}
|
|
2076
|
-
</ul>
|
|
2077
|
-
}
|
|
2078
|
-
</ng-container>
|
|
2079
|
-
`, host: {
|
|
2080
|
-
'[class.active-menuitem]': 'active()',
|
|
2081
|
-
}, 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"] }]
|
|
2082
|
-
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], parentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKey", required: false }] }] } });
|
|
2083
|
-
|
|
2084
|
-
/**
|
|
2085
|
-
* Main menu component that displays filtered menu items.
|
|
2086
|
-
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
2087
|
-
* The filtering happens internally in LayoutService via computed signal.
|
|
2088
|
-
*/
|
|
2089
|
-
class AppMenu {
|
|
2090
|
-
layoutService = inject(LayoutService);
|
|
2091
|
-
/**
|
|
2092
|
-
* Filtered menu items from layout service.
|
|
2093
|
-
* This signal is computed in LayoutService and automatically updates when:
|
|
2094
|
-
* - Raw menu changes (setMenu called)
|
|
2095
|
-
* - Permission state changes (user permissions updated)
|
|
2096
|
-
*/
|
|
2097
|
-
menuItems = this.layoutService.menu;
|
|
2098
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2099
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
|
|
2100
|
-
<ul>
|
|
2101
|
-
@for (
|
|
2102
|
-
item of menuItems();
|
|
2103
|
-
track 'menu' + item.id + '' + i;
|
|
2104
|
-
let i = $index
|
|
2105
|
-
) {
|
|
2106
|
-
<li app-menuitem [item]="item" [index]="i"></li>
|
|
2107
|
-
}
|
|
2108
|
-
</ul>
|
|
2109
|
-
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
|
|
2110
|
-
}
|
|
2111
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, decorators: [{
|
|
2112
|
-
type: Component,
|
|
2113
|
-
args: [{
|
|
2114
|
-
selector: 'app-menu',
|
|
2115
|
-
imports: [AppMenuitem, RouterModule],
|
|
2116
|
-
template: `<div class="layout-menu">
|
|
2117
|
-
<ul>
|
|
2118
|
-
@for (
|
|
2119
|
-
item of menuItems();
|
|
2120
|
-
track 'menu' + item.id + '' + i;
|
|
2121
|
-
let i = $index
|
|
2122
|
-
) {
|
|
2123
|
-
<li app-menuitem [item]="item" [index]="i"></li>
|
|
2124
|
-
}
|
|
2125
|
-
</ul>
|
|
2126
|
-
</div>`,
|
|
2127
|
-
}]
|
|
2128
|
-
}] });
|
|
2679
|
+
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }], navContainer: [{ type: i0.ViewChild, args: ['navContainer', { isSignal: true }] }] } });
|
|
2129
2680
|
|
|
2130
2681
|
class AppSidebar {
|
|
2131
2682
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -2177,11 +2728,14 @@ class AppLayout {
|
|
|
2177
2728
|
isOutsideClicked(event) {
|
|
2178
2729
|
const sidebarEl = this.document.querySelector('.layout-sidebar');
|
|
2179
2730
|
const topbarEl = this.document.querySelector('.layout-menu-button');
|
|
2731
|
+
const topbarNavEl = this.document.querySelector('.layout-topbar-nav');
|
|
2180
2732
|
const eventTarget = event.target;
|
|
2181
2733
|
return !(sidebarEl?.isSameNode(eventTarget) ||
|
|
2182
2734
|
sidebarEl?.contains(eventTarget) ||
|
|
2183
2735
|
topbarEl?.isSameNode(eventTarget) ||
|
|
2184
|
-
topbarEl?.contains(eventTarget)
|
|
2736
|
+
topbarEl?.contains(eventTarget) ||
|
|
2737
|
+
topbarNavEl?.isSameNode(eventTarget) ||
|
|
2738
|
+
topbarNavEl?.contains(eventTarget));
|
|
2185
2739
|
}
|
|
2186
2740
|
hideMenu() {
|
|
2187
2741
|
this.layoutService.updateLayoutState({
|
|
@@ -2207,9 +2761,11 @@ class AppLayout {
|
|
|
2207
2761
|
return {
|
|
2208
2762
|
'layout-overlay': config.menuMode === 'overlay',
|
|
2209
2763
|
'layout-static': config.menuMode === 'static',
|
|
2764
|
+
'layout-topbar-mode': config.menuMode === 'topbar',
|
|
2210
2765
|
'layout-static-inactive': state.staticMenuDesktopInactive && config.menuMode === 'static',
|
|
2211
2766
|
'layout-overlay-active': state.overlayMenuActive,
|
|
2212
2767
|
'layout-mobile-active': state.staticMenuMobileActive,
|
|
2768
|
+
'layout-topbar-nav-hidden': !state.topbarMenuVisible && config.menuMode === 'topbar',
|
|
2213
2769
|
};
|
|
2214
2770
|
}, ...(ngDevMode ? [{ debugName: "containerClass" }] : []));
|
|
2215
2771
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -2301,6 +2857,10 @@ const LAYOUT_MESSAGES = {
|
|
|
2301
2857
|
'menu.event.manager': 'Event Manager',
|
|
2302
2858
|
'menu.notifications': 'Notifications',
|
|
2303
2859
|
'menu.localization': 'Localization',
|
|
2860
|
+
'menu.projects': 'Projects',
|
|
2861
|
+
'menu.projects.active': 'Active Projects',
|
|
2862
|
+
'menu.projects.archived': 'Archived Projects',
|
|
2863
|
+
'menu.projects.reports': 'Project Reports',
|
|
2304
2864
|
// Profile
|
|
2305
2865
|
'layout.profile.title': 'Profile',
|
|
2306
2866
|
'layout.profile.profile.picture.alt': 'Profile picture',
|
|
@@ -2330,6 +2890,7 @@ const LAYOUT_MESSAGES = {
|
|
|
2330
2890
|
'layout.configurator.menu.mode': 'Menu Mode',
|
|
2331
2891
|
'layout.configurator.menu.mode.static': 'Static',
|
|
2332
2892
|
'layout.configurator.menu.mode.overlay': 'Overlay',
|
|
2893
|
+
'layout.configurator.menu.mode.topbar': 'Topbar',
|
|
2333
2894
|
// Topbar accessibility
|
|
2334
2895
|
'layout.topbar.toggle.menu': 'Toggle menu',
|
|
2335
2896
|
'layout.topbar.toggle.dark.mode': 'Toggle dark mode',
|
|
@@ -2348,6 +2909,8 @@ const LAYOUT_MESSAGES = {
|
|
|
2348
2909
|
'notification.empty': 'No notifications',
|
|
2349
2910
|
// Home/Dashboard
|
|
2350
2911
|
'home.where.users.from': 'Where users are from',
|
|
2912
|
+
'layout.topbar.search': 'Search',
|
|
2913
|
+
'layout.topbar.search.placeholder': 'Search for content...',
|
|
2351
2914
|
};
|
|
2352
2915
|
|
|
2353
2916
|
// Components
|
|
@@ -2356,5 +2919,5 @@ const LAYOUT_MESSAGES = {
|
|
|
2356
2919
|
* Generated bundle index. Do not edit.
|
|
2357
2920
|
*/
|
|
2358
2921
|
|
|
2359
|
-
export { AppCompanyBranchSelector, AppConfigurator, AppFloatingConfigurator, AppFooter, AppLauncher, AppLayout, AppMenu, AppMenuitem, AppProfile, AppSidebar, AppTopbar, GreenTheme, LAYOUT_AUTH_API, LAYOUT_AUTH_STATE, LAYOUT_IS_RTL, LAYOUT_LANGUAGE_SELECTOR, LAYOUT_LANGUAGE_SELECTOR_PANEL, LAYOUT_MESSAGES, LAYOUT_NOTIFICATION_BELL, LayoutPersistenceService, LayoutService, NavyBlueTheme, filterAppsByPermissions, filterMenuByPermissions };
|
|
2922
|
+
export { AppCompanyBranchSelector, AppConfigurator, AppFloatingConfigurator, AppFooter, AppLauncher, AppLayout, AppMenu, AppMenuitem, AppProfile, AppSearchbar, AppSidebar, AppTopbar, GreenTheme, LAYOUT_AUTH_API, LAYOUT_AUTH_STATE, LAYOUT_IS_RTL, LAYOUT_LANGUAGE_SELECTOR, LAYOUT_LANGUAGE_SELECTOR_PANEL, LAYOUT_MESSAGES, LAYOUT_NOTIFICATION_BELL, LAYOUT_SEARCHBAR, LAYOUT_SEARCH_ADAPTER, LayoutPersistenceService, LayoutService, NavyBlueTheme, filterAppsByPermissions, filterMenuByPermissions };
|
|
2360
2923
|
//# sourceMappingURL=flusys-ng-layout.mjs.map
|