@retalia/sidebar-navigation 21.1.13 → 21.1.14
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 +42 -42
- package/karma.conf.js +44 -0
- package/ng-package.json +8 -0
- package/package.json +14 -26
- package/{lib → src}/assets/fonts/Effra_Std_It.svg +5123 -5123
- package/{lib → src}/assets/fonts/Effra_Std_Md.svg +4128 -4128
- package/{lib → src}/assets/fonts/Effra_Std_Rg.svg +4690 -4690
- package/{lib → src}/assets/icons/sidebar-burger.svg +3 -3
- package/{lib → src}/assets/icons/sidebar-cross.svg +3 -3
- package/{lib → src}/assets/icons/subItem-arrow.svg +3 -3
- package/{lib → src}/assets/icons/topbar-info.svg +3 -3
- package/{lib → src}/assets/icons/topbar-logout.svg +4 -4
- package/{lib → src}/assets/icons/topbar-menu-burger.svg +5 -5
- package/{lib → src}/assets/icons/topbar-menu-close.svg +3 -3
- package/{lib → src}/assets/icons/topbar-notification.svg +3 -3
- package/{lib → src}/assets/icons/topbar-profile.svg +4 -4
- package/{lib → src}/assets/icons/topbar-settings.svg +3 -3
- package/{lib → src}/assets/icons/topmenu-arrow.svg +3 -3
- package/{lib → src}/assets/logo/_K3_imagine_White.svg +14 -14
- package/src/lib/data/support-routes.ts +56 -0
- package/src/lib/data/test-data.ts +628 -0
- package/src/lib/data/test-data2.ts +1742 -0
- package/src/lib/models/cookie-names.ts +12 -0
- package/src/lib/models/environment.ts +4 -0
- package/src/lib/models/http-response.ts +3 -0
- package/src/lib/models/module.ts +39 -0
- package/src/lib/navigation-lib.component.html +20 -0
- package/src/lib/navigation-lib.component.scss +30 -0
- package/src/lib/navigation-lib.component.ts +26 -0
- package/src/lib/navigation-lib.module.ts +26 -0
- package/src/lib/navigation-lib.service.ts +9 -0
- package/src/lib/services/auth.service.ts +39 -0
- package/src/lib/services/cookie.service.ts +33 -0
- package/src/lib/services/http.service.ts +26 -0
- package/src/lib/services/module.service.ts +153 -0
- package/src/lib/services/sub-item.service.ts +56 -0
- package/src/lib/sidebar/shop-modal/shop-modal.component.html +17 -0
- package/src/lib/sidebar/shop-modal/shop-modal.component.scss +73 -0
- package/src/lib/sidebar/shop-modal/shop-modal.component.ts +36 -0
- package/src/lib/sidebar/sidebar.component.html +86 -0
- package/src/lib/sidebar/sidebar.component.scss +260 -0
- package/src/lib/sidebar/sidebar.component.ts +267 -0
- package/src/lib/sidebar/sub-item/sub-item.component.html +18 -0
- package/src/lib/sidebar/sub-item/sub-item.component.scss +154 -0
- package/src/lib/sidebar/sub-item/sub-item.component.ts +61 -0
- package/src/lib/topbar/topbar.component.html +29 -0
- package/src/lib/topbar/topbar.component.scss +176 -0
- package/src/lib/topbar/topbar.component.ts +76 -0
- package/src/public-api.ts +14 -0
- package/src/styles/app.scss +53 -0
- package/src/test.ts +15 -0
- package/tsconfig.lib.json +21 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +17 -0
- package/fesm2022/retalia-sidebar-navigation.mjs +0 -1388
- package/fesm2022/retalia-sidebar-navigation.mjs.map +0 -1
- package/production-0.0.2.tgz +0 -0
- package/types/retalia-sidebar-navigation.d.ts +0 -232
- /package/{lib → src}/assets/fonts/Effra_Std_It.eot +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_It.ttf +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_It.woff +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_It.woff2 +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Md.eot +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Md.ttf +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Md.woff +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Md.woff2 +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Rg.eot +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Rg.ttf +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Rg.woff +0 -0
- /package/{lib → src}/assets/fonts/Effra_Std_Rg.woff2 +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Black.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-BlackItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Bold.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-BoldItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-ExtraBold.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-ExtraLight.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Italic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Light.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-LightItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Medium.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-MediumItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Regular.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-SemiBold.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-Thin.ttf +0 -0
- /package/{lib → src}/assets/fonts/Poppins-ThinItalic.ttf +0 -0
- /package/{lib → src}/assets/icons/topbar-avatar.png +0 -0
- /package/{lib → src}/assets/logo/full-white.png +0 -0
- /package/{lib → src}/assets/logo/full.png +0 -0
- /package/{lib → src}/assets/logo/mobile-white.png +0 -0
- /package/{lib → src}/assets/logo/mobile.png +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export enum CookieNames {
|
|
2
|
+
groupId = 'groupId',
|
|
3
|
+
id = 'id',
|
|
4
|
+
languageCode = 'languageCode',
|
|
5
|
+
portalApiToken = 'portalApiToken',
|
|
6
|
+
portalRefreshToken = 'portalRefreshToken',
|
|
7
|
+
recentlyLoggedIn = 'recentlyLoggedIn',
|
|
8
|
+
shopId = 'shopId',
|
|
9
|
+
tenantId = 'tenantId',
|
|
10
|
+
clientId = 'client_id',
|
|
11
|
+
isSidebarOpen = 'isSidebarOpen',
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ModulesInfo {
|
|
2
|
+
modulesDetails: ModuleItem[];
|
|
3
|
+
shops: Shop[];
|
|
4
|
+
flags?: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface ModuleItem {
|
|
7
|
+
id: number;
|
|
8
|
+
authorizationId: string;
|
|
9
|
+
name: string;
|
|
10
|
+
url: string;
|
|
11
|
+
relationName: string;
|
|
12
|
+
displayOrder: number;
|
|
13
|
+
entityAccess: number[]; // Shops
|
|
14
|
+
sidebarEntries: SidebarEntry[];
|
|
15
|
+
|
|
16
|
+
children?: SidebarEntry[]; // Remap sidebarEntries to children to allow for recursive fns
|
|
17
|
+
icon?: string;
|
|
18
|
+
translatedName?: string;
|
|
19
|
+
visible?: boolean; // to control visibility of sidebar items
|
|
20
|
+
active?: boolean; // to control active state on page load
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SidebarEntry {
|
|
24
|
+
moduleAuthorizationId: string;
|
|
25
|
+
authorizationId?: string;
|
|
26
|
+
name: string;
|
|
27
|
+
url: string;
|
|
28
|
+
children: SidebarEntry[];
|
|
29
|
+
|
|
30
|
+
icon?: string;
|
|
31
|
+
translatedName?: string;
|
|
32
|
+
visible?: boolean; // to control visibility of sidebar items
|
|
33
|
+
active?: boolean; // to control active state on page load
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Shop {
|
|
37
|
+
id: number;
|
|
38
|
+
description: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div id="root">
|
|
2
|
+
<div id="main">
|
|
3
|
+
<sidebar [environment]="environment" (selectedRoute)="routeSelected($event)"></sidebar>
|
|
4
|
+
<topbar [environment]="environment"></topbar>
|
|
5
|
+
@if (!useCustomPage) {
|
|
6
|
+
<div class="page-container">
|
|
7
|
+
<div class="page-body">
|
|
8
|
+
<div class="content">
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
}
|
|
14
|
+
@if (useCustomPage) {
|
|
15
|
+
<div class="content">
|
|
16
|
+
<slot></slot>
|
|
17
|
+
</div>
|
|
18
|
+
}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@import '../styles/app.scss';
|
|
2
|
+
|
|
3
|
+
#root {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-flow: column;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
#main {
|
|
9
|
+
display: flex;
|
|
10
|
+
height: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.page-container {
|
|
14
|
+
font-family: 'Effra';
|
|
15
|
+
padding: 0 24px;
|
|
16
|
+
margin: 0 auto;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.page-body {
|
|
20
|
+
padding-top: 25px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.content {
|
|
24
|
+
margin-top: 90px;
|
|
25
|
+
flex-grow: 1;
|
|
26
|
+
|
|
27
|
+
@include responsive(mobileonly) {
|
|
28
|
+
padding: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
EventEmitter,
|
|
4
|
+
Input,
|
|
5
|
+
Output,
|
|
6
|
+
ViewEncapsulation,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { Environment } from './models/environment';
|
|
9
|
+
import { SidebarEntry } from './models/module';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'sidebar-navigation',
|
|
13
|
+
templateUrl: './navigation-lib.component.html',
|
|
14
|
+
styleUrls: ['./navigation-lib.component.scss'],
|
|
15
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
16
|
+
standalone: false
|
|
17
|
+
})
|
|
18
|
+
export class NavigationLibComponent {
|
|
19
|
+
@Output() selectedRoute: EventEmitter<SidebarEntry> = new EventEmitter();
|
|
20
|
+
@Input() useCustomPage = false;
|
|
21
|
+
@Input() environment: Environment = <Environment>{};
|
|
22
|
+
|
|
23
|
+
public routeSelected(selectedItem): void {
|
|
24
|
+
this.selectedRoute.emit(selectedItem);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
|
4
|
+
import { NgxSmartModalModule } from 'ngx-smart-modal';
|
|
5
|
+
|
|
6
|
+
import { NavigationLibComponent } from './navigation-lib.component';
|
|
7
|
+
import { SidebarComponent } from './sidebar/sidebar.component';
|
|
8
|
+
import { TopbarComponent } from './topbar/topbar.component';
|
|
9
|
+
import { SubItemComponent } from './sidebar/sub-item/sub-item.component';
|
|
10
|
+
import { SubItemService } from './services/sub-item.service';
|
|
11
|
+
import { ShopModalComponent } from './sidebar/shop-modal/shop-modal.component';
|
|
12
|
+
|
|
13
|
+
import { ModuleService } from './services/module.service';
|
|
14
|
+
import { AuthService } from './services/auth.service';
|
|
15
|
+
import { CookieService } from './services/cookie.service';
|
|
16
|
+
|
|
17
|
+
@NgModule({ declarations: [
|
|
18
|
+
SidebarComponent,
|
|
19
|
+
SubItemComponent,
|
|
20
|
+
TopbarComponent,
|
|
21
|
+
NavigationLibComponent,
|
|
22
|
+
ShopModalComponent,
|
|
23
|
+
],
|
|
24
|
+
exports: [NavigationLibComponent], imports: [CommonModule,
|
|
25
|
+
NgxSmartModalModule.forRoot()], providers: [SubItemService, ModuleService, AuthService, CookieService, provideHttpClient(withInterceptorsFromDi())] })
|
|
26
|
+
export class NavigationLibModule {}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Inject, Injectable, DOCUMENT } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// @dynamic
|
|
5
|
+
@Injectable({
|
|
6
|
+
providedIn: 'root',
|
|
7
|
+
})
|
|
8
|
+
export class AuthService {
|
|
9
|
+
environmentUrl: string;
|
|
10
|
+
_refreshTokenStore: string;
|
|
11
|
+
_tokenStore: string;
|
|
12
|
+
|
|
13
|
+
constructor(@Inject(DOCUMENT) private document: Document) {}
|
|
14
|
+
|
|
15
|
+
public logout(): void {
|
|
16
|
+
this.clearCookies();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
clearCookies(): void {
|
|
20
|
+
let cookies = document.cookie.split('; ');
|
|
21
|
+
for (let c = 0; c < cookies.length; c++) {
|
|
22
|
+
let domain = window.location.hostname.split('.');
|
|
23
|
+
while (domain.length > 0) {
|
|
24
|
+
let cookieBase =
|
|
25
|
+
encodeURIComponent(cookies[c].split(';')[0].split('=')[0]) +
|
|
26
|
+
'=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=' +
|
|
27
|
+
domain.join('.') +
|
|
28
|
+
' ;path=';
|
|
29
|
+
let p = location.pathname.split('/');
|
|
30
|
+
document.cookie = cookieBase + '/';
|
|
31
|
+
while (p.length > 0) {
|
|
32
|
+
document.cookie = cookieBase + p.join('/');
|
|
33
|
+
p.pop();
|
|
34
|
+
}
|
|
35
|
+
domain.shift();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Injectable, Inject, DOCUMENT } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
set as setCookie,
|
|
5
|
+
get as getCookie,
|
|
6
|
+
remove as removeCookie,
|
|
7
|
+
} from 'es-cookie';
|
|
8
|
+
import { CookieNames } from '../models/cookie-names';
|
|
9
|
+
import { getDomain } from 'tldjs';
|
|
10
|
+
|
|
11
|
+
// @dynamic
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class CookieService {
|
|
14
|
+
constructor(@Inject(DOCUMENT) private document: Document) {}
|
|
15
|
+
|
|
16
|
+
private getDomain(): string {
|
|
17
|
+
let tld = getDomain(this.document.location.href);
|
|
18
|
+
tld = tld != null ? '.' + tld : '';
|
|
19
|
+
return tld;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getCookie(cookieName: CookieNames): string | undefined {
|
|
23
|
+
return getCookie(cookieName);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setCookie(cookieName: CookieNames, value: any): void {
|
|
27
|
+
setCookie(cookieName, value, { path: '/', domain: this.getDomain(), sameSite : 'none' , secure : true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
removeCookie(cookieName: CookieNames): void {
|
|
31
|
+
removeCookie(cookieName, { path: '/', domain: this.getDomain() });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HttpResponse } from '../models/http-response';
|
|
2
|
+
|
|
3
|
+
export async function httpGet<T>(
|
|
4
|
+
requestUrl: string,
|
|
5
|
+
tokenIn: string
|
|
6
|
+
): Promise<HttpResponse<T>> {
|
|
7
|
+
const token = getCookieValue('portalApiToken');
|
|
8
|
+
const response: HttpResponse<T> = await fetch(requestUrl, {
|
|
9
|
+
method: 'get',
|
|
10
|
+
headers: new Headers({
|
|
11
|
+
Authorization: 'Bearer ' + token,
|
|
12
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
response.parsedBody = response.ok
|
|
16
|
+
? (response.parsedBody = await response.json())
|
|
17
|
+
: [];
|
|
18
|
+
return response;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getCookieValue(cookieName: string) {
|
|
22
|
+
const b = document.cookie.match(
|
|
23
|
+
'(^|[^;]+)\\s*' + cookieName + '\\s*=\\s*([^;]+)'
|
|
24
|
+
);
|
|
25
|
+
return b ? b.pop() : '';
|
|
26
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { ModuleItem, ModulesInfo, SidebarEntry } from '../models/module';
|
|
3
|
+
import { httpGet } from './http.service';
|
|
4
|
+
import { Subject } from 'rxjs';
|
|
5
|
+
import { testModuleDetails } from '../data/test-data';
|
|
6
|
+
|
|
7
|
+
@Injectable({
|
|
8
|
+
providedIn: 'root',
|
|
9
|
+
})
|
|
10
|
+
export class ModuleService {
|
|
11
|
+
storedModuleFlags: string[] = [];
|
|
12
|
+
storedModuleFlagsChange: Subject<string[]> = new Subject<string[]>();
|
|
13
|
+
currentRouteName: Subject<string> = new Subject<string>();
|
|
14
|
+
currentModule;
|
|
15
|
+
modules: ModuleItem[];
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.storedModuleFlagsChange.subscribe((value) => {
|
|
19
|
+
this.storedModuleFlags = value;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async getUserModules(
|
|
24
|
+
gatewayUrl: string,
|
|
25
|
+
token: string
|
|
26
|
+
): Promise<ModulesInfo> {
|
|
27
|
+
if (gatewayUrl === 'TEST') {
|
|
28
|
+
this.storedModuleFlagsChange.next(testModuleDetails.flags);
|
|
29
|
+
return Promise.resolve(testModuleDetails);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const requestUrl = gatewayUrl + '/Module';
|
|
33
|
+
const response = await httpGet<ModulesInfo>(requestUrl, token);
|
|
34
|
+
this.storedModuleFlagsChange.next(response.parsedBody.flags);
|
|
35
|
+
return Promise.resolve(response.parsedBody);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public setNavigationTitle(selectedItem?: ModuleItem | SidebarEntry) {
|
|
39
|
+
var pathArray = window.location.pathname.split('/');
|
|
40
|
+
let translatedModuleName = '';
|
|
41
|
+
let item = selectedItem;
|
|
42
|
+
if (pathArray.length > 1 && !item) {
|
|
43
|
+
let currentPath = window.location.pathname;
|
|
44
|
+
item = this.findNestedUrlMatch(this.modules, currentPath);
|
|
45
|
+
item
|
|
46
|
+
? (translatedModuleName = item.translatedName || item.name)
|
|
47
|
+
: (translatedModuleName = pathArray[pathArray.length - 1]);
|
|
48
|
+
} else if (item) {
|
|
49
|
+
translatedModuleName = item.translatedName || item.name;
|
|
50
|
+
}
|
|
51
|
+
translatedModuleName === 'main' ? (translatedModuleName = '') : '';
|
|
52
|
+
this.currentRouteName.next(translatedModuleName);
|
|
53
|
+
|
|
54
|
+
if (item) {
|
|
55
|
+
this.setItemVisbility(item);
|
|
56
|
+
} else {
|
|
57
|
+
this.currentModule = this.modules.find(
|
|
58
|
+
(m) =>
|
|
59
|
+
m.url.toLowerCase().replace(/\//g, '') ===
|
|
60
|
+
pathArray[1].toLowerCase().replace(/\//g, '')
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public findNestedUrlMatch(subItems, urlPath: string) {
|
|
66
|
+
let nameMatch;
|
|
67
|
+
subItems.forEach((i: ModuleItem | SidebarEntry) => {
|
|
68
|
+
if (
|
|
69
|
+
i.url &&
|
|
70
|
+
i.url.replace(/\//g, '') &&
|
|
71
|
+
i.url.toLowerCase().replace(/\//g, '') ===
|
|
72
|
+
urlPath.toLowerCase().replace(/\//g, '')
|
|
73
|
+
) {
|
|
74
|
+
nameMatch = i;
|
|
75
|
+
i.active = true;
|
|
76
|
+
} else {
|
|
77
|
+
i.active = false;
|
|
78
|
+
const childMatch = this.findNestedUrlMatch(i.children, urlPath);
|
|
79
|
+
if (childMatch) {
|
|
80
|
+
nameMatch = childMatch;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return nameMatch;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public setItemVisbility(item): void {
|
|
89
|
+
let activeModule: ModuleItem = this.modules.find(
|
|
90
|
+
(m: ModuleItem) =>
|
|
91
|
+
m.authorizationId ===
|
|
92
|
+
(item.authorizationId || item.moduleAuthorizationId)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
this.currentModule ? '' : (this.currentModule = activeModule);
|
|
96
|
+
activeModule.children.map((se: SidebarEntry) => {
|
|
97
|
+
se.visible = true;
|
|
98
|
+
if (item.name !== se.name) {
|
|
99
|
+
let isCurrent = se.children.some((sec) => sec.name === item.name);
|
|
100
|
+
se.children.map((sec) => {
|
|
101
|
+
sec.visible = isCurrent;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public mapModuleItems(modules: ModuleItem[]): ModuleItem[] {
|
|
108
|
+
modules.sort(
|
|
109
|
+
(a, b) => a.displayOrder - b.displayOrder || a.name.localeCompare(b.name)
|
|
110
|
+
); // Alphabetically if modules share a displayOrder
|
|
111
|
+
const isSupportAdmin = this.storedModuleFlags.includes('SupportAdmin');
|
|
112
|
+
const isTenantAdmin = this.storedModuleFlags.includes('TenantAdmin');
|
|
113
|
+
let portalModule = modules.find((m) => m.authorizationId === 'portal');
|
|
114
|
+
isTenantAdmin ? '' : (modules = modules.filter((m) => m !== portalModule));
|
|
115
|
+
|
|
116
|
+
// modules = this.mapAdminItems(modules, isSupportAdmin); // Pending support for individual item flags
|
|
117
|
+
this.modules = modules.map((m) => {
|
|
118
|
+
if (m && m.sidebarEntries) {
|
|
119
|
+
m.children = JSON.parse(JSON.stringify(m.sidebarEntries));
|
|
120
|
+
m.sidebarEntries = [];
|
|
121
|
+
} else {
|
|
122
|
+
m.children = [];
|
|
123
|
+
}
|
|
124
|
+
m.visible = true;
|
|
125
|
+
m.sidebarEntries ? `` : (m.children = []);
|
|
126
|
+
m.url ? `` : (m.url = '/');
|
|
127
|
+
return m;
|
|
128
|
+
});
|
|
129
|
+
this.setNavigationTitle();
|
|
130
|
+
|
|
131
|
+
return this.modules;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public mapAdminItems(modules, isSupportAdmin: boolean): ModuleItem[] {
|
|
135
|
+
modules.forEach((m) => {
|
|
136
|
+
if (!isSupportAdmin && m.isAdminOnly) {
|
|
137
|
+
modules = modules.filter((i) => i.name !== m.name);
|
|
138
|
+
}
|
|
139
|
+
if (m.children) {
|
|
140
|
+
m.children = this.mapAdminItems(m.children, isSupportAdmin);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return modules;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public getCurrentModule(selectedItem: ModuleItem | SidebarEntry): ModuleItem {
|
|
148
|
+
var moduleRoute = window.location.pathname.split('/')[1];
|
|
149
|
+
if (!moduleRoute) return null;
|
|
150
|
+
this.setNavigationTitle(selectedItem);
|
|
151
|
+
return this.modules.filter(m => m.url.indexOf(moduleRoute) > 0)[0];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EventEmitter, Injectable } from '@angular/core';
|
|
2
|
+
import { ModuleItem } from '../models/module';
|
|
3
|
+
import { getDomain } from 'tldjs';
|
|
4
|
+
import { set as setCookie } from 'es-cookie';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class SubItemService {
|
|
8
|
+
subItemClickEvent = new EventEmitter();
|
|
9
|
+
_storage: Storage;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this._storage = window.localStorage;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public saveItemInfo(selectedModule: ModuleItem, shopId: number): void {
|
|
16
|
+
let moduleKey = `context${selectedModule.relationName}Id`;
|
|
17
|
+
let entity: any = selectedModule;
|
|
18
|
+
|
|
19
|
+
// The POS module is a special case and we dont want to change the shopId context when
|
|
20
|
+
// changing for the other modules. We only change the shopId context for the POS when
|
|
21
|
+
// explicitly clicking a new shopId on the POS module.
|
|
22
|
+
// For legacy reasons the POS uses the generic key 'shopId' and modules use 'contextShopId'.
|
|
23
|
+
if (selectedModule.authorizationId === 'pos') {
|
|
24
|
+
moduleKey = 'shopId';
|
|
25
|
+
entity = shopId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (
|
|
29
|
+
(selectedModule.authorizationId !== 'pos' &&
|
|
30
|
+
selectedModule.entityAccess.length > 0) ||
|
|
31
|
+
selectedModule.name === 'orb'
|
|
32
|
+
) {
|
|
33
|
+
entity = shopId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let tld = getDomain(window.location.href);
|
|
37
|
+
tld = tld !== null ? `.${tld}` : '';
|
|
38
|
+
|
|
39
|
+
// ? Sets the new data into storage, with a formatted domain
|
|
40
|
+
this._storage.setItem(moduleKey, entity.toString());
|
|
41
|
+
|
|
42
|
+
// ? TEMP: Sets the new cookie, with a formatted domain
|
|
43
|
+
setCookie(moduleKey, entity.toString(), {
|
|
44
|
+
path: '/',
|
|
45
|
+
domain: tld,
|
|
46
|
+
sameSite : 'none',
|
|
47
|
+
secure : true
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
subItemclicked(item) {
|
|
52
|
+
this.subItemClickEvent.emit(item);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// This service exists to allow the EventEmitter to bubble up from the recursive subItem component to the parent sidebar component.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<ngx-smart-modal #shopModal identifier="shopModal">
|
|
2
|
+
<h1 class="shopSelect">Shop Select</h1>
|
|
3
|
+
@if (availableShops) {
|
|
4
|
+
@if (availableShops.length > 8 || copyAvailableShops.length > 8) {
|
|
5
|
+
<input class="shopSearchInput" type="search" autocomplete="off" id="search" [value]="shopSearchValue"
|
|
6
|
+
required="false" [placeholder]="shopSearchPlaceholder" (keyup)="handleShopSearch($event)"
|
|
7
|
+
(click)="handleShopSearch($event)" />
|
|
8
|
+
}
|
|
9
|
+
<div class="shopButtons">
|
|
10
|
+
@for (shop of availableShops; track shop) {
|
|
11
|
+
<button class="shopButton"
|
|
12
|
+
[attr.data-testid]="'nav-shop' + shop.id"
|
|
13
|
+
(click)="shopSelected(shop)">{{shop.description}}</button>
|
|
14
|
+
}
|
|
15
|
+
</div>
|
|
16
|
+
}
|
|
17
|
+
</ngx-smart-modal>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
@import "../../../styles/app.scss";
|
|
2
|
+
|
|
3
|
+
@font-face {
|
|
4
|
+
font-family: 'Poppins';
|
|
5
|
+
src: url('../../../assets/fonts/Poppins-Regular.ttf') format('truetype');
|
|
6
|
+
font-weight: 400;
|
|
7
|
+
font-style: normal;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@font-face {
|
|
11
|
+
font-family: 'Poppins';
|
|
12
|
+
src: url('../../../assets/fonts/Poppins-Bold.ttf') format('truetype');
|
|
13
|
+
font-weight: 700;
|
|
14
|
+
font-style: normal;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@font-face {
|
|
18
|
+
font-family: 'Poppins';
|
|
19
|
+
src: url('../../../assets/fonts/Poppins-Italic.ttf') format('truetype');
|
|
20
|
+
font-weight: 400;
|
|
21
|
+
font-style: italic;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@font-face {
|
|
25
|
+
font-family: 'Poppins';
|
|
26
|
+
src: url('../../../assets/fonts/Poppins-SemiBold.ttf') format('truetype');
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
font-style: normal;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#shopModal {
|
|
32
|
+
direction: ltr;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.shopButtons {
|
|
36
|
+
display: block;
|
|
37
|
+
padding: 10px 0px;
|
|
38
|
+
margin: auto;
|
|
39
|
+
width: 100%;
|
|
40
|
+
text-align: center;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.shopButton {
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
min-width: 90px;
|
|
46
|
+
border-radius: 5px;
|
|
47
|
+
border: none;
|
|
48
|
+
outline: none;
|
|
49
|
+
padding: 5px 10px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.shopButton + .shopButton {
|
|
53
|
+
margin-left: 10px;
|
|
54
|
+
margin-bottom: 10px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.shopSearchInput {
|
|
58
|
+
display: block;
|
|
59
|
+
direction: ltr;
|
|
60
|
+
width: 90%;
|
|
61
|
+
height: inherit;
|
|
62
|
+
margin: auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
input:invalid {
|
|
66
|
+
box-shadow: none; /* prevent Mozilla Firefox from creating a red border */
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.shopSelect {
|
|
70
|
+
font-size: 18px;
|
|
71
|
+
font-family: "Poppins", sans-serif;
|
|
72
|
+
text-align: center;
|
|
73
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
import { NgxSmartModalService } from 'ngx-smart-modal';
|
|
3
|
+
import { Shop } from '../../models/module';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'shop-modal',
|
|
7
|
+
templateUrl: './shop-modal.component.html',
|
|
8
|
+
styleUrls: ['./shop-modal.component.scss'],
|
|
9
|
+
standalone: false
|
|
10
|
+
})
|
|
11
|
+
export class ShopModalComponent {
|
|
12
|
+
@Input() availableShops: Shop[] = [];
|
|
13
|
+
copyAvailableShops: Shop[] = [];
|
|
14
|
+
@Output() selectedShop: EventEmitter<Shop> = new EventEmitter();
|
|
15
|
+
@Input() shopSearchValue: any = '';
|
|
16
|
+
shopSearchPlaceholder = 'Search for a Shop ...';
|
|
17
|
+
|
|
18
|
+
constructor(public ngxSmartModalService: NgxSmartModalService) {}
|
|
19
|
+
|
|
20
|
+
shopSelected(shop): void {
|
|
21
|
+
this.selectedShop.emit(shop);
|
|
22
|
+
this.ngxSmartModalService.getModal('shopModal').close();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
handleShopSearch(e): void {
|
|
26
|
+
if (this.copyAvailableShops.length < 1) {
|
|
27
|
+
this.copyAvailableShops = [...this.availableShops];
|
|
28
|
+
}
|
|
29
|
+
this.shopSearchValue = e && e.target ? e.target.value : '';
|
|
30
|
+
this.availableShops = this.copyAvailableShops.filter((shop) =>
|
|
31
|
+
shop.description
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.includes(this.shopSearchValue.toLowerCase())
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|