@flusys/ng-iam 0.1.0-beta.3 → 1.0.0-rc
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 +225 -25
- package/fesm2022/{flusys-ng-iam-action-form-page.component-BVWZMlLU.mjs → flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs} +181 -210
- package/fesm2022/flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-action-list-page.component-Daf93zpS.mjs +289 -0
- package/fesm2022/flusys-ng-iam-action-list-page.component-Daf93zpS.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-flusys-ng-iam-D_i24Gub.mjs → flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs} +967 -1087
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs +92 -0
- package/fesm2022/flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-permission-page.component-C0yLMdW5.mjs → flusys-ng-iam-permission-page.component-CmxOBJPu.mjs} +45 -16
- package/fesm2022/flusys-ng-iam-permission-page.component-CmxOBJPu.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-role-form-page.component-Co6bbHik.mjs → flusys-ng-iam-role-form-page.component-ByNueI1a.mjs} +110 -138
- package/fesm2022/flusys-ng-iam-role-form-page.component-ByNueI1a.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-role-list-page.component-CFly5KnH.mjs +316 -0
- package/fesm2022/flusys-ng-iam-role-list-page.component-CFly5KnH.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam.mjs +1 -1
- package/package.json +5 -5
- package/types/flusys-ng-iam.d.ts +89 -37
- package/fesm2022/flusys-ng-iam-action-form-page.component-BVWZMlLU.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-action-list-page.component-CCp7M7xo.mjs +0 -282
- package/fesm2022/flusys-ng-iam-action-list-page.component-CCp7M7xo.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-D_i24Gub.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-iam-container.component-BFKoNtdz.mjs +0 -97
- package/fesm2022/flusys-ng-iam-iam-container.component-BFKoNtdz.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-permission-page.component-C0yLMdW5.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-form-page.component-Co6bbHik.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-list-page.component-KJUVOrf0.mjs +0 -267
- package/fesm2022/flusys-ng-iam-role-list-page.component-KJUVOrf0.mjs.map +0 -1
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, signal, input, output, computed, ChangeDetectionStrategy, Component,
|
|
3
|
-
import { HttpClient
|
|
4
|
-
import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, COMPANY_API_PROVIDER,
|
|
2
|
+
import { inject, Injectable, signal, input, output, computed, effect, ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
|
|
3
|
+
import { HttpClient } from '@angular/common/http';
|
|
4
|
+
import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, ROLE_ACTION_PERMISSIONS, HasPermissionDirective, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, USER_ROLE_PERMISSIONS, USER_PERMISSION_PROVIDER, UserSelectComponent, USER_ACTION_PERMISSIONS, PROFILE_PERMISSION_PROVIDER } from '@flusys/ng-shared';
|
|
5
5
|
import { APP_CONFIG, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
|
|
6
|
-
import * as i3$1 from 'primeng/api';
|
|
7
6
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
|
8
|
-
import { of, firstValueFrom } from 'rxjs';
|
|
7
|
+
import { of, firstValueFrom, map as map$1 } from 'rxjs';
|
|
9
8
|
import { tap, catchError, map } from 'rxjs/operators';
|
|
10
9
|
import * as i1 from '@angular/common';
|
|
11
10
|
import { CommonModule } from '@angular/common';
|
|
12
11
|
import * as i1$1 from '@angular/forms';
|
|
13
12
|
import { FormsModule } from '@angular/forms';
|
|
14
|
-
import * as
|
|
15
|
-
import
|
|
16
|
-
import * as
|
|
17
|
-
import * as
|
|
18
|
-
import * as
|
|
19
|
-
import * as i7 from 'primeng/
|
|
20
|
-
import * as i8 from 'primeng/treetable';
|
|
13
|
+
import * as i2 from 'primeng/button';
|
|
14
|
+
import * as i6 from 'primeng/tooltip';
|
|
15
|
+
import * as i3 from 'primeng/checkbox';
|
|
16
|
+
import * as i4 from 'primeng/select';
|
|
17
|
+
import * as i5 from 'primeng/tag';
|
|
18
|
+
import * as i7 from 'primeng/treetable';
|
|
21
19
|
import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
|
|
22
20
|
import * as i5$1 from 'primeng/table';
|
|
23
21
|
|
|
@@ -60,10 +58,10 @@ class RoleApiService extends ApiResourceService {
|
|
|
60
58
|
const http = inject(HttpClient);
|
|
61
59
|
super('iam/roles', http);
|
|
62
60
|
}
|
|
63
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
64
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
61
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
62
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, providedIn: 'root' });
|
|
65
63
|
}
|
|
66
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
64
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, decorators: [{
|
|
67
65
|
type: Injectable,
|
|
68
66
|
args: [{
|
|
69
67
|
providedIn: 'root',
|
|
@@ -83,11 +81,11 @@ class ActionApiService extends ApiResourceService {
|
|
|
83
81
|
}
|
|
84
82
|
/**
|
|
85
83
|
* Get actions for permission assignment
|
|
86
|
-
*
|
|
84
|
+
* POST /iam/actions/tree-for-permission
|
|
87
85
|
* Returns actions filtered by company whitelist if enabled
|
|
88
86
|
*/
|
|
89
87
|
getActionsForPermission() {
|
|
90
|
-
return this.http.
|
|
88
|
+
return this.http.post(`${this.appConfig.apiBaseUrl}/iam/actions/tree-for-permission`, {});
|
|
91
89
|
}
|
|
92
90
|
/**
|
|
93
91
|
* Get actions in hierarchical tree structure
|
|
@@ -112,10 +110,10 @@ class ActionApiService extends ApiResourceService {
|
|
|
112
110
|
}
|
|
113
111
|
return this.http.post(`${this.appConfig.apiBaseUrl}/iam/actions/tree`, body);
|
|
114
112
|
}
|
|
115
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
116
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
113
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
114
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, providedIn: 'root' });
|
|
117
115
|
}
|
|
118
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
116
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, decorators: [{
|
|
119
117
|
type: Injectable,
|
|
120
118
|
args: [{
|
|
121
119
|
providedIn: 'root',
|
|
@@ -435,7 +433,7 @@ class ActionPermissionLogicService {
|
|
|
435
433
|
const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
|
|
436
434
|
const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
|
|
437
435
|
if (validationResult.valid) {
|
|
438
|
-
return '
|
|
436
|
+
return '[OK] Prerequisites Satisfied\n\nAll required actions are already selected.\nYou can safely add this action.';
|
|
439
437
|
}
|
|
440
438
|
const allMissingActions = this.getAllMissingPrerequisitesRecursive(action, currentSelection, allActions);
|
|
441
439
|
const logicTree = this.buildTooltipLogicTree(action.permissionLogic, currentSelection, allActions, 0);
|
|
@@ -497,13 +495,11 @@ class ActionPermissionLogicService {
|
|
|
497
495
|
if (!targetAction.permissionLogic || !targetAction.id)
|
|
498
496
|
return;
|
|
499
497
|
if (depth > 50) {
|
|
500
|
-
console.error('[Permission Logic] Circular dependency detected', targetAction.id);
|
|
501
498
|
return;
|
|
502
499
|
}
|
|
503
500
|
if (visited.has(targetAction.id))
|
|
504
501
|
return;
|
|
505
502
|
if (stack.has(targetAction.id)) {
|
|
506
|
-
console.warn('[Permission Logic] Circular reference detected:', targetAction.id);
|
|
507
503
|
return;
|
|
508
504
|
}
|
|
509
505
|
visited.add(targetAction.id);
|
|
@@ -800,10 +796,10 @@ class ActionPermissionLogicService {
|
|
|
800
796
|
}
|
|
801
797
|
return `${indent}(invalid)`;
|
|
802
798
|
}
|
|
803
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
804
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
799
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
800
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, providedIn: 'root' });
|
|
805
801
|
}
|
|
806
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
802
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, decorators: [{
|
|
807
803
|
type: Injectable,
|
|
808
804
|
args: [{
|
|
809
805
|
providedIn: 'root',
|
|
@@ -837,14 +833,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
837
833
|
}
|
|
838
834
|
/**
|
|
839
835
|
* Get user's direct action permissions
|
|
840
|
-
*
|
|
836
|
+
* POST /iam/permissions/get-user-actions
|
|
841
837
|
*/
|
|
842
838
|
getUserActions(userId, query) {
|
|
843
|
-
|
|
844
|
-
if (query?.branchId) {
|
|
845
|
-
params = params.set('branchId', query.branchId);
|
|
846
|
-
}
|
|
847
|
-
return this.http.get(this.getUrl(`permissions/user-actions/${userId}`), { params });
|
|
839
|
+
return this.http.post(this.getUrl('permissions/get-user-actions'), { userId, branchId: query?.branchId });
|
|
848
840
|
}
|
|
849
841
|
// =============================================================================
|
|
850
842
|
// User → Role (Role Assignments)
|
|
@@ -858,14 +850,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
858
850
|
}
|
|
859
851
|
/**
|
|
860
852
|
* Get user's role assignments
|
|
861
|
-
*
|
|
853
|
+
* POST /iam/permissions/get-user-roles
|
|
862
854
|
*/
|
|
863
855
|
getUserRoles(userId, query) {
|
|
864
|
-
|
|
865
|
-
if (query?.branchId) {
|
|
866
|
-
params = params.set('branchId', query.branchId);
|
|
867
|
-
}
|
|
868
|
-
return this.http.get(this.getUrl(`permissions/user-roles/${userId}`), { params });
|
|
856
|
+
return this.http.post(this.getUrl('permissions/get-user-roles'), { userId, branchId: query?.branchId });
|
|
869
857
|
}
|
|
870
858
|
// =============================================================================
|
|
871
859
|
// Role → Action (Role Permissions)
|
|
@@ -879,10 +867,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
879
867
|
}
|
|
880
868
|
/**
|
|
881
869
|
* Get role's action permissions
|
|
882
|
-
*
|
|
870
|
+
* POST /iam/permissions/get-role-actions
|
|
883
871
|
*/
|
|
884
872
|
getRoleActions(roleId, query) {
|
|
885
|
-
return this.http.
|
|
873
|
+
return this.http.post(this.getUrl('permissions/get-role-actions'), { roleId });
|
|
886
874
|
}
|
|
887
875
|
// =============================================================================
|
|
888
876
|
// Company → Action (Company Whitelisting)
|
|
@@ -896,15 +884,15 @@ class PermissionApiService extends BaseApiService {
|
|
|
896
884
|
}
|
|
897
885
|
/**
|
|
898
886
|
* Get company's whitelisted actions
|
|
899
|
-
*
|
|
887
|
+
* POST /iam/permissions/get-company-actions
|
|
900
888
|
*/
|
|
901
889
|
getCompanyActions(companyId) {
|
|
902
|
-
return this.http.
|
|
890
|
+
return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
|
|
903
891
|
}
|
|
904
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
905
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
892
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
893
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, providedIn: 'root' });
|
|
906
894
|
}
|
|
907
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
895
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, decorators: [{
|
|
908
896
|
type: Injectable,
|
|
909
897
|
args: [{
|
|
910
898
|
providedIn: 'root',
|
|
@@ -933,10 +921,10 @@ class MyPermissionsApiService {
|
|
|
933
921
|
getMyPermissions(dto = {}) {
|
|
934
922
|
return this.http.post(`${this.baseUrl}/my-permissions`, dto);
|
|
935
923
|
}
|
|
936
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
937
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
924
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MyPermissionsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
925
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MyPermissionsApiService, providedIn: 'root' });
|
|
938
926
|
}
|
|
939
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
927
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MyPermissionsApiService, decorators: [{
|
|
940
928
|
type: Injectable,
|
|
941
929
|
args: [{
|
|
942
930
|
providedIn: 'root',
|
|
@@ -986,11 +974,9 @@ class PermissionStateService {
|
|
|
986
974
|
// Update shared permission validator
|
|
987
975
|
const actionCodes = response.data?.frontendActions.map((a) => a.code) ?? [];
|
|
988
976
|
this.permissionValidator.setPermissions(actionCodes);
|
|
989
|
-
}), catchError((
|
|
990
|
-
console.error('Failed to load permissions:', error);
|
|
977
|
+
}), catchError(() => {
|
|
991
978
|
this._permissions.set(null);
|
|
992
979
|
this._isLoading.set(false);
|
|
993
|
-
// Clear shared permission validator
|
|
994
980
|
this.permissionValidator.clearPermissions();
|
|
995
981
|
return of(void 0);
|
|
996
982
|
}), map(() => void 0));
|
|
@@ -1004,10 +990,10 @@ class PermissionStateService {
|
|
|
1004
990
|
return (this._permissions() !== null &&
|
|
1005
991
|
this.permissionValidator.isPermissionsLoaded());
|
|
1006
992
|
}
|
|
1007
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1008
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
993
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
994
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, providedIn: 'root' });
|
|
1009
995
|
}
|
|
1010
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
996
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, decorators: [{
|
|
1011
997
|
type: Injectable,
|
|
1012
998
|
args: [{ providedIn: 'root' }]
|
|
1013
999
|
}] });
|
|
@@ -1041,77 +1027,85 @@ class LogicBuilderComponent {
|
|
|
1041
1027
|
actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
|
|
1042
1028
|
logicChange = output();
|
|
1043
1029
|
availableActions = computed(() => this.actions(), ...(ngDevMode ? [{ debugName: "availableActions" }] : []));
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1030
|
+
/** Internal builder tree state (private writable + public readonly pattern) */
|
|
1031
|
+
_builderTree = signal(null, ...(ngDevMode ? [{ debugName: "_builderTree" }] : []));
|
|
1032
|
+
builderLogic = this._builderTree.asReadonly();
|
|
1033
|
+
constructor() {
|
|
1034
|
+
// Sync builderTree with logic input changes
|
|
1035
|
+
effect(() => {
|
|
1036
|
+
const logic = this.logic();
|
|
1037
|
+
if (!logic) {
|
|
1038
|
+
this._builderTree.set(null);
|
|
1039
|
+
}
|
|
1040
|
+
else if (!this._builderTree()) {
|
|
1041
|
+
this._builderTree.set(toBuilderNode(logic));
|
|
1042
|
+
}
|
|
1043
|
+
}, { allowSignalWrites: true });
|
|
1044
|
+
}
|
|
1056
1045
|
initializeLogic() {
|
|
1057
|
-
this.
|
|
1046
|
+
this._builderTree.set({
|
|
1058
1047
|
id: crypto.randomUUID(),
|
|
1059
1048
|
type: 'group',
|
|
1060
1049
|
operator: 'AND',
|
|
1061
1050
|
children: [],
|
|
1062
|
-
};
|
|
1051
|
+
});
|
|
1063
1052
|
this.emitChange();
|
|
1064
1053
|
}
|
|
1065
1054
|
clearLogic() {
|
|
1066
|
-
this.
|
|
1055
|
+
this._builderTree.set(null);
|
|
1067
1056
|
this.logicChange.emit(null);
|
|
1068
1057
|
}
|
|
1069
1058
|
toggleOperator(nodeId) {
|
|
1070
|
-
|
|
1059
|
+
const tree = this._builderTree();
|
|
1060
|
+
if (!tree)
|
|
1071
1061
|
return;
|
|
1072
|
-
this.
|
|
1062
|
+
this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
|
|
1073
1063
|
...node,
|
|
1074
1064
|
operator: node.operator === 'AND' ? 'OR' : 'AND',
|
|
1075
|
-
}));
|
|
1065
|
+
})));
|
|
1076
1066
|
this.emitChange();
|
|
1077
1067
|
}
|
|
1078
1068
|
addChildNode(parentId, type) {
|
|
1079
|
-
|
|
1069
|
+
const tree = this._builderTree();
|
|
1070
|
+
if (!tree)
|
|
1080
1071
|
return;
|
|
1081
1072
|
const newNode = type === 'group'
|
|
1082
1073
|
? { id: crypto.randomUUID(), type: 'group', operator: 'AND', children: [] }
|
|
1083
1074
|
: { id: crypto.randomUUID(), type: 'action', actionId: '' };
|
|
1084
|
-
this.
|
|
1075
|
+
this._builderTree.set(this.updateNodeInTree(tree, parentId, (node) => ({
|
|
1085
1076
|
...node,
|
|
1086
1077
|
children: [...(node.children || []), newNode],
|
|
1087
|
-
}));
|
|
1078
|
+
})));
|
|
1088
1079
|
this.emitChange();
|
|
1089
1080
|
}
|
|
1090
1081
|
removeNode(nodeId) {
|
|
1091
|
-
|
|
1082
|
+
const tree = this._builderTree();
|
|
1083
|
+
if (!tree)
|
|
1092
1084
|
return;
|
|
1093
|
-
if (
|
|
1085
|
+
if (tree.id === nodeId) {
|
|
1094
1086
|
this.clearLogic();
|
|
1095
1087
|
return;
|
|
1096
1088
|
}
|
|
1097
|
-
this.
|
|
1089
|
+
this._builderTree.set(this.removeNodeFromTree(tree, nodeId));
|
|
1098
1090
|
this.emitChange();
|
|
1099
1091
|
}
|
|
1100
1092
|
updateActionId(nodeId, actionId) {
|
|
1101
|
-
|
|
1093
|
+
const tree = this._builderTree();
|
|
1094
|
+
if (!tree)
|
|
1102
1095
|
return;
|
|
1103
|
-
this.
|
|
1096
|
+
this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
|
|
1104
1097
|
...node,
|
|
1105
1098
|
actionId,
|
|
1106
|
-
}));
|
|
1099
|
+
})));
|
|
1107
1100
|
this.emitChange();
|
|
1108
1101
|
}
|
|
1109
1102
|
emitChange() {
|
|
1110
|
-
|
|
1103
|
+
const tree = this._builderTree();
|
|
1104
|
+
if (!tree) {
|
|
1111
1105
|
this.logicChange.emit(null);
|
|
1112
1106
|
return;
|
|
1113
1107
|
}
|
|
1114
|
-
this.logicChange.emit(toLogicNode(
|
|
1108
|
+
this.logicChange.emit(toLogicNode(tree));
|
|
1115
1109
|
}
|
|
1116
1110
|
updateNodeInTree(node, targetId, updater) {
|
|
1117
1111
|
if (node.id === targetId)
|
|
@@ -1135,11 +1129,11 @@ class LogicBuilderComponent {
|
|
|
1135
1129
|
}
|
|
1136
1130
|
return node;
|
|
1137
1131
|
}
|
|
1138
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1139
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
1132
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1133
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: LogicBuilderComponent, isStandalone: true, selector: "lib-logic-builder", inputs: { logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logicChange: "logicChange" }, ngImport: i0, template: `
|
|
1140
1134
|
<div class="logic-builder">
|
|
1141
1135
|
<div class="flex justify-between items-center mb-3">
|
|
1142
|
-
<h4 class="text-sm font-semibold">Permission Logic</h4>
|
|
1136
|
+
<h4 class="text-sm font-semibold m-0">Permission Logic</h4>
|
|
1143
1137
|
@if (!builderLogic()) {
|
|
1144
1138
|
<p-button
|
|
1145
1139
|
label="Add Logic"
|
|
@@ -1158,13 +1152,13 @@ class LogicBuilderComponent {
|
|
|
1158
1152
|
</div>
|
|
1159
1153
|
|
|
1160
1154
|
@if (builderLogic()) {
|
|
1161
|
-
<div class="border rounded p-3 bg-
|
|
1162
|
-
<div class="mb-3 text-sm text-
|
|
1155
|
+
<div class="border border-surface rounded p-3 bg-surface-50">
|
|
1156
|
+
<div class="mb-3 text-sm text-muted-color">
|
|
1163
1157
|
Define permission requirements using AND/OR logic with actions
|
|
1164
1158
|
</div>
|
|
1165
1159
|
|
|
1166
1160
|
<!-- Root Node -->
|
|
1167
|
-
<div class="
|
|
1161
|
+
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1168
1162
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
|
|
1169
1163
|
</div>
|
|
1170
1164
|
</div>
|
|
@@ -1173,11 +1167,11 @@ class LogicBuilderComponent {
|
|
|
1173
1167
|
|
|
1174
1168
|
<!-- Recursive Node Template -->
|
|
1175
1169
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1176
|
-
<div class="
|
|
1177
|
-
<div class="
|
|
1170
|
+
<div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
|
|
1171
|
+
<div class="flex items-center gap-3 mb-2">
|
|
1178
1172
|
<span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
|
|
1179
1173
|
|
|
1180
|
-
<div class="
|
|
1174
|
+
<div class="flex-1 flex items-center gap-2">
|
|
1181
1175
|
@if (node.type === 'group') {
|
|
1182
1176
|
<!-- Group: show operator toggle -->
|
|
1183
1177
|
<span
|
|
@@ -1187,13 +1181,13 @@ class LogicBuilderComponent {
|
|
|
1187
1181
|
title="Click to toggle">
|
|
1188
1182
|
{{ node.operator }}
|
|
1189
1183
|
</span>
|
|
1190
|
-
<span class="text-
|
|
1184
|
+
<span class="text-muted-color text-xs">({{ node.children?.length || 0 }} conditions)</span>
|
|
1191
1185
|
}
|
|
1192
1186
|
|
|
1193
1187
|
@if (node.type === 'action') {
|
|
1194
1188
|
<!-- Action: show action selector -->
|
|
1195
1189
|
<select
|
|
1196
|
-
class="flex-1"
|
|
1190
|
+
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1197
1191
|
[ngModel]="node.actionId"
|
|
1198
1192
|
(ngModelChange)="updateActionId(node.id, $event)">
|
|
1199
1193
|
<option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
|
|
@@ -1204,7 +1198,7 @@ class LogicBuilderComponent {
|
|
|
1204
1198
|
}
|
|
1205
1199
|
</div>
|
|
1206
1200
|
|
|
1207
|
-
<div class="
|
|
1201
|
+
<div class="flex gap-1">
|
|
1208
1202
|
<p-button
|
|
1209
1203
|
icon="pi pi-trash"
|
|
1210
1204
|
[text]="true"
|
|
@@ -1217,7 +1211,7 @@ class LogicBuilderComponent {
|
|
|
1217
1211
|
|
|
1218
1212
|
<!-- Children for group nodes -->
|
|
1219
1213
|
@if (node.type === 'group' && node.children && node.children.length > 0) {
|
|
1220
|
-
<div class="
|
|
1214
|
+
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1221
1215
|
@for (child of node.children; track child.id) {
|
|
1222
1216
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
1223
1217
|
}
|
|
@@ -1226,9 +1220,9 @@ class LogicBuilderComponent {
|
|
|
1226
1220
|
|
|
1227
1221
|
<!-- Add child buttons for group nodes -->
|
|
1228
1222
|
@if (node.type === 'group') {
|
|
1229
|
-
<div class="
|
|
1230
|
-
<div class="text-xs font-semibold text-
|
|
1231
|
-
<div class="
|
|
1223
|
+
<div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
|
|
1224
|
+
<div class="text-xs font-semibold text-muted-color mb-2">Add Condition:</div>
|
|
1225
|
+
<div class="flex gap-2 flex-wrap">
|
|
1232
1226
|
<p-button
|
|
1233
1227
|
label="Group"
|
|
1234
1228
|
icon="pi pi-sitemap"
|
|
@@ -1247,14 +1241,14 @@ class LogicBuilderComponent {
|
|
|
1247
1241
|
}
|
|
1248
1242
|
</div>
|
|
1249
1243
|
</ng-template>
|
|
1250
|
-
`, isInline: true, styles: [":host{display:block}.
|
|
1244
|
+
`, isInline: true, styles: [":host{display:block}.node-type{display:inline-flex;align-items:center;padding:.25rem .5rem;border-radius:.25rem;font-weight:600;font-size:.75rem;text-transform:uppercase}.node-type.group{background-color:var(--p-blue-100, #dbeafe);color:var(--p-blue-700, #1e40af)}.node-type.action{background-color:var(--p-green-100, #d1fae5);color:var(--p-green-700, #065f46)}:host-context(.p-dark) .node-type.group{background-color:#3b82f633;color:var(--p-blue-400, #60a5fa)}:host-context(.p-dark) .node-type.action{background-color:#22c55e33;color:var(--p-green-400, #4ade80)}.operator-badge{display:inline-flex;align-items:center;padding:.125rem .5rem;border-radius:9999px;font-weight:600;font-size:.75rem;cursor:pointer;transition:opacity .2s}.operator-badge.and{background-color:var(--p-yellow-100, #fef3c7);color:var(--p-yellow-700, #92400e)}.operator-badge.or{background-color:var(--p-indigo-100, #e0e7ff);color:var(--p-indigo-700, #3730a3)}:host-context(.p-dark) .operator-badge.and{background-color:#eab30833;color:var(--p-yellow-400, #facc15)}:host-context(.p-dark) .operator-badge.or{background-color:#6366f133;color:var(--p-indigo-400, #818cf8)}.operator-badge:hover{opacity:.8}.action-select{background-color:var(--p-surface-0, #ffffff);color:var(--p-text-color, #1f2937)}:host-context(.p-dark) .action-select{background-color:var(--p-surface-900, #1f2937);color:var(--p-text-color, #f9fafb)}.action-select:focus{outline:none;border-color:var(--p-primary-color, #3b82f6)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.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: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1251
1245
|
}
|
|
1252
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1246
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, decorators: [{
|
|
1253
1247
|
type: Component,
|
|
1254
|
-
args: [{ selector: 'lib-logic-builder', standalone: true, imports: [
|
|
1248
|
+
args: [{ selector: 'lib-logic-builder', standalone: true, imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1255
1249
|
<div class="logic-builder">
|
|
1256
1250
|
<div class="flex justify-between items-center mb-3">
|
|
1257
|
-
<h4 class="text-sm font-semibold">Permission Logic</h4>
|
|
1251
|
+
<h4 class="text-sm font-semibold m-0">Permission Logic</h4>
|
|
1258
1252
|
@if (!builderLogic()) {
|
|
1259
1253
|
<p-button
|
|
1260
1254
|
label="Add Logic"
|
|
@@ -1273,13 +1267,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1273
1267
|
</div>
|
|
1274
1268
|
|
|
1275
1269
|
@if (builderLogic()) {
|
|
1276
|
-
<div class="border rounded p-3 bg-
|
|
1277
|
-
<div class="mb-3 text-sm text-
|
|
1270
|
+
<div class="border border-surface rounded p-3 bg-surface-50">
|
|
1271
|
+
<div class="mb-3 text-sm text-muted-color">
|
|
1278
1272
|
Define permission requirements using AND/OR logic with actions
|
|
1279
1273
|
</div>
|
|
1280
1274
|
|
|
1281
1275
|
<!-- Root Node -->
|
|
1282
|
-
<div class="
|
|
1276
|
+
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1283
1277
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
|
|
1284
1278
|
</div>
|
|
1285
1279
|
</div>
|
|
@@ -1288,11 +1282,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1288
1282
|
|
|
1289
1283
|
<!-- Recursive Node Template -->
|
|
1290
1284
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1291
|
-
<div class="
|
|
1292
|
-
<div class="
|
|
1285
|
+
<div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
|
|
1286
|
+
<div class="flex items-center gap-3 mb-2">
|
|
1293
1287
|
<span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
|
|
1294
1288
|
|
|
1295
|
-
<div class="
|
|
1289
|
+
<div class="flex-1 flex items-center gap-2">
|
|
1296
1290
|
@if (node.type === 'group') {
|
|
1297
1291
|
<!-- Group: show operator toggle -->
|
|
1298
1292
|
<span
|
|
@@ -1302,13 +1296,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1302
1296
|
title="Click to toggle">
|
|
1303
1297
|
{{ node.operator }}
|
|
1304
1298
|
</span>
|
|
1305
|
-
<span class="text-
|
|
1299
|
+
<span class="text-muted-color text-xs">({{ node.children?.length || 0 }} conditions)</span>
|
|
1306
1300
|
}
|
|
1307
1301
|
|
|
1308
1302
|
@if (node.type === 'action') {
|
|
1309
1303
|
<!-- Action: show action selector -->
|
|
1310
1304
|
<select
|
|
1311
|
-
class="flex-1"
|
|
1305
|
+
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1312
1306
|
[ngModel]="node.actionId"
|
|
1313
1307
|
(ngModelChange)="updateActionId(node.id, $event)">
|
|
1314
1308
|
<option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
|
|
@@ -1319,7 +1313,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1319
1313
|
}
|
|
1320
1314
|
</div>
|
|
1321
1315
|
|
|
1322
|
-
<div class="
|
|
1316
|
+
<div class="flex gap-1">
|
|
1323
1317
|
<p-button
|
|
1324
1318
|
icon="pi pi-trash"
|
|
1325
1319
|
[text]="true"
|
|
@@ -1332,7 +1326,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1332
1326
|
|
|
1333
1327
|
<!-- Children for group nodes -->
|
|
1334
1328
|
@if (node.type === 'group' && node.children && node.children.length > 0) {
|
|
1335
|
-
<div class="
|
|
1329
|
+
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1336
1330
|
@for (child of node.children; track child.id) {
|
|
1337
1331
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
1338
1332
|
}
|
|
@@ -1341,9 +1335,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1341
1335
|
|
|
1342
1336
|
<!-- Add child buttons for group nodes -->
|
|
1343
1337
|
@if (node.type === 'group') {
|
|
1344
|
-
<div class="
|
|
1345
|
-
<div class="text-xs font-semibold text-
|
|
1346
|
-
<div class="
|
|
1338
|
+
<div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
|
|
1339
|
+
<div class="text-xs font-semibold text-muted-color mb-2">Add Condition:</div>
|
|
1340
|
+
<div class="flex gap-2 flex-wrap">
|
|
1347
1341
|
<p-button
|
|
1348
1342
|
label="Group"
|
|
1349
1343
|
icon="pi pi-sitemap"
|
|
@@ -1362,8 +1356,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
1362
1356
|
}
|
|
1363
1357
|
</div>
|
|
1364
1358
|
</ng-template>
|
|
1365
|
-
`, styles: [":host{display:block}.
|
|
1366
|
-
}], propDecorators: { logic: [{ type: i0.Input, args: [{ isSignal: true, alias: "logic", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], logicChange: [{ type: i0.Output, args: ["logicChange"] }] } });
|
|
1359
|
+
`, styles: [":host{display:block}.node-type{display:inline-flex;align-items:center;padding:.25rem .5rem;border-radius:.25rem;font-weight:600;font-size:.75rem;text-transform:uppercase}.node-type.group{background-color:var(--p-blue-100, #dbeafe);color:var(--p-blue-700, #1e40af)}.node-type.action{background-color:var(--p-green-100, #d1fae5);color:var(--p-green-700, #065f46)}:host-context(.p-dark) .node-type.group{background-color:#3b82f633;color:var(--p-blue-400, #60a5fa)}:host-context(.p-dark) .node-type.action{background-color:#22c55e33;color:var(--p-green-400, #4ade80)}.operator-badge{display:inline-flex;align-items:center;padding:.125rem .5rem;border-radius:9999px;font-weight:600;font-size:.75rem;cursor:pointer;transition:opacity .2s}.operator-badge.and{background-color:var(--p-yellow-100, #fef3c7);color:var(--p-yellow-700, #92400e)}.operator-badge.or{background-color:var(--p-indigo-100, #e0e7ff);color:var(--p-indigo-700, #3730a3)}:host-context(.p-dark) .operator-badge.and{background-color:#eab30833;color:var(--p-yellow-400, #facc15)}:host-context(.p-dark) .operator-badge.or{background-color:#6366f133;color:var(--p-indigo-400, #818cf8)}.operator-badge:hover{opacity:.8}.action-select{background-color:var(--p-surface-0, #ffffff);color:var(--p-text-color, #1f2937)}:host-context(.p-dark) .action-select{background-color:var(--p-surface-900, #1f2937);color:var(--p-text-color, #f9fafb)}.action-select:focus{outline:none;border-color:var(--p-primary-color, #3b82f6)}\n"] }]
|
|
1360
|
+
}], ctorParameters: () => [], propDecorators: { logic: [{ type: i0.Input, args: [{ isSignal: true, alias: "logic", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], logicChange: [{ type: i0.Output, args: ["logicChange"] }] } });
|
|
1367
1361
|
|
|
1368
1362
|
/**
|
|
1369
1363
|
* Tree Utility Functions
|
|
@@ -1560,7 +1554,10 @@ function convertActionToTreeNode(actions) {
|
|
|
1560
1554
|
* ```
|
|
1561
1555
|
*/
|
|
1562
1556
|
class RoleActionSelectorComponent {
|
|
1557
|
+
// Permission constants for template
|
|
1558
|
+
ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
|
|
1563
1559
|
// Dependencies
|
|
1560
|
+
destroyRef = inject(DestroyRef);
|
|
1564
1561
|
roleApi = inject(RoleApiService);
|
|
1565
1562
|
actionApi = inject(ActionApiService);
|
|
1566
1563
|
permissionApi = inject(PermissionApiService);
|
|
@@ -1641,6 +1638,10 @@ class RoleActionSelectorComponent {
|
|
|
1641
1638
|
loadDataAbortController = null;
|
|
1642
1639
|
constructor() {
|
|
1643
1640
|
this.loadRoles();
|
|
1641
|
+
// Cleanup on destroy
|
|
1642
|
+
this.destroyRef.onDestroy(() => {
|
|
1643
|
+
this.loadDataAbortController?.abort();
|
|
1644
|
+
});
|
|
1644
1645
|
// Effect: Load data when role selection changes
|
|
1645
1646
|
effect(() => {
|
|
1646
1647
|
const roleId = this.selectedRoleId();
|
|
@@ -1650,12 +1651,7 @@ class RoleActionSelectorComponent {
|
|
|
1650
1651
|
this.loadDataAbortController = new AbortController();
|
|
1651
1652
|
this.onRoleChange(this.loadDataAbortController.signal).catch((err) => {
|
|
1652
1653
|
if (err.name !== 'AbortError') {
|
|
1653
|
-
|
|
1654
|
-
this.messageService.add({
|
|
1655
|
-
severity: 'error',
|
|
1656
|
-
summary: 'Error',
|
|
1657
|
-
detail: 'Failed to load role permissions. Please refresh.',
|
|
1658
|
-
});
|
|
1654
|
+
// Error toast handled by global interceptor
|
|
1659
1655
|
this.loading.set(false);
|
|
1660
1656
|
}
|
|
1661
1657
|
});
|
|
@@ -1665,9 +1661,6 @@ class RoleActionSelectorComponent {
|
|
|
1665
1661
|
}
|
|
1666
1662
|
});
|
|
1667
1663
|
}
|
|
1668
|
-
ngOnDestroy() {
|
|
1669
|
-
this.loadDataAbortController?.abort();
|
|
1670
|
-
}
|
|
1671
1664
|
/**
|
|
1672
1665
|
* Load roles from API
|
|
1673
1666
|
*/
|
|
@@ -1678,13 +1671,8 @@ class RoleActionSelectorComponent {
|
|
|
1678
1671
|
}));
|
|
1679
1672
|
this.roles.set(response?.data ?? []);
|
|
1680
1673
|
}
|
|
1681
|
-
catch
|
|
1682
|
-
|
|
1683
|
-
this.messageService.add({
|
|
1684
|
-
severity: 'error',
|
|
1685
|
-
summary: 'Error',
|
|
1686
|
-
detail: 'Failed to load roles',
|
|
1687
|
-
});
|
|
1674
|
+
catch {
|
|
1675
|
+
// Error toast handled by global interceptor
|
|
1688
1676
|
}
|
|
1689
1677
|
}
|
|
1690
1678
|
/**
|
|
@@ -1700,13 +1688,7 @@ class RoleActionSelectorComponent {
|
|
|
1700
1688
|
if (signal.aborted)
|
|
1701
1689
|
return;
|
|
1702
1690
|
// Get flat list (filtered by company if enabled)
|
|
1703
|
-
const flatActions = (actionsResponse?.data ?? []).filter((a) =>
|
|
1704
|
-
if (!a.id) {
|
|
1705
|
-
console.warn('[Role Selector] Skipping action without ID:', a.name);
|
|
1706
|
-
return false;
|
|
1707
|
-
}
|
|
1708
|
-
return true;
|
|
1709
|
-
});
|
|
1691
|
+
const flatActions = (actionsResponse?.data ?? []).filter((a) => !!a.id);
|
|
1710
1692
|
// Build tree structure from flat list with parentId
|
|
1711
1693
|
const actionsTree = buildTreeFromFlat(flatActions);
|
|
1712
1694
|
// Store both representations
|
|
@@ -1743,15 +1725,15 @@ class RoleActionSelectorComponent {
|
|
|
1743
1725
|
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
1744
1726
|
if (prerequisiteInfo) {
|
|
1745
1727
|
const actionHint = isCurrentlySelected
|
|
1746
|
-
? '\n\n
|
|
1747
|
-
: '\n\n
|
|
1728
|
+
? '\n\n---\n[Remove] Click to remove'
|
|
1729
|
+
: '\n\n---\n[Add] Click to add (auto-selects required)';
|
|
1748
1730
|
return `${prerequisiteInfo}${actionHint}`;
|
|
1749
1731
|
}
|
|
1750
1732
|
}
|
|
1751
1733
|
if (isCurrentlySelected) {
|
|
1752
|
-
return '
|
|
1734
|
+
return '[Assigned] Assigned to role\n\n[Remove] Click to remove';
|
|
1753
1735
|
}
|
|
1754
|
-
return '
|
|
1736
|
+
return '[Add] Click to assign to role';
|
|
1755
1737
|
}
|
|
1756
1738
|
/**
|
|
1757
1739
|
* Check if action has unmet prerequisites
|
|
@@ -1848,13 +1830,8 @@ class RoleActionSelectorComponent {
|
|
|
1848
1830
|
// Update baseline
|
|
1849
1831
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
1850
1832
|
}
|
|
1851
|
-
catch
|
|
1852
|
-
|
|
1853
|
-
this.messageService.add({
|
|
1854
|
-
severity: 'error',
|
|
1855
|
-
summary: 'Error',
|
|
1856
|
-
detail: error?.error?.message || 'Failed to update role permissions',
|
|
1857
|
-
});
|
|
1833
|
+
catch {
|
|
1834
|
+
// Error toast handled by global interceptor
|
|
1858
1835
|
}
|
|
1859
1836
|
finally {
|
|
1860
1837
|
this.saving.set(false);
|
|
@@ -1869,16 +1846,17 @@ class RoleActionSelectorComponent {
|
|
|
1869
1846
|
this._selectionMap.set({});
|
|
1870
1847
|
this._initialSelection.set({});
|
|
1871
1848
|
}
|
|
1872
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1873
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
1849
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1850
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", ngImport: i0, template: `
|
|
1874
1851
|
<div class="role-action-selector">
|
|
1875
1852
|
<!-- Role Selector -->
|
|
1876
1853
|
<div class="mb-4">
|
|
1877
|
-
<div class="grid">
|
|
1878
|
-
<div
|
|
1854
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
1855
|
+
<div>
|
|
1879
1856
|
<label class="block font-semibold mb-2">Select Role</label>
|
|
1880
1857
|
<p-select
|
|
1881
|
-
[
|
|
1858
|
+
[ngModel]="selectedRoleId()"
|
|
1859
|
+
(ngModelChange)="selectedRoleId.set($event)"
|
|
1882
1860
|
[options]="roles()"
|
|
1883
1861
|
optionLabel="name"
|
|
1884
1862
|
optionValue="id"
|
|
@@ -1895,24 +1873,21 @@ class RoleActionSelectorComponent {
|
|
|
1895
1873
|
<!-- Loading State -->
|
|
1896
1874
|
@if (loading()) {
|
|
1897
1875
|
<div
|
|
1898
|
-
class="surface-card p-5 border
|
|
1876
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
1899
1877
|
>
|
|
1900
|
-
<i
|
|
1901
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
1902
|
-
style="font-size: 3rem"
|
|
1903
|
-
></i>
|
|
1878
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
1904
1879
|
</div>
|
|
1905
1880
|
}
|
|
1906
1881
|
|
|
1907
1882
|
<!-- Action List -->
|
|
1908
1883
|
@if (!loading() && actions().length > 0) {
|
|
1909
|
-
<div class="surface-card p-4 border
|
|
1884
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
1910
1885
|
<div
|
|
1911
|
-
class="flex flex-
|
|
1886
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
1912
1887
|
>
|
|
1913
1888
|
<div>
|
|
1914
1889
|
<h5 class="m-0 mb-1">Action Permissions</h5>
|
|
1915
|
-
<p class="text-sm text-color
|
|
1890
|
+
<p class="text-sm text-muted-color m-0">
|
|
1916
1891
|
{{ actions().length }} actions available
|
|
1917
1892
|
</p>
|
|
1918
1893
|
</div>
|
|
@@ -1934,6 +1909,7 @@ class RoleActionSelectorComponent {
|
|
|
1934
1909
|
(onClick)="deselectAll()"
|
|
1935
1910
|
/>
|
|
1936
1911
|
<p-button
|
|
1912
|
+
*hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
|
|
1937
1913
|
label="Save Changes"
|
|
1938
1914
|
icon="pi pi-save"
|
|
1939
1915
|
[disabled]="!canSave()"
|
|
@@ -1947,14 +1923,12 @@ class RoleActionSelectorComponent {
|
|
|
1947
1923
|
|
|
1948
1924
|
<!-- Validation Warning -->
|
|
1949
1925
|
@if (invalidActionsCount() > 0) {
|
|
1950
|
-
<div class="
|
|
1951
|
-
<div class="flex items-
|
|
1952
|
-
<i
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
1957
|
-
<p class="text-sm text-orange-700 mt-1">
|
|
1926
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
1927
|
+
<div class="flex items-start gap-2">
|
|
1928
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
1929
|
+
<div class="flex-1">
|
|
1930
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
1931
|
+
<p class="text-sm mt-1 mb-0">
|
|
1958
1932
|
{{ invalidActionsCount() }} selected
|
|
1959
1933
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
1960
1934
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -1966,33 +1940,33 @@ class RoleActionSelectorComponent {
|
|
|
1966
1940
|
}
|
|
1967
1941
|
|
|
1968
1942
|
<!-- Action Tree Table -->
|
|
1969
|
-
<
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
<ng-template
|
|
1994
|
-
<tr [class.
|
|
1995
|
-
<td
|
|
1943
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
1944
|
+
<p-treeTable
|
|
1945
|
+
[value]="treeNodes()"
|
|
1946
|
+
dataKey="id"
|
|
1947
|
+
styleClass="p-treetable-sm"
|
|
1948
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
1949
|
+
>
|
|
1950
|
+
<ng-template #header>
|
|
1951
|
+
<tr>
|
|
1952
|
+
<th class="w-12">
|
|
1953
|
+
<p-checkbox
|
|
1954
|
+
[ngModel]="allSelected()"
|
|
1955
|
+
[binary]="true"
|
|
1956
|
+
(ngModelChange)="toggleAll()"
|
|
1957
|
+
pTooltip="Select/Deselect All"
|
|
1958
|
+
tooltipPosition="top"
|
|
1959
|
+
/>
|
|
1960
|
+
</th>
|
|
1961
|
+
<th>Name</th>
|
|
1962
|
+
<th class="hidden md:table-cell">Code</th>
|
|
1963
|
+
<th>Type</th>
|
|
1964
|
+
<th class="hidden lg:table-cell">Requirements</th>
|
|
1965
|
+
</tr>
|
|
1966
|
+
</ng-template>
|
|
1967
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
1968
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
1969
|
+
<td class="w-12">
|
|
1996
1970
|
<p-checkbox
|
|
1997
1971
|
[ngModel]="selectionMap()[rowData.id]"
|
|
1998
1972
|
[binary]="true"
|
|
@@ -2003,7 +1977,7 @@ class RoleActionSelectorComponent {
|
|
|
2003
1977
|
</td>
|
|
2004
1978
|
<td>
|
|
2005
1979
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2006
|
-
<span class="inline-flex
|
|
1980
|
+
<span class="inline-flex items-center gap-2">
|
|
2007
1981
|
{{ rowData.name }}
|
|
2008
1982
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2009
1983
|
<i
|
|
@@ -2014,7 +1988,7 @@ class RoleActionSelectorComponent {
|
|
|
2014
1988
|
}
|
|
2015
1989
|
</span>
|
|
2016
1990
|
</td>
|
|
2017
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
1991
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2018
1992
|
<td>
|
|
2019
1993
|
<p-tag
|
|
2020
1994
|
[value]="rowData.actionType"
|
|
@@ -2023,42 +1997,41 @@ class RoleActionSelectorComponent {
|
|
|
2023
1997
|
"
|
|
2024
1998
|
/>
|
|
2025
1999
|
</td>
|
|
2026
|
-
<td>
|
|
2000
|
+
<td class="hidden lg:table-cell">
|
|
2027
2001
|
@if (rowData.permissionLogic) {
|
|
2028
|
-
<span class="text-sm text-muted">Has prerequisites</span>
|
|
2002
|
+
<span class="text-sm text-muted-color">Has prerequisites</span>
|
|
2029
2003
|
} @else {
|
|
2030
|
-
<span class="text-muted">-</span>
|
|
2004
|
+
<span class="text-muted-color">-</span>
|
|
2031
2005
|
}
|
|
2032
2006
|
</td>
|
|
2033
2007
|
</tr>
|
|
2034
2008
|
</ng-template>
|
|
2035
|
-
<ng-template
|
|
2009
|
+
<ng-template #emptymessage>
|
|
2036
2010
|
<tr>
|
|
2037
|
-
<td colspan="5" class="text-center p-4">
|
|
2011
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2038
2012
|
@if (loading()) {
|
|
2039
2013
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2040
2014
|
} @else { No actions available. }
|
|
2041
2015
|
</td>
|
|
2042
2016
|
</tr>
|
|
2043
2017
|
</ng-template>
|
|
2044
|
-
|
|
2018
|
+
</p-treeTable>
|
|
2019
|
+
</div>
|
|
2045
2020
|
</div>
|
|
2046
2021
|
|
|
2047
2022
|
<!-- Change Summary -->
|
|
2048
2023
|
@if (hasChanges()) {
|
|
2049
|
-
<div class="
|
|
2050
|
-
<div class="flex
|
|
2051
|
-
<i class="pi pi-info-circle text-
|
|
2052
|
-
<
|
|
2024
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2025
|
+
<div class="flex items-center gap-2 mb-3">
|
|
2026
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
2027
|
+
<span class="font-bold">Pending Changes</span>
|
|
2053
2028
|
</div>
|
|
2054
|
-
<div class="
|
|
2029
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2055
2030
|
@if (pendingAdd().length > 0) {
|
|
2056
|
-
<div
|
|
2057
|
-
<div class="flex
|
|
2031
|
+
<div>
|
|
2032
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2058
2033
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2059
|
-
<strong class="text-sm"
|
|
2060
|
-
>To Add ({{ pendingAdd().length }})</strong
|
|
2061
|
-
>
|
|
2034
|
+
<strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
|
|
2062
2035
|
</div>
|
|
2063
2036
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2064
2037
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2068,12 +2041,10 @@ class RoleActionSelectorComponent {
|
|
|
2068
2041
|
</div>
|
|
2069
2042
|
}
|
|
2070
2043
|
@if (pendingRemove().length > 0) {
|
|
2071
|
-
<div
|
|
2072
|
-
<div class="flex
|
|
2044
|
+
<div>
|
|
2045
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2073
2046
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2074
|
-
<strong class="text-sm"
|
|
2075
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
2076
|
-
>
|
|
2047
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2077
2048
|
</div>
|
|
2078
2049
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2079
2050
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2088,31 +2059,29 @@ class RoleActionSelectorComponent {
|
|
|
2088
2059
|
}
|
|
2089
2060
|
|
|
2090
2061
|
@if (!loading() && actions().length === 0) {
|
|
2091
|
-
<div class="surface-card p-5 border
|
|
2092
|
-
<i
|
|
2093
|
-
|
|
2094
|
-
style="font-size: 3rem; display: block;"
|
|
2095
|
-
></i>
|
|
2096
|
-
<p class="text-color-secondary m-0">
|
|
2062
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2063
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
2064
|
+
<p class="text-muted-color m-0">
|
|
2097
2065
|
No actions available for this role.
|
|
2098
2066
|
</p>
|
|
2099
2067
|
</div>
|
|
2100
2068
|
}
|
|
2101
2069
|
}
|
|
2102
2070
|
</div>
|
|
2103
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.
|
|
2071
|
+
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.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: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { 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"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2104
2072
|
}
|
|
2105
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
2073
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
|
|
2106
2074
|
type: Component,
|
|
2107
|
-
args: [{ selector: 'flusys-role-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule], template: `
|
|
2075
|
+
args: [{ selector: 'flusys-role-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
|
|
2108
2076
|
<div class="role-action-selector">
|
|
2109
2077
|
<!-- Role Selector -->
|
|
2110
2078
|
<div class="mb-4">
|
|
2111
|
-
<div class="grid">
|
|
2112
|
-
<div
|
|
2079
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2080
|
+
<div>
|
|
2113
2081
|
<label class="block font-semibold mb-2">Select Role</label>
|
|
2114
2082
|
<p-select
|
|
2115
|
-
[
|
|
2083
|
+
[ngModel]="selectedRoleId()"
|
|
2084
|
+
(ngModelChange)="selectedRoleId.set($event)"
|
|
2116
2085
|
[options]="roles()"
|
|
2117
2086
|
optionLabel="name"
|
|
2118
2087
|
optionValue="id"
|
|
@@ -2129,24 +2098,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2129
2098
|
<!-- Loading State -->
|
|
2130
2099
|
@if (loading()) {
|
|
2131
2100
|
<div
|
|
2132
|
-
class="surface-card p-5 border
|
|
2101
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2133
2102
|
>
|
|
2134
|
-
<i
|
|
2135
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2136
|
-
style="font-size: 3rem"
|
|
2137
|
-
></i>
|
|
2103
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2138
2104
|
</div>
|
|
2139
2105
|
}
|
|
2140
2106
|
|
|
2141
2107
|
<!-- Action List -->
|
|
2142
2108
|
@if (!loading() && actions().length > 0) {
|
|
2143
|
-
<div class="surface-card p-4 border
|
|
2109
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2144
2110
|
<div
|
|
2145
|
-
class="flex flex-
|
|
2111
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2146
2112
|
>
|
|
2147
2113
|
<div>
|
|
2148
2114
|
<h5 class="m-0 mb-1">Action Permissions</h5>
|
|
2149
|
-
<p class="text-sm text-color
|
|
2115
|
+
<p class="text-sm text-muted-color m-0">
|
|
2150
2116
|
{{ actions().length }} actions available
|
|
2151
2117
|
</p>
|
|
2152
2118
|
</div>
|
|
@@ -2168,6 +2134,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2168
2134
|
(onClick)="deselectAll()"
|
|
2169
2135
|
/>
|
|
2170
2136
|
<p-button
|
|
2137
|
+
*hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
|
|
2171
2138
|
label="Save Changes"
|
|
2172
2139
|
icon="pi pi-save"
|
|
2173
2140
|
[disabled]="!canSave()"
|
|
@@ -2181,14 +2148,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2181
2148
|
|
|
2182
2149
|
<!-- Validation Warning -->
|
|
2183
2150
|
@if (invalidActionsCount() > 0) {
|
|
2184
|
-
<div class="
|
|
2185
|
-
<div class="flex items-
|
|
2186
|
-
<i
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
2191
|
-
<p class="text-sm text-orange-700 mt-1">
|
|
2151
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2152
|
+
<div class="flex items-start gap-2">
|
|
2153
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2154
|
+
<div class="flex-1">
|
|
2155
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
2156
|
+
<p class="text-sm mt-1 mb-0">
|
|
2192
2157
|
{{ invalidActionsCount() }} selected
|
|
2193
2158
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
2194
2159
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -2200,33 +2165,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2200
2165
|
}
|
|
2201
2166
|
|
|
2202
2167
|
<!-- Action Tree Table -->
|
|
2203
|
-
<
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
<ng-template
|
|
2228
|
-
<tr [class.
|
|
2229
|
-
<td
|
|
2168
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2169
|
+
<p-treeTable
|
|
2170
|
+
[value]="treeNodes()"
|
|
2171
|
+
dataKey="id"
|
|
2172
|
+
styleClass="p-treetable-sm"
|
|
2173
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2174
|
+
>
|
|
2175
|
+
<ng-template #header>
|
|
2176
|
+
<tr>
|
|
2177
|
+
<th class="w-12">
|
|
2178
|
+
<p-checkbox
|
|
2179
|
+
[ngModel]="allSelected()"
|
|
2180
|
+
[binary]="true"
|
|
2181
|
+
(ngModelChange)="toggleAll()"
|
|
2182
|
+
pTooltip="Select/Deselect All"
|
|
2183
|
+
tooltipPosition="top"
|
|
2184
|
+
/>
|
|
2185
|
+
</th>
|
|
2186
|
+
<th>Name</th>
|
|
2187
|
+
<th class="hidden md:table-cell">Code</th>
|
|
2188
|
+
<th>Type</th>
|
|
2189
|
+
<th class="hidden lg:table-cell">Requirements</th>
|
|
2190
|
+
</tr>
|
|
2191
|
+
</ng-template>
|
|
2192
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
2193
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
2194
|
+
<td class="w-12">
|
|
2230
2195
|
<p-checkbox
|
|
2231
2196
|
[ngModel]="selectionMap()[rowData.id]"
|
|
2232
2197
|
[binary]="true"
|
|
@@ -2237,7 +2202,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2237
2202
|
</td>
|
|
2238
2203
|
<td>
|
|
2239
2204
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2240
|
-
<span class="inline-flex
|
|
2205
|
+
<span class="inline-flex items-center gap-2">
|
|
2241
2206
|
{{ rowData.name }}
|
|
2242
2207
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2243
2208
|
<i
|
|
@@ -2248,7 +2213,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2248
2213
|
}
|
|
2249
2214
|
</span>
|
|
2250
2215
|
</td>
|
|
2251
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
2216
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2252
2217
|
<td>
|
|
2253
2218
|
<p-tag
|
|
2254
2219
|
[value]="rowData.actionType"
|
|
@@ -2257,42 +2222,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2257
2222
|
"
|
|
2258
2223
|
/>
|
|
2259
2224
|
</td>
|
|
2260
|
-
<td>
|
|
2225
|
+
<td class="hidden lg:table-cell">
|
|
2261
2226
|
@if (rowData.permissionLogic) {
|
|
2262
|
-
<span class="text-sm text-muted">Has prerequisites</span>
|
|
2227
|
+
<span class="text-sm text-muted-color">Has prerequisites</span>
|
|
2263
2228
|
} @else {
|
|
2264
|
-
<span class="text-muted">-</span>
|
|
2229
|
+
<span class="text-muted-color">-</span>
|
|
2265
2230
|
}
|
|
2266
2231
|
</td>
|
|
2267
2232
|
</tr>
|
|
2268
2233
|
</ng-template>
|
|
2269
|
-
<ng-template
|
|
2234
|
+
<ng-template #emptymessage>
|
|
2270
2235
|
<tr>
|
|
2271
|
-
<td colspan="5" class="text-center p-4">
|
|
2236
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2272
2237
|
@if (loading()) {
|
|
2273
2238
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2274
2239
|
} @else { No actions available. }
|
|
2275
2240
|
</td>
|
|
2276
2241
|
</tr>
|
|
2277
2242
|
</ng-template>
|
|
2278
|
-
|
|
2243
|
+
</p-treeTable>
|
|
2244
|
+
</div>
|
|
2279
2245
|
</div>
|
|
2280
2246
|
|
|
2281
2247
|
<!-- Change Summary -->
|
|
2282
2248
|
@if (hasChanges()) {
|
|
2283
|
-
<div class="
|
|
2284
|
-
<div class="flex
|
|
2285
|
-
<i class="pi pi-info-circle text-
|
|
2286
|
-
<
|
|
2249
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2250
|
+
<div class="flex items-center gap-2 mb-3">
|
|
2251
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
2252
|
+
<span class="font-bold">Pending Changes</span>
|
|
2287
2253
|
</div>
|
|
2288
|
-
<div class="
|
|
2254
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2289
2255
|
@if (pendingAdd().length > 0) {
|
|
2290
|
-
<div
|
|
2291
|
-
<div class="flex
|
|
2256
|
+
<div>
|
|
2257
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2292
2258
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2293
|
-
<strong class="text-sm"
|
|
2294
|
-
>To Add ({{ pendingAdd().length }})</strong
|
|
2295
|
-
>
|
|
2259
|
+
<strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
|
|
2296
2260
|
</div>
|
|
2297
2261
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2298
2262
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2302,12 +2266,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2302
2266
|
</div>
|
|
2303
2267
|
}
|
|
2304
2268
|
@if (pendingRemove().length > 0) {
|
|
2305
|
-
<div
|
|
2306
|
-
<div class="flex
|
|
2269
|
+
<div>
|
|
2270
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2307
2271
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2308
|
-
<strong class="text-sm"
|
|
2309
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
2310
|
-
>
|
|
2272
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2311
2273
|
</div>
|
|
2312
2274
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2313
2275
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2322,19 +2284,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2322
2284
|
}
|
|
2323
2285
|
|
|
2324
2286
|
@if (!loading() && actions().length === 0) {
|
|
2325
|
-
<div class="surface-card p-5 border
|
|
2326
|
-
<i
|
|
2327
|
-
|
|
2328
|
-
style="font-size: 3rem; display: block;"
|
|
2329
|
-
></i>
|
|
2330
|
-
<p class="text-color-secondary m-0">
|
|
2287
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2288
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
2289
|
+
<p class="text-muted-color m-0">
|
|
2331
2290
|
No actions available for this role.
|
|
2332
2291
|
</p>
|
|
2333
2292
|
</div>
|
|
2334
2293
|
}
|
|
2335
2294
|
}
|
|
2336
2295
|
</div>
|
|
2337
|
-
`, styles: [":host{display:block}\n"] }]
|
|
2296
|
+
`, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
|
|
2338
2297
|
}], ctorParameters: () => [] });
|
|
2339
2298
|
|
|
2340
2299
|
/**
|
|
@@ -2372,6 +2331,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2372
2331
|
* ```
|
|
2373
2332
|
*/
|
|
2374
2333
|
class CompanyActionSelectorComponent {
|
|
2334
|
+
// Permission constants for template
|
|
2335
|
+
COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
|
|
2375
2336
|
// Dependencies
|
|
2376
2337
|
companyApiProvider = inject(COMPANY_API_PROVIDER);
|
|
2377
2338
|
actionApi = inject(ActionApiService);
|
|
@@ -2379,6 +2340,7 @@ class CompanyActionSelectorComponent {
|
|
|
2379
2340
|
messageService = inject(MessageService);
|
|
2380
2341
|
confirmationService = inject(ConfirmationService);
|
|
2381
2342
|
permissionLogic = inject(ActionPermissionLogicService);
|
|
2343
|
+
destroyRef = inject(DestroyRef);
|
|
2382
2344
|
// State - Company Selection
|
|
2383
2345
|
selectedCompanyId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
|
|
2384
2346
|
companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
|
|
@@ -2453,6 +2415,10 @@ class CompanyActionSelectorComponent {
|
|
|
2453
2415
|
// AbortController for data loading
|
|
2454
2416
|
loadDataAbortController = null;
|
|
2455
2417
|
constructor() {
|
|
2418
|
+
// Cleanup on destroy
|
|
2419
|
+
this.destroyRef.onDestroy(() => {
|
|
2420
|
+
this.loadDataAbortController?.abort();
|
|
2421
|
+
});
|
|
2456
2422
|
this.loadCompanies();
|
|
2457
2423
|
// Effect: Load data when company selection changes
|
|
2458
2424
|
effect(() => {
|
|
@@ -2463,12 +2429,7 @@ class CompanyActionSelectorComponent {
|
|
|
2463
2429
|
this.loadDataAbortController = new AbortController();
|
|
2464
2430
|
this.loadData(this.loadDataAbortController.signal).catch((err) => {
|
|
2465
2431
|
if (err.name !== 'AbortError') {
|
|
2466
|
-
|
|
2467
|
-
this.messageService.add({
|
|
2468
|
-
severity: 'error',
|
|
2469
|
-
summary: 'Error',
|
|
2470
|
-
detail: 'Failed to load company actions. Please refresh.',
|
|
2471
|
-
});
|
|
2432
|
+
// Error toast handled by global interceptor
|
|
2472
2433
|
this.loading.set(false);
|
|
2473
2434
|
}
|
|
2474
2435
|
});
|
|
@@ -2478,9 +2439,6 @@ class CompanyActionSelectorComponent {
|
|
|
2478
2439
|
}
|
|
2479
2440
|
});
|
|
2480
2441
|
}
|
|
2481
|
-
ngOnDestroy() {
|
|
2482
|
-
this.loadDataAbortController?.abort();
|
|
2483
|
-
}
|
|
2484
2442
|
/**
|
|
2485
2443
|
* Load companies from API
|
|
2486
2444
|
*/
|
|
@@ -2492,13 +2450,8 @@ class CompanyActionSelectorComponent {
|
|
|
2492
2450
|
}));
|
|
2493
2451
|
this.companies.set(response?.data ?? []);
|
|
2494
2452
|
}
|
|
2495
|
-
catch
|
|
2496
|
-
|
|
2497
|
-
this.messageService.add({
|
|
2498
|
-
severity: 'error',
|
|
2499
|
-
summary: 'Error',
|
|
2500
|
-
detail: 'Failed to load companies',
|
|
2501
|
-
});
|
|
2453
|
+
catch {
|
|
2454
|
+
// Error toast handled by global interceptor
|
|
2502
2455
|
}
|
|
2503
2456
|
}
|
|
2504
2457
|
/**
|
|
@@ -2516,13 +2469,7 @@ class CompanyActionSelectorComponent {
|
|
|
2516
2469
|
return;
|
|
2517
2470
|
const actionsTree = actionsResponse?.data ?? [];
|
|
2518
2471
|
// Flatten tree for business logic
|
|
2519
|
-
const actionsList = flattenTree(actionsTree).filter((a) =>
|
|
2520
|
-
if (!a.id) {
|
|
2521
|
-
console.warn('[Company Selector] Skipping action without ID:', a.name);
|
|
2522
|
-
return false;
|
|
2523
|
-
}
|
|
2524
|
-
return true;
|
|
2525
|
-
});
|
|
2472
|
+
const actionsList = flattenTree(actionsTree).filter((a) => !!a.id);
|
|
2526
2473
|
// Store both representations
|
|
2527
2474
|
this._actionsTree.set(actionsTree);
|
|
2528
2475
|
this._actions.set(actionsList);
|
|
@@ -2565,15 +2512,15 @@ class CompanyActionSelectorComponent {
|
|
|
2565
2512
|
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
2566
2513
|
if (prerequisiteInfo) {
|
|
2567
2514
|
const actionHint = isCurrentlySelected
|
|
2568
|
-
? '\n\n
|
|
2569
|
-
: '\n\n
|
|
2515
|
+
? '\n\n---\n[Remove] Click to remove'
|
|
2516
|
+
: '\n\n---\n[Add] Click to add (auto-selects required)';
|
|
2570
2517
|
return `${prerequisiteInfo}${actionHint}`;
|
|
2571
2518
|
}
|
|
2572
2519
|
}
|
|
2573
2520
|
if (isCurrentlySelected) {
|
|
2574
|
-
return '
|
|
2521
|
+
return '[Selected] Selected\n\n[Remove] Click to remove from company whitelist';
|
|
2575
2522
|
}
|
|
2576
|
-
return '
|
|
2523
|
+
return '[Add] Click to add to company whitelist';
|
|
2577
2524
|
}
|
|
2578
2525
|
/**
|
|
2579
2526
|
* Handle action checkbox toggle
|
|
@@ -2669,13 +2616,8 @@ class CompanyActionSelectorComponent {
|
|
|
2669
2616
|
// Update baseline
|
|
2670
2617
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
2671
2618
|
}
|
|
2672
|
-
catch
|
|
2673
|
-
|
|
2674
|
-
this.messageService.add({
|
|
2675
|
-
severity: 'error',
|
|
2676
|
-
summary: 'Error',
|
|
2677
|
-
detail: error?.error?.message || 'Failed to update company action whitelist',
|
|
2678
|
-
});
|
|
2619
|
+
catch {
|
|
2620
|
+
// Error toast handled by global interceptor
|
|
2679
2621
|
}
|
|
2680
2622
|
finally {
|
|
2681
2623
|
this.saving.set(false);
|
|
@@ -2737,16 +2679,17 @@ class CompanyActionSelectorComponent {
|
|
|
2737
2679
|
this._selectionMap.set({});
|
|
2738
2680
|
this._initialSelection.set({});
|
|
2739
2681
|
}
|
|
2740
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
2741
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
2682
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2683
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", ngImport: i0, template: `
|
|
2742
2684
|
<div class="company-action-selector">
|
|
2743
2685
|
<!-- Company Selector -->
|
|
2744
2686
|
<div class="mb-4">
|
|
2745
|
-
<div class="grid">
|
|
2746
|
-
<div
|
|
2687
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2688
|
+
<div>
|
|
2747
2689
|
<label class="block font-semibold mb-2">Select Company</label>
|
|
2748
2690
|
<p-select
|
|
2749
|
-
[
|
|
2691
|
+
[ngModel]="selectedCompanyId()"
|
|
2692
|
+
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2750
2693
|
[options]="companies()"
|
|
2751
2694
|
optionLabel="name"
|
|
2752
2695
|
optionValue="id"
|
|
@@ -2763,24 +2706,21 @@ class CompanyActionSelectorComponent {
|
|
|
2763
2706
|
<!-- Loading State -->
|
|
2764
2707
|
@if (loading()) {
|
|
2765
2708
|
<div
|
|
2766
|
-
class="surface-card p-5 border
|
|
2709
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2767
2710
|
>
|
|
2768
|
-
<i
|
|
2769
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2770
|
-
style="font-size: 3rem"
|
|
2771
|
-
></i>
|
|
2711
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2772
2712
|
</div>
|
|
2773
2713
|
}
|
|
2774
2714
|
|
|
2775
2715
|
<!-- Action List -->
|
|
2776
2716
|
@if (!loading() && actions().length > 0) {
|
|
2777
|
-
<div class="surface-card p-4 border
|
|
2717
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2778
2718
|
<div
|
|
2779
|
-
class="flex flex-
|
|
2719
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2780
2720
|
>
|
|
2781
2721
|
<div>
|
|
2782
2722
|
<h5 class="m-0 mb-1">Action Whitelist</h5>
|
|
2783
|
-
<p class="text-sm text-color
|
|
2723
|
+
<p class="text-sm text-muted-color m-0">
|
|
2784
2724
|
{{ actions().length }} actions available
|
|
2785
2725
|
</p>
|
|
2786
2726
|
</div>
|
|
@@ -2802,6 +2742,7 @@ class CompanyActionSelectorComponent {
|
|
|
2802
2742
|
(onClick)="deselectAll()"
|
|
2803
2743
|
/>
|
|
2804
2744
|
<p-button
|
|
2745
|
+
*hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
|
|
2805
2746
|
label="Save Changes"
|
|
2806
2747
|
icon="pi pi-save"
|
|
2807
2748
|
[disabled]="!canSave()"
|
|
@@ -2815,14 +2756,12 @@ class CompanyActionSelectorComponent {
|
|
|
2815
2756
|
|
|
2816
2757
|
<!-- Validation Warning -->
|
|
2817
2758
|
@if (invalidActionsCount() > 0) {
|
|
2818
|
-
<div class="
|
|
2819
|
-
<div class="flex items-
|
|
2820
|
-
<i
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
2825
|
-
<p class="text-sm text-orange-700 mt-1">
|
|
2759
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2760
|
+
<div class="flex items-start gap-2">
|
|
2761
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2762
|
+
<div class="flex-1">
|
|
2763
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
2764
|
+
<p class="text-sm mt-1 mb-0">
|
|
2826
2765
|
{{ invalidActionsCount() }} selected
|
|
2827
2766
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
2828
2767
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -2834,33 +2773,33 @@ class CompanyActionSelectorComponent {
|
|
|
2834
2773
|
}
|
|
2835
2774
|
|
|
2836
2775
|
<!-- Action Tree Table -->
|
|
2837
|
-
<
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
<ng-template
|
|
2862
|
-
<tr [class.
|
|
2863
|
-
<td
|
|
2776
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2777
|
+
<p-treeTable
|
|
2778
|
+
[value]="treeNodes()"
|
|
2779
|
+
dataKey="id"
|
|
2780
|
+
styleClass="p-treetable-sm"
|
|
2781
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2782
|
+
>
|
|
2783
|
+
<ng-template #header>
|
|
2784
|
+
<tr>
|
|
2785
|
+
<th class="w-12">
|
|
2786
|
+
<p-checkbox
|
|
2787
|
+
[ngModel]="allSelected()"
|
|
2788
|
+
[binary]="true"
|
|
2789
|
+
(ngModelChange)="toggleAll()"
|
|
2790
|
+
pTooltip="Select/Deselect All"
|
|
2791
|
+
tooltipPosition="top"
|
|
2792
|
+
/>
|
|
2793
|
+
</th>
|
|
2794
|
+
<th>Name</th>
|
|
2795
|
+
<th class="hidden md:table-cell">Code</th>
|
|
2796
|
+
<th>Type</th>
|
|
2797
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
2798
|
+
</tr>
|
|
2799
|
+
</ng-template>
|
|
2800
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
2801
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
2802
|
+
<td class="w-12">
|
|
2864
2803
|
<p-checkbox
|
|
2865
2804
|
[ngModel]="selectionMap()[rowData.id]"
|
|
2866
2805
|
[binary]="true"
|
|
@@ -2871,7 +2810,7 @@ class CompanyActionSelectorComponent {
|
|
|
2871
2810
|
</td>
|
|
2872
2811
|
<td>
|
|
2873
2812
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2874
|
-
<span class="inline-flex
|
|
2813
|
+
<span class="inline-flex items-center gap-2">
|
|
2875
2814
|
{{ rowData.name }}
|
|
2876
2815
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2877
2816
|
<i
|
|
@@ -2882,7 +2821,7 @@ class CompanyActionSelectorComponent {
|
|
|
2882
2821
|
}
|
|
2883
2822
|
</span>
|
|
2884
2823
|
</td>
|
|
2885
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
2824
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2886
2825
|
<td>
|
|
2887
2826
|
<p-tag
|
|
2888
2827
|
[value]="rowData.actionType"
|
|
@@ -2891,36 +2830,35 @@ class CompanyActionSelectorComponent {
|
|
|
2891
2830
|
"
|
|
2892
2831
|
/>
|
|
2893
2832
|
</td>
|
|
2894
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
2833
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
2895
2834
|
</tr>
|
|
2896
2835
|
</ng-template>
|
|
2897
|
-
<ng-template
|
|
2836
|
+
<ng-template #emptymessage>
|
|
2898
2837
|
<tr>
|
|
2899
|
-
<td colspan="5" class="text-center p-4">
|
|
2838
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2900
2839
|
@if (loading()) {
|
|
2901
2840
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2902
2841
|
} @else { No actions available. }
|
|
2903
2842
|
</td>
|
|
2904
2843
|
</tr>
|
|
2905
2844
|
</ng-template>
|
|
2906
|
-
|
|
2845
|
+
</p-treeTable>
|
|
2846
|
+
</div>
|
|
2907
2847
|
</div>
|
|
2908
2848
|
|
|
2909
2849
|
<!-- Change Summary -->
|
|
2910
2850
|
@if (hasChanges()) {
|
|
2911
|
-
<div class="
|
|
2912
|
-
<div class="flex
|
|
2913
|
-
<i class="pi pi-info-circle text-
|
|
2914
|
-
<
|
|
2851
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2852
|
+
<div class="flex items-center gap-2 mb-3">
|
|
2853
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
2854
|
+
<span class="font-bold">Pending Changes</span>
|
|
2915
2855
|
</div>
|
|
2916
|
-
<div class="
|
|
2856
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2917
2857
|
@if (pendingAdd().length > 0) {
|
|
2918
|
-
<div
|
|
2858
|
+
<div>
|
|
2919
2859
|
<div class="flex items-center gap-2 mb-2">
|
|
2920
2860
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2921
|
-
<strong class="text-sm">
|
|
2922
|
-
To Whitelist ({{ pendingAdd().length }})
|
|
2923
|
-
</strong>
|
|
2861
|
+
<strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
|
|
2924
2862
|
</div>
|
|
2925
2863
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2926
2864
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2929,14 +2867,11 @@ class CompanyActionSelectorComponent {
|
|
|
2929
2867
|
</ul>
|
|
2930
2868
|
</div>
|
|
2931
2869
|
}
|
|
2932
|
-
|
|
2933
2870
|
@if (pendingRemove().length > 0) {
|
|
2934
|
-
<div
|
|
2871
|
+
<div>
|
|
2935
2872
|
<div class="flex items-center gap-2 mb-2">
|
|
2936
2873
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2937
|
-
<strong class="text-sm">
|
|
2938
|
-
To Remove ({{ pendingRemove().length }})
|
|
2939
|
-
</strong>
|
|
2874
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2940
2875
|
</div>
|
|
2941
2876
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2942
2877
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2951,31 +2886,29 @@ class CompanyActionSelectorComponent {
|
|
|
2951
2886
|
}
|
|
2952
2887
|
|
|
2953
2888
|
@if (!loading() && actions().length === 0) {
|
|
2954
|
-
<div class="surface-card p-5 border
|
|
2955
|
-
<i
|
|
2956
|
-
|
|
2957
|
-
style="font-size: 3rem; display: block;"
|
|
2958
|
-
></i>
|
|
2959
|
-
<p class="text-color-secondary m-0">
|
|
2889
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2890
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
2891
|
+
<p class="text-muted-color m-0">
|
|
2960
2892
|
No actions available for this company.
|
|
2961
2893
|
</p>
|
|
2962
2894
|
</div>
|
|
2963
2895
|
}
|
|
2964
2896
|
}
|
|
2965
2897
|
</div>
|
|
2966
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.
|
|
2898
|
+
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.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: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { 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"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2967
2899
|
}
|
|
2968
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
2900
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
|
|
2969
2901
|
type: Component,
|
|
2970
|
-
args: [{ selector: 'flusys-company-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule], template: `
|
|
2902
|
+
args: [{ selector: 'flusys-company-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
|
|
2971
2903
|
<div class="company-action-selector">
|
|
2972
2904
|
<!-- Company Selector -->
|
|
2973
2905
|
<div class="mb-4">
|
|
2974
|
-
<div class="grid">
|
|
2975
|
-
<div
|
|
2906
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2907
|
+
<div>
|
|
2976
2908
|
<label class="block font-semibold mb-2">Select Company</label>
|
|
2977
2909
|
<p-select
|
|
2978
|
-
[
|
|
2910
|
+
[ngModel]="selectedCompanyId()"
|
|
2911
|
+
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2979
2912
|
[options]="companies()"
|
|
2980
2913
|
optionLabel="name"
|
|
2981
2914
|
optionValue="id"
|
|
@@ -2992,24 +2925,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
2992
2925
|
<!-- Loading State -->
|
|
2993
2926
|
@if (loading()) {
|
|
2994
2927
|
<div
|
|
2995
|
-
class="surface-card p-5 border
|
|
2928
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2996
2929
|
>
|
|
2997
|
-
<i
|
|
2998
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2999
|
-
style="font-size: 3rem"
|
|
3000
|
-
></i>
|
|
2930
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3001
2931
|
</div>
|
|
3002
2932
|
}
|
|
3003
2933
|
|
|
3004
2934
|
<!-- Action List -->
|
|
3005
2935
|
@if (!loading() && actions().length > 0) {
|
|
3006
|
-
<div class="surface-card p-4 border
|
|
2936
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3007
2937
|
<div
|
|
3008
|
-
class="flex flex-
|
|
2938
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3009
2939
|
>
|
|
3010
2940
|
<div>
|
|
3011
2941
|
<h5 class="m-0 mb-1">Action Whitelist</h5>
|
|
3012
|
-
<p class="text-sm text-color
|
|
2942
|
+
<p class="text-sm text-muted-color m-0">
|
|
3013
2943
|
{{ actions().length }} actions available
|
|
3014
2944
|
</p>
|
|
3015
2945
|
</div>
|
|
@@ -3031,6 +2961,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3031
2961
|
(onClick)="deselectAll()"
|
|
3032
2962
|
/>
|
|
3033
2963
|
<p-button
|
|
2964
|
+
*hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
|
|
3034
2965
|
label="Save Changes"
|
|
3035
2966
|
icon="pi pi-save"
|
|
3036
2967
|
[disabled]="!canSave()"
|
|
@@ -3044,14 +2975,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3044
2975
|
|
|
3045
2976
|
<!-- Validation Warning -->
|
|
3046
2977
|
@if (invalidActionsCount() > 0) {
|
|
3047
|
-
<div class="
|
|
3048
|
-
<div class="flex items-
|
|
3049
|
-
<i
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
3054
|
-
<p class="text-sm text-orange-700 mt-1">
|
|
2978
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2979
|
+
<div class="flex items-start gap-2">
|
|
2980
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2981
|
+
<div class="flex-1">
|
|
2982
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
2983
|
+
<p class="text-sm mt-1 mb-0">
|
|
3055
2984
|
{{ invalidActionsCount() }} selected
|
|
3056
2985
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
3057
2986
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -3063,33 +2992,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3063
2992
|
}
|
|
3064
2993
|
|
|
3065
2994
|
<!-- Action Tree Table -->
|
|
3066
|
-
<
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
<ng-template
|
|
3091
|
-
<tr [class.
|
|
3092
|
-
<td
|
|
2995
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2996
|
+
<p-treeTable
|
|
2997
|
+
[value]="treeNodes()"
|
|
2998
|
+
dataKey="id"
|
|
2999
|
+
styleClass="p-treetable-sm"
|
|
3000
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
3001
|
+
>
|
|
3002
|
+
<ng-template #header>
|
|
3003
|
+
<tr>
|
|
3004
|
+
<th class="w-12">
|
|
3005
|
+
<p-checkbox
|
|
3006
|
+
[ngModel]="allSelected()"
|
|
3007
|
+
[binary]="true"
|
|
3008
|
+
(ngModelChange)="toggleAll()"
|
|
3009
|
+
pTooltip="Select/Deselect All"
|
|
3010
|
+
tooltipPosition="top"
|
|
3011
|
+
/>
|
|
3012
|
+
</th>
|
|
3013
|
+
<th>Name</th>
|
|
3014
|
+
<th class="hidden md:table-cell">Code</th>
|
|
3015
|
+
<th>Type</th>
|
|
3016
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
3017
|
+
</tr>
|
|
3018
|
+
</ng-template>
|
|
3019
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
3020
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
3021
|
+
<td class="w-12">
|
|
3093
3022
|
<p-checkbox
|
|
3094
3023
|
[ngModel]="selectionMap()[rowData.id]"
|
|
3095
3024
|
[binary]="true"
|
|
@@ -3100,7 +3029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3100
3029
|
</td>
|
|
3101
3030
|
<td>
|
|
3102
3031
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
3103
|
-
<span class="inline-flex
|
|
3032
|
+
<span class="inline-flex items-center gap-2">
|
|
3104
3033
|
{{ rowData.name }}
|
|
3105
3034
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
3106
3035
|
<i
|
|
@@ -3111,7 +3040,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3111
3040
|
}
|
|
3112
3041
|
</span>
|
|
3113
3042
|
</td>
|
|
3114
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
3043
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
3115
3044
|
<td>
|
|
3116
3045
|
<p-tag
|
|
3117
3046
|
[value]="rowData.actionType"
|
|
@@ -3120,36 +3049,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3120
3049
|
"
|
|
3121
3050
|
/>
|
|
3122
3051
|
</td>
|
|
3123
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
3052
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
3124
3053
|
</tr>
|
|
3125
3054
|
</ng-template>
|
|
3126
|
-
<ng-template
|
|
3055
|
+
<ng-template #emptymessage>
|
|
3127
3056
|
<tr>
|
|
3128
|
-
<td colspan="5" class="text-center p-4">
|
|
3057
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
3129
3058
|
@if (loading()) {
|
|
3130
3059
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
3131
3060
|
} @else { No actions available. }
|
|
3132
3061
|
</td>
|
|
3133
3062
|
</tr>
|
|
3134
3063
|
</ng-template>
|
|
3135
|
-
|
|
3064
|
+
</p-treeTable>
|
|
3065
|
+
</div>
|
|
3136
3066
|
</div>
|
|
3137
3067
|
|
|
3138
3068
|
<!-- Change Summary -->
|
|
3139
3069
|
@if (hasChanges()) {
|
|
3140
|
-
<div class="
|
|
3141
|
-
<div class="flex
|
|
3142
|
-
<i class="pi pi-info-circle text-
|
|
3143
|
-
<
|
|
3070
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3071
|
+
<div class="flex items-center gap-2 mb-3">
|
|
3072
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
3073
|
+
<span class="font-bold">Pending Changes</span>
|
|
3144
3074
|
</div>
|
|
3145
|
-
<div class="
|
|
3075
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3146
3076
|
@if (pendingAdd().length > 0) {
|
|
3147
|
-
<div
|
|
3077
|
+
<div>
|
|
3148
3078
|
<div class="flex items-center gap-2 mb-2">
|
|
3149
3079
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3150
|
-
<strong class="text-sm">
|
|
3151
|
-
To Whitelist ({{ pendingAdd().length }})
|
|
3152
|
-
</strong>
|
|
3080
|
+
<strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
|
|
3153
3081
|
</div>
|
|
3154
3082
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3155
3083
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -3158,14 +3086,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3158
3086
|
</ul>
|
|
3159
3087
|
</div>
|
|
3160
3088
|
}
|
|
3161
|
-
|
|
3162
3089
|
@if (pendingRemove().length > 0) {
|
|
3163
|
-
<div
|
|
3090
|
+
<div>
|
|
3164
3091
|
<div class="flex items-center gap-2 mb-2">
|
|
3165
3092
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3166
|
-
<strong class="text-sm">
|
|
3167
|
-
To Remove ({{ pendingRemove().length }})
|
|
3168
|
-
</strong>
|
|
3093
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3169
3094
|
</div>
|
|
3170
3095
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3171
3096
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -3180,19 +3105,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3180
3105
|
}
|
|
3181
3106
|
|
|
3182
3107
|
@if (!loading() && actions().length === 0) {
|
|
3183
|
-
<div class="surface-card p-5 border
|
|
3184
|
-
<i
|
|
3185
|
-
|
|
3186
|
-
style="font-size: 3rem; display: block;"
|
|
3187
|
-
></i>
|
|
3188
|
-
<p class="text-color-secondary m-0">
|
|
3108
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3109
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
3110
|
+
<p class="text-muted-color m-0">
|
|
3189
3111
|
No actions available for this company.
|
|
3190
3112
|
</p>
|
|
3191
3113
|
</div>
|
|
3192
3114
|
}
|
|
3193
3115
|
}
|
|
3194
3116
|
</div>
|
|
3195
|
-
`, styles: [":host{display:block}\n"] }]
|
|
3117
|
+
`, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
|
|
3196
3118
|
}], ctorParameters: () => [] });
|
|
3197
3119
|
|
|
3198
3120
|
/**
|
|
@@ -3226,18 +3148,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3226
3148
|
* ```
|
|
3227
3149
|
*/
|
|
3228
3150
|
class UserRoleSelectorComponent {
|
|
3151
|
+
// Permission constants for template
|
|
3152
|
+
USER_ROLE_PERMISSIONS = USER_ROLE_PERMISSIONS;
|
|
3229
3153
|
// Dependencies
|
|
3230
3154
|
appConfig = inject(APP_CONFIG);
|
|
3231
3155
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
3232
|
-
userProvider = inject(USER_PROVIDER);
|
|
3233
3156
|
userPermissionProvider = inject(USER_PERMISSION_PROVIDER);
|
|
3234
3157
|
roleApi = inject(RoleApiService);
|
|
3235
3158
|
permissionApi = inject(PermissionApiService);
|
|
3236
3159
|
messageService = inject(MessageService);
|
|
3160
|
+
destroyRef = inject(DestroyRef);
|
|
3161
|
+
// AbortController for data loading
|
|
3162
|
+
loadDataAbortController = null;
|
|
3237
3163
|
// State - User/Branch Selection
|
|
3238
|
-
selectedUserId = signal(
|
|
3164
|
+
selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
|
|
3239
3165
|
selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
|
|
3240
|
-
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
3241
3166
|
branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : []));
|
|
3242
3167
|
// State - Loading/Saving
|
|
3243
3168
|
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
@@ -3271,7 +3196,11 @@ class UserRoleSelectorComponent {
|
|
|
3271
3196
|
hasChanges = computed(() => {
|
|
3272
3197
|
const current = this.selectionMap();
|
|
3273
3198
|
const initial = this.initialSelection();
|
|
3274
|
-
|
|
3199
|
+
const currentKeys = Object.keys(current);
|
|
3200
|
+
const initialKeys = Object.keys(initial);
|
|
3201
|
+
if (currentKeys.length !== initialKeys.length)
|
|
3202
|
+
return true;
|
|
3203
|
+
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
3275
3204
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
3276
3205
|
// Computed - Pending Changes
|
|
3277
3206
|
pendingAdd = computed(() => {
|
|
@@ -3291,7 +3220,10 @@ class UserRoleSelectorComponent {
|
|
|
3291
3220
|
return this.hasChanges() && !this.saving();
|
|
3292
3221
|
}, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
3293
3222
|
constructor() {
|
|
3294
|
-
|
|
3223
|
+
// Cleanup on destroy
|
|
3224
|
+
this.destroyRef.onDestroy(() => {
|
|
3225
|
+
this.loadDataAbortController?.abort();
|
|
3226
|
+
});
|
|
3295
3227
|
// Effect: Load user branches and data when user changes
|
|
3296
3228
|
effect(() => {
|
|
3297
3229
|
const userId = this.selectedUserId();
|
|
@@ -3318,28 +3250,6 @@ class UserRoleSelectorComponent {
|
|
|
3318
3250
|
}
|
|
3319
3251
|
});
|
|
3320
3252
|
}
|
|
3321
|
-
/**
|
|
3322
|
-
* Load users from provider
|
|
3323
|
-
*/
|
|
3324
|
-
async loadUsers() {
|
|
3325
|
-
try {
|
|
3326
|
-
const response = await this.userProvider
|
|
3327
|
-
.getUsers({
|
|
3328
|
-
page: 0,
|
|
3329
|
-
pageSize: MAX_DROPDOWN_ITEMS,
|
|
3330
|
-
})
|
|
3331
|
-
.toPromise();
|
|
3332
|
-
this.users.set(response?.data ?? []);
|
|
3333
|
-
}
|
|
3334
|
-
catch (err) {
|
|
3335
|
-
console.error('[User Role Selector] Failed to load users:', err);
|
|
3336
|
-
this.messageService.add({
|
|
3337
|
-
severity: 'error',
|
|
3338
|
-
summary: 'Error',
|
|
3339
|
-
detail: 'Failed to load users',
|
|
3340
|
-
});
|
|
3341
|
-
}
|
|
3342
|
-
}
|
|
3343
3253
|
/**
|
|
3344
3254
|
* Load user's permitted branches
|
|
3345
3255
|
*/
|
|
@@ -3360,13 +3270,8 @@ class UserRoleSelectorComponent {
|
|
|
3360
3270
|
})) ?? [];
|
|
3361
3271
|
this.branches.set(userBranches);
|
|
3362
3272
|
}
|
|
3363
|
-
catch
|
|
3364
|
-
|
|
3365
|
-
this.messageService.add({
|
|
3366
|
-
severity: 'error',
|
|
3367
|
-
summary: 'Error',
|
|
3368
|
-
detail: 'Failed to load user permitted branches',
|
|
3369
|
-
});
|
|
3273
|
+
catch {
|
|
3274
|
+
// Error toast handled by global interceptor
|
|
3370
3275
|
}
|
|
3371
3276
|
}
|
|
3372
3277
|
/**
|
|
@@ -3403,13 +3308,8 @@ class UserRoleSelectorComponent {
|
|
|
3403
3308
|
this._selectionMap.set(selMap);
|
|
3404
3309
|
this._initialSelection.set({ ...selMap });
|
|
3405
3310
|
}
|
|
3406
|
-
catch
|
|
3407
|
-
|
|
3408
|
-
this.messageService.add({
|
|
3409
|
-
severity: 'error',
|
|
3410
|
-
summary: 'Error',
|
|
3411
|
-
detail: 'Failed to load user role assignments',
|
|
3412
|
-
});
|
|
3311
|
+
catch {
|
|
3312
|
+
// Error toast handled by global interceptor
|
|
3413
3313
|
}
|
|
3414
3314
|
finally {
|
|
3415
3315
|
this.loading.set(false);
|
|
@@ -3422,9 +3322,9 @@ class UserRoleSelectorComponent {
|
|
|
3422
3322
|
const selMap = this.selectionMap();
|
|
3423
3323
|
const isCurrentlySelected = selMap[role.id];
|
|
3424
3324
|
if (isCurrentlySelected) {
|
|
3425
|
-
return '
|
|
3325
|
+
return '[Assigned] Assigned to user\n\n[Remove] Click to remove role';
|
|
3426
3326
|
}
|
|
3427
|
-
return '
|
|
3327
|
+
return '[Add] Click to assign role to user';
|
|
3428
3328
|
}
|
|
3429
3329
|
/**
|
|
3430
3330
|
* Handle role checkbox toggle
|
|
@@ -3516,13 +3416,8 @@ class UserRoleSelectorComponent {
|
|
|
3516
3416
|
// Update baseline
|
|
3517
3417
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
3518
3418
|
}
|
|
3519
|
-
catch
|
|
3520
|
-
|
|
3521
|
-
this.messageService.add({
|
|
3522
|
-
severity: 'error',
|
|
3523
|
-
summary: 'Error',
|
|
3524
|
-
detail: error?.error?.message || 'Failed to update user role assignments',
|
|
3525
|
-
});
|
|
3419
|
+
catch {
|
|
3420
|
+
// Error toast handled by global interceptor
|
|
3526
3421
|
}
|
|
3527
3422
|
finally {
|
|
3528
3423
|
this.saving.set(false);
|
|
@@ -3536,62 +3431,34 @@ class UserRoleSelectorComponent {
|
|
|
3536
3431
|
this._selectionMap.set({});
|
|
3537
3432
|
this._initialSelection.set({});
|
|
3538
3433
|
}
|
|
3539
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
3540
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
3434
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3435
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserRoleSelectorComponent, isStandalone: true, selector: "flusys-user-role-selector", ngImport: i0, template: `
|
|
3541
3436
|
<div class="user-role-selector">
|
|
3542
3437
|
<!-- User and Branch Selectors -->
|
|
3543
|
-
<div class="surface-card p-4 border
|
|
3544
|
-
<div class="
|
|
3545
|
-
<div
|
|
3546
|
-
<label class="block font-semibold mb-2
|
|
3438
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
3439
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
3440
|
+
<div>
|
|
3441
|
+
<label class="block font-semibold mb-2">
|
|
3547
3442
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
3548
3443
|
Select User
|
|
3549
3444
|
</label>
|
|
3550
|
-
<
|
|
3551
|
-
[
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
[showClear]="true"
|
|
3557
|
-
[filter]="true"
|
|
3558
|
-
styleClass="w-full"
|
|
3559
|
-
>
|
|
3560
|
-
<ng-template #selectedItem let-user>
|
|
3561
|
-
@if (user) {
|
|
3562
|
-
<div class="flex align-items-center gap-2">
|
|
3563
|
-
<i class="pi pi-user text-primary"></i>
|
|
3564
|
-
<div>
|
|
3565
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
3566
|
-
<div class="text-xs text-color-secondary">
|
|
3567
|
-
{{ user.email }}
|
|
3568
|
-
</div>
|
|
3569
|
-
</div>
|
|
3570
|
-
</div>
|
|
3571
|
-
}
|
|
3572
|
-
</ng-template>
|
|
3573
|
-
<ng-template #item let-user>
|
|
3574
|
-
<div class="flex align-items-center gap-2">
|
|
3575
|
-
<i class="pi pi-user text-color-secondary"></i>
|
|
3576
|
-
<div>
|
|
3577
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
3578
|
-
<div class="text-xs text-color-secondary">
|
|
3579
|
-
{{ user.email }}
|
|
3580
|
-
</div>
|
|
3581
|
-
</div>
|
|
3582
|
-
</div>
|
|
3583
|
-
</ng-template>
|
|
3584
|
-
</p-select>
|
|
3445
|
+
<lib-user-select
|
|
3446
|
+
[value]="selectedUserId()"
|
|
3447
|
+
(valueChange)="selectedUserId.set($event)"
|
|
3448
|
+
[isEditMode]="true"
|
|
3449
|
+
placeHolder="Select a user"
|
|
3450
|
+
/>
|
|
3585
3451
|
</div>
|
|
3586
3452
|
|
|
3587
3453
|
@if (showBranchSelector()) {
|
|
3588
|
-
<div
|
|
3589
|
-
<label class="block font-semibold mb-2
|
|
3454
|
+
<div>
|
|
3455
|
+
<label class="block font-semibold mb-2">
|
|
3590
3456
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
3591
3457
|
Select Branch
|
|
3592
3458
|
</label>
|
|
3593
3459
|
<p-select
|
|
3594
|
-
[
|
|
3460
|
+
[ngModel]="selectedBranchId()"
|
|
3461
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
3595
3462
|
[options]="filteredBranches()"
|
|
3596
3463
|
optionLabel="name"
|
|
3597
3464
|
optionValue="id"
|
|
@@ -3602,20 +3469,20 @@ class UserRoleSelectorComponent {
|
|
|
3602
3469
|
>
|
|
3603
3470
|
<ng-template #selectedItem let-branch>
|
|
3604
3471
|
@if (branch) {
|
|
3605
|
-
<div class="flex
|
|
3472
|
+
<div class="flex items-center gap-2">
|
|
3606
3473
|
<i class="pi pi-building text-primary"></i>
|
|
3607
3474
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
3608
3475
|
</div>
|
|
3609
3476
|
}
|
|
3610
3477
|
</ng-template>
|
|
3611
3478
|
<ng-template #item let-branch>
|
|
3612
|
-
<div class="flex
|
|
3613
|
-
<i class="pi pi-building text-color
|
|
3479
|
+
<div class="flex items-center gap-2">
|
|
3480
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
3614
3481
|
<span>{{ branch.name }}</span>
|
|
3615
3482
|
</div>
|
|
3616
3483
|
</ng-template>
|
|
3617
3484
|
</p-select>
|
|
3618
|
-
<small class="text-color
|
|
3485
|
+
<small class="text-muted-color block mt-1">
|
|
3619
3486
|
{{ filteredBranches().length }} permitted branch{{
|
|
3620
3487
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
3621
3488
|
}}
|
|
@@ -3630,24 +3497,21 @@ class UserRoleSelectorComponent {
|
|
|
3630
3497
|
<!-- Loading State -->
|
|
3631
3498
|
@if (loading()) {
|
|
3632
3499
|
<div
|
|
3633
|
-
class="surface-card p-5 border
|
|
3500
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
3634
3501
|
>
|
|
3635
|
-
<i
|
|
3636
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
3637
|
-
style="font-size: 3rem"
|
|
3638
|
-
></i>
|
|
3502
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3639
3503
|
</div>
|
|
3640
3504
|
}
|
|
3641
3505
|
|
|
3642
3506
|
<!-- Role List -->
|
|
3643
3507
|
@if (!loading() && roles().length > 0) {
|
|
3644
|
-
<div class="surface-card p-4 border
|
|
3508
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3645
3509
|
<div
|
|
3646
|
-
class="flex flex-
|
|
3510
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3647
3511
|
>
|
|
3648
3512
|
<div>
|
|
3649
3513
|
<h5 class="m-0 mb-1">Role Assignments</h5>
|
|
3650
|
-
<p class="text-sm text-color
|
|
3514
|
+
<p class="text-sm text-muted-color m-0">
|
|
3651
3515
|
{{ roles().length }} roles available
|
|
3652
3516
|
</p>
|
|
3653
3517
|
</div>
|
|
@@ -3669,6 +3533,7 @@ class UserRoleSelectorComponent {
|
|
|
3669
3533
|
(onClick)="deselectAll()"
|
|
3670
3534
|
/>
|
|
3671
3535
|
<p-button
|
|
3536
|
+
*hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
|
|
3672
3537
|
label="Save Changes"
|
|
3673
3538
|
icon="pi pi-save"
|
|
3674
3539
|
[disabled]="!canSave()"
|
|
@@ -3681,77 +3546,78 @@ class UserRoleSelectorComponent {
|
|
|
3681
3546
|
</div>
|
|
3682
3547
|
|
|
3683
3548
|
<!-- Role Table -->
|
|
3684
|
-
<
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3549
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
3550
|
+
<p-table
|
|
3551
|
+
[value]="roles()"
|
|
3552
|
+
[rows]="10"
|
|
3553
|
+
[paginator]="roles().length > 10"
|
|
3554
|
+
[rowsPerPageOptions]="[10, 20, 50]"
|
|
3555
|
+
[globalFilterFields]="['name', 'code', 'description']"
|
|
3556
|
+
[showCurrentPageReport]="true"
|
|
3557
|
+
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
|
|
3558
|
+
styleClass="p-datatable-sm"
|
|
3559
|
+
[tableStyle]="{ 'min-width': '35rem' }"
|
|
3560
|
+
>
|
|
3561
|
+
<ng-template #header>
|
|
3562
|
+
<tr>
|
|
3563
|
+
<th style="width: 3rem">
|
|
3564
|
+
<p-checkbox
|
|
3565
|
+
[ngModel]="allSelected()"
|
|
3566
|
+
[binary]="true"
|
|
3567
|
+
(ngModelChange)="toggleAll()"
|
|
3568
|
+
pTooltip="Select/Deselect All"
|
|
3569
|
+
tooltipPosition="top"
|
|
3570
|
+
/>
|
|
3571
|
+
</th>
|
|
3572
|
+
<th>Name</th>
|
|
3573
|
+
<th class="hidden sm:table-cell">Code</th>
|
|
3574
|
+
<th class="hidden md:table-cell">Description</th>
|
|
3575
|
+
</tr>
|
|
3576
|
+
</ng-template>
|
|
3577
|
+
<ng-template #body let-role>
|
|
3578
|
+
<tr>
|
|
3579
|
+
<td>
|
|
3580
|
+
<p-checkbox
|
|
3581
|
+
[ngModel]="selectionMap()[role.id]"
|
|
3582
|
+
[binary]="true"
|
|
3583
|
+
(ngModelChange)="onRoleToggle(role, $event)"
|
|
3584
|
+
[pTooltip]="getTooltip(role)"
|
|
3585
|
+
tooltipPosition="top"
|
|
3586
|
+
/>
|
|
3587
|
+
</td>
|
|
3588
|
+
<td>{{ role.name }}</td>
|
|
3589
|
+
<td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
|
|
3590
|
+
<td class="hidden md:table-cell">{{ role.description || '-' }}</td>
|
|
3591
|
+
</tr>
|
|
3592
|
+
</ng-template>
|
|
3593
|
+
<ng-template #emptymessage>
|
|
3594
|
+
<tr>
|
|
3595
|
+
<td colspan="4" class="text-center p-4 text-muted-color">
|
|
3596
|
+
@if (loading()) {
|
|
3597
|
+
<i class="pi pi-spin pi-spinner"></i> Loading roles...
|
|
3598
|
+
} @else {
|
|
3599
|
+
No roles available.
|
|
3600
|
+
}
|
|
3601
|
+
</td>
|
|
3602
|
+
</tr>
|
|
3603
|
+
</ng-template>
|
|
3604
|
+
</p-table>
|
|
3605
|
+
</div>
|
|
3738
3606
|
</div>
|
|
3739
3607
|
|
|
3740
3608
|
<!-- Change Summary -->
|
|
3741
3609
|
@if (hasChanges()) {
|
|
3742
|
-
<div class="
|
|
3743
|
-
<div class="flex
|
|
3744
|
-
<i class="pi pi-info-circle text-
|
|
3745
|
-
<
|
|
3610
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3611
|
+
<div class="flex items-center gap-2 mb-3">
|
|
3612
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
3613
|
+
<span class="font-bold">Pending Changes</span>
|
|
3746
3614
|
</div>
|
|
3747
|
-
<div class="
|
|
3615
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3748
3616
|
@if (pendingAdd().length > 0) {
|
|
3749
|
-
<div
|
|
3750
|
-
<div class="flex
|
|
3617
|
+
<div>
|
|
3618
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3751
3619
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3752
|
-
<strong class="text-sm"
|
|
3753
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
3754
|
-
>
|
|
3620
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
3755
3621
|
</div>
|
|
3756
3622
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3757
3623
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3761,12 +3627,10 @@ class UserRoleSelectorComponent {
|
|
|
3761
3627
|
</div>
|
|
3762
3628
|
}
|
|
3763
3629
|
@if (pendingRemove().length > 0) {
|
|
3764
|
-
<div
|
|
3765
|
-
<div class="flex
|
|
3630
|
+
<div>
|
|
3631
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3766
3632
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3767
|
-
<strong class="text-sm"
|
|
3768
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
3769
|
-
>
|
|
3633
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3770
3634
|
</div>
|
|
3771
3635
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3772
3636
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3781,77 +3645,46 @@ class UserRoleSelectorComponent {
|
|
|
3781
3645
|
}
|
|
3782
3646
|
|
|
3783
3647
|
@if (!loading() && roles().length === 0) {
|
|
3784
|
-
<div class="surface-card p-5 border
|
|
3785
|
-
<i
|
|
3786
|
-
|
|
3787
|
-
style="font-size: 3rem; display: block;"
|
|
3788
|
-
></i>
|
|
3789
|
-
<p class="text-color-secondary m-0">
|
|
3648
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3649
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
3650
|
+
<p class="text-muted-color m-0">
|
|
3790
3651
|
No roles available for this user.
|
|
3791
3652
|
</p>
|
|
3792
3653
|
</div>
|
|
3793
3654
|
}
|
|
3794
3655
|
}
|
|
3795
3656
|
</div>
|
|
3796
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type:
|
|
3657
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.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: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { 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"] }, { kind: "component", type: i5$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3797
3658
|
}
|
|
3798
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
3659
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
|
|
3799
3660
|
type: Component,
|
|
3800
|
-
args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule], template: `
|
|
3661
|
+
args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
|
|
3801
3662
|
<div class="user-role-selector">
|
|
3802
3663
|
<!-- User and Branch Selectors -->
|
|
3803
|
-
<div class="surface-card p-4 border
|
|
3804
|
-
<div class="
|
|
3805
|
-
<div
|
|
3806
|
-
<label class="block font-semibold mb-2
|
|
3664
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
3665
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
3666
|
+
<div>
|
|
3667
|
+
<label class="block font-semibold mb-2">
|
|
3807
3668
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
3808
3669
|
Select User
|
|
3809
3670
|
</label>
|
|
3810
|
-
<
|
|
3811
|
-
[
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
[showClear]="true"
|
|
3817
|
-
[filter]="true"
|
|
3818
|
-
styleClass="w-full"
|
|
3819
|
-
>
|
|
3820
|
-
<ng-template #selectedItem let-user>
|
|
3821
|
-
@if (user) {
|
|
3822
|
-
<div class="flex align-items-center gap-2">
|
|
3823
|
-
<i class="pi pi-user text-primary"></i>
|
|
3824
|
-
<div>
|
|
3825
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
3826
|
-
<div class="text-xs text-color-secondary">
|
|
3827
|
-
{{ user.email }}
|
|
3828
|
-
</div>
|
|
3829
|
-
</div>
|
|
3830
|
-
</div>
|
|
3831
|
-
}
|
|
3832
|
-
</ng-template>
|
|
3833
|
-
<ng-template #item let-user>
|
|
3834
|
-
<div class="flex align-items-center gap-2">
|
|
3835
|
-
<i class="pi pi-user text-color-secondary"></i>
|
|
3836
|
-
<div>
|
|
3837
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
3838
|
-
<div class="text-xs text-color-secondary">
|
|
3839
|
-
{{ user.email }}
|
|
3840
|
-
</div>
|
|
3841
|
-
</div>
|
|
3842
|
-
</div>
|
|
3843
|
-
</ng-template>
|
|
3844
|
-
</p-select>
|
|
3671
|
+
<lib-user-select
|
|
3672
|
+
[value]="selectedUserId()"
|
|
3673
|
+
(valueChange)="selectedUserId.set($event)"
|
|
3674
|
+
[isEditMode]="true"
|
|
3675
|
+
placeHolder="Select a user"
|
|
3676
|
+
/>
|
|
3845
3677
|
</div>
|
|
3846
3678
|
|
|
3847
3679
|
@if (showBranchSelector()) {
|
|
3848
|
-
<div
|
|
3849
|
-
<label class="block font-semibold mb-2
|
|
3680
|
+
<div>
|
|
3681
|
+
<label class="block font-semibold mb-2">
|
|
3850
3682
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
3851
3683
|
Select Branch
|
|
3852
3684
|
</label>
|
|
3853
3685
|
<p-select
|
|
3854
|
-
[
|
|
3686
|
+
[ngModel]="selectedBranchId()"
|
|
3687
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
3855
3688
|
[options]="filteredBranches()"
|
|
3856
3689
|
optionLabel="name"
|
|
3857
3690
|
optionValue="id"
|
|
@@ -3862,20 +3695,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3862
3695
|
>
|
|
3863
3696
|
<ng-template #selectedItem let-branch>
|
|
3864
3697
|
@if (branch) {
|
|
3865
|
-
<div class="flex
|
|
3698
|
+
<div class="flex items-center gap-2">
|
|
3866
3699
|
<i class="pi pi-building text-primary"></i>
|
|
3867
3700
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
3868
3701
|
</div>
|
|
3869
3702
|
}
|
|
3870
3703
|
</ng-template>
|
|
3871
3704
|
<ng-template #item let-branch>
|
|
3872
|
-
<div class="flex
|
|
3873
|
-
<i class="pi pi-building text-color
|
|
3705
|
+
<div class="flex items-center gap-2">
|
|
3706
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
3874
3707
|
<span>{{ branch.name }}</span>
|
|
3875
3708
|
</div>
|
|
3876
3709
|
</ng-template>
|
|
3877
3710
|
</p-select>
|
|
3878
|
-
<small class="text-color
|
|
3711
|
+
<small class="text-muted-color block mt-1">
|
|
3879
3712
|
{{ filteredBranches().length }} permitted branch{{
|
|
3880
3713
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
3881
3714
|
}}
|
|
@@ -3890,24 +3723,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3890
3723
|
<!-- Loading State -->
|
|
3891
3724
|
@if (loading()) {
|
|
3892
3725
|
<div
|
|
3893
|
-
class="surface-card p-5 border
|
|
3726
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
3894
3727
|
>
|
|
3895
|
-
<i
|
|
3896
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
3897
|
-
style="font-size: 3rem"
|
|
3898
|
-
></i>
|
|
3728
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3899
3729
|
</div>
|
|
3900
3730
|
}
|
|
3901
3731
|
|
|
3902
3732
|
<!-- Role List -->
|
|
3903
3733
|
@if (!loading() && roles().length > 0) {
|
|
3904
|
-
<div class="surface-card p-4 border
|
|
3734
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3905
3735
|
<div
|
|
3906
|
-
class="flex flex-
|
|
3736
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3907
3737
|
>
|
|
3908
3738
|
<div>
|
|
3909
3739
|
<h5 class="m-0 mb-1">Role Assignments</h5>
|
|
3910
|
-
<p class="text-sm text-color
|
|
3740
|
+
<p class="text-sm text-muted-color m-0">
|
|
3911
3741
|
{{ roles().length }} roles available
|
|
3912
3742
|
</p>
|
|
3913
3743
|
</div>
|
|
@@ -3929,6 +3759,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3929
3759
|
(onClick)="deselectAll()"
|
|
3930
3760
|
/>
|
|
3931
3761
|
<p-button
|
|
3762
|
+
*hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
|
|
3932
3763
|
label="Save Changes"
|
|
3933
3764
|
icon="pi pi-save"
|
|
3934
3765
|
[disabled]="!canSave()"
|
|
@@ -3941,77 +3772,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
3941
3772
|
</div>
|
|
3942
3773
|
|
|
3943
3774
|
<!-- Role Table -->
|
|
3944
|
-
<
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3775
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
3776
|
+
<p-table
|
|
3777
|
+
[value]="roles()"
|
|
3778
|
+
[rows]="10"
|
|
3779
|
+
[paginator]="roles().length > 10"
|
|
3780
|
+
[rowsPerPageOptions]="[10, 20, 50]"
|
|
3781
|
+
[globalFilterFields]="['name', 'code', 'description']"
|
|
3782
|
+
[showCurrentPageReport]="true"
|
|
3783
|
+
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
|
|
3784
|
+
styleClass="p-datatable-sm"
|
|
3785
|
+
[tableStyle]="{ 'min-width': '35rem' }"
|
|
3786
|
+
>
|
|
3787
|
+
<ng-template #header>
|
|
3788
|
+
<tr>
|
|
3789
|
+
<th style="width: 3rem">
|
|
3790
|
+
<p-checkbox
|
|
3791
|
+
[ngModel]="allSelected()"
|
|
3792
|
+
[binary]="true"
|
|
3793
|
+
(ngModelChange)="toggleAll()"
|
|
3794
|
+
pTooltip="Select/Deselect All"
|
|
3795
|
+
tooltipPosition="top"
|
|
3796
|
+
/>
|
|
3797
|
+
</th>
|
|
3798
|
+
<th>Name</th>
|
|
3799
|
+
<th class="hidden sm:table-cell">Code</th>
|
|
3800
|
+
<th class="hidden md:table-cell">Description</th>
|
|
3801
|
+
</tr>
|
|
3802
|
+
</ng-template>
|
|
3803
|
+
<ng-template #body let-role>
|
|
3804
|
+
<tr>
|
|
3805
|
+
<td>
|
|
3806
|
+
<p-checkbox
|
|
3807
|
+
[ngModel]="selectionMap()[role.id]"
|
|
3808
|
+
[binary]="true"
|
|
3809
|
+
(ngModelChange)="onRoleToggle(role, $event)"
|
|
3810
|
+
[pTooltip]="getTooltip(role)"
|
|
3811
|
+
tooltipPosition="top"
|
|
3812
|
+
/>
|
|
3813
|
+
</td>
|
|
3814
|
+
<td>{{ role.name }}</td>
|
|
3815
|
+
<td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
|
|
3816
|
+
<td class="hidden md:table-cell">{{ role.description || '-' }}</td>
|
|
3817
|
+
</tr>
|
|
3818
|
+
</ng-template>
|
|
3819
|
+
<ng-template #emptymessage>
|
|
3820
|
+
<tr>
|
|
3821
|
+
<td colspan="4" class="text-center p-4 text-muted-color">
|
|
3822
|
+
@if (loading()) {
|
|
3823
|
+
<i class="pi pi-spin pi-spinner"></i> Loading roles...
|
|
3824
|
+
} @else {
|
|
3825
|
+
No roles available.
|
|
3826
|
+
}
|
|
3827
|
+
</td>
|
|
3828
|
+
</tr>
|
|
3829
|
+
</ng-template>
|
|
3830
|
+
</p-table>
|
|
3831
|
+
</div>
|
|
3998
3832
|
</div>
|
|
3999
3833
|
|
|
4000
3834
|
<!-- Change Summary -->
|
|
4001
3835
|
@if (hasChanges()) {
|
|
4002
|
-
<div class="
|
|
4003
|
-
<div class="flex
|
|
4004
|
-
<i class="pi pi-info-circle text-
|
|
4005
|
-
<
|
|
3836
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3837
|
+
<div class="flex items-center gap-2 mb-3">
|
|
3838
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
3839
|
+
<span class="font-bold">Pending Changes</span>
|
|
4006
3840
|
</div>
|
|
4007
|
-
<div class="
|
|
3841
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4008
3842
|
@if (pendingAdd().length > 0) {
|
|
4009
|
-
<div
|
|
4010
|
-
<div class="flex
|
|
3843
|
+
<div>
|
|
3844
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4011
3845
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4012
|
-
<strong class="text-sm"
|
|
4013
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4014
|
-
>
|
|
3846
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4015
3847
|
</div>
|
|
4016
3848
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4017
3849
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -4021,12 +3853,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4021
3853
|
</div>
|
|
4022
3854
|
}
|
|
4023
3855
|
@if (pendingRemove().length > 0) {
|
|
4024
|
-
<div
|
|
4025
|
-
<div class="flex
|
|
3856
|
+
<div>
|
|
3857
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4026
3858
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4027
|
-
<strong class="text-sm"
|
|
4028
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4029
|
-
>
|
|
3859
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4030
3860
|
</div>
|
|
4031
3861
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4032
3862
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -4041,12 +3871,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4041
3871
|
}
|
|
4042
3872
|
|
|
4043
3873
|
@if (!loading() && roles().length === 0) {
|
|
4044
|
-
<div class="surface-card p-5 border
|
|
4045
|
-
<i
|
|
4046
|
-
|
|
4047
|
-
style="font-size: 3rem; display: block;"
|
|
4048
|
-
></i>
|
|
4049
|
-
<p class="text-color-secondary m-0">
|
|
3874
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3875
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
3876
|
+
<p class="text-muted-color m-0">
|
|
4050
3877
|
No roles available for this user.
|
|
4051
3878
|
</p>
|
|
4052
3879
|
</div>
|
|
@@ -4088,19 +3915,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4088
3915
|
* ```
|
|
4089
3916
|
*/
|
|
4090
3917
|
class UserActionSelectorComponent {
|
|
3918
|
+
// Permission constants for template
|
|
3919
|
+
USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
|
|
4091
3920
|
// Dependencies
|
|
4092
3921
|
appConfig = inject(APP_CONFIG);
|
|
4093
3922
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
4094
|
-
userProvider = inject(USER_PROVIDER);
|
|
4095
3923
|
userPermissionProvider = inject(USER_PERMISSION_PROVIDER);
|
|
4096
3924
|
actionApi = inject(ActionApiService);
|
|
4097
3925
|
permissionApi = inject(PermissionApiService);
|
|
4098
3926
|
permissionLogic = inject(ActionPermissionLogicService);
|
|
4099
3927
|
messageService = inject(MessageService);
|
|
3928
|
+
destroyRef = inject(DestroyRef);
|
|
3929
|
+
// AbortController for data loading
|
|
3930
|
+
loadDataAbortController = null;
|
|
4100
3931
|
// State - User/Branch Selection
|
|
4101
|
-
selectedUserId = signal(
|
|
3932
|
+
selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
|
|
4102
3933
|
selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
|
|
4103
|
-
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
4104
3934
|
branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : []));
|
|
4105
3935
|
// State - Loading/Saving
|
|
4106
3936
|
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
@@ -4139,7 +3969,11 @@ class UserActionSelectorComponent {
|
|
|
4139
3969
|
hasChanges = computed(() => {
|
|
4140
3970
|
const current = this.selectionMap();
|
|
4141
3971
|
const initial = this.initialSelection();
|
|
4142
|
-
|
|
3972
|
+
const currentKeys = Object.keys(current);
|
|
3973
|
+
const initialKeys = Object.keys(initial);
|
|
3974
|
+
if (currentKeys.length !== initialKeys.length)
|
|
3975
|
+
return true;
|
|
3976
|
+
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
4143
3977
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
4144
3978
|
// Computed - Validation
|
|
4145
3979
|
actionsWithUnmetPrerequisites = computed(() => {
|
|
@@ -4175,7 +4009,10 @@ class UserActionSelectorComponent {
|
|
|
4175
4009
|
return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
|
|
4176
4010
|
}, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
4177
4011
|
constructor() {
|
|
4178
|
-
|
|
4012
|
+
// Cleanup on destroy
|
|
4013
|
+
this.destroyRef.onDestroy(() => {
|
|
4014
|
+
this.loadDataAbortController?.abort();
|
|
4015
|
+
});
|
|
4179
4016
|
// Effect: Load user branches and data when user changes
|
|
4180
4017
|
effect(() => {
|
|
4181
4018
|
const userId = this.selectedUserId();
|
|
@@ -4202,28 +4039,6 @@ class UserActionSelectorComponent {
|
|
|
4202
4039
|
}
|
|
4203
4040
|
});
|
|
4204
4041
|
}
|
|
4205
|
-
/**
|
|
4206
|
-
* Load users from provider
|
|
4207
|
-
*/
|
|
4208
|
-
async loadUsers() {
|
|
4209
|
-
try {
|
|
4210
|
-
const response = await this.userProvider
|
|
4211
|
-
.getUsers({
|
|
4212
|
-
page: 0,
|
|
4213
|
-
pageSize: MAX_DROPDOWN_ITEMS,
|
|
4214
|
-
})
|
|
4215
|
-
.toPromise();
|
|
4216
|
-
this.users.set(response?.data ?? []);
|
|
4217
|
-
}
|
|
4218
|
-
catch (err) {
|
|
4219
|
-
console.error('[User Selector] Failed to load users:', err);
|
|
4220
|
-
this.messageService.add({
|
|
4221
|
-
severity: 'error',
|
|
4222
|
-
summary: 'Error',
|
|
4223
|
-
detail: 'Failed to load users',
|
|
4224
|
-
});
|
|
4225
|
-
}
|
|
4226
|
-
}
|
|
4227
4042
|
/**
|
|
4228
4043
|
* Load user's permitted branches
|
|
4229
4044
|
*/
|
|
@@ -4244,13 +4059,8 @@ class UserActionSelectorComponent {
|
|
|
4244
4059
|
})) ?? [];
|
|
4245
4060
|
this.branches.set(userBranches);
|
|
4246
4061
|
}
|
|
4247
|
-
catch
|
|
4248
|
-
|
|
4249
|
-
this.messageService.add({
|
|
4250
|
-
severity: 'error',
|
|
4251
|
-
summary: 'Error',
|
|
4252
|
-
detail: 'Failed to load user permitted branches',
|
|
4253
|
-
});
|
|
4062
|
+
catch {
|
|
4063
|
+
// Error toast handled by global interceptor
|
|
4254
4064
|
}
|
|
4255
4065
|
}
|
|
4256
4066
|
/**
|
|
@@ -4290,13 +4100,8 @@ class UserActionSelectorComponent {
|
|
|
4290
4100
|
this._selectionMap.set(selMap);
|
|
4291
4101
|
this._initialSelection.set({ ...selMap });
|
|
4292
4102
|
}
|
|
4293
|
-
catch
|
|
4294
|
-
|
|
4295
|
-
this.messageService.add({
|
|
4296
|
-
severity: 'error',
|
|
4297
|
-
summary: 'Error',
|
|
4298
|
-
detail: 'Failed to load user action permissions',
|
|
4299
|
-
});
|
|
4103
|
+
catch {
|
|
4104
|
+
// Error toast handled by global interceptor
|
|
4300
4105
|
}
|
|
4301
4106
|
finally {
|
|
4302
4107
|
this.loading.set(false);
|
|
@@ -4312,15 +4117,15 @@ class UserActionSelectorComponent {
|
|
|
4312
4117
|
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
4313
4118
|
if (prerequisiteInfo) {
|
|
4314
4119
|
const actionHint = isCurrentlySelected
|
|
4315
|
-
? '\n\n
|
|
4316
|
-
: '\n\n
|
|
4120
|
+
? '\n\n---\n[Remove] Click to remove'
|
|
4121
|
+
: '\n\n---\n[Add] Click to add (auto-selects required)';
|
|
4317
4122
|
return `${prerequisiteInfo}${actionHint}`;
|
|
4318
4123
|
}
|
|
4319
4124
|
}
|
|
4320
4125
|
if (isCurrentlySelected) {
|
|
4321
|
-
return '
|
|
4126
|
+
return '[Assigned] Assigned to user\n\n[Remove] Click to remove direct permission';
|
|
4322
4127
|
}
|
|
4323
|
-
return '
|
|
4128
|
+
return '[Add] Click to assign direct permission';
|
|
4324
4129
|
}
|
|
4325
4130
|
/**
|
|
4326
4131
|
* Check if action has unmet prerequisites
|
|
@@ -4331,12 +4136,17 @@ class UserActionSelectorComponent {
|
|
|
4331
4136
|
: false;
|
|
4332
4137
|
}
|
|
4333
4138
|
/**
|
|
4334
|
-
* Handle action
|
|
4139
|
+
* Handle action toggle with dependency management
|
|
4335
4140
|
*/
|
|
4336
4141
|
onActionToggle(action, newValue) {
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4142
|
+
if (!newValue) {
|
|
4143
|
+
// Unchecking - validate dependencies
|
|
4144
|
+
this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
4145
|
+
}
|
|
4146
|
+
else {
|
|
4147
|
+
// Checking - validate prerequisites
|
|
4148
|
+
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
4149
|
+
}
|
|
4340
4150
|
}
|
|
4341
4151
|
/**
|
|
4342
4152
|
* Toggle all actions
|
|
@@ -4387,6 +4197,12 @@ class UserActionSelectorComponent {
|
|
|
4387
4197
|
});
|
|
4388
4198
|
return;
|
|
4389
4199
|
}
|
|
4200
|
+
// Pre-save validation
|
|
4201
|
+
const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
|
|
4202
|
+
if (invalidActions.length > 0) {
|
|
4203
|
+
this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4390
4206
|
// Build payload
|
|
4391
4207
|
const items = [];
|
|
4392
4208
|
this.pendingAdd().forEach((action) => {
|
|
@@ -4426,13 +4242,8 @@ class UserActionSelectorComponent {
|
|
|
4426
4242
|
// Update baseline
|
|
4427
4243
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
4428
4244
|
}
|
|
4429
|
-
catch
|
|
4430
|
-
|
|
4431
|
-
this.messageService.add({
|
|
4432
|
-
severity: 'error',
|
|
4433
|
-
summary: 'Error',
|
|
4434
|
-
detail: error?.error?.message || 'Failed to update user action permissions',
|
|
4435
|
-
});
|
|
4245
|
+
catch {
|
|
4246
|
+
// Error toast handled by global interceptor
|
|
4436
4247
|
}
|
|
4437
4248
|
finally {
|
|
4438
4249
|
this.saving.set(false);
|
|
@@ -4447,62 +4258,34 @@ class UserActionSelectorComponent {
|
|
|
4447
4258
|
this._selectionMap.set({});
|
|
4448
4259
|
this._initialSelection.set({});
|
|
4449
4260
|
}
|
|
4450
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
4451
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
4261
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4262
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
|
|
4452
4263
|
<div class="user-action-selector">
|
|
4453
4264
|
<!-- User and Branch Selectors -->
|
|
4454
|
-
<div class="surface-card p-4 border
|
|
4455
|
-
<div class="
|
|
4456
|
-
<div
|
|
4457
|
-
<label class="block font-semibold mb-2
|
|
4265
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
4266
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
4267
|
+
<div>
|
|
4268
|
+
<label class="block font-semibold mb-2">
|
|
4458
4269
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
4459
4270
|
Select User
|
|
4460
4271
|
</label>
|
|
4461
|
-
<
|
|
4462
|
-
[
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
[showClear]="true"
|
|
4468
|
-
[filter]="true"
|
|
4469
|
-
styleClass="w-full"
|
|
4470
|
-
>
|
|
4471
|
-
<ng-template #selectedItem let-user>
|
|
4472
|
-
@if (user) {
|
|
4473
|
-
<div class="flex align-items-center gap-2">
|
|
4474
|
-
<i class="pi pi-user text-primary"></i>
|
|
4475
|
-
<div>
|
|
4476
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
4477
|
-
<div class="text-xs text-color-secondary">
|
|
4478
|
-
{{ user.email }}
|
|
4479
|
-
</div>
|
|
4480
|
-
</div>
|
|
4481
|
-
</div>
|
|
4482
|
-
}
|
|
4483
|
-
</ng-template>
|
|
4484
|
-
<ng-template #item let-user>
|
|
4485
|
-
<div class="flex align-items-center gap-2">
|
|
4486
|
-
<i class="pi pi-user text-color-secondary"></i>
|
|
4487
|
-
<div>
|
|
4488
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
4489
|
-
<div class="text-xs text-color-secondary">
|
|
4490
|
-
{{ user.email }}
|
|
4491
|
-
</div>
|
|
4492
|
-
</div>
|
|
4493
|
-
</div>
|
|
4494
|
-
</ng-template>
|
|
4495
|
-
</p-select>
|
|
4272
|
+
<lib-user-select
|
|
4273
|
+
[value]="selectedUserId()"
|
|
4274
|
+
(valueChange)="selectedUserId.set($event)"
|
|
4275
|
+
[isEditMode]="true"
|
|
4276
|
+
placeHolder="Select a user"
|
|
4277
|
+
/>
|
|
4496
4278
|
</div>
|
|
4497
4279
|
|
|
4498
4280
|
@if (showBranchSelector()) {
|
|
4499
|
-
<div
|
|
4500
|
-
<label class="block font-semibold mb-2
|
|
4281
|
+
<div>
|
|
4282
|
+
<label class="block font-semibold mb-2">
|
|
4501
4283
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
4502
4284
|
Select Branch
|
|
4503
4285
|
</label>
|
|
4504
4286
|
<p-select
|
|
4505
|
-
[
|
|
4287
|
+
[ngModel]="selectedBranchId()"
|
|
4288
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
4506
4289
|
[options]="filteredBranches()"
|
|
4507
4290
|
optionLabel="name"
|
|
4508
4291
|
optionValue="id"
|
|
@@ -4513,20 +4296,20 @@ class UserActionSelectorComponent {
|
|
|
4513
4296
|
>
|
|
4514
4297
|
<ng-template #selectedItem let-branch>
|
|
4515
4298
|
@if (branch) {
|
|
4516
|
-
<div class="flex
|
|
4299
|
+
<div class="flex items-center gap-2">
|
|
4517
4300
|
<i class="pi pi-building text-primary"></i>
|
|
4518
4301
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
4519
4302
|
</div>
|
|
4520
4303
|
}
|
|
4521
4304
|
</ng-template>
|
|
4522
4305
|
<ng-template #item let-branch>
|
|
4523
|
-
<div class="flex
|
|
4524
|
-
<i class="pi pi-building text-color
|
|
4306
|
+
<div class="flex items-center gap-2">
|
|
4307
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
4525
4308
|
<span>{{ branch.name }}</span>
|
|
4526
4309
|
</div>
|
|
4527
4310
|
</ng-template>
|
|
4528
4311
|
</p-select>
|
|
4529
|
-
<small class="text-color
|
|
4312
|
+
<small class="text-muted-color block mt-1">
|
|
4530
4313
|
{{ filteredBranches().length }} permitted branch{{
|
|
4531
4314
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
4532
4315
|
}}
|
|
@@ -4540,20 +4323,20 @@ class UserActionSelectorComponent {
|
|
|
4540
4323
|
@if (selectedUserId()) {
|
|
4541
4324
|
<!-- Loading State -->
|
|
4542
4325
|
@if (loading()) {
|
|
4543
|
-
<div class="flex justify-
|
|
4544
|
-
<i class="pi pi-spin pi-spinner
|
|
4326
|
+
<div class="flex justify-center p-5">
|
|
4327
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
4545
4328
|
</div>
|
|
4546
4329
|
}
|
|
4547
4330
|
|
|
4548
4331
|
<!-- Action List -->
|
|
4549
4332
|
@if (!loading() && actions().length > 0) {
|
|
4550
|
-
<div class="surface-card p-4 border
|
|
4333
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4551
4334
|
<div
|
|
4552
|
-
class="flex flex-
|
|
4335
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4553
4336
|
>
|
|
4554
4337
|
<div>
|
|
4555
4338
|
<h5 class="m-0 mb-1">Direct Action Permissions</h5>
|
|
4556
|
-
<p class="text-sm text-color
|
|
4339
|
+
<p class="text-sm text-muted-color m-0">
|
|
4557
4340
|
{{ actions().length }} actions available
|
|
4558
4341
|
</p>
|
|
4559
4342
|
</div>
|
|
@@ -4575,6 +4358,7 @@ class UserActionSelectorComponent {
|
|
|
4575
4358
|
(onClick)="deselectAll()"
|
|
4576
4359
|
/>
|
|
4577
4360
|
<p-button
|
|
4361
|
+
*hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
|
|
4578
4362
|
label="Save Changes"
|
|
4579
4363
|
icon="pi pi-save"
|
|
4580
4364
|
[disabled]="!canSave()"
|
|
@@ -4586,34 +4370,52 @@ class UserActionSelectorComponent {
|
|
|
4586
4370
|
</div>
|
|
4587
4371
|
</div>
|
|
4588
4372
|
|
|
4373
|
+
<!-- Validation Warning -->
|
|
4374
|
+
@if (invalidActionsCount() > 0) {
|
|
4375
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
4376
|
+
<div class="flex items-start gap-2">
|
|
4377
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
4378
|
+
<div class="flex-1">
|
|
4379
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
4380
|
+
<p class="text-sm mt-1 mb-0">
|
|
4381
|
+
{{ invalidActionsCount() }} selected
|
|
4382
|
+
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
4383
|
+
unmet prerequisites. Fix before saving or use auto-fix on
|
|
4384
|
+
save.
|
|
4385
|
+
</p>
|
|
4386
|
+
</div>
|
|
4387
|
+
</div>
|
|
4388
|
+
</div>
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4589
4391
|
<!-- Action Tree Table -->
|
|
4590
|
-
<
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
<ng-template
|
|
4615
|
-
<tr>
|
|
4616
|
-
<td
|
|
4392
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
4393
|
+
<p-treeTable
|
|
4394
|
+
[value]="treeNodes()"
|
|
4395
|
+
dataKey="id"
|
|
4396
|
+
styleClass="p-treetable-sm"
|
|
4397
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
4398
|
+
>
|
|
4399
|
+
<ng-template #header>
|
|
4400
|
+
<tr>
|
|
4401
|
+
<th class="w-12">
|
|
4402
|
+
<p-checkbox
|
|
4403
|
+
[ngModel]="allSelected()"
|
|
4404
|
+
[binary]="true"
|
|
4405
|
+
(ngModelChange)="toggleAll()"
|
|
4406
|
+
pTooltip="Select/Deselect All"
|
|
4407
|
+
tooltipPosition="top"
|
|
4408
|
+
/>
|
|
4409
|
+
</th>
|
|
4410
|
+
<th>Name</th>
|
|
4411
|
+
<th class="hidden md:table-cell">Code</th>
|
|
4412
|
+
<th>Type</th>
|
|
4413
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
4414
|
+
</tr>
|
|
4415
|
+
</ng-template>
|
|
4416
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
4417
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
4418
|
+
<td class="w-12">
|
|
4617
4419
|
<p-checkbox
|
|
4618
4420
|
[ngModel]="selectionMap()[rowData.id]"
|
|
4619
4421
|
[binary]="true"
|
|
@@ -4624,9 +4426,18 @@ class UserActionSelectorComponent {
|
|
|
4624
4426
|
</td>
|
|
4625
4427
|
<td>
|
|
4626
4428
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4627
|
-
<span
|
|
4429
|
+
<span class="inline-flex items-center gap-2">
|
|
4430
|
+
{{ rowData.name }}
|
|
4431
|
+
@if (hasUnmetPrerequisites(rowData)) {
|
|
4432
|
+
<i
|
|
4433
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
4434
|
+
pTooltip="This action has unmet prerequisites and will fail validation on save"
|
|
4435
|
+
tooltipPosition="top"
|
|
4436
|
+
></i>
|
|
4437
|
+
}
|
|
4438
|
+
</span>
|
|
4628
4439
|
</td>
|
|
4629
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
4440
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
4630
4441
|
<td>
|
|
4631
4442
|
<p-tag
|
|
4632
4443
|
[value]="rowData.actionType"
|
|
@@ -4635,12 +4446,12 @@ class UserActionSelectorComponent {
|
|
|
4635
4446
|
"
|
|
4636
4447
|
/>
|
|
4637
4448
|
</td>
|
|
4638
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
4449
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4639
4450
|
</tr>
|
|
4640
4451
|
</ng-template>
|
|
4641
|
-
<ng-template
|
|
4452
|
+
<ng-template #emptymessage>
|
|
4642
4453
|
<tr>
|
|
4643
|
-
<td colspan="5" class="text-center p-4">
|
|
4454
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4644
4455
|
@if (loading()) {
|
|
4645
4456
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
4646
4457
|
} @else {
|
|
@@ -4649,24 +4460,23 @@ class UserActionSelectorComponent {
|
|
|
4649
4460
|
</td>
|
|
4650
4461
|
</tr>
|
|
4651
4462
|
</ng-template>
|
|
4652
|
-
|
|
4463
|
+
</p-treeTable>
|
|
4464
|
+
</div>
|
|
4653
4465
|
</div>
|
|
4654
4466
|
|
|
4655
4467
|
<!-- Change Summary -->
|
|
4656
4468
|
@if (hasChanges()) {
|
|
4657
|
-
<div class="
|
|
4658
|
-
<div class="flex
|
|
4659
|
-
<i class="pi pi-info-circle text-
|
|
4660
|
-
<
|
|
4469
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
4470
|
+
<div class="flex items-center gap-2 mb-3">
|
|
4471
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
4472
|
+
<span class="font-bold">Pending Changes</span>
|
|
4661
4473
|
</div>
|
|
4662
|
-
<div class="
|
|
4474
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4663
4475
|
@if (pendingAdd().length > 0) {
|
|
4664
|
-
<div
|
|
4665
|
-
<div class="flex
|
|
4476
|
+
<div>
|
|
4477
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4666
4478
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4667
|
-
<strong class="text-sm"
|
|
4668
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4669
|
-
>
|
|
4479
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4670
4480
|
</div>
|
|
4671
4481
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4672
4482
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4676,12 +4486,10 @@ class UserActionSelectorComponent {
|
|
|
4676
4486
|
</div>
|
|
4677
4487
|
}
|
|
4678
4488
|
@if (pendingRemove().length > 0) {
|
|
4679
|
-
<div
|
|
4680
|
-
<div class="flex
|
|
4489
|
+
<div>
|
|
4490
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4681
4491
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4682
|
-
<strong class="text-sm"
|
|
4683
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4684
|
-
>
|
|
4492
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4685
4493
|
</div>
|
|
4686
4494
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4687
4495
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4696,77 +4504,46 @@ class UserActionSelectorComponent {
|
|
|
4696
4504
|
}
|
|
4697
4505
|
|
|
4698
4506
|
@if (!loading() && actions().length === 0) {
|
|
4699
|
-
<div class="surface-card p-5 border
|
|
4700
|
-
<i
|
|
4701
|
-
|
|
4702
|
-
style="font-size: 3rem; display: block;"
|
|
4703
|
-
></i>
|
|
4704
|
-
<p class="text-color-secondary m-0">
|
|
4507
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
4508
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
4509
|
+
<p class="text-muted-color m-0">
|
|
4705
4510
|
No actions available for this user.
|
|
4706
4511
|
</p>
|
|
4707
4512
|
</div>
|
|
4708
4513
|
}
|
|
4709
4514
|
}
|
|
4710
4515
|
</div>
|
|
4711
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.
|
|
4516
|
+
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.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: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { 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"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4712
4517
|
}
|
|
4713
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
4518
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
|
|
4714
4519
|
type: Component,
|
|
4715
|
-
args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule], template: `
|
|
4520
|
+
args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
|
|
4716
4521
|
<div class="user-action-selector">
|
|
4717
4522
|
<!-- User and Branch Selectors -->
|
|
4718
|
-
<div class="surface-card p-4 border
|
|
4719
|
-
<div class="
|
|
4720
|
-
<div
|
|
4721
|
-
<label class="block font-semibold mb-2
|
|
4523
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
4524
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
4525
|
+
<div>
|
|
4526
|
+
<label class="block font-semibold mb-2">
|
|
4722
4527
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
4723
4528
|
Select User
|
|
4724
4529
|
</label>
|
|
4725
|
-
<
|
|
4726
|
-
[
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
[showClear]="true"
|
|
4732
|
-
[filter]="true"
|
|
4733
|
-
styleClass="w-full"
|
|
4734
|
-
>
|
|
4735
|
-
<ng-template #selectedItem let-user>
|
|
4736
|
-
@if (user) {
|
|
4737
|
-
<div class="flex align-items-center gap-2">
|
|
4738
|
-
<i class="pi pi-user text-primary"></i>
|
|
4739
|
-
<div>
|
|
4740
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
4741
|
-
<div class="text-xs text-color-secondary">
|
|
4742
|
-
{{ user.email }}
|
|
4743
|
-
</div>
|
|
4744
|
-
</div>
|
|
4745
|
-
</div>
|
|
4746
|
-
}
|
|
4747
|
-
</ng-template>
|
|
4748
|
-
<ng-template #item let-user>
|
|
4749
|
-
<div class="flex align-items-center gap-2">
|
|
4750
|
-
<i class="pi pi-user text-color-secondary"></i>
|
|
4751
|
-
<div>
|
|
4752
|
-
<div class="font-semibold">{{ user.name }}</div>
|
|
4753
|
-
<div class="text-xs text-color-secondary">
|
|
4754
|
-
{{ user.email }}
|
|
4755
|
-
</div>
|
|
4756
|
-
</div>
|
|
4757
|
-
</div>
|
|
4758
|
-
</ng-template>
|
|
4759
|
-
</p-select>
|
|
4530
|
+
<lib-user-select
|
|
4531
|
+
[value]="selectedUserId()"
|
|
4532
|
+
(valueChange)="selectedUserId.set($event)"
|
|
4533
|
+
[isEditMode]="true"
|
|
4534
|
+
placeHolder="Select a user"
|
|
4535
|
+
/>
|
|
4760
4536
|
</div>
|
|
4761
4537
|
|
|
4762
4538
|
@if (showBranchSelector()) {
|
|
4763
|
-
<div
|
|
4764
|
-
<label class="block font-semibold mb-2
|
|
4539
|
+
<div>
|
|
4540
|
+
<label class="block font-semibold mb-2">
|
|
4765
4541
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
4766
4542
|
Select Branch
|
|
4767
4543
|
</label>
|
|
4768
4544
|
<p-select
|
|
4769
|
-
[
|
|
4545
|
+
[ngModel]="selectedBranchId()"
|
|
4546
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
4770
4547
|
[options]="filteredBranches()"
|
|
4771
4548
|
optionLabel="name"
|
|
4772
4549
|
optionValue="id"
|
|
@@ -4777,20 +4554,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4777
4554
|
>
|
|
4778
4555
|
<ng-template #selectedItem let-branch>
|
|
4779
4556
|
@if (branch) {
|
|
4780
|
-
<div class="flex
|
|
4557
|
+
<div class="flex items-center gap-2">
|
|
4781
4558
|
<i class="pi pi-building text-primary"></i>
|
|
4782
4559
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
4783
4560
|
</div>
|
|
4784
4561
|
}
|
|
4785
4562
|
</ng-template>
|
|
4786
4563
|
<ng-template #item let-branch>
|
|
4787
|
-
<div class="flex
|
|
4788
|
-
<i class="pi pi-building text-color
|
|
4564
|
+
<div class="flex items-center gap-2">
|
|
4565
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
4789
4566
|
<span>{{ branch.name }}</span>
|
|
4790
4567
|
</div>
|
|
4791
4568
|
</ng-template>
|
|
4792
4569
|
</p-select>
|
|
4793
|
-
<small class="text-color
|
|
4570
|
+
<small class="text-muted-color block mt-1">
|
|
4794
4571
|
{{ filteredBranches().length }} permitted branch{{
|
|
4795
4572
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
4796
4573
|
}}
|
|
@@ -4804,20 +4581,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4804
4581
|
@if (selectedUserId()) {
|
|
4805
4582
|
<!-- Loading State -->
|
|
4806
4583
|
@if (loading()) {
|
|
4807
|
-
<div class="flex justify-
|
|
4808
|
-
<i class="pi pi-spin pi-spinner
|
|
4584
|
+
<div class="flex justify-center p-5">
|
|
4585
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
4809
4586
|
</div>
|
|
4810
4587
|
}
|
|
4811
4588
|
|
|
4812
4589
|
<!-- Action List -->
|
|
4813
4590
|
@if (!loading() && actions().length > 0) {
|
|
4814
|
-
<div class="surface-card p-4 border
|
|
4591
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4815
4592
|
<div
|
|
4816
|
-
class="flex flex-
|
|
4593
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4817
4594
|
>
|
|
4818
4595
|
<div>
|
|
4819
4596
|
<h5 class="m-0 mb-1">Direct Action Permissions</h5>
|
|
4820
|
-
<p class="text-sm text-color
|
|
4597
|
+
<p class="text-sm text-muted-color m-0">
|
|
4821
4598
|
{{ actions().length }} actions available
|
|
4822
4599
|
</p>
|
|
4823
4600
|
</div>
|
|
@@ -4839,6 +4616,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4839
4616
|
(onClick)="deselectAll()"
|
|
4840
4617
|
/>
|
|
4841
4618
|
<p-button
|
|
4619
|
+
*hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
|
|
4842
4620
|
label="Save Changes"
|
|
4843
4621
|
icon="pi pi-save"
|
|
4844
4622
|
[disabled]="!canSave()"
|
|
@@ -4850,34 +4628,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4850
4628
|
</div>
|
|
4851
4629
|
</div>
|
|
4852
4630
|
|
|
4631
|
+
<!-- Validation Warning -->
|
|
4632
|
+
@if (invalidActionsCount() > 0) {
|
|
4633
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
4634
|
+
<div class="flex items-start gap-2">
|
|
4635
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
4636
|
+
<div class="flex-1">
|
|
4637
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
4638
|
+
<p class="text-sm mt-1 mb-0">
|
|
4639
|
+
{{ invalidActionsCount() }} selected
|
|
4640
|
+
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
4641
|
+
unmet prerequisites. Fix before saving or use auto-fix on
|
|
4642
|
+
save.
|
|
4643
|
+
</p>
|
|
4644
|
+
</div>
|
|
4645
|
+
</div>
|
|
4646
|
+
</div>
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4853
4649
|
<!-- Action Tree Table -->
|
|
4854
|
-
<
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
<ng-template
|
|
4879
|
-
<tr>
|
|
4880
|
-
<td
|
|
4650
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
4651
|
+
<p-treeTable
|
|
4652
|
+
[value]="treeNodes()"
|
|
4653
|
+
dataKey="id"
|
|
4654
|
+
styleClass="p-treetable-sm"
|
|
4655
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
4656
|
+
>
|
|
4657
|
+
<ng-template #header>
|
|
4658
|
+
<tr>
|
|
4659
|
+
<th class="w-12">
|
|
4660
|
+
<p-checkbox
|
|
4661
|
+
[ngModel]="allSelected()"
|
|
4662
|
+
[binary]="true"
|
|
4663
|
+
(ngModelChange)="toggleAll()"
|
|
4664
|
+
pTooltip="Select/Deselect All"
|
|
4665
|
+
tooltipPosition="top"
|
|
4666
|
+
/>
|
|
4667
|
+
</th>
|
|
4668
|
+
<th>Name</th>
|
|
4669
|
+
<th class="hidden md:table-cell">Code</th>
|
|
4670
|
+
<th>Type</th>
|
|
4671
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
4672
|
+
</tr>
|
|
4673
|
+
</ng-template>
|
|
4674
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
4675
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
4676
|
+
<td class="w-12">
|
|
4881
4677
|
<p-checkbox
|
|
4882
4678
|
[ngModel]="selectionMap()[rowData.id]"
|
|
4883
4679
|
[binary]="true"
|
|
@@ -4888,9 +4684,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4888
4684
|
</td>
|
|
4889
4685
|
<td>
|
|
4890
4686
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4891
|
-
<span
|
|
4687
|
+
<span class="inline-flex items-center gap-2">
|
|
4688
|
+
{{ rowData.name }}
|
|
4689
|
+
@if (hasUnmetPrerequisites(rowData)) {
|
|
4690
|
+
<i
|
|
4691
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
4692
|
+
pTooltip="This action has unmet prerequisites and will fail validation on save"
|
|
4693
|
+
tooltipPosition="top"
|
|
4694
|
+
></i>
|
|
4695
|
+
}
|
|
4696
|
+
</span>
|
|
4892
4697
|
</td>
|
|
4893
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
4698
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
4894
4699
|
<td>
|
|
4895
4700
|
<p-tag
|
|
4896
4701
|
[value]="rowData.actionType"
|
|
@@ -4899,12 +4704,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4899
4704
|
"
|
|
4900
4705
|
/>
|
|
4901
4706
|
</td>
|
|
4902
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
4707
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4903
4708
|
</tr>
|
|
4904
4709
|
</ng-template>
|
|
4905
|
-
<ng-template
|
|
4710
|
+
<ng-template #emptymessage>
|
|
4906
4711
|
<tr>
|
|
4907
|
-
<td colspan="5" class="text-center p-4">
|
|
4712
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4908
4713
|
@if (loading()) {
|
|
4909
4714
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
4910
4715
|
} @else {
|
|
@@ -4913,24 +4718,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4913
4718
|
</td>
|
|
4914
4719
|
</tr>
|
|
4915
4720
|
</ng-template>
|
|
4916
|
-
|
|
4721
|
+
</p-treeTable>
|
|
4722
|
+
</div>
|
|
4917
4723
|
</div>
|
|
4918
4724
|
|
|
4919
4725
|
<!-- Change Summary -->
|
|
4920
4726
|
@if (hasChanges()) {
|
|
4921
|
-
<div class="
|
|
4922
|
-
<div class="flex
|
|
4923
|
-
<i class="pi pi-info-circle text-
|
|
4924
|
-
<
|
|
4727
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
4728
|
+
<div class="flex items-center gap-2 mb-3">
|
|
4729
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
4730
|
+
<span class="font-bold">Pending Changes</span>
|
|
4925
4731
|
</div>
|
|
4926
|
-
<div class="
|
|
4732
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4927
4733
|
@if (pendingAdd().length > 0) {
|
|
4928
|
-
<div
|
|
4929
|
-
<div class="flex
|
|
4734
|
+
<div>
|
|
4735
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4930
4736
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4931
|
-
<strong class="text-sm"
|
|
4932
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4933
|
-
>
|
|
4737
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4934
4738
|
</div>
|
|
4935
4739
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4936
4740
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4940,12 +4744,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4940
4744
|
</div>
|
|
4941
4745
|
}
|
|
4942
4746
|
@if (pendingRemove().length > 0) {
|
|
4943
|
-
<div
|
|
4944
|
-
<div class="flex
|
|
4747
|
+
<div>
|
|
4748
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4945
4749
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4946
|
-
<strong class="text-sm"
|
|
4947
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4948
|
-
>
|
|
4750
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4949
4751
|
</div>
|
|
4950
4752
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4951
4753
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4960,23 +4762,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4960
4762
|
}
|
|
4961
4763
|
|
|
4962
4764
|
@if (!loading() && actions().length === 0) {
|
|
4963
|
-
<div class="surface-card p-5 border
|
|
4964
|
-
<i
|
|
4965
|
-
|
|
4966
|
-
style="font-size: 3rem; display: block;"
|
|
4967
|
-
></i>
|
|
4968
|
-
<p class="text-color-secondary m-0">
|
|
4765
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
4766
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
4767
|
+
<p class="text-muted-color m-0">
|
|
4969
4768
|
No actions available for this user.
|
|
4970
4769
|
</p>
|
|
4971
4770
|
</div>
|
|
4972
4771
|
}
|
|
4973
4772
|
}
|
|
4974
4773
|
</div>
|
|
4975
|
-
`, styles: [":host{display:block}\n"] }]
|
|
4774
|
+
`, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
|
|
4976
4775
|
}], ctorParameters: () => [] });
|
|
4977
4776
|
|
|
4978
4777
|
// Logic Builder Component
|
|
4979
4778
|
|
|
4779
|
+
/**
|
|
4780
|
+
* Profile Permission Provider Adapter
|
|
4781
|
+
*
|
|
4782
|
+
* Implements IProfilePermissionProvider using ng-iam's PermissionApiService.
|
|
4783
|
+
* Allows ng-auth profile page to display user permissions without
|
|
4784
|
+
* depending on ng-iam directly.
|
|
4785
|
+
*
|
|
4786
|
+
* @example
|
|
4787
|
+
* // In app.config.ts
|
|
4788
|
+
* providers: [
|
|
4789
|
+
* ...provideIamProviders()
|
|
4790
|
+
* ]
|
|
4791
|
+
*
|
|
4792
|
+
* // In profile component
|
|
4793
|
+
* private readonly permissionProvider = inject(PROFILE_PERMISSION_PROVIDER, { optional: true });
|
|
4794
|
+
*/
|
|
4795
|
+
class ProfilePermissionProviderAdapter {
|
|
4796
|
+
permissionApi = inject(PermissionApiService);
|
|
4797
|
+
getUserRoles(userId, branchId) {
|
|
4798
|
+
return this.permissionApi
|
|
4799
|
+
.getUserRoles(userId, branchId ? { branchId } : undefined)
|
|
4800
|
+
.pipe(map$1((response) => ({
|
|
4801
|
+
success: response.success,
|
|
4802
|
+
message: response.message,
|
|
4803
|
+
data: (response.data ?? []).map((role) => ({
|
|
4804
|
+
id: role.roleId,
|
|
4805
|
+
name: role.roleName,
|
|
4806
|
+
description: null,
|
|
4807
|
+
})),
|
|
4808
|
+
})));
|
|
4809
|
+
}
|
|
4810
|
+
getUserActions(userId, branchId) {
|
|
4811
|
+
return this.permissionApi
|
|
4812
|
+
.getUserActions(userId, branchId ? { branchId } : undefined)
|
|
4813
|
+
.pipe(map$1((response) => ({
|
|
4814
|
+
success: response.success,
|
|
4815
|
+
message: response.message,
|
|
4816
|
+
data: (response.data ?? []).map((action) => ({
|
|
4817
|
+
id: action.actionId,
|
|
4818
|
+
name: action.actionName,
|
|
4819
|
+
code: action.actionCode,
|
|
4820
|
+
description: null,
|
|
4821
|
+
})),
|
|
4822
|
+
})));
|
|
4823
|
+
}
|
|
4824
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePermissionProviderAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4825
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePermissionProviderAdapter });
|
|
4826
|
+
}
|
|
4827
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePermissionProviderAdapter, decorators: [{
|
|
4828
|
+
type: Injectable
|
|
4829
|
+
}] });
|
|
4830
|
+
|
|
4831
|
+
/**
|
|
4832
|
+
* Provide IAM Provider Adapters
|
|
4833
|
+
*
|
|
4834
|
+
* Registers IAM implementations for provider interfaces from ng-shared.
|
|
4835
|
+
* This allows ng-auth profile page to display permissions without direct dependencies.
|
|
4836
|
+
*
|
|
4837
|
+
* @example
|
|
4838
|
+
* // In app.config.ts
|
|
4839
|
+
* import { provideIamProviders } from '@flusys/ng-iam';
|
|
4840
|
+
*
|
|
4841
|
+
* export const appConfig: ApplicationConfig = {
|
|
4842
|
+
* providers: [
|
|
4843
|
+
* ...provideIamProviders(),
|
|
4844
|
+
* // ... other providers
|
|
4845
|
+
* ]
|
|
4846
|
+
* };
|
|
4847
|
+
*
|
|
4848
|
+
* @returns Array of Angular providers
|
|
4849
|
+
*/
|
|
4850
|
+
function provideIamProviders() {
|
|
4851
|
+
return [
|
|
4852
|
+
// Profile permission provider (for auth profile page)
|
|
4853
|
+
{
|
|
4854
|
+
provide: PROFILE_PERMISSION_PROVIDER,
|
|
4855
|
+
useClass: ProfilePermissionProviderAdapter,
|
|
4856
|
+
},
|
|
4857
|
+
];
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4980
4860
|
/**
|
|
4981
4861
|
* IAM Routes Configuration
|
|
4982
4862
|
*
|
|
@@ -4990,7 +4870,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
4990
4870
|
const IAM_ROUTES = [
|
|
4991
4871
|
{
|
|
4992
4872
|
path: '',
|
|
4993
|
-
loadComponent: () => import('./flusys-ng-iam-iam-container.component-
|
|
4873
|
+
loadComponent: () => import('./flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs').then((m) => m.IamContainerComponent),
|
|
4994
4874
|
children: [
|
|
4995
4875
|
// Actions Management
|
|
4996
4876
|
{
|
|
@@ -4998,15 +4878,15 @@ const IAM_ROUTES = [
|
|
|
4998
4878
|
children: [
|
|
4999
4879
|
{
|
|
5000
4880
|
path: '',
|
|
5001
|
-
loadComponent: () => import('./flusys-ng-iam-action-list-page.component-
|
|
4881
|
+
loadComponent: () => import('./flusys-ng-iam-action-list-page.component-Daf93zpS.mjs').then((m) => m.ActionListPageComponent),
|
|
5002
4882
|
},
|
|
5003
4883
|
{
|
|
5004
4884
|
path: 'new',
|
|
5005
|
-
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-
|
|
4885
|
+
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
|
|
5006
4886
|
},
|
|
5007
4887
|
{
|
|
5008
4888
|
path: ':id',
|
|
5009
|
-
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-
|
|
4889
|
+
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
|
|
5010
4890
|
},
|
|
5011
4891
|
],
|
|
5012
4892
|
},
|
|
@@ -5016,22 +4896,22 @@ const IAM_ROUTES = [
|
|
|
5016
4896
|
children: [
|
|
5017
4897
|
{
|
|
5018
4898
|
path: '',
|
|
5019
|
-
loadComponent: () => import('./flusys-ng-iam-role-list-page.component-
|
|
4899
|
+
loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CFly5KnH.mjs').then((m) => m.RoleListPageComponent),
|
|
5020
4900
|
},
|
|
5021
4901
|
{
|
|
5022
4902
|
path: 'new',
|
|
5023
|
-
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-
|
|
4903
|
+
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
|
|
5024
4904
|
},
|
|
5025
4905
|
{
|
|
5026
4906
|
path: ':id',
|
|
5027
|
-
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-
|
|
4907
|
+
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
|
|
5028
4908
|
},
|
|
5029
4909
|
],
|
|
5030
4910
|
},
|
|
5031
4911
|
// Permissions Management (User permission assignment)
|
|
5032
4912
|
{
|
|
5033
4913
|
path: 'permissions',
|
|
5034
|
-
loadComponent: () => import('./flusys-ng-iam-permission-page.component-
|
|
4914
|
+
loadComponent: () => import('./flusys-ng-iam-permission-page.component-CmxOBJPu.mjs').then((m) => m.PermissionPageComponent),
|
|
5035
4915
|
},
|
|
5036
4916
|
// Default redirect to actions
|
|
5037
4917
|
{
|
|
@@ -5049,5 +4929,5 @@ const IAM_ROUTES = [
|
|
|
5049
4929
|
* Generated bundle index. Do not edit.
|
|
5050
4930
|
*/
|
|
5051
4931
|
|
|
5052
|
-
export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_ROUTES as I, LogicBuilderComponent as L, MAX_DROPDOWN_ITEMS as M, PermissionApiService as P, RoleApiService as R, UserRoleSelectorComponent as U, ActionType as a, RoleActionSelectorComponent as b, convertActionToTreeNode as c, UserActionSelectorComponent as d, ActionPermissionLogicService as e, MyPermissionsApiService as f, PermissionStateService as g };
|
|
5053
|
-
//# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-
|
|
4932
|
+
export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_ROUTES as I, LogicBuilderComponent as L, MAX_DROPDOWN_ITEMS as M, PermissionApiService as P, RoleApiService as R, UserRoleSelectorComponent as U, ActionType as a, RoleActionSelectorComponent as b, convertActionToTreeNode as c, UserActionSelectorComponent as d, ActionPermissionLogicService as e, MyPermissionsApiService as f, PermissionStateService as g, ProfilePermissionProviderAdapter as h, provideIamProviders as p };
|
|
4933
|
+
//# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs.map
|