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