@skysoftware-co/bayan-core-widgets-ui 0.0.3 → 0.0.6

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.
Files changed (60) hide show
  1. package/README.md +2 -2
  2. package/ng-package.json +7 -0
  3. package/package.json +5 -1
  4. package/src/assets/i18n/ar.json +725 -0
  5. package/src/assets/i18n/en.json +683 -0
  6. package/src/assets/i18n/fr.json +687 -0
  7. package/src/lib/shared/common-methods/navigation.utils.ts +21 -0
  8. package/src/lib/shared/menu.dtos.ts +107 -0
  9. package/src/lib/shared/menu.service.ts +157 -0
  10. package/src/lib/top-menu-widget/components/about-dialog-widget/about-dialog-widget.component.html +37 -0
  11. package/src/lib/top-menu-widget/components/about-dialog-widget/about-dialog-widget.component.ts +68 -0
  12. package/src/lib/top-menu-widget/components/change-password-widget/change-password-widget.component.html +56 -0
  13. package/src/lib/top-menu-widget/components/change-password-widget/change-password-widget.component.ts +158 -0
  14. package/src/lib/top-menu-widget/components/global-search-widget/global-search-widget.component.html +39 -0
  15. package/src/lib/top-menu-widget/components/global-search-widget/global-search-widget.component.ts +153 -0
  16. package/src/lib/top-menu-widget/components/item-widget/item-widget.component.html +39 -0
  17. package/src/lib/top-menu-widget/components/item-widget/item-widget.component.ts +95 -0
  18. package/src/lib/top-menu-widget/components/notifications-widget/notifications-widget.component.html +10 -0
  19. package/src/lib/top-menu-widget/components/notifications-widget/notifications-widget.component.ts +89 -0
  20. package/src/lib/top-menu-widget/components/settings-widget/settings-widget.component.html +119 -0
  21. package/src/lib/top-menu-widget/components/settings-widget/settings-widget.component.ts +233 -0
  22. package/src/lib/top-menu-widget/components/user-panel-widget/user-panel-widget.component.html +53 -0
  23. package/src/lib/top-menu-widget/components/user-panel-widget/user-panel-widget.component.ts +140 -0
  24. package/src/lib/top-menu-widget/top-menu-widget.component.html +103 -0
  25. package/src/lib/top-menu-widget/top-menu-widget.component.ts +148 -0
  26. package/src/lib/top-menu-widget/top-menu-widget.models.ts +29 -0
  27. package/src/lib/top-menu-widget/top-menu-widget.styles.css +576 -0
  28. package/{public-api.d.ts → src/public-api.ts} +7 -4
  29. package/tsconfig.lib.json +16 -0
  30. package/tsconfig.lib.prod.json +9 -0
  31. package/tsconfig.spec.json +13 -0
  32. package/fesm2022/skysoftware-co-bayan-core-widgets-ui.mjs +0 -1092
  33. package/fesm2022/skysoftware-co-bayan-core-widgets-ui.mjs.map +0 -1
  34. package/index.d.ts +0 -6
  35. package/lib/shared/common-methods/navigation.utils.d.ts +0 -4
  36. package/lib/shared/common-methods/navigation.utils.d.ts.map +0 -1
  37. package/lib/shared/menu.dtos.d.ts +0 -91
  38. package/lib/shared/menu.dtos.d.ts.map +0 -1
  39. package/lib/shared/menu.service.d.ts +0 -24
  40. package/lib/shared/menu.service.d.ts.map +0 -1
  41. package/lib/top-menu-widget/components/about-dialog-widget/about-dialog-widget.component.d.ts +0 -18
  42. package/lib/top-menu-widget/components/about-dialog-widget/about-dialog-widget.component.d.ts.map +0 -1
  43. package/lib/top-menu-widget/components/change-password-widget/change-password-widget.component.d.ts +0 -30
  44. package/lib/top-menu-widget/components/change-password-widget/change-password-widget.component.d.ts.map +0 -1
  45. package/lib/top-menu-widget/components/global-search-widget/global-search-widget.component.d.ts +0 -59
  46. package/lib/top-menu-widget/components/global-search-widget/global-search-widget.component.d.ts.map +0 -1
  47. package/lib/top-menu-widget/components/item-widget/item-widget.component.d.ts +0 -29
  48. package/lib/top-menu-widget/components/item-widget/item-widget.component.d.ts.map +0 -1
  49. package/lib/top-menu-widget/components/notifications-widget/notifications-widget.component.d.ts +0 -23
  50. package/lib/top-menu-widget/components/notifications-widget/notifications-widget.component.d.ts.map +0 -1
  51. package/lib/top-menu-widget/components/settings-widget/settings-widget.component.d.ts +0 -37
  52. package/lib/top-menu-widget/components/settings-widget/settings-widget.component.d.ts.map +0 -1
  53. package/lib/top-menu-widget/components/user-panel-widget/user-panel-widget.component.d.ts +0 -43
  54. package/lib/top-menu-widget/components/user-panel-widget/user-panel-widget.component.d.ts.map +0 -1
  55. package/lib/top-menu-widget/top-menu-widget.component.d.ts +0 -76
  56. package/lib/top-menu-widget/top-menu-widget.component.d.ts.map +0 -1
  57. package/lib/top-menu-widget/top-menu-widget.models.d.ts +0 -36
  58. package/lib/top-menu-widget/top-menu-widget.models.d.ts.map +0 -1
  59. package/public-api.d.ts.map +0 -1
  60. package/skysoftware-co-bayan-core-widgets-ui.d.ts.map +0 -1
@@ -0,0 +1,21 @@
1
+ export function resolveUrl(baseUrl: string | null | undefined, url: string | null | undefined): string | null {
2
+ if (!url) {
3
+ return null;
4
+ }
5
+
6
+ return baseUrl ? joinUrl(baseUrl, url) : url;
7
+ }
8
+
9
+ export function isExternalNavigation(baseUrl: string | null | undefined, url: string): boolean {
10
+ return isAbsoluteUrl(url) || (!!baseUrl && isAbsoluteUrl(baseUrl));
11
+ }
12
+
13
+ export function joinUrl(baseUrl: string, url: string): string {
14
+ const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
15
+ const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
16
+ return `${normalizedBaseUrl}${normalizedUrl}`;
17
+ }
18
+
19
+ function isAbsoluteUrl(url: string): boolean {
20
+ return /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(url);
21
+ }
@@ -0,0 +1,107 @@
1
+ export interface ApiEnvelope<T> {
2
+ ResponseData: T;
3
+ ApiVersion: number;
4
+ }
5
+
6
+ export interface TopMenuShortcut {
7
+ MenuName: string;
8
+ MenuUrl: string | null;
9
+ Children: TopMenuShortcut[];
10
+ }
11
+
12
+ export interface SystemMenu {
13
+ MenuName: string;
14
+ MenuUrl: string | null;
15
+ IconClass: string | null;
16
+ Expanded: boolean;
17
+ Children: SystemMenu[];
18
+ }
19
+
20
+ export interface GlobalSearchMenu {
21
+ MenuTitle: string;
22
+ MenuSubTitle: string;
23
+ IconClass: string;
24
+ MenuUrl: string;
25
+ IsTranslatable: boolean;
26
+ }
27
+
28
+ export interface PropertyOption {
29
+ PropertyId: number;
30
+ PropertyName: string;
31
+ }
32
+
33
+ export interface UserPanel {
34
+ UserDisplayName: string;
35
+ ActivePropertyId: number;
36
+ ActivePropertyName: string;
37
+ PhotoPath: string | null;
38
+ NameInitials: string;
39
+ IsSSOLogin: boolean;
40
+ Properties: PropertyOption[];
41
+ }
42
+
43
+ export interface SwitchPropertyRequest {
44
+ PropertyId: number;
45
+ }
46
+
47
+ export interface SwitchPropertyResult {
48
+ PropertyId: number;
49
+ }
50
+
51
+ export enum EmployeeNamesModeOption {
52
+ ShortNames = 1,
53
+ StandardNames = 2,
54
+ FullNames = 3,
55
+ }
56
+
57
+ export interface AppFramePreferences {
58
+ UseAlternateNames: boolean;
59
+ DisplayAlternateNames: boolean;
60
+ EmployeeNamesMode: EmployeeNamesModeOption;
61
+ VersionNumber: string;
62
+ }
63
+
64
+ export interface SetDisplayAlternateNamesRequest {
65
+ DisplayAlternateNames: boolean;
66
+ }
67
+
68
+ export interface SetEmployeeNamesModeRequest {
69
+ EmployeeNamesMode: EmployeeNamesModeOption;
70
+ }
71
+
72
+ export enum BlockableModule {
73
+ HumanResources = 4,
74
+ Timekeeping = 5,
75
+ }
76
+
77
+ export interface ModuleBlockState {
78
+ IsBlocked: boolean;
79
+ UserCanBlockModule: boolean;
80
+ }
81
+
82
+ export interface BlockReleaseModuleRequest {
83
+ Module: BlockableModule;
84
+ }
85
+
86
+ export interface NotificationsSummary {
87
+ TotalNotificationsCount: number;
88
+ NotificationsUrl: string;
89
+ }
90
+
91
+ export interface ChangePasswordRequest {
92
+ CurrentPassword: string;
93
+ NewPassword: string;
94
+ }
95
+
96
+ export enum SystemModule {
97
+ Corporate = 1,
98
+ Cluster = 2,
99
+ Setup = 3,
100
+ HumanResources = 4,
101
+ Timekeeping = 5,
102
+ Payroll = 6,
103
+ Training = 7,
104
+ Workflow = 8,
105
+ Appraisal = 9,
106
+ BusinessIntelligence = 10,
107
+ }
@@ -0,0 +1,157 @@
1
+ import { HttpClient, HttpParams } from '@angular/common/http';
2
+ import { Injectable, inject } from '@angular/core';
3
+ import { catchError, map, Observable, throwError } from 'rxjs';
4
+ import {
5
+ ApiEnvelope,
6
+ AppFramePreferences,
7
+ BlockableModule,
8
+ BlockReleaseModuleRequest,
9
+ ChangePasswordRequest,
10
+ GlobalSearchMenu,
11
+ ModuleBlockState,
12
+ NotificationsSummary,
13
+ SetDisplayAlternateNamesRequest,
14
+ SetEmployeeNamesModeRequest,
15
+ SwitchPropertyRequest,
16
+ SwitchPropertyResult,
17
+ SystemMenu,
18
+ SystemModule,
19
+ TopMenuShortcut,
20
+ UserPanel,
21
+ } from './menu.dtos';
22
+
23
+ @Injectable({
24
+ providedIn: 'root',
25
+ })
26
+ export class BayanCoreTopMenuService {
27
+ private readonly http = inject(HttpClient);
28
+
29
+ private unwrap<T>(obs: Observable<ApiEnvelope<T>>): Observable<T> {
30
+ return obs.pipe(
31
+ map((env) => {
32
+ if (!env.ResponseData) {
33
+ throw new Error('No data received');
34
+ }
35
+
36
+ return env.ResponseData;
37
+ }),
38
+ catchError((error) => throwError(() => error)),
39
+ );
40
+ }
41
+
42
+ private getBaseUrl(baseUrl: string): string {
43
+ let url = baseUrl.replace('/api','');
44
+ return `${url.replace(/\/$/, '')}/api/app-frame`;
45
+ }
46
+
47
+ getMicroserviceMenusByProperty(
48
+ baseUrl: string,
49
+ propertyId: number | string,
50
+ systemModule: SystemModule,
51
+ ): Observable<SystemMenu[]> {
52
+ const params = new HttpParams()
53
+ .set('PropertyId', propertyId)
54
+ .set('SystemModule', systemModule);
55
+
56
+ return this.unwrap(
57
+ this.http.get<ApiEnvelope<SystemMenu[]>>(`${this.getBaseUrl(baseUrl)}/left-menu`, { params }),
58
+ );
59
+ }
60
+
61
+ getMicroserviceTopMenusShortcut(
62
+ baseUrl: string,
63
+ propertyId: number | string,
64
+ systemModule: SystemModule,
65
+ ): Observable<TopMenuShortcut[]> {
66
+ const params = new HttpParams()
67
+ .set('PropertyId', propertyId)
68
+ .set('SystemModule', systemModule);
69
+
70
+ return this.unwrap(
71
+ this.http.get<ApiEnvelope<TopMenuShortcut[]>>(`${this.getBaseUrl(baseUrl)}/top-shortcuts`, {
72
+ params,
73
+ }),
74
+ );
75
+ }
76
+
77
+ getGlobalSearchMenus(baseUrl: string, propertyId?: number): Observable<GlobalSearchMenu[]> {
78
+ let params = new HttpParams();
79
+
80
+ if (propertyId != null) {
81
+ params = params.set('PropertyId', propertyId);
82
+ }
83
+
84
+ return this.unwrap(
85
+ this.http.get<ApiEnvelope<GlobalSearchMenu[]>>(
86
+ `${this.getBaseUrl(baseUrl)}/global-search-menu`,
87
+ { params },
88
+ ),
89
+ );
90
+ }
91
+
92
+ getUserPanel(baseUrl: string): Observable<UserPanel> {
93
+ return this.unwrap(this.http.get<ApiEnvelope<UserPanel>>(`${this.getBaseUrl(baseUrl)}/user-panel`));
94
+ }
95
+
96
+ switchProperty(baseUrl: string, request: SwitchPropertyRequest): Observable<SwitchPropertyResult> {
97
+ return this.unwrap(
98
+ this.http.post<ApiEnvelope<SwitchPropertyResult>>(
99
+ `${this.getBaseUrl(baseUrl)}/switch-property`,
100
+ request,
101
+ ),
102
+ );
103
+ }
104
+
105
+ getPreferences(baseUrl: string): Observable<AppFramePreferences> {
106
+ return this.unwrap(
107
+ this.http.get<ApiEnvelope<AppFramePreferences>>(`${this.getBaseUrl(baseUrl)}/preferences`),
108
+ );
109
+ }
110
+
111
+ setDisplayAlternateNames(
112
+ baseUrl: string,
113
+ request: SetDisplayAlternateNamesRequest,
114
+ ): Observable<void> {
115
+ return this.http.post<void>(`${this.getBaseUrl(baseUrl)}/preferences/alternate-names`, request);
116
+ }
117
+
118
+ setEmployeeNamesMode(baseUrl: string, request: SetEmployeeNamesModeRequest): Observable<void> {
119
+ return this.http.post<void>(
120
+ `${this.getBaseUrl(baseUrl)}/preferences/employee-names-mode`,
121
+ request,
122
+ );
123
+ }
124
+
125
+ getModuleBlockState(
126
+ baseUrl: string,
127
+ systemModule: SystemModule | BlockableModule,
128
+ ): Observable<ModuleBlockState> {
129
+ const params = new HttpParams().set('SystemModule', systemModule);
130
+
131
+ return this.unwrap(
132
+ this.http.get<ApiEnvelope<ModuleBlockState>>(`${this.getBaseUrl(baseUrl)}/module-block-state`, {
133
+ params,
134
+ }),
135
+ );
136
+ }
137
+
138
+ blockModule(baseUrl: string, request: BlockReleaseModuleRequest): Observable<void> {
139
+ return this.http.post<void>(`${this.getBaseUrl(baseUrl)}/block-module`, request);
140
+ }
141
+
142
+ releaseModule(baseUrl: string, request: BlockReleaseModuleRequest): Observable<void> {
143
+ return this.http.post<void>(`${this.getBaseUrl(baseUrl)}/release-module`, request);
144
+ }
145
+
146
+ getNotificationsSummary(baseUrl: string): Observable<NotificationsSummary> {
147
+ return this.unwrap(
148
+ this.http.get<ApiEnvelope<NotificationsSummary>>(
149
+ `${this.getBaseUrl(baseUrl)}/notifications-summary`,
150
+ ),
151
+ );
152
+ }
153
+
154
+ changePassword(baseUrl: string, request: ChangePasswordRequest): Observable<boolean> {
155
+ return this.http.post<boolean>(`${this.getBaseUrl(baseUrl)}/change-password`, request);
156
+ }
157
+ }
@@ -0,0 +1,37 @@
1
+ @if (visible) {
2
+ <div [class]="backdropClass" (click)="onBackdropClick($event)">
3
+ <div [class]="modalClass" role="dialog" aria-labelledby="topMenuAboutDialogTitle" aria-modal="true">
4
+ <div [class]="headerClass">
5
+ <h4 [class]="titleClass" id="topMenuAboutDialogTitle">{{ config.title || 'AboutSkyBayan' | translate }}</h4>
6
+ <button type="button" [class]="closeBtnClass" (click)="closeDialog()" [attr.aria-label]="(config.closeButtonLabel || 'Close') | translate">
7
+ <span aria-hidden="true">&times;</span>
8
+ </button>
9
+ </div>
10
+
11
+ <div [class]="bodyClass">
12
+ <div class="row align-items-start">
13
+ <div class="col-4">
14
+ @if (config.logoUrl) {
15
+ <img [src]="config.logoUrl" alt="Logo" [class]="logoClass">
16
+ }
17
+ </div>
18
+ <div class="col-8">
19
+ <h3 [class]="productNameClass">{{ (config.title || 'AboutSkyBayan') | translate }}</h3>
20
+ <p [class]="versionTextClass">{{ (config.versionLabel || 'Version') | translate }} {{ config.version || '' }}</p>
21
+ <p [class]="upToDateClass">
22
+ <fa-icon [icon]="checkCircleIcon" class="text-info me-1"></fa-icon>
23
+ <small class="text-muted">{{ (config.statusLabel || 'SkyBayanUpToDate') | translate }}</small>
24
+ </p>
25
+ <p [class]="copyrightClass">{{ (config.copyright || '') | translate }}</p>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div [class]="actionsClass">
31
+ <button type="button" [class]="actionBtnClass" (click)="openLicense()">{{ (config.licenseButtonLabel || 'LicenseInformation') | translate }}</button>
32
+ <button type="button" [class]="actionBtnClass" (click)="openReleaseNotes()">{{ (config.releaseNotesButtonLabel || 'ReleaseNotes') | translate }}</button>
33
+ <button type="button" [class]="actionBtnClass" (click)="openSupport()">{{ (config.supportButtonLabel || 'TechnicalSupport') | translate }}</button>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ }
@@ -0,0 +1,68 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
3
+ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
4
+ import { faCheckCircle } from '@fortawesome/pro-solid-svg-icons';
5
+ import { TranslatePipe } from '@skysoftware-co/sky-components-ui';
6
+ import { TopMenuWidgetAboutDialogConfig } from '../../top-menu-widget.models';
7
+
8
+ @Component({
9
+ selector: 'bayan-core-about-dialog-widget',
10
+ standalone: true,
11
+ imports: [CommonModule, FontAwesomeModule, TranslatePipe],
12
+ templateUrl: './about-dialog-widget.component.html',
13
+ styleUrls: ['../../top-menu-widget.styles.css'],
14
+ changeDetection: ChangeDetectionStrategy.OnPush,
15
+ })
16
+ export class BayanCoreAboutDialogWidgetComponent {
17
+
18
+ @Input() baseUrl = '';
19
+ @Input() visible = false;
20
+ @Input({ required: true }) config!: TopMenuWidgetAboutDialogConfig;
21
+ @Input() licenseUrl = '';
22
+ @Input() releaseNotesUrl = '';
23
+ @Input() supportUrl = 'https://skyits.com/contact';
24
+
25
+ // CSS class attributes as @Input with defaults
26
+ @Input() backdropClass: string = 'about-backdrop';
27
+ @Input() modalClass: string = 'about-modal';
28
+ @Input() headerClass: string = 'about-modal-header';
29
+ @Input() titleClass: string = 'about-modal-title';
30
+ @Input() closeBtnClass: string = 'about-close-btn';
31
+ @Input() bodyClass: string = 'about-modal-body';
32
+ @Input() logoClass: string = 'img-fluid about-logo';
33
+ @Input() productNameClass: string = 'about-product-name';
34
+ @Input() versionTextClass: string = 'about-version-text mb-1';
35
+ @Input() upToDateClass: string = 'about-up-to-date mb-2';
36
+ @Input() copyrightClass: string = 'about-copyright mb-0';
37
+ @Input() actionsClass: string = 'about-modal-actions';
38
+ @Input() actionBtnClass: string = 'about-action-btn';
39
+
40
+ @Output() visibleChange = new EventEmitter<boolean>();
41
+ @Output() licenseClick = new EventEmitter<void>();
42
+ @Output() releaseNotesClick = new EventEmitter<void>();
43
+ @Output() supportClick = new EventEmitter<void>();
44
+
45
+ readonly checkCircleIcon = faCheckCircle;
46
+
47
+ onBackdropClick(event: MouseEvent): void {
48
+ if ((event.target as HTMLElement).classList.contains('about-backdrop')) {
49
+ this.closeDialog();
50
+ }
51
+ }
52
+
53
+ closeDialog(): void {
54
+ this.visibleChange.emit(false);
55
+ }
56
+
57
+ openLicense() {
58
+ window.open(this.licenseUrl, '_blank');
59
+ }
60
+
61
+ openReleaseNotes() {
62
+ window.open(this.releaseNotesUrl, '_blank');
63
+ }
64
+
65
+ openSupport() {
66
+ window.open(this.supportUrl, '_blank');
67
+ }
68
+ }
@@ -0,0 +1,56 @@
1
+ <dx-popup
2
+ [visible]="visible"
3
+ (onHiding)="closeDialog()"
4
+ [title]="config.title | translate"
5
+ [showCloseButton]="true"
6
+ [width]="450"
7
+ [height]="'auto'"
8
+ [dragEnabled]="false"
9
+ [shading]="true"
10
+ shadingColor="rgba(0,0,0,0.4)">
11
+ <div *dxTemplate="let data of 'content'">
12
+ <dx-form
13
+ #changePasswordForm
14
+ [formData]="formData"
15
+ labelLocation="top"
16
+ [showColonAfterLabel]="false"
17
+ class="mb-3 mx-2">
18
+ <dxi-item
19
+ dataField="currentPassword"
20
+ editorType="dxTextBox"
21
+ [editorOptions]="getCurrentPasswordEditorOptions()">
22
+ <dxo-label [text]="config.currentPasswordLabel | translate"></dxo-label>
23
+ <dxi-validation-rule type="required"></dxi-validation-rule>
24
+ </dxi-item>
25
+
26
+ <dxi-item
27
+ dataField="newPassword"
28
+ editorType="dxTextBox"
29
+ [editorOptions]="getNewPasswordEditorOptions()">
30
+ <dxo-label [text]="config.newPasswordLabel | translate"></dxo-label>
31
+ <dxi-validation-rule type="required"></dxi-validation-rule>
32
+ </dxi-item>
33
+
34
+ <dxi-item
35
+ dataField="confirmNewPassword"
36
+ editorType="dxTextBox"
37
+ [editorOptions]="getConfirmPasswordEditorOptions()">
38
+ <dxo-label [text]="config.confirmNewPasswordLabel | translate"></dxo-label>
39
+ <dxi-validation-rule type="required"></dxi-validation-rule>
40
+ <dxi-validation-rule
41
+ type="compare"
42
+ [comparisonTarget]="passwordComparison">
43
+ </dxi-validation-rule>
44
+ </dxi-item>
45
+ </dx-form>
46
+
47
+ <sky-footer-actions
48
+ [primaryButtonText]="config.primaryButtonText | translate"
49
+ [showSecondaryButton]="false"
50
+ [isLoading]="isSubmitting"
51
+ [disabledPrimaryButton]="isSubmitting || !isChangePasswordValid()"
52
+ (PrimaryButtonClick)="onSaveButtonClick()"
53
+ [errorMessage]="errorMessage">
54
+ </sky-footer-actions>
55
+ </div>
56
+ </dx-popup>
@@ -0,0 +1,158 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild, signal, WritableSignal, inject } from '@angular/core';
3
+ import { DxFormComponent, DxFormModule, DxPopupModule } from 'devextreme-angular';
4
+ import { SkyFooterActionsComponent, TranslatePipe } from '@skysoftware-co/sky-components-ui';
5
+ import {
6
+ TopMenuWidgetChangePasswordDialogConfig,
7
+ TopMenuWidgetChangePasswordPayload,
8
+ } from '../../top-menu-widget.models';
9
+ import { BayanCoreTopMenuService } from '../../../shared/menu.service';
10
+ import { SkyAlertToastService } from '@skysoftware-co/sky-components-ui';
11
+ // import { SkyAlertType } from '@skysoftware-co/sky-components-ui/lib/constants/constants';
12
+
13
+
14
+ @Component({
15
+ selector: 'bayan-core-change-password-widget',
16
+ standalone: true,
17
+ imports: [CommonModule, DxPopupModule, DxFormModule, SkyFooterActionsComponent, TranslatePipe],
18
+ templateUrl: './change-password-widget.component.html',
19
+ changeDetection: ChangeDetectionStrategy.OnPush,
20
+ })
21
+ export class BayanCoreChangePasswordWidgetComponent {
22
+ private readonly menuService = inject(BayanCoreTopMenuService);
23
+ private readonly alertToastService = inject(SkyAlertToastService);
24
+ private readonly translate = inject(TranslatePipe);
25
+
26
+ @Input() baseUrl = '';
27
+ @Input() visible = false;
28
+ @Input({ required: true }) config!: TopMenuWidgetChangePasswordDialogConfig;
29
+ @Input() isSubmitting = false;
30
+ @Output() visibleChange = new EventEmitter<boolean>();
31
+
32
+ @ViewChild('changePasswordForm') changePasswordForm?: DxFormComponent;
33
+
34
+ readonly currentPasswordMode = signal<'password' | 'text'>('password');
35
+ readonly newPasswordMode = signal<'password' | 'text'>('password');
36
+ readonly confirmNewPasswordMode = signal<'password' | 'text'>('password');
37
+
38
+ formData: TopMenuWidgetChangePasswordPayload = {
39
+ currentPassword: '',
40
+ newPassword: '',
41
+ confirmNewPassword: '',
42
+ };
43
+ errorMessage: WritableSignal<string> = signal('');
44
+
45
+ closeDialog(): void {
46
+ this.visible = false;
47
+ this.visibleChange.emit(false);
48
+ this.resetForm();
49
+ this.errorMessage.set('');
50
+ }
51
+
52
+ resetForm(): void {
53
+ this.formData = {
54
+ currentPassword: '',
55
+ newPassword: '',
56
+ confirmNewPassword: '',
57
+ };
58
+ this.errorMessage.set('');
59
+ }
60
+
61
+ onSaveButtonClick(): void {
62
+ const isValid = this.changePasswordForm?.instance.validate().isValid ?? false;
63
+ if (!isValid) {
64
+ return;
65
+ }
66
+ this.isSubmitting = true;
67
+ this.menuService.changePassword(this.baseUrl, {
68
+ CurrentPassword: this.formData.currentPassword,
69
+ NewPassword: this.formData.newPassword
70
+ }).subscribe({
71
+ next: () => {
72
+ this.isSubmitting = false;
73
+ this.closeDialog();
74
+ this.showAlert('PasswordChangedSuccessfully');
75
+ },
76
+ error: (e: any) => {
77
+ this.isSubmitting = false;
78
+ this.errorMessage.set(this.translate.transform("FailedToChangePassword"));
79
+ // Optionally show error notification here
80
+ }
81
+ });
82
+ }
83
+ togglePasswordMode(field: 'currentPasswordMode' | 'newPasswordMode' | 'confirmNewPasswordMode'): void {
84
+ const current = this[field]();
85
+ this[field].set(current === 'text' ? 'password' : 'text');
86
+ }
87
+
88
+ updateField(field: keyof TopMenuWidgetChangePasswordPayload, value: string): void {
89
+ this.formData = {
90
+ ...this.formData,
91
+ [field]: value,
92
+ };
93
+ }
94
+
95
+ isChangePasswordValid(): boolean {
96
+ return !!this.formData.currentPassword && !!this.formData.newPassword && !!this.formData.confirmNewPassword && this.formData.newPassword === this.formData.confirmNewPassword;
97
+ }
98
+
99
+ getCurrentPasswordEditorOptions(): object {
100
+ return {
101
+ maxLength: 256,
102
+ disabled: this.isSubmitting,
103
+ mode: this.currentPasswordMode(),
104
+ onValueChanged: (event: { value?: string }) => this.updateField('currentPassword', event.value ?? ''),
105
+ buttons: [{
106
+ name: 'password',
107
+ location: 'after',
108
+ options: {
109
+ stylingMode: 'text',
110
+ icon: this.currentPasswordMode() === 'text' ? 'eyeopen' : 'eyeclose',
111
+ onClick: () => this.togglePasswordMode('currentPasswordMode'),
112
+ },
113
+ }],
114
+ };
115
+ }
116
+
117
+ getNewPasswordEditorOptions(): object {
118
+ return {
119
+ maxLength: 256,
120
+ disabled: this.isSubmitting,
121
+ mode: this.newPasswordMode(),
122
+ onValueChanged: (event: { value?: string }) => this.updateField('newPassword', event.value ?? ''),
123
+ buttons: [{
124
+ name: 'password',
125
+ location: 'after',
126
+ options: {
127
+ stylingMode: 'text',
128
+ icon: this.newPasswordMode() === 'text' ? 'eyeopen' : 'eyeclose',
129
+ onClick: () => this.togglePasswordMode('newPasswordMode'),
130
+ },
131
+ }],
132
+ };
133
+ }
134
+
135
+ getConfirmPasswordEditorOptions(): object {
136
+ return {
137
+ maxLength: 256,
138
+ disabled: this.isSubmitting,
139
+ mode: this.confirmNewPasswordMode(),
140
+ onValueChanged: (event: { value?: string }) => this.updateField('confirmNewPassword', event.value ?? ''),
141
+ buttons: [{
142
+ name: 'password',
143
+ location: 'after',
144
+ options: {
145
+ stylingMode: 'text',
146
+ icon: this.confirmNewPasswordMode() === 'text' ? 'eyeopen' : 'eyeclose',
147
+ onClick: () => this.togglePasswordMode('confirmNewPasswordMode'),
148
+ },
149
+ }],
150
+ };
151
+ }
152
+
153
+ passwordComparison = (): string => this.formData.newPassword;
154
+
155
+ showAlert(message: string, type: string = 'success') {
156
+ this.alertToastService.toastInformation(message, type as any);
157
+ }
158
+ }
@@ -0,0 +1,39 @@
1
+ <div [class]="searchWrapperClass">
2
+ <dx-autocomplete
3
+ #searchAutoComplete
4
+ [placeholder]="placeholder | translate"
5
+ [dataSource]="dataSource"
6
+ valueExpr="menuSubTitle"
7
+ [searchExpr]="['menuSubTitle']"
8
+ [showClearButton]="false"
9
+ [showDropDownButton]="false"
10
+ [searchMode]="searchMode"
11
+ [width]="'100%'"
12
+ [stylingMode]="stylingMode"
13
+ [wrapItemText]="true"
14
+ [searchTimeout]="100"
15
+ (onInput)="onInput($event)"
16
+ (onEnterKey)="onEnterKey($event)">
17
+ <div class="w-100" *dxTemplate="let item of 'item'">
18
+ <a
19
+ [class]="searchItemClass"
20
+ href="{{item.MenuUrl}}" >
21
+ <div [class]="searchItemTitleClass">
22
+ <fa-icon [icon]="getIconForClass(item.iconClass)" [class]="iconClass"></fa-icon>
23
+ @if (item.isTranslatable) {
24
+ {{ item.menuTitle | translate }}
25
+ } @else {
26
+ {{ item.menuTitle }}
27
+ }
28
+ </div>
29
+ <div [class]="searchItemSubtitleClass">
30
+ @if (item.isTranslatable) {
31
+ {{ item.menuSubTitle | translate }}
32
+ } @else {
33
+ {{ item.menuSubTitle }}
34
+ }
35
+ </div>
36
+ </a>
37
+ </div>
38
+ </dx-autocomplete>
39
+ </div>