@flusys/ng-iam 1.0.0-beta → 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 -24
- package/fesm2022/{flusys-ng-iam-action-form-page.component-0b9GwJqa.mjs → flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs} +175 -203
- 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-C-MQjakK.mjs → flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs} +892 -818
- 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-e_RX5mky.mjs → flusys-ng-iam-permission-page.component-CmxOBJPu.mjs} +42 -13
- package/fesm2022/flusys-ng-iam-permission-page.component-CmxOBJPu.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-role-form-page.component-eZM1EPps.mjs → flusys-ng-iam-role-form-page.component-ByNueI1a.mjs} +107 -135
- 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 +87 -23
- package/fesm2022/flusys-ng-iam-action-form-page.component-0b9GwJqa.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-action-list-page.component-BpvewEGL.mjs +0 -281
- package/fesm2022/flusys-ng-iam-action-list-page.component-BpvewEGL.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-C-MQjakK.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-iam-container.component-Chl5MDkV.mjs +0 -97
- package/fesm2022/flusys-ng-iam-iam-container.component-Chl5MDkV.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-permission-page.component-e_RX5mky.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-form-page.component-eZM1EPps.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-list-page.component-BgzxmHjk.mjs +0 -266
- package/fesm2022/flusys-ng-iam-role-list-page.component-BgzxmHjk.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, USER_PERMISSION_PROVIDER, UserSelectComponent } from '@flusys/ng-shared';
|
|
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
|
|
|
@@ -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
|
|
@@ -835,14 +833,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
835
833
|
}
|
|
836
834
|
/**
|
|
837
835
|
* Get user's direct action permissions
|
|
838
|
-
*
|
|
836
|
+
* POST /iam/permissions/get-user-actions
|
|
839
837
|
*/
|
|
840
838
|
getUserActions(userId, query) {
|
|
841
|
-
|
|
842
|
-
if (query?.branchId) {
|
|
843
|
-
params = params.set('branchId', query.branchId);
|
|
844
|
-
}
|
|
845
|
-
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 });
|
|
846
840
|
}
|
|
847
841
|
// =============================================================================
|
|
848
842
|
// User → Role (Role Assignments)
|
|
@@ -856,14 +850,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
856
850
|
}
|
|
857
851
|
/**
|
|
858
852
|
* Get user's role assignments
|
|
859
|
-
*
|
|
853
|
+
* POST /iam/permissions/get-user-roles
|
|
860
854
|
*/
|
|
861
855
|
getUserRoles(userId, query) {
|
|
862
|
-
|
|
863
|
-
if (query?.branchId) {
|
|
864
|
-
params = params.set('branchId', query.branchId);
|
|
865
|
-
}
|
|
866
|
-
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 });
|
|
867
857
|
}
|
|
868
858
|
// =============================================================================
|
|
869
859
|
// Role → Action (Role Permissions)
|
|
@@ -877,10 +867,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
877
867
|
}
|
|
878
868
|
/**
|
|
879
869
|
* Get role's action permissions
|
|
880
|
-
*
|
|
870
|
+
* POST /iam/permissions/get-role-actions
|
|
881
871
|
*/
|
|
882
872
|
getRoleActions(roleId, query) {
|
|
883
|
-
return this.http.
|
|
873
|
+
return this.http.post(this.getUrl('permissions/get-role-actions'), { roleId });
|
|
884
874
|
}
|
|
885
875
|
// =============================================================================
|
|
886
876
|
// Company → Action (Company Whitelisting)
|
|
@@ -894,10 +884,10 @@ class PermissionApiService extends BaseApiService {
|
|
|
894
884
|
}
|
|
895
885
|
/**
|
|
896
886
|
* Get company's whitelisted actions
|
|
897
|
-
*
|
|
887
|
+
* POST /iam/permissions/get-company-actions
|
|
898
888
|
*/
|
|
899
889
|
getCompanyActions(companyId) {
|
|
900
|
-
return this.http.
|
|
890
|
+
return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
|
|
901
891
|
}
|
|
902
892
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
903
893
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, providedIn: 'root' });
|
|
@@ -1037,77 +1027,85 @@ class LogicBuilderComponent {
|
|
|
1037
1027
|
actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
|
|
1038
1028
|
logicChange = output();
|
|
1039
1029
|
availableActions = computed(() => this.actions(), ...(ngDevMode ? [{ debugName: "availableActions" }] : []));
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
+
}
|
|
1052
1045
|
initializeLogic() {
|
|
1053
|
-
this.
|
|
1046
|
+
this._builderTree.set({
|
|
1054
1047
|
id: crypto.randomUUID(),
|
|
1055
1048
|
type: 'group',
|
|
1056
1049
|
operator: 'AND',
|
|
1057
1050
|
children: [],
|
|
1058
|
-
};
|
|
1051
|
+
});
|
|
1059
1052
|
this.emitChange();
|
|
1060
1053
|
}
|
|
1061
1054
|
clearLogic() {
|
|
1062
|
-
this.
|
|
1055
|
+
this._builderTree.set(null);
|
|
1063
1056
|
this.logicChange.emit(null);
|
|
1064
1057
|
}
|
|
1065
1058
|
toggleOperator(nodeId) {
|
|
1066
|
-
|
|
1059
|
+
const tree = this._builderTree();
|
|
1060
|
+
if (!tree)
|
|
1067
1061
|
return;
|
|
1068
|
-
this.
|
|
1062
|
+
this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
|
|
1069
1063
|
...node,
|
|
1070
1064
|
operator: node.operator === 'AND' ? 'OR' : 'AND',
|
|
1071
|
-
}));
|
|
1065
|
+
})));
|
|
1072
1066
|
this.emitChange();
|
|
1073
1067
|
}
|
|
1074
1068
|
addChildNode(parentId, type) {
|
|
1075
|
-
|
|
1069
|
+
const tree = this._builderTree();
|
|
1070
|
+
if (!tree)
|
|
1076
1071
|
return;
|
|
1077
1072
|
const newNode = type === 'group'
|
|
1078
1073
|
? { id: crypto.randomUUID(), type: 'group', operator: 'AND', children: [] }
|
|
1079
1074
|
: { id: crypto.randomUUID(), type: 'action', actionId: '' };
|
|
1080
|
-
this.
|
|
1075
|
+
this._builderTree.set(this.updateNodeInTree(tree, parentId, (node) => ({
|
|
1081
1076
|
...node,
|
|
1082
1077
|
children: [...(node.children || []), newNode],
|
|
1083
|
-
}));
|
|
1078
|
+
})));
|
|
1084
1079
|
this.emitChange();
|
|
1085
1080
|
}
|
|
1086
1081
|
removeNode(nodeId) {
|
|
1087
|
-
|
|
1082
|
+
const tree = this._builderTree();
|
|
1083
|
+
if (!tree)
|
|
1088
1084
|
return;
|
|
1089
|
-
if (
|
|
1085
|
+
if (tree.id === nodeId) {
|
|
1090
1086
|
this.clearLogic();
|
|
1091
1087
|
return;
|
|
1092
1088
|
}
|
|
1093
|
-
this.
|
|
1089
|
+
this._builderTree.set(this.removeNodeFromTree(tree, nodeId));
|
|
1094
1090
|
this.emitChange();
|
|
1095
1091
|
}
|
|
1096
1092
|
updateActionId(nodeId, actionId) {
|
|
1097
|
-
|
|
1093
|
+
const tree = this._builderTree();
|
|
1094
|
+
if (!tree)
|
|
1098
1095
|
return;
|
|
1099
|
-
this.
|
|
1096
|
+
this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
|
|
1100
1097
|
...node,
|
|
1101
1098
|
actionId,
|
|
1102
|
-
}));
|
|
1099
|
+
})));
|
|
1103
1100
|
this.emitChange();
|
|
1104
1101
|
}
|
|
1105
1102
|
emitChange() {
|
|
1106
|
-
|
|
1103
|
+
const tree = this._builderTree();
|
|
1104
|
+
if (!tree) {
|
|
1107
1105
|
this.logicChange.emit(null);
|
|
1108
1106
|
return;
|
|
1109
1107
|
}
|
|
1110
|
-
this.logicChange.emit(toLogicNode(
|
|
1108
|
+
this.logicChange.emit(toLogicNode(tree));
|
|
1111
1109
|
}
|
|
1112
1110
|
updateNodeInTree(node, targetId, updater) {
|
|
1113
1111
|
if (node.id === targetId)
|
|
@@ -1135,7 +1133,7 @@ class LogicBuilderComponent {
|
|
|
1135
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: `
|
|
1136
1134
|
<div class="logic-builder">
|
|
1137
1135
|
<div class="flex justify-between items-center mb-3">
|
|
1138
|
-
<h4 class="text-sm font-semibold">Permission Logic</h4>
|
|
1136
|
+
<h4 class="text-sm font-semibold m-0">Permission Logic</h4>
|
|
1139
1137
|
@if (!builderLogic()) {
|
|
1140
1138
|
<p-button
|
|
1141
1139
|
label="Add Logic"
|
|
@@ -1154,13 +1152,13 @@ class LogicBuilderComponent {
|
|
|
1154
1152
|
</div>
|
|
1155
1153
|
|
|
1156
1154
|
@if (builderLogic()) {
|
|
1157
|
-
<div class="border rounded p-3 bg-
|
|
1158
|
-
<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">
|
|
1159
1157
|
Define permission requirements using AND/OR logic with actions
|
|
1160
1158
|
</div>
|
|
1161
1159
|
|
|
1162
1160
|
<!-- Root Node -->
|
|
1163
|
-
<div class="
|
|
1161
|
+
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1164
1162
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
|
|
1165
1163
|
</div>
|
|
1166
1164
|
</div>
|
|
@@ -1169,11 +1167,11 @@ class LogicBuilderComponent {
|
|
|
1169
1167
|
|
|
1170
1168
|
<!-- Recursive Node Template -->
|
|
1171
1169
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1172
|
-
<div class="
|
|
1173
|
-
<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">
|
|
1174
1172
|
<span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
|
|
1175
1173
|
|
|
1176
|
-
<div class="
|
|
1174
|
+
<div class="flex-1 flex items-center gap-2">
|
|
1177
1175
|
@if (node.type === 'group') {
|
|
1178
1176
|
<!-- Group: show operator toggle -->
|
|
1179
1177
|
<span
|
|
@@ -1183,13 +1181,13 @@ class LogicBuilderComponent {
|
|
|
1183
1181
|
title="Click to toggle">
|
|
1184
1182
|
{{ node.operator }}
|
|
1185
1183
|
</span>
|
|
1186
|
-
<span class="text-
|
|
1184
|
+
<span class="text-muted-color text-xs">({{ node.children?.length || 0 }} conditions)</span>
|
|
1187
1185
|
}
|
|
1188
1186
|
|
|
1189
1187
|
@if (node.type === 'action') {
|
|
1190
1188
|
<!-- Action: show action selector -->
|
|
1191
1189
|
<select
|
|
1192
|
-
class="flex-1"
|
|
1190
|
+
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1193
1191
|
[ngModel]="node.actionId"
|
|
1194
1192
|
(ngModelChange)="updateActionId(node.id, $event)">
|
|
1195
1193
|
<option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
|
|
@@ -1200,7 +1198,7 @@ class LogicBuilderComponent {
|
|
|
1200
1198
|
}
|
|
1201
1199
|
</div>
|
|
1202
1200
|
|
|
1203
|
-
<div class="
|
|
1201
|
+
<div class="flex gap-1">
|
|
1204
1202
|
<p-button
|
|
1205
1203
|
icon="pi pi-trash"
|
|
1206
1204
|
[text]="true"
|
|
@@ -1213,7 +1211,7 @@ class LogicBuilderComponent {
|
|
|
1213
1211
|
|
|
1214
1212
|
<!-- Children for group nodes -->
|
|
1215
1213
|
@if (node.type === 'group' && node.children && node.children.length > 0) {
|
|
1216
|
-
<div class="
|
|
1214
|
+
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1217
1215
|
@for (child of node.children; track child.id) {
|
|
1218
1216
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
1219
1217
|
}
|
|
@@ -1222,9 +1220,9 @@ class LogicBuilderComponent {
|
|
|
1222
1220
|
|
|
1223
1221
|
<!-- Add child buttons for group nodes -->
|
|
1224
1222
|
@if (node.type === 'group') {
|
|
1225
|
-
<div class="
|
|
1226
|
-
<div class="text-xs font-semibold text-
|
|
1227
|
-
<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">
|
|
1228
1226
|
<p-button
|
|
1229
1227
|
label="Group"
|
|
1230
1228
|
icon="pi pi-sitemap"
|
|
@@ -1243,14 +1241,14 @@ class LogicBuilderComponent {
|
|
|
1243
1241
|
}
|
|
1244
1242
|
</div>
|
|
1245
1243
|
</ng-template>
|
|
1246
|
-
`, 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 });
|
|
1247
1245
|
}
|
|
1248
1246
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, decorators: [{
|
|
1249
1247
|
type: Component,
|
|
1250
|
-
args: [{ selector: 'lib-logic-builder', standalone: true, imports: [
|
|
1248
|
+
args: [{ selector: 'lib-logic-builder', standalone: true, imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1251
1249
|
<div class="logic-builder">
|
|
1252
1250
|
<div class="flex justify-between items-center mb-3">
|
|
1253
|
-
<h4 class="text-sm font-semibold">Permission Logic</h4>
|
|
1251
|
+
<h4 class="text-sm font-semibold m-0">Permission Logic</h4>
|
|
1254
1252
|
@if (!builderLogic()) {
|
|
1255
1253
|
<p-button
|
|
1256
1254
|
label="Add Logic"
|
|
@@ -1269,13 +1267,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1269
1267
|
</div>
|
|
1270
1268
|
|
|
1271
1269
|
@if (builderLogic()) {
|
|
1272
|
-
<div class="border rounded p-3 bg-
|
|
1273
|
-
<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">
|
|
1274
1272
|
Define permission requirements using AND/OR logic with actions
|
|
1275
1273
|
</div>
|
|
1276
1274
|
|
|
1277
1275
|
<!-- Root Node -->
|
|
1278
|
-
<div class="
|
|
1276
|
+
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1279
1277
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
|
|
1280
1278
|
</div>
|
|
1281
1279
|
</div>
|
|
@@ -1284,11 +1282,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1284
1282
|
|
|
1285
1283
|
<!-- Recursive Node Template -->
|
|
1286
1284
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1287
|
-
<div class="
|
|
1288
|
-
<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">
|
|
1289
1287
|
<span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
|
|
1290
1288
|
|
|
1291
|
-
<div class="
|
|
1289
|
+
<div class="flex-1 flex items-center gap-2">
|
|
1292
1290
|
@if (node.type === 'group') {
|
|
1293
1291
|
<!-- Group: show operator toggle -->
|
|
1294
1292
|
<span
|
|
@@ -1298,13 +1296,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1298
1296
|
title="Click to toggle">
|
|
1299
1297
|
{{ node.operator }}
|
|
1300
1298
|
</span>
|
|
1301
|
-
<span class="text-
|
|
1299
|
+
<span class="text-muted-color text-xs">({{ node.children?.length || 0 }} conditions)</span>
|
|
1302
1300
|
}
|
|
1303
1301
|
|
|
1304
1302
|
@if (node.type === 'action') {
|
|
1305
1303
|
<!-- Action: show action selector -->
|
|
1306
1304
|
<select
|
|
1307
|
-
class="flex-1"
|
|
1305
|
+
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1308
1306
|
[ngModel]="node.actionId"
|
|
1309
1307
|
(ngModelChange)="updateActionId(node.id, $event)">
|
|
1310
1308
|
<option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
|
|
@@ -1315,7 +1313,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1315
1313
|
}
|
|
1316
1314
|
</div>
|
|
1317
1315
|
|
|
1318
|
-
<div class="
|
|
1316
|
+
<div class="flex gap-1">
|
|
1319
1317
|
<p-button
|
|
1320
1318
|
icon="pi pi-trash"
|
|
1321
1319
|
[text]="true"
|
|
@@ -1328,7 +1326,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1328
1326
|
|
|
1329
1327
|
<!-- Children for group nodes -->
|
|
1330
1328
|
@if (node.type === 'group' && node.children && node.children.length > 0) {
|
|
1331
|
-
<div class="
|
|
1329
|
+
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1332
1330
|
@for (child of node.children; track child.id) {
|
|
1333
1331
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
1334
1332
|
}
|
|
@@ -1337,9 +1335,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1337
1335
|
|
|
1338
1336
|
<!-- Add child buttons for group nodes -->
|
|
1339
1337
|
@if (node.type === 'group') {
|
|
1340
|
-
<div class="
|
|
1341
|
-
<div class="text-xs font-semibold text-
|
|
1342
|
-
<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">
|
|
1343
1341
|
<p-button
|
|
1344
1342
|
label="Group"
|
|
1345
1343
|
icon="pi pi-sitemap"
|
|
@@ -1358,8 +1356,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1358
1356
|
}
|
|
1359
1357
|
</div>
|
|
1360
1358
|
</ng-template>
|
|
1361
|
-
`, styles: [":host{display:block}.
|
|
1362
|
-
}], 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"] }] } });
|
|
1363
1361
|
|
|
1364
1362
|
/**
|
|
1365
1363
|
* Tree Utility Functions
|
|
@@ -1556,7 +1554,10 @@ function convertActionToTreeNode(actions) {
|
|
|
1556
1554
|
* ```
|
|
1557
1555
|
*/
|
|
1558
1556
|
class RoleActionSelectorComponent {
|
|
1557
|
+
// Permission constants for template
|
|
1558
|
+
ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
|
|
1559
1559
|
// Dependencies
|
|
1560
|
+
destroyRef = inject(DestroyRef);
|
|
1560
1561
|
roleApi = inject(RoleApiService);
|
|
1561
1562
|
actionApi = inject(ActionApiService);
|
|
1562
1563
|
permissionApi = inject(PermissionApiService);
|
|
@@ -1637,6 +1638,10 @@ class RoleActionSelectorComponent {
|
|
|
1637
1638
|
loadDataAbortController = null;
|
|
1638
1639
|
constructor() {
|
|
1639
1640
|
this.loadRoles();
|
|
1641
|
+
// Cleanup on destroy
|
|
1642
|
+
this.destroyRef.onDestroy(() => {
|
|
1643
|
+
this.loadDataAbortController?.abort();
|
|
1644
|
+
});
|
|
1640
1645
|
// Effect: Load data when role selection changes
|
|
1641
1646
|
effect(() => {
|
|
1642
1647
|
const roleId = this.selectedRoleId();
|
|
@@ -1646,11 +1651,7 @@ class RoleActionSelectorComponent {
|
|
|
1646
1651
|
this.loadDataAbortController = new AbortController();
|
|
1647
1652
|
this.onRoleChange(this.loadDataAbortController.signal).catch((err) => {
|
|
1648
1653
|
if (err.name !== 'AbortError') {
|
|
1649
|
-
|
|
1650
|
-
severity: 'error',
|
|
1651
|
-
summary: 'Error',
|
|
1652
|
-
detail: 'Failed to load role permissions. Please refresh.',
|
|
1653
|
-
});
|
|
1654
|
+
// Error toast handled by global interceptor
|
|
1654
1655
|
this.loading.set(false);
|
|
1655
1656
|
}
|
|
1656
1657
|
});
|
|
@@ -1660,9 +1661,6 @@ class RoleActionSelectorComponent {
|
|
|
1660
1661
|
}
|
|
1661
1662
|
});
|
|
1662
1663
|
}
|
|
1663
|
-
ngOnDestroy() {
|
|
1664
|
-
this.loadDataAbortController?.abort();
|
|
1665
|
-
}
|
|
1666
1664
|
/**
|
|
1667
1665
|
* Load roles from API
|
|
1668
1666
|
*/
|
|
@@ -1674,11 +1672,7 @@ class RoleActionSelectorComponent {
|
|
|
1674
1672
|
this.roles.set(response?.data ?? []);
|
|
1675
1673
|
}
|
|
1676
1674
|
catch {
|
|
1677
|
-
|
|
1678
|
-
severity: 'error',
|
|
1679
|
-
summary: 'Error',
|
|
1680
|
-
detail: 'Failed to load roles',
|
|
1681
|
-
});
|
|
1675
|
+
// Error toast handled by global interceptor
|
|
1682
1676
|
}
|
|
1683
1677
|
}
|
|
1684
1678
|
/**
|
|
@@ -1836,13 +1830,8 @@ class RoleActionSelectorComponent {
|
|
|
1836
1830
|
// Update baseline
|
|
1837
1831
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
1838
1832
|
}
|
|
1839
|
-
catch
|
|
1840
|
-
|
|
1841
|
-
this.messageService.add({
|
|
1842
|
-
severity: 'error',
|
|
1843
|
-
summary: 'Error',
|
|
1844
|
-
detail: error?.error?.message || 'Failed to update role permissions',
|
|
1845
|
-
});
|
|
1833
|
+
catch {
|
|
1834
|
+
// Error toast handled by global interceptor
|
|
1846
1835
|
}
|
|
1847
1836
|
finally {
|
|
1848
1837
|
this.saving.set(false);
|
|
@@ -1862,11 +1851,12 @@ class RoleActionSelectorComponent {
|
|
|
1862
1851
|
<div class="role-action-selector">
|
|
1863
1852
|
<!-- Role Selector -->
|
|
1864
1853
|
<div class="mb-4">
|
|
1865
|
-
<div class="grid">
|
|
1866
|
-
<div
|
|
1854
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
1855
|
+
<div>
|
|
1867
1856
|
<label class="block font-semibold mb-2">Select Role</label>
|
|
1868
1857
|
<p-select
|
|
1869
|
-
[
|
|
1858
|
+
[ngModel]="selectedRoleId()"
|
|
1859
|
+
(ngModelChange)="selectedRoleId.set($event)"
|
|
1870
1860
|
[options]="roles()"
|
|
1871
1861
|
optionLabel="name"
|
|
1872
1862
|
optionValue="id"
|
|
@@ -1883,24 +1873,21 @@ class RoleActionSelectorComponent {
|
|
|
1883
1873
|
<!-- Loading State -->
|
|
1884
1874
|
@if (loading()) {
|
|
1885
1875
|
<div
|
|
1886
|
-
class="surface-card p-5 border
|
|
1876
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
1887
1877
|
>
|
|
1888
|
-
<i
|
|
1889
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
1890
|
-
style="font-size: 3rem"
|
|
1891
|
-
></i>
|
|
1878
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
1892
1879
|
</div>
|
|
1893
1880
|
}
|
|
1894
1881
|
|
|
1895
1882
|
<!-- Action List -->
|
|
1896
1883
|
@if (!loading() && actions().length > 0) {
|
|
1897
|
-
<div class="surface-card p-4 border
|
|
1884
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
1898
1885
|
<div
|
|
1899
|
-
class="flex flex-
|
|
1886
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
1900
1887
|
>
|
|
1901
1888
|
<div>
|
|
1902
1889
|
<h5 class="m-0 mb-1">Action Permissions</h5>
|
|
1903
|
-
<p class="text-sm text-color
|
|
1890
|
+
<p class="text-sm text-muted-color m-0">
|
|
1904
1891
|
{{ actions().length }} actions available
|
|
1905
1892
|
</p>
|
|
1906
1893
|
</div>
|
|
@@ -1922,6 +1909,7 @@ class RoleActionSelectorComponent {
|
|
|
1922
1909
|
(onClick)="deselectAll()"
|
|
1923
1910
|
/>
|
|
1924
1911
|
<p-button
|
|
1912
|
+
*hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
|
|
1925
1913
|
label="Save Changes"
|
|
1926
1914
|
icon="pi pi-save"
|
|
1927
1915
|
[disabled]="!canSave()"
|
|
@@ -1935,14 +1923,12 @@ class RoleActionSelectorComponent {
|
|
|
1935
1923
|
|
|
1936
1924
|
<!-- Validation Warning -->
|
|
1937
1925
|
@if (invalidActionsCount() > 0) {
|
|
1938
|
-
<div class="
|
|
1939
|
-
<div class="flex items-
|
|
1940
|
-
<i
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
1945
|
-
<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">
|
|
1946
1932
|
{{ invalidActionsCount() }} selected
|
|
1947
1933
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
1948
1934
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -1954,33 +1940,33 @@ class RoleActionSelectorComponent {
|
|
|
1954
1940
|
}
|
|
1955
1941
|
|
|
1956
1942
|
<!-- Action Tree Table -->
|
|
1957
|
-
<
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
<ng-template
|
|
1982
|
-
<tr [class.
|
|
1983
|
-
<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">
|
|
1984
1970
|
<p-checkbox
|
|
1985
1971
|
[ngModel]="selectionMap()[rowData.id]"
|
|
1986
1972
|
[binary]="true"
|
|
@@ -1991,7 +1977,7 @@ class RoleActionSelectorComponent {
|
|
|
1991
1977
|
</td>
|
|
1992
1978
|
<td>
|
|
1993
1979
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
1994
|
-
<span class="inline-flex
|
|
1980
|
+
<span class="inline-flex items-center gap-2">
|
|
1995
1981
|
{{ rowData.name }}
|
|
1996
1982
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
1997
1983
|
<i
|
|
@@ -2002,7 +1988,7 @@ class RoleActionSelectorComponent {
|
|
|
2002
1988
|
}
|
|
2003
1989
|
</span>
|
|
2004
1990
|
</td>
|
|
2005
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
1991
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2006
1992
|
<td>
|
|
2007
1993
|
<p-tag
|
|
2008
1994
|
[value]="rowData.actionType"
|
|
@@ -2011,42 +1997,41 @@ class RoleActionSelectorComponent {
|
|
|
2011
1997
|
"
|
|
2012
1998
|
/>
|
|
2013
1999
|
</td>
|
|
2014
|
-
<td>
|
|
2000
|
+
<td class="hidden lg:table-cell">
|
|
2015
2001
|
@if (rowData.permissionLogic) {
|
|
2016
|
-
<span class="text-sm text-muted">Has prerequisites</span>
|
|
2002
|
+
<span class="text-sm text-muted-color">Has prerequisites</span>
|
|
2017
2003
|
} @else {
|
|
2018
|
-
<span class="text-muted">-</span>
|
|
2004
|
+
<span class="text-muted-color">-</span>
|
|
2019
2005
|
}
|
|
2020
2006
|
</td>
|
|
2021
2007
|
</tr>
|
|
2022
2008
|
</ng-template>
|
|
2023
|
-
<ng-template
|
|
2009
|
+
<ng-template #emptymessage>
|
|
2024
2010
|
<tr>
|
|
2025
|
-
<td colspan="5" class="text-center p-4">
|
|
2011
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2026
2012
|
@if (loading()) {
|
|
2027
2013
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2028
2014
|
} @else { No actions available. }
|
|
2029
2015
|
</td>
|
|
2030
2016
|
</tr>
|
|
2031
2017
|
</ng-template>
|
|
2032
|
-
|
|
2018
|
+
</p-treeTable>
|
|
2019
|
+
</div>
|
|
2033
2020
|
</div>
|
|
2034
2021
|
|
|
2035
2022
|
<!-- Change Summary -->
|
|
2036
2023
|
@if (hasChanges()) {
|
|
2037
|
-
<div class="
|
|
2038
|
-
<div class="flex
|
|
2039
|
-
<i class="pi pi-info-circle text-
|
|
2040
|
-
<
|
|
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>
|
|
2041
2028
|
</div>
|
|
2042
|
-
<div class="
|
|
2029
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2043
2030
|
@if (pendingAdd().length > 0) {
|
|
2044
|
-
<div
|
|
2045
|
-
<div class="flex
|
|
2031
|
+
<div>
|
|
2032
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2046
2033
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2047
|
-
<strong class="text-sm"
|
|
2048
|
-
>To Add ({{ pendingAdd().length }})</strong
|
|
2049
|
-
>
|
|
2034
|
+
<strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
|
|
2050
2035
|
</div>
|
|
2051
2036
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2052
2037
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2056,12 +2041,10 @@ class RoleActionSelectorComponent {
|
|
|
2056
2041
|
</div>
|
|
2057
2042
|
}
|
|
2058
2043
|
@if (pendingRemove().length > 0) {
|
|
2059
|
-
<div
|
|
2060
|
-
<div class="flex
|
|
2044
|
+
<div>
|
|
2045
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2061
2046
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2062
|
-
<strong class="text-sm"
|
|
2063
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
2064
|
-
>
|
|
2047
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2065
2048
|
</div>
|
|
2066
2049
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2067
2050
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2076,31 +2059,29 @@ class RoleActionSelectorComponent {
|
|
|
2076
2059
|
}
|
|
2077
2060
|
|
|
2078
2061
|
@if (!loading() && actions().length === 0) {
|
|
2079
|
-
<div class="surface-card p-5 border
|
|
2080
|
-
<i
|
|
2081
|
-
|
|
2082
|
-
style="font-size: 3rem; display: block;"
|
|
2083
|
-
></i>
|
|
2084
|
-
<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">
|
|
2085
2065
|
No actions available for this role.
|
|
2086
2066
|
</p>
|
|
2087
2067
|
</div>
|
|
2088
2068
|
}
|
|
2089
2069
|
}
|
|
2090
2070
|
</div>
|
|
2091
|
-
`, 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 });
|
|
2092
2072
|
}
|
|
2093
2073
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
|
|
2094
2074
|
type: Component,
|
|
2095
|
-
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: `
|
|
2096
2076
|
<div class="role-action-selector">
|
|
2097
2077
|
<!-- Role Selector -->
|
|
2098
2078
|
<div class="mb-4">
|
|
2099
|
-
<div class="grid">
|
|
2100
|
-
<div
|
|
2079
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2080
|
+
<div>
|
|
2101
2081
|
<label class="block font-semibold mb-2">Select Role</label>
|
|
2102
2082
|
<p-select
|
|
2103
|
-
[
|
|
2083
|
+
[ngModel]="selectedRoleId()"
|
|
2084
|
+
(ngModelChange)="selectedRoleId.set($event)"
|
|
2104
2085
|
[options]="roles()"
|
|
2105
2086
|
optionLabel="name"
|
|
2106
2087
|
optionValue="id"
|
|
@@ -2117,24 +2098,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2117
2098
|
<!-- Loading State -->
|
|
2118
2099
|
@if (loading()) {
|
|
2119
2100
|
<div
|
|
2120
|
-
class="surface-card p-5 border
|
|
2101
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2121
2102
|
>
|
|
2122
|
-
<i
|
|
2123
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2124
|
-
style="font-size: 3rem"
|
|
2125
|
-
></i>
|
|
2103
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2126
2104
|
</div>
|
|
2127
2105
|
}
|
|
2128
2106
|
|
|
2129
2107
|
<!-- Action List -->
|
|
2130
2108
|
@if (!loading() && actions().length > 0) {
|
|
2131
|
-
<div class="surface-card p-4 border
|
|
2109
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2132
2110
|
<div
|
|
2133
|
-
class="flex flex-
|
|
2111
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2134
2112
|
>
|
|
2135
2113
|
<div>
|
|
2136
2114
|
<h5 class="m-0 mb-1">Action Permissions</h5>
|
|
2137
|
-
<p class="text-sm text-color
|
|
2115
|
+
<p class="text-sm text-muted-color m-0">
|
|
2138
2116
|
{{ actions().length }} actions available
|
|
2139
2117
|
</p>
|
|
2140
2118
|
</div>
|
|
@@ -2156,6 +2134,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2156
2134
|
(onClick)="deselectAll()"
|
|
2157
2135
|
/>
|
|
2158
2136
|
<p-button
|
|
2137
|
+
*hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
|
|
2159
2138
|
label="Save Changes"
|
|
2160
2139
|
icon="pi pi-save"
|
|
2161
2140
|
[disabled]="!canSave()"
|
|
@@ -2169,14 +2148,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2169
2148
|
|
|
2170
2149
|
<!-- Validation Warning -->
|
|
2171
2150
|
@if (invalidActionsCount() > 0) {
|
|
2172
|
-
<div class="
|
|
2173
|
-
<div class="flex items-
|
|
2174
|
-
<i
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
2179
|
-
<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">
|
|
2180
2157
|
{{ invalidActionsCount() }} selected
|
|
2181
2158
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
2182
2159
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -2188,33 +2165,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2188
2165
|
}
|
|
2189
2166
|
|
|
2190
2167
|
<!-- Action Tree Table -->
|
|
2191
|
-
<
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
<ng-template
|
|
2216
|
-
<tr [class.
|
|
2217
|
-
<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">
|
|
2218
2195
|
<p-checkbox
|
|
2219
2196
|
[ngModel]="selectionMap()[rowData.id]"
|
|
2220
2197
|
[binary]="true"
|
|
@@ -2225,7 +2202,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2225
2202
|
</td>
|
|
2226
2203
|
<td>
|
|
2227
2204
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2228
|
-
<span class="inline-flex
|
|
2205
|
+
<span class="inline-flex items-center gap-2">
|
|
2229
2206
|
{{ rowData.name }}
|
|
2230
2207
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2231
2208
|
<i
|
|
@@ -2236,7 +2213,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2236
2213
|
}
|
|
2237
2214
|
</span>
|
|
2238
2215
|
</td>
|
|
2239
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
2216
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2240
2217
|
<td>
|
|
2241
2218
|
<p-tag
|
|
2242
2219
|
[value]="rowData.actionType"
|
|
@@ -2245,42 +2222,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2245
2222
|
"
|
|
2246
2223
|
/>
|
|
2247
2224
|
</td>
|
|
2248
|
-
<td>
|
|
2225
|
+
<td class="hidden lg:table-cell">
|
|
2249
2226
|
@if (rowData.permissionLogic) {
|
|
2250
|
-
<span class="text-sm text-muted">Has prerequisites</span>
|
|
2227
|
+
<span class="text-sm text-muted-color">Has prerequisites</span>
|
|
2251
2228
|
} @else {
|
|
2252
|
-
<span class="text-muted">-</span>
|
|
2229
|
+
<span class="text-muted-color">-</span>
|
|
2253
2230
|
}
|
|
2254
2231
|
</td>
|
|
2255
2232
|
</tr>
|
|
2256
2233
|
</ng-template>
|
|
2257
|
-
<ng-template
|
|
2234
|
+
<ng-template #emptymessage>
|
|
2258
2235
|
<tr>
|
|
2259
|
-
<td colspan="5" class="text-center p-4">
|
|
2236
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2260
2237
|
@if (loading()) {
|
|
2261
2238
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2262
2239
|
} @else { No actions available. }
|
|
2263
2240
|
</td>
|
|
2264
2241
|
</tr>
|
|
2265
2242
|
</ng-template>
|
|
2266
|
-
|
|
2243
|
+
</p-treeTable>
|
|
2244
|
+
</div>
|
|
2267
2245
|
</div>
|
|
2268
2246
|
|
|
2269
2247
|
<!-- Change Summary -->
|
|
2270
2248
|
@if (hasChanges()) {
|
|
2271
|
-
<div class="
|
|
2272
|
-
<div class="flex
|
|
2273
|
-
<i class="pi pi-info-circle text-
|
|
2274
|
-
<
|
|
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>
|
|
2275
2253
|
</div>
|
|
2276
|
-
<div class="
|
|
2254
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2277
2255
|
@if (pendingAdd().length > 0) {
|
|
2278
|
-
<div
|
|
2279
|
-
<div class="flex
|
|
2256
|
+
<div>
|
|
2257
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2280
2258
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2281
|
-
<strong class="text-sm"
|
|
2282
|
-
>To Add ({{ pendingAdd().length }})</strong
|
|
2283
|
-
>
|
|
2259
|
+
<strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
|
|
2284
2260
|
</div>
|
|
2285
2261
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2286
2262
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2290,12 +2266,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2290
2266
|
</div>
|
|
2291
2267
|
}
|
|
2292
2268
|
@if (pendingRemove().length > 0) {
|
|
2293
|
-
<div
|
|
2294
|
-
<div class="flex
|
|
2269
|
+
<div>
|
|
2270
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2295
2271
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2296
|
-
<strong class="text-sm"
|
|
2297
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
2298
|
-
>
|
|
2272
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2299
2273
|
</div>
|
|
2300
2274
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2301
2275
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2310,19 +2284,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2310
2284
|
}
|
|
2311
2285
|
|
|
2312
2286
|
@if (!loading() && actions().length === 0) {
|
|
2313
|
-
<div class="surface-card p-5 border
|
|
2314
|
-
<i
|
|
2315
|
-
|
|
2316
|
-
style="font-size: 3rem; display: block;"
|
|
2317
|
-
></i>
|
|
2318
|
-
<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">
|
|
2319
2290
|
No actions available for this role.
|
|
2320
2291
|
</p>
|
|
2321
2292
|
</div>
|
|
2322
2293
|
}
|
|
2323
2294
|
}
|
|
2324
2295
|
</div>
|
|
2325
|
-
`, 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"] }]
|
|
2326
2297
|
}], ctorParameters: () => [] });
|
|
2327
2298
|
|
|
2328
2299
|
/**
|
|
@@ -2360,6 +2331,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2360
2331
|
* ```
|
|
2361
2332
|
*/
|
|
2362
2333
|
class CompanyActionSelectorComponent {
|
|
2334
|
+
// Permission constants for template
|
|
2335
|
+
COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
|
|
2363
2336
|
// Dependencies
|
|
2364
2337
|
companyApiProvider = inject(COMPANY_API_PROVIDER);
|
|
2365
2338
|
actionApi = inject(ActionApiService);
|
|
@@ -2367,6 +2340,7 @@ class CompanyActionSelectorComponent {
|
|
|
2367
2340
|
messageService = inject(MessageService);
|
|
2368
2341
|
confirmationService = inject(ConfirmationService);
|
|
2369
2342
|
permissionLogic = inject(ActionPermissionLogicService);
|
|
2343
|
+
destroyRef = inject(DestroyRef);
|
|
2370
2344
|
// State - Company Selection
|
|
2371
2345
|
selectedCompanyId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
|
|
2372
2346
|
companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
|
|
@@ -2441,6 +2415,10 @@ class CompanyActionSelectorComponent {
|
|
|
2441
2415
|
// AbortController for data loading
|
|
2442
2416
|
loadDataAbortController = null;
|
|
2443
2417
|
constructor() {
|
|
2418
|
+
// Cleanup on destroy
|
|
2419
|
+
this.destroyRef.onDestroy(() => {
|
|
2420
|
+
this.loadDataAbortController?.abort();
|
|
2421
|
+
});
|
|
2444
2422
|
this.loadCompanies();
|
|
2445
2423
|
// Effect: Load data when company selection changes
|
|
2446
2424
|
effect(() => {
|
|
@@ -2451,11 +2429,7 @@ class CompanyActionSelectorComponent {
|
|
|
2451
2429
|
this.loadDataAbortController = new AbortController();
|
|
2452
2430
|
this.loadData(this.loadDataAbortController.signal).catch((err) => {
|
|
2453
2431
|
if (err.name !== 'AbortError') {
|
|
2454
|
-
|
|
2455
|
-
severity: 'error',
|
|
2456
|
-
summary: 'Error',
|
|
2457
|
-
detail: 'Failed to load company actions. Please refresh.',
|
|
2458
|
-
});
|
|
2432
|
+
// Error toast handled by global interceptor
|
|
2459
2433
|
this.loading.set(false);
|
|
2460
2434
|
}
|
|
2461
2435
|
});
|
|
@@ -2465,9 +2439,6 @@ class CompanyActionSelectorComponent {
|
|
|
2465
2439
|
}
|
|
2466
2440
|
});
|
|
2467
2441
|
}
|
|
2468
|
-
ngOnDestroy() {
|
|
2469
|
-
this.loadDataAbortController?.abort();
|
|
2470
|
-
}
|
|
2471
2442
|
/**
|
|
2472
2443
|
* Load companies from API
|
|
2473
2444
|
*/
|
|
@@ -2480,11 +2451,7 @@ class CompanyActionSelectorComponent {
|
|
|
2480
2451
|
this.companies.set(response?.data ?? []);
|
|
2481
2452
|
}
|
|
2482
2453
|
catch {
|
|
2483
|
-
|
|
2484
|
-
severity: 'error',
|
|
2485
|
-
summary: 'Error',
|
|
2486
|
-
detail: 'Failed to load companies',
|
|
2487
|
-
});
|
|
2454
|
+
// Error toast handled by global interceptor
|
|
2488
2455
|
}
|
|
2489
2456
|
}
|
|
2490
2457
|
/**
|
|
@@ -2649,13 +2616,8 @@ class CompanyActionSelectorComponent {
|
|
|
2649
2616
|
// Update baseline
|
|
2650
2617
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
2651
2618
|
}
|
|
2652
|
-
catch
|
|
2653
|
-
|
|
2654
|
-
this.messageService.add({
|
|
2655
|
-
severity: 'error',
|
|
2656
|
-
summary: 'Error',
|
|
2657
|
-
detail: error?.error?.message || 'Failed to update company action whitelist',
|
|
2658
|
-
});
|
|
2619
|
+
catch {
|
|
2620
|
+
// Error toast handled by global interceptor
|
|
2659
2621
|
}
|
|
2660
2622
|
finally {
|
|
2661
2623
|
this.saving.set(false);
|
|
@@ -2722,11 +2684,12 @@ class CompanyActionSelectorComponent {
|
|
|
2722
2684
|
<div class="company-action-selector">
|
|
2723
2685
|
<!-- Company Selector -->
|
|
2724
2686
|
<div class="mb-4">
|
|
2725
|
-
<div class="grid">
|
|
2726
|
-
<div
|
|
2687
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2688
|
+
<div>
|
|
2727
2689
|
<label class="block font-semibold mb-2">Select Company</label>
|
|
2728
2690
|
<p-select
|
|
2729
|
-
[
|
|
2691
|
+
[ngModel]="selectedCompanyId()"
|
|
2692
|
+
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2730
2693
|
[options]="companies()"
|
|
2731
2694
|
optionLabel="name"
|
|
2732
2695
|
optionValue="id"
|
|
@@ -2743,24 +2706,21 @@ class CompanyActionSelectorComponent {
|
|
|
2743
2706
|
<!-- Loading State -->
|
|
2744
2707
|
@if (loading()) {
|
|
2745
2708
|
<div
|
|
2746
|
-
class="surface-card p-5 border
|
|
2709
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2747
2710
|
>
|
|
2748
|
-
<i
|
|
2749
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2750
|
-
style="font-size: 3rem"
|
|
2751
|
-
></i>
|
|
2711
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2752
2712
|
</div>
|
|
2753
2713
|
}
|
|
2754
2714
|
|
|
2755
2715
|
<!-- Action List -->
|
|
2756
2716
|
@if (!loading() && actions().length > 0) {
|
|
2757
|
-
<div class="surface-card p-4 border
|
|
2717
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2758
2718
|
<div
|
|
2759
|
-
class="flex flex-
|
|
2719
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2760
2720
|
>
|
|
2761
2721
|
<div>
|
|
2762
2722
|
<h5 class="m-0 mb-1">Action Whitelist</h5>
|
|
2763
|
-
<p class="text-sm text-color
|
|
2723
|
+
<p class="text-sm text-muted-color m-0">
|
|
2764
2724
|
{{ actions().length }} actions available
|
|
2765
2725
|
</p>
|
|
2766
2726
|
</div>
|
|
@@ -2782,6 +2742,7 @@ class CompanyActionSelectorComponent {
|
|
|
2782
2742
|
(onClick)="deselectAll()"
|
|
2783
2743
|
/>
|
|
2784
2744
|
<p-button
|
|
2745
|
+
*hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
|
|
2785
2746
|
label="Save Changes"
|
|
2786
2747
|
icon="pi pi-save"
|
|
2787
2748
|
[disabled]="!canSave()"
|
|
@@ -2795,14 +2756,12 @@ class CompanyActionSelectorComponent {
|
|
|
2795
2756
|
|
|
2796
2757
|
<!-- Validation Warning -->
|
|
2797
2758
|
@if (invalidActionsCount() > 0) {
|
|
2798
|
-
<div class="
|
|
2799
|
-
<div class="flex items-
|
|
2800
|
-
<i
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
2805
|
-
<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">
|
|
2806
2765
|
{{ invalidActionsCount() }} selected
|
|
2807
2766
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
2808
2767
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -2814,33 +2773,33 @@ class CompanyActionSelectorComponent {
|
|
|
2814
2773
|
}
|
|
2815
2774
|
|
|
2816
2775
|
<!-- Action Tree Table -->
|
|
2817
|
-
<
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
<ng-template
|
|
2842
|
-
<tr [class.
|
|
2843
|
-
<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">
|
|
2844
2803
|
<p-checkbox
|
|
2845
2804
|
[ngModel]="selectionMap()[rowData.id]"
|
|
2846
2805
|
[binary]="true"
|
|
@@ -2851,7 +2810,7 @@ class CompanyActionSelectorComponent {
|
|
|
2851
2810
|
</td>
|
|
2852
2811
|
<td>
|
|
2853
2812
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2854
|
-
<span class="inline-flex
|
|
2813
|
+
<span class="inline-flex items-center gap-2">
|
|
2855
2814
|
{{ rowData.name }}
|
|
2856
2815
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2857
2816
|
<i
|
|
@@ -2862,7 +2821,7 @@ class CompanyActionSelectorComponent {
|
|
|
2862
2821
|
}
|
|
2863
2822
|
</span>
|
|
2864
2823
|
</td>
|
|
2865
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
2824
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2866
2825
|
<td>
|
|
2867
2826
|
<p-tag
|
|
2868
2827
|
[value]="rowData.actionType"
|
|
@@ -2871,36 +2830,35 @@ class CompanyActionSelectorComponent {
|
|
|
2871
2830
|
"
|
|
2872
2831
|
/>
|
|
2873
2832
|
</td>
|
|
2874
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
2833
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
2875
2834
|
</tr>
|
|
2876
2835
|
</ng-template>
|
|
2877
|
-
<ng-template
|
|
2836
|
+
<ng-template #emptymessage>
|
|
2878
2837
|
<tr>
|
|
2879
|
-
<td colspan="5" class="text-center p-4">
|
|
2838
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2880
2839
|
@if (loading()) {
|
|
2881
2840
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2882
2841
|
} @else { No actions available. }
|
|
2883
2842
|
</td>
|
|
2884
2843
|
</tr>
|
|
2885
2844
|
</ng-template>
|
|
2886
|
-
|
|
2845
|
+
</p-treeTable>
|
|
2846
|
+
</div>
|
|
2887
2847
|
</div>
|
|
2888
2848
|
|
|
2889
2849
|
<!-- Change Summary -->
|
|
2890
2850
|
@if (hasChanges()) {
|
|
2891
|
-
<div class="
|
|
2892
|
-
<div class="flex
|
|
2893
|
-
<i class="pi pi-info-circle text-
|
|
2894
|
-
<
|
|
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>
|
|
2895
2855
|
</div>
|
|
2896
|
-
<div class="
|
|
2856
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2897
2857
|
@if (pendingAdd().length > 0) {
|
|
2898
|
-
<div
|
|
2858
|
+
<div>
|
|
2899
2859
|
<div class="flex items-center gap-2 mb-2">
|
|
2900
2860
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2901
|
-
<strong class="text-sm">
|
|
2902
|
-
To Whitelist ({{ pendingAdd().length }})
|
|
2903
|
-
</strong>
|
|
2861
|
+
<strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
|
|
2904
2862
|
</div>
|
|
2905
2863
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2906
2864
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2909,14 +2867,11 @@ class CompanyActionSelectorComponent {
|
|
|
2909
2867
|
</ul>
|
|
2910
2868
|
</div>
|
|
2911
2869
|
}
|
|
2912
|
-
|
|
2913
2870
|
@if (pendingRemove().length > 0) {
|
|
2914
|
-
<div
|
|
2871
|
+
<div>
|
|
2915
2872
|
<div class="flex items-center gap-2 mb-2">
|
|
2916
2873
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2917
|
-
<strong class="text-sm">
|
|
2918
|
-
To Remove ({{ pendingRemove().length }})
|
|
2919
|
-
</strong>
|
|
2874
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2920
2875
|
</div>
|
|
2921
2876
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2922
2877
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2931,31 +2886,29 @@ class CompanyActionSelectorComponent {
|
|
|
2931
2886
|
}
|
|
2932
2887
|
|
|
2933
2888
|
@if (!loading() && actions().length === 0) {
|
|
2934
|
-
<div class="surface-card p-5 border
|
|
2935
|
-
<i
|
|
2936
|
-
|
|
2937
|
-
style="font-size: 3rem; display: block;"
|
|
2938
|
-
></i>
|
|
2939
|
-
<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">
|
|
2940
2892
|
No actions available for this company.
|
|
2941
2893
|
</p>
|
|
2942
2894
|
</div>
|
|
2943
2895
|
}
|
|
2944
2896
|
}
|
|
2945
2897
|
</div>
|
|
2946
|
-
`, 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 });
|
|
2947
2899
|
}
|
|
2948
2900
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
|
|
2949
2901
|
type: Component,
|
|
2950
|
-
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: `
|
|
2951
2903
|
<div class="company-action-selector">
|
|
2952
2904
|
<!-- Company Selector -->
|
|
2953
2905
|
<div class="mb-4">
|
|
2954
|
-
<div class="grid">
|
|
2955
|
-
<div
|
|
2906
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2907
|
+
<div>
|
|
2956
2908
|
<label class="block font-semibold mb-2">Select Company</label>
|
|
2957
2909
|
<p-select
|
|
2958
|
-
[
|
|
2910
|
+
[ngModel]="selectedCompanyId()"
|
|
2911
|
+
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2959
2912
|
[options]="companies()"
|
|
2960
2913
|
optionLabel="name"
|
|
2961
2914
|
optionValue="id"
|
|
@@ -2972,24 +2925,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2972
2925
|
<!-- Loading State -->
|
|
2973
2926
|
@if (loading()) {
|
|
2974
2927
|
<div
|
|
2975
|
-
class="surface-card p-5 border
|
|
2928
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2976
2929
|
>
|
|
2977
|
-
<i
|
|
2978
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2979
|
-
style="font-size: 3rem"
|
|
2980
|
-
></i>
|
|
2930
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2981
2931
|
</div>
|
|
2982
2932
|
}
|
|
2983
2933
|
|
|
2984
2934
|
<!-- Action List -->
|
|
2985
2935
|
@if (!loading() && actions().length > 0) {
|
|
2986
|
-
<div class="surface-card p-4 border
|
|
2936
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2987
2937
|
<div
|
|
2988
|
-
class="flex flex-
|
|
2938
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2989
2939
|
>
|
|
2990
2940
|
<div>
|
|
2991
2941
|
<h5 class="m-0 mb-1">Action Whitelist</h5>
|
|
2992
|
-
<p class="text-sm text-color
|
|
2942
|
+
<p class="text-sm text-muted-color m-0">
|
|
2993
2943
|
{{ actions().length }} actions available
|
|
2994
2944
|
</p>
|
|
2995
2945
|
</div>
|
|
@@ -3011,6 +2961,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3011
2961
|
(onClick)="deselectAll()"
|
|
3012
2962
|
/>
|
|
3013
2963
|
<p-button
|
|
2964
|
+
*hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
|
|
3014
2965
|
label="Save Changes"
|
|
3015
2966
|
icon="pi pi-save"
|
|
3016
2967
|
[disabled]="!canSave()"
|
|
@@ -3024,14 +2975,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3024
2975
|
|
|
3025
2976
|
<!-- Validation Warning -->
|
|
3026
2977
|
@if (invalidActionsCount() > 0) {
|
|
3027
|
-
<div class="
|
|
3028
|
-
<div class="flex items-
|
|
3029
|
-
<i
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
<strong class="text-orange-800">Validation Warning:</strong>
|
|
3034
|
-
<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">
|
|
3035
2984
|
{{ invalidActionsCount() }} selected
|
|
3036
2985
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
3037
2986
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -3043,33 +2992,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3043
2992
|
}
|
|
3044
2993
|
|
|
3045
2994
|
<!-- Action Tree Table -->
|
|
3046
|
-
<
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
<ng-template
|
|
3071
|
-
<tr [class.
|
|
3072
|
-
<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">
|
|
3073
3022
|
<p-checkbox
|
|
3074
3023
|
[ngModel]="selectionMap()[rowData.id]"
|
|
3075
3024
|
[binary]="true"
|
|
@@ -3080,7 +3029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3080
3029
|
</td>
|
|
3081
3030
|
<td>
|
|
3082
3031
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
3083
|
-
<span class="inline-flex
|
|
3032
|
+
<span class="inline-flex items-center gap-2">
|
|
3084
3033
|
{{ rowData.name }}
|
|
3085
3034
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
3086
3035
|
<i
|
|
@@ -3091,7 +3040,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3091
3040
|
}
|
|
3092
3041
|
</span>
|
|
3093
3042
|
</td>
|
|
3094
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
3043
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
3095
3044
|
<td>
|
|
3096
3045
|
<p-tag
|
|
3097
3046
|
[value]="rowData.actionType"
|
|
@@ -3100,36 +3049,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3100
3049
|
"
|
|
3101
3050
|
/>
|
|
3102
3051
|
</td>
|
|
3103
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
3052
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
3104
3053
|
</tr>
|
|
3105
3054
|
</ng-template>
|
|
3106
|
-
<ng-template
|
|
3055
|
+
<ng-template #emptymessage>
|
|
3107
3056
|
<tr>
|
|
3108
|
-
<td colspan="5" class="text-center p-4">
|
|
3057
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
3109
3058
|
@if (loading()) {
|
|
3110
3059
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
3111
3060
|
} @else { No actions available. }
|
|
3112
3061
|
</td>
|
|
3113
3062
|
</tr>
|
|
3114
3063
|
</ng-template>
|
|
3115
|
-
|
|
3064
|
+
</p-treeTable>
|
|
3065
|
+
</div>
|
|
3116
3066
|
</div>
|
|
3117
3067
|
|
|
3118
3068
|
<!-- Change Summary -->
|
|
3119
3069
|
@if (hasChanges()) {
|
|
3120
|
-
<div class="
|
|
3121
|
-
<div class="flex
|
|
3122
|
-
<i class="pi pi-info-circle text-
|
|
3123
|
-
<
|
|
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>
|
|
3124
3074
|
</div>
|
|
3125
|
-
<div class="
|
|
3075
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3126
3076
|
@if (pendingAdd().length > 0) {
|
|
3127
|
-
<div
|
|
3077
|
+
<div>
|
|
3128
3078
|
<div class="flex items-center gap-2 mb-2">
|
|
3129
3079
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3130
|
-
<strong class="text-sm">
|
|
3131
|
-
To Whitelist ({{ pendingAdd().length }})
|
|
3132
|
-
</strong>
|
|
3080
|
+
<strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
|
|
3133
3081
|
</div>
|
|
3134
3082
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3135
3083
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -3138,14 +3086,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3138
3086
|
</ul>
|
|
3139
3087
|
</div>
|
|
3140
3088
|
}
|
|
3141
|
-
|
|
3142
3089
|
@if (pendingRemove().length > 0) {
|
|
3143
|
-
<div
|
|
3090
|
+
<div>
|
|
3144
3091
|
<div class="flex items-center gap-2 mb-2">
|
|
3145
3092
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3146
|
-
<strong class="text-sm">
|
|
3147
|
-
To Remove ({{ pendingRemove().length }})
|
|
3148
|
-
</strong>
|
|
3093
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3149
3094
|
</div>
|
|
3150
3095
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3151
3096
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -3160,19 +3105,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3160
3105
|
}
|
|
3161
3106
|
|
|
3162
3107
|
@if (!loading() && actions().length === 0) {
|
|
3163
|
-
<div class="surface-card p-5 border
|
|
3164
|
-
<i
|
|
3165
|
-
|
|
3166
|
-
style="font-size: 3rem; display: block;"
|
|
3167
|
-
></i>
|
|
3168
|
-
<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">
|
|
3169
3111
|
No actions available for this company.
|
|
3170
3112
|
</p>
|
|
3171
3113
|
</div>
|
|
3172
3114
|
}
|
|
3173
3115
|
}
|
|
3174
3116
|
</div>
|
|
3175
|
-
`, 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"] }]
|
|
3176
3118
|
}], ctorParameters: () => [] });
|
|
3177
3119
|
|
|
3178
3120
|
/**
|
|
@@ -3206,6 +3148,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3206
3148
|
* ```
|
|
3207
3149
|
*/
|
|
3208
3150
|
class UserRoleSelectorComponent {
|
|
3151
|
+
// Permission constants for template
|
|
3152
|
+
USER_ROLE_PERMISSIONS = USER_ROLE_PERMISSIONS;
|
|
3209
3153
|
// Dependencies
|
|
3210
3154
|
appConfig = inject(APP_CONFIG);
|
|
3211
3155
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
@@ -3213,6 +3157,9 @@ class UserRoleSelectorComponent {
|
|
|
3213
3157
|
roleApi = inject(RoleApiService);
|
|
3214
3158
|
permissionApi = inject(PermissionApiService);
|
|
3215
3159
|
messageService = inject(MessageService);
|
|
3160
|
+
destroyRef = inject(DestroyRef);
|
|
3161
|
+
// AbortController for data loading
|
|
3162
|
+
loadDataAbortController = null;
|
|
3216
3163
|
// State - User/Branch Selection
|
|
3217
3164
|
selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
|
|
3218
3165
|
selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
|
|
@@ -3249,7 +3196,11 @@ class UserRoleSelectorComponent {
|
|
|
3249
3196
|
hasChanges = computed(() => {
|
|
3250
3197
|
const current = this.selectionMap();
|
|
3251
3198
|
const initial = this.initialSelection();
|
|
3252
|
-
|
|
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]);
|
|
3253
3204
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
3254
3205
|
// Computed - Pending Changes
|
|
3255
3206
|
pendingAdd = computed(() => {
|
|
@@ -3269,6 +3220,10 @@ class UserRoleSelectorComponent {
|
|
|
3269
3220
|
return this.hasChanges() && !this.saving();
|
|
3270
3221
|
}, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
3271
3222
|
constructor() {
|
|
3223
|
+
// Cleanup on destroy
|
|
3224
|
+
this.destroyRef.onDestroy(() => {
|
|
3225
|
+
this.loadDataAbortController?.abort();
|
|
3226
|
+
});
|
|
3272
3227
|
// Effect: Load user branches and data when user changes
|
|
3273
3228
|
effect(() => {
|
|
3274
3229
|
const userId = this.selectedUserId();
|
|
@@ -3316,11 +3271,7 @@ class UserRoleSelectorComponent {
|
|
|
3316
3271
|
this.branches.set(userBranches);
|
|
3317
3272
|
}
|
|
3318
3273
|
catch {
|
|
3319
|
-
|
|
3320
|
-
severity: 'error',
|
|
3321
|
-
summary: 'Error',
|
|
3322
|
-
detail: 'Failed to load user permitted branches',
|
|
3323
|
-
});
|
|
3274
|
+
// Error toast handled by global interceptor
|
|
3324
3275
|
}
|
|
3325
3276
|
}
|
|
3326
3277
|
/**
|
|
@@ -3358,11 +3309,7 @@ class UserRoleSelectorComponent {
|
|
|
3358
3309
|
this._initialSelection.set({ ...selMap });
|
|
3359
3310
|
}
|
|
3360
3311
|
catch {
|
|
3361
|
-
|
|
3362
|
-
severity: 'error',
|
|
3363
|
-
summary: 'Error',
|
|
3364
|
-
detail: 'Failed to load user role assignments',
|
|
3365
|
-
});
|
|
3312
|
+
// Error toast handled by global interceptor
|
|
3366
3313
|
}
|
|
3367
3314
|
finally {
|
|
3368
3315
|
this.loading.set(false);
|
|
@@ -3469,13 +3416,8 @@ class UserRoleSelectorComponent {
|
|
|
3469
3416
|
// Update baseline
|
|
3470
3417
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
3471
3418
|
}
|
|
3472
|
-
catch
|
|
3473
|
-
|
|
3474
|
-
this.messageService.add({
|
|
3475
|
-
severity: 'error',
|
|
3476
|
-
summary: 'Error',
|
|
3477
|
-
detail: error?.error?.message || 'Failed to update user role assignments',
|
|
3478
|
-
});
|
|
3419
|
+
catch {
|
|
3420
|
+
// Error toast handled by global interceptor
|
|
3479
3421
|
}
|
|
3480
3422
|
finally {
|
|
3481
3423
|
this.saving.set(false);
|
|
@@ -3493,28 +3435,30 @@ class UserRoleSelectorComponent {
|
|
|
3493
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: `
|
|
3494
3436
|
<div class="user-role-selector">
|
|
3495
3437
|
<!-- User and Branch Selectors -->
|
|
3496
|
-
<div class="surface-card p-4 border
|
|
3497
|
-
<div class="
|
|
3498
|
-
<div
|
|
3499
|
-
<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">
|
|
3500
3442
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
3501
3443
|
Select User
|
|
3502
3444
|
</label>
|
|
3503
3445
|
<lib-user-select
|
|
3504
|
-
[
|
|
3446
|
+
[value]="selectedUserId()"
|
|
3447
|
+
(valueChange)="selectedUserId.set($event)"
|
|
3505
3448
|
[isEditMode]="true"
|
|
3506
3449
|
placeHolder="Select a user"
|
|
3507
3450
|
/>
|
|
3508
3451
|
</div>
|
|
3509
3452
|
|
|
3510
3453
|
@if (showBranchSelector()) {
|
|
3511
|
-
<div
|
|
3512
|
-
<label class="block font-semibold mb-2
|
|
3454
|
+
<div>
|
|
3455
|
+
<label class="block font-semibold mb-2">
|
|
3513
3456
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
3514
3457
|
Select Branch
|
|
3515
3458
|
</label>
|
|
3516
3459
|
<p-select
|
|
3517
|
-
[
|
|
3460
|
+
[ngModel]="selectedBranchId()"
|
|
3461
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
3518
3462
|
[options]="filteredBranches()"
|
|
3519
3463
|
optionLabel="name"
|
|
3520
3464
|
optionValue="id"
|
|
@@ -3525,20 +3469,20 @@ class UserRoleSelectorComponent {
|
|
|
3525
3469
|
>
|
|
3526
3470
|
<ng-template #selectedItem let-branch>
|
|
3527
3471
|
@if (branch) {
|
|
3528
|
-
<div class="flex
|
|
3472
|
+
<div class="flex items-center gap-2">
|
|
3529
3473
|
<i class="pi pi-building text-primary"></i>
|
|
3530
3474
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
3531
3475
|
</div>
|
|
3532
3476
|
}
|
|
3533
3477
|
</ng-template>
|
|
3534
3478
|
<ng-template #item let-branch>
|
|
3535
|
-
<div class="flex
|
|
3536
|
-
<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>
|
|
3537
3481
|
<span>{{ branch.name }}</span>
|
|
3538
3482
|
</div>
|
|
3539
3483
|
</ng-template>
|
|
3540
3484
|
</p-select>
|
|
3541
|
-
<small class="text-color
|
|
3485
|
+
<small class="text-muted-color block mt-1">
|
|
3542
3486
|
{{ filteredBranches().length }} permitted branch{{
|
|
3543
3487
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
3544
3488
|
}}
|
|
@@ -3553,24 +3497,21 @@ class UserRoleSelectorComponent {
|
|
|
3553
3497
|
<!-- Loading State -->
|
|
3554
3498
|
@if (loading()) {
|
|
3555
3499
|
<div
|
|
3556
|
-
class="surface-card p-5 border
|
|
3500
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
3557
3501
|
>
|
|
3558
|
-
<i
|
|
3559
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
3560
|
-
style="font-size: 3rem"
|
|
3561
|
-
></i>
|
|
3502
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3562
3503
|
</div>
|
|
3563
3504
|
}
|
|
3564
3505
|
|
|
3565
3506
|
<!-- Role List -->
|
|
3566
3507
|
@if (!loading() && roles().length > 0) {
|
|
3567
|
-
<div class="surface-card p-4 border
|
|
3508
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3568
3509
|
<div
|
|
3569
|
-
class="flex flex-
|
|
3510
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3570
3511
|
>
|
|
3571
3512
|
<div>
|
|
3572
3513
|
<h5 class="m-0 mb-1">Role Assignments</h5>
|
|
3573
|
-
<p class="text-sm text-color
|
|
3514
|
+
<p class="text-sm text-muted-color m-0">
|
|
3574
3515
|
{{ roles().length }} roles available
|
|
3575
3516
|
</p>
|
|
3576
3517
|
</div>
|
|
@@ -3592,6 +3533,7 @@ class UserRoleSelectorComponent {
|
|
|
3592
3533
|
(onClick)="deselectAll()"
|
|
3593
3534
|
/>
|
|
3594
3535
|
<p-button
|
|
3536
|
+
*hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
|
|
3595
3537
|
label="Save Changes"
|
|
3596
3538
|
icon="pi pi-save"
|
|
3597
3539
|
[disabled]="!canSave()"
|
|
@@ -3604,77 +3546,78 @@ class UserRoleSelectorComponent {
|
|
|
3604
3546
|
</div>
|
|
3605
3547
|
|
|
3606
3548
|
<!-- Role Table -->
|
|
3607
|
-
<
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
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>
|
|
3661
3606
|
</div>
|
|
3662
3607
|
|
|
3663
3608
|
<!-- Change Summary -->
|
|
3664
3609
|
@if (hasChanges()) {
|
|
3665
|
-
<div class="
|
|
3666
|
-
<div class="flex
|
|
3667
|
-
<i class="pi pi-info-circle text-
|
|
3668
|
-
<
|
|
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>
|
|
3669
3614
|
</div>
|
|
3670
|
-
<div class="
|
|
3615
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3671
3616
|
@if (pendingAdd().length > 0) {
|
|
3672
|
-
<div
|
|
3673
|
-
<div class="flex
|
|
3617
|
+
<div>
|
|
3618
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3674
3619
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3675
|
-
<strong class="text-sm"
|
|
3676
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
3677
|
-
>
|
|
3620
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
3678
3621
|
</div>
|
|
3679
3622
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3680
3623
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3684,12 +3627,10 @@ class UserRoleSelectorComponent {
|
|
|
3684
3627
|
</div>
|
|
3685
3628
|
}
|
|
3686
3629
|
@if (pendingRemove().length > 0) {
|
|
3687
|
-
<div
|
|
3688
|
-
<div class="flex
|
|
3630
|
+
<div>
|
|
3631
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3689
3632
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3690
|
-
<strong class="text-sm"
|
|
3691
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
3692
|
-
>
|
|
3633
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3693
3634
|
</div>
|
|
3694
3635
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3695
3636
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3704,47 +3645,46 @@ class UserRoleSelectorComponent {
|
|
|
3704
3645
|
}
|
|
3705
3646
|
|
|
3706
3647
|
@if (!loading() && roles().length === 0) {
|
|
3707
|
-
<div class="surface-card p-5 border
|
|
3708
|
-
<i
|
|
3709
|
-
|
|
3710
|
-
style="font-size: 3rem; display: block;"
|
|
3711
|
-
></i>
|
|
3712
|
-
<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">
|
|
3713
3651
|
No roles available for this user.
|
|
3714
3652
|
</p>
|
|
3715
3653
|
</div>
|
|
3716
3654
|
}
|
|
3717
3655
|
}
|
|
3718
3656
|
</div>
|
|
3719
|
-
`, 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 });
|
|
3720
3658
|
}
|
|
3721
3659
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
|
|
3722
3660
|
type: Component,
|
|
3723
|
-
args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, UserSelectComponent], template: `
|
|
3661
|
+
args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
|
|
3724
3662
|
<div class="user-role-selector">
|
|
3725
3663
|
<!-- User and Branch Selectors -->
|
|
3726
|
-
<div class="surface-card p-4 border
|
|
3727
|
-
<div class="
|
|
3728
|
-
<div
|
|
3729
|
-
<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">
|
|
3730
3668
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
3731
3669
|
Select User
|
|
3732
3670
|
</label>
|
|
3733
3671
|
<lib-user-select
|
|
3734
|
-
[
|
|
3672
|
+
[value]="selectedUserId()"
|
|
3673
|
+
(valueChange)="selectedUserId.set($event)"
|
|
3735
3674
|
[isEditMode]="true"
|
|
3736
3675
|
placeHolder="Select a user"
|
|
3737
3676
|
/>
|
|
3738
3677
|
</div>
|
|
3739
3678
|
|
|
3740
3679
|
@if (showBranchSelector()) {
|
|
3741
|
-
<div
|
|
3742
|
-
<label class="block font-semibold mb-2
|
|
3680
|
+
<div>
|
|
3681
|
+
<label class="block font-semibold mb-2">
|
|
3743
3682
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
3744
3683
|
Select Branch
|
|
3745
3684
|
</label>
|
|
3746
3685
|
<p-select
|
|
3747
|
-
[
|
|
3686
|
+
[ngModel]="selectedBranchId()"
|
|
3687
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
3748
3688
|
[options]="filteredBranches()"
|
|
3749
3689
|
optionLabel="name"
|
|
3750
3690
|
optionValue="id"
|
|
@@ -3755,20 +3695,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3755
3695
|
>
|
|
3756
3696
|
<ng-template #selectedItem let-branch>
|
|
3757
3697
|
@if (branch) {
|
|
3758
|
-
<div class="flex
|
|
3698
|
+
<div class="flex items-center gap-2">
|
|
3759
3699
|
<i class="pi pi-building text-primary"></i>
|
|
3760
3700
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
3761
3701
|
</div>
|
|
3762
3702
|
}
|
|
3763
3703
|
</ng-template>
|
|
3764
3704
|
<ng-template #item let-branch>
|
|
3765
|
-
<div class="flex
|
|
3766
|
-
<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>
|
|
3767
3707
|
<span>{{ branch.name }}</span>
|
|
3768
3708
|
</div>
|
|
3769
3709
|
</ng-template>
|
|
3770
3710
|
</p-select>
|
|
3771
|
-
<small class="text-color
|
|
3711
|
+
<small class="text-muted-color block mt-1">
|
|
3772
3712
|
{{ filteredBranches().length }} permitted branch{{
|
|
3773
3713
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
3774
3714
|
}}
|
|
@@ -3783,24 +3723,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3783
3723
|
<!-- Loading State -->
|
|
3784
3724
|
@if (loading()) {
|
|
3785
3725
|
<div
|
|
3786
|
-
class="surface-card p-5 border
|
|
3726
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
3787
3727
|
>
|
|
3788
|
-
<i
|
|
3789
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
3790
|
-
style="font-size: 3rem"
|
|
3791
|
-
></i>
|
|
3728
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3792
3729
|
</div>
|
|
3793
3730
|
}
|
|
3794
3731
|
|
|
3795
3732
|
<!-- Role List -->
|
|
3796
3733
|
@if (!loading() && roles().length > 0) {
|
|
3797
|
-
<div class="surface-card p-4 border
|
|
3734
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3798
3735
|
<div
|
|
3799
|
-
class="flex flex-
|
|
3736
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3800
3737
|
>
|
|
3801
3738
|
<div>
|
|
3802
3739
|
<h5 class="m-0 mb-1">Role Assignments</h5>
|
|
3803
|
-
<p class="text-sm text-color
|
|
3740
|
+
<p class="text-sm text-muted-color m-0">
|
|
3804
3741
|
{{ roles().length }} roles available
|
|
3805
3742
|
</p>
|
|
3806
3743
|
</div>
|
|
@@ -3822,6 +3759,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3822
3759
|
(onClick)="deselectAll()"
|
|
3823
3760
|
/>
|
|
3824
3761
|
<p-button
|
|
3762
|
+
*hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
|
|
3825
3763
|
label="Save Changes"
|
|
3826
3764
|
icon="pi pi-save"
|
|
3827
3765
|
[disabled]="!canSave()"
|
|
@@ -3834,77 +3772,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3834
3772
|
</div>
|
|
3835
3773
|
|
|
3836
3774
|
<!-- Role Table -->
|
|
3837
|
-
<
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
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>
|
|
3891
3832
|
</div>
|
|
3892
3833
|
|
|
3893
3834
|
<!-- Change Summary -->
|
|
3894
3835
|
@if (hasChanges()) {
|
|
3895
|
-
<div class="
|
|
3896
|
-
<div class="flex
|
|
3897
|
-
<i class="pi pi-info-circle text-
|
|
3898
|
-
<
|
|
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>
|
|
3899
3840
|
</div>
|
|
3900
|
-
<div class="
|
|
3841
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3901
3842
|
@if (pendingAdd().length > 0) {
|
|
3902
|
-
<div
|
|
3903
|
-
<div class="flex
|
|
3843
|
+
<div>
|
|
3844
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3904
3845
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3905
|
-
<strong class="text-sm"
|
|
3906
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
3907
|
-
>
|
|
3846
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
3908
3847
|
</div>
|
|
3909
3848
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3910
3849
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3914,12 +3853,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3914
3853
|
</div>
|
|
3915
3854
|
}
|
|
3916
3855
|
@if (pendingRemove().length > 0) {
|
|
3917
|
-
<div
|
|
3918
|
-
<div class="flex
|
|
3856
|
+
<div>
|
|
3857
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3919
3858
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3920
|
-
<strong class="text-sm"
|
|
3921
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
3922
|
-
>
|
|
3859
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3923
3860
|
</div>
|
|
3924
3861
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3925
3862
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3934,12 +3871,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3934
3871
|
}
|
|
3935
3872
|
|
|
3936
3873
|
@if (!loading() && roles().length === 0) {
|
|
3937
|
-
<div class="surface-card p-5 border
|
|
3938
|
-
<i
|
|
3939
|
-
|
|
3940
|
-
style="font-size: 3rem; display: block;"
|
|
3941
|
-
></i>
|
|
3942
|
-
<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">
|
|
3943
3877
|
No roles available for this user.
|
|
3944
3878
|
</p>
|
|
3945
3879
|
</div>
|
|
@@ -3981,6 +3915,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3981
3915
|
* ```
|
|
3982
3916
|
*/
|
|
3983
3917
|
class UserActionSelectorComponent {
|
|
3918
|
+
// Permission constants for template
|
|
3919
|
+
USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
|
|
3984
3920
|
// Dependencies
|
|
3985
3921
|
appConfig = inject(APP_CONFIG);
|
|
3986
3922
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
@@ -3989,6 +3925,9 @@ class UserActionSelectorComponent {
|
|
|
3989
3925
|
permissionApi = inject(PermissionApiService);
|
|
3990
3926
|
permissionLogic = inject(ActionPermissionLogicService);
|
|
3991
3927
|
messageService = inject(MessageService);
|
|
3928
|
+
destroyRef = inject(DestroyRef);
|
|
3929
|
+
// AbortController for data loading
|
|
3930
|
+
loadDataAbortController = null;
|
|
3992
3931
|
// State - User/Branch Selection
|
|
3993
3932
|
selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
|
|
3994
3933
|
selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
|
|
@@ -4030,7 +3969,11 @@ class UserActionSelectorComponent {
|
|
|
4030
3969
|
hasChanges = computed(() => {
|
|
4031
3970
|
const current = this.selectionMap();
|
|
4032
3971
|
const initial = this.initialSelection();
|
|
4033
|
-
|
|
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]);
|
|
4034
3977
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
4035
3978
|
// Computed - Validation
|
|
4036
3979
|
actionsWithUnmetPrerequisites = computed(() => {
|
|
@@ -4066,6 +4009,10 @@ class UserActionSelectorComponent {
|
|
|
4066
4009
|
return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
|
|
4067
4010
|
}, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
4068
4011
|
constructor() {
|
|
4012
|
+
// Cleanup on destroy
|
|
4013
|
+
this.destroyRef.onDestroy(() => {
|
|
4014
|
+
this.loadDataAbortController?.abort();
|
|
4015
|
+
});
|
|
4069
4016
|
// Effect: Load user branches and data when user changes
|
|
4070
4017
|
effect(() => {
|
|
4071
4018
|
const userId = this.selectedUserId();
|
|
@@ -4113,11 +4060,7 @@ class UserActionSelectorComponent {
|
|
|
4113
4060
|
this.branches.set(userBranches);
|
|
4114
4061
|
}
|
|
4115
4062
|
catch {
|
|
4116
|
-
|
|
4117
|
-
severity: 'error',
|
|
4118
|
-
summary: 'Error',
|
|
4119
|
-
detail: 'Failed to load user permitted branches',
|
|
4120
|
-
});
|
|
4063
|
+
// Error toast handled by global interceptor
|
|
4121
4064
|
}
|
|
4122
4065
|
}
|
|
4123
4066
|
/**
|
|
@@ -4158,11 +4101,7 @@ class UserActionSelectorComponent {
|
|
|
4158
4101
|
this._initialSelection.set({ ...selMap });
|
|
4159
4102
|
}
|
|
4160
4103
|
catch {
|
|
4161
|
-
|
|
4162
|
-
severity: 'error',
|
|
4163
|
-
summary: 'Error',
|
|
4164
|
-
detail: 'Failed to load user action permissions',
|
|
4165
|
-
});
|
|
4104
|
+
// Error toast handled by global interceptor
|
|
4166
4105
|
}
|
|
4167
4106
|
finally {
|
|
4168
4107
|
this.loading.set(false);
|
|
@@ -4197,12 +4136,17 @@ class UserActionSelectorComponent {
|
|
|
4197
4136
|
: false;
|
|
4198
4137
|
}
|
|
4199
4138
|
/**
|
|
4200
|
-
* Handle action
|
|
4139
|
+
* Handle action toggle with dependency management
|
|
4201
4140
|
*/
|
|
4202
4141
|
onActionToggle(action, newValue) {
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
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
|
+
}
|
|
4206
4150
|
}
|
|
4207
4151
|
/**
|
|
4208
4152
|
* Toggle all actions
|
|
@@ -4253,6 +4197,12 @@ class UserActionSelectorComponent {
|
|
|
4253
4197
|
});
|
|
4254
4198
|
return;
|
|
4255
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
|
+
}
|
|
4256
4206
|
// Build payload
|
|
4257
4207
|
const items = [];
|
|
4258
4208
|
this.pendingAdd().forEach((action) => {
|
|
@@ -4292,13 +4242,8 @@ class UserActionSelectorComponent {
|
|
|
4292
4242
|
// Update baseline
|
|
4293
4243
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
4294
4244
|
}
|
|
4295
|
-
catch
|
|
4296
|
-
|
|
4297
|
-
this.messageService.add({
|
|
4298
|
-
severity: 'error',
|
|
4299
|
-
summary: 'Error',
|
|
4300
|
-
detail: error?.error?.message || 'Failed to update user action permissions',
|
|
4301
|
-
});
|
|
4245
|
+
catch {
|
|
4246
|
+
// Error toast handled by global interceptor
|
|
4302
4247
|
}
|
|
4303
4248
|
finally {
|
|
4304
4249
|
this.saving.set(false);
|
|
@@ -4317,28 +4262,30 @@ class UserActionSelectorComponent {
|
|
|
4317
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: `
|
|
4318
4263
|
<div class="user-action-selector">
|
|
4319
4264
|
<!-- User and Branch Selectors -->
|
|
4320
|
-
<div class="surface-card p-4 border
|
|
4321
|
-
<div class="
|
|
4322
|
-
<div
|
|
4323
|
-
<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">
|
|
4324
4269
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
4325
4270
|
Select User
|
|
4326
4271
|
</label>
|
|
4327
4272
|
<lib-user-select
|
|
4328
|
-
[
|
|
4273
|
+
[value]="selectedUserId()"
|
|
4274
|
+
(valueChange)="selectedUserId.set($event)"
|
|
4329
4275
|
[isEditMode]="true"
|
|
4330
4276
|
placeHolder="Select a user"
|
|
4331
4277
|
/>
|
|
4332
4278
|
</div>
|
|
4333
4279
|
|
|
4334
4280
|
@if (showBranchSelector()) {
|
|
4335
|
-
<div
|
|
4336
|
-
<label class="block font-semibold mb-2
|
|
4281
|
+
<div>
|
|
4282
|
+
<label class="block font-semibold mb-2">
|
|
4337
4283
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
4338
4284
|
Select Branch
|
|
4339
4285
|
</label>
|
|
4340
4286
|
<p-select
|
|
4341
|
-
[
|
|
4287
|
+
[ngModel]="selectedBranchId()"
|
|
4288
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
4342
4289
|
[options]="filteredBranches()"
|
|
4343
4290
|
optionLabel="name"
|
|
4344
4291
|
optionValue="id"
|
|
@@ -4349,20 +4296,20 @@ class UserActionSelectorComponent {
|
|
|
4349
4296
|
>
|
|
4350
4297
|
<ng-template #selectedItem let-branch>
|
|
4351
4298
|
@if (branch) {
|
|
4352
|
-
<div class="flex
|
|
4299
|
+
<div class="flex items-center gap-2">
|
|
4353
4300
|
<i class="pi pi-building text-primary"></i>
|
|
4354
4301
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
4355
4302
|
</div>
|
|
4356
4303
|
}
|
|
4357
4304
|
</ng-template>
|
|
4358
4305
|
<ng-template #item let-branch>
|
|
4359
|
-
<div class="flex
|
|
4360
|
-
<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>
|
|
4361
4308
|
<span>{{ branch.name }}</span>
|
|
4362
4309
|
</div>
|
|
4363
4310
|
</ng-template>
|
|
4364
4311
|
</p-select>
|
|
4365
|
-
<small class="text-color
|
|
4312
|
+
<small class="text-muted-color block mt-1">
|
|
4366
4313
|
{{ filteredBranches().length }} permitted branch{{
|
|
4367
4314
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
4368
4315
|
}}
|
|
@@ -4376,20 +4323,20 @@ class UserActionSelectorComponent {
|
|
|
4376
4323
|
@if (selectedUserId()) {
|
|
4377
4324
|
<!-- Loading State -->
|
|
4378
4325
|
@if (loading()) {
|
|
4379
|
-
<div class="flex justify-
|
|
4380
|
-
<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>
|
|
4381
4328
|
</div>
|
|
4382
4329
|
}
|
|
4383
4330
|
|
|
4384
4331
|
<!-- Action List -->
|
|
4385
4332
|
@if (!loading() && actions().length > 0) {
|
|
4386
|
-
<div class="surface-card p-4 border
|
|
4333
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4387
4334
|
<div
|
|
4388
|
-
class="flex flex-
|
|
4335
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4389
4336
|
>
|
|
4390
4337
|
<div>
|
|
4391
4338
|
<h5 class="m-0 mb-1">Direct Action Permissions</h5>
|
|
4392
|
-
<p class="text-sm text-color
|
|
4339
|
+
<p class="text-sm text-muted-color m-0">
|
|
4393
4340
|
{{ actions().length }} actions available
|
|
4394
4341
|
</p>
|
|
4395
4342
|
</div>
|
|
@@ -4411,6 +4358,7 @@ class UserActionSelectorComponent {
|
|
|
4411
4358
|
(onClick)="deselectAll()"
|
|
4412
4359
|
/>
|
|
4413
4360
|
<p-button
|
|
4361
|
+
*hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
|
|
4414
4362
|
label="Save Changes"
|
|
4415
4363
|
icon="pi pi-save"
|
|
4416
4364
|
[disabled]="!canSave()"
|
|
@@ -4422,34 +4370,52 @@ class UserActionSelectorComponent {
|
|
|
4422
4370
|
</div>
|
|
4423
4371
|
</div>
|
|
4424
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
|
+
|
|
4425
4391
|
<!-- Action Tree Table -->
|
|
4426
|
-
<
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
<ng-template
|
|
4451
|
-
<tr>
|
|
4452
|
-
<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">
|
|
4453
4419
|
<p-checkbox
|
|
4454
4420
|
[ngModel]="selectionMap()[rowData.id]"
|
|
4455
4421
|
[binary]="true"
|
|
@@ -4460,9 +4426,18 @@ class UserActionSelectorComponent {
|
|
|
4460
4426
|
</td>
|
|
4461
4427
|
<td>
|
|
4462
4428
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4463
|
-
<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>
|
|
4464
4439
|
</td>
|
|
4465
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
4440
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
4466
4441
|
<td>
|
|
4467
4442
|
<p-tag
|
|
4468
4443
|
[value]="rowData.actionType"
|
|
@@ -4471,12 +4446,12 @@ class UserActionSelectorComponent {
|
|
|
4471
4446
|
"
|
|
4472
4447
|
/>
|
|
4473
4448
|
</td>
|
|
4474
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
4449
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4475
4450
|
</tr>
|
|
4476
4451
|
</ng-template>
|
|
4477
|
-
<ng-template
|
|
4452
|
+
<ng-template #emptymessage>
|
|
4478
4453
|
<tr>
|
|
4479
|
-
<td colspan="5" class="text-center p-4">
|
|
4454
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4480
4455
|
@if (loading()) {
|
|
4481
4456
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
4482
4457
|
} @else {
|
|
@@ -4485,24 +4460,23 @@ class UserActionSelectorComponent {
|
|
|
4485
4460
|
</td>
|
|
4486
4461
|
</tr>
|
|
4487
4462
|
</ng-template>
|
|
4488
|
-
|
|
4463
|
+
</p-treeTable>
|
|
4464
|
+
</div>
|
|
4489
4465
|
</div>
|
|
4490
4466
|
|
|
4491
4467
|
<!-- Change Summary -->
|
|
4492
4468
|
@if (hasChanges()) {
|
|
4493
|
-
<div class="
|
|
4494
|
-
<div class="flex
|
|
4495
|
-
<i class="pi pi-info-circle text-
|
|
4496
|
-
<
|
|
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>
|
|
4497
4473
|
</div>
|
|
4498
|
-
<div class="
|
|
4474
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4499
4475
|
@if (pendingAdd().length > 0) {
|
|
4500
|
-
<div
|
|
4501
|
-
<div class="flex
|
|
4476
|
+
<div>
|
|
4477
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4502
4478
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4503
|
-
<strong class="text-sm"
|
|
4504
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4505
|
-
>
|
|
4479
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4506
4480
|
</div>
|
|
4507
4481
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4508
4482
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4512,12 +4486,10 @@ class UserActionSelectorComponent {
|
|
|
4512
4486
|
</div>
|
|
4513
4487
|
}
|
|
4514
4488
|
@if (pendingRemove().length > 0) {
|
|
4515
|
-
<div
|
|
4516
|
-
<div class="flex
|
|
4489
|
+
<div>
|
|
4490
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4517
4491
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4518
|
-
<strong class="text-sm"
|
|
4519
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4520
|
-
>
|
|
4492
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4521
4493
|
</div>
|
|
4522
4494
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4523
4495
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4532,47 +4504,46 @@ class UserActionSelectorComponent {
|
|
|
4532
4504
|
}
|
|
4533
4505
|
|
|
4534
4506
|
@if (!loading() && actions().length === 0) {
|
|
4535
|
-
<div class="surface-card p-5 border
|
|
4536
|
-
<i
|
|
4537
|
-
|
|
4538
|
-
style="font-size: 3rem; display: block;"
|
|
4539
|
-
></i>
|
|
4540
|
-
<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">
|
|
4541
4510
|
No actions available for this user.
|
|
4542
4511
|
</p>
|
|
4543
4512
|
</div>
|
|
4544
4513
|
}
|
|
4545
4514
|
}
|
|
4546
4515
|
</div>
|
|
4547
|
-
`, 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 });
|
|
4548
4517
|
}
|
|
4549
4518
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
|
|
4550
4519
|
type: Component,
|
|
4551
|
-
args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, UserSelectComponent], template: `
|
|
4520
|
+
args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
|
|
4552
4521
|
<div class="user-action-selector">
|
|
4553
4522
|
<!-- User and Branch Selectors -->
|
|
4554
|
-
<div class="surface-card p-4 border
|
|
4555
|
-
<div class="
|
|
4556
|
-
<div
|
|
4557
|
-
<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">
|
|
4558
4527
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
4559
4528
|
Select User
|
|
4560
4529
|
</label>
|
|
4561
4530
|
<lib-user-select
|
|
4562
|
-
[
|
|
4531
|
+
[value]="selectedUserId()"
|
|
4532
|
+
(valueChange)="selectedUserId.set($event)"
|
|
4563
4533
|
[isEditMode]="true"
|
|
4564
4534
|
placeHolder="Select a user"
|
|
4565
4535
|
/>
|
|
4566
4536
|
</div>
|
|
4567
4537
|
|
|
4568
4538
|
@if (showBranchSelector()) {
|
|
4569
|
-
<div
|
|
4570
|
-
<label class="block font-semibold mb-2
|
|
4539
|
+
<div>
|
|
4540
|
+
<label class="block font-semibold mb-2">
|
|
4571
4541
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
4572
4542
|
Select Branch
|
|
4573
4543
|
</label>
|
|
4574
4544
|
<p-select
|
|
4575
|
-
[
|
|
4545
|
+
[ngModel]="selectedBranchId()"
|
|
4546
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
4576
4547
|
[options]="filteredBranches()"
|
|
4577
4548
|
optionLabel="name"
|
|
4578
4549
|
optionValue="id"
|
|
@@ -4583,20 +4554,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4583
4554
|
>
|
|
4584
4555
|
<ng-template #selectedItem let-branch>
|
|
4585
4556
|
@if (branch) {
|
|
4586
|
-
<div class="flex
|
|
4557
|
+
<div class="flex items-center gap-2">
|
|
4587
4558
|
<i class="pi pi-building text-primary"></i>
|
|
4588
4559
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
4589
4560
|
</div>
|
|
4590
4561
|
}
|
|
4591
4562
|
</ng-template>
|
|
4592
4563
|
<ng-template #item let-branch>
|
|
4593
|
-
<div class="flex
|
|
4594
|
-
<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>
|
|
4595
4566
|
<span>{{ branch.name }}</span>
|
|
4596
4567
|
</div>
|
|
4597
4568
|
</ng-template>
|
|
4598
4569
|
</p-select>
|
|
4599
|
-
<small class="text-color
|
|
4570
|
+
<small class="text-muted-color block mt-1">
|
|
4600
4571
|
{{ filteredBranches().length }} permitted branch{{
|
|
4601
4572
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
4602
4573
|
}}
|
|
@@ -4610,20 +4581,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4610
4581
|
@if (selectedUserId()) {
|
|
4611
4582
|
<!-- Loading State -->
|
|
4612
4583
|
@if (loading()) {
|
|
4613
|
-
<div class="flex justify-
|
|
4614
|
-
<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>
|
|
4615
4586
|
</div>
|
|
4616
4587
|
}
|
|
4617
4588
|
|
|
4618
4589
|
<!-- Action List -->
|
|
4619
4590
|
@if (!loading() && actions().length > 0) {
|
|
4620
|
-
<div class="surface-card p-4 border
|
|
4591
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4621
4592
|
<div
|
|
4622
|
-
class="flex flex-
|
|
4593
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4623
4594
|
>
|
|
4624
4595
|
<div>
|
|
4625
4596
|
<h5 class="m-0 mb-1">Direct Action Permissions</h5>
|
|
4626
|
-
<p class="text-sm text-color
|
|
4597
|
+
<p class="text-sm text-muted-color m-0">
|
|
4627
4598
|
{{ actions().length }} actions available
|
|
4628
4599
|
</p>
|
|
4629
4600
|
</div>
|
|
@@ -4645,6 +4616,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4645
4616
|
(onClick)="deselectAll()"
|
|
4646
4617
|
/>
|
|
4647
4618
|
<p-button
|
|
4619
|
+
*hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
|
|
4648
4620
|
label="Save Changes"
|
|
4649
4621
|
icon="pi pi-save"
|
|
4650
4622
|
[disabled]="!canSave()"
|
|
@@ -4656,34 +4628,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4656
4628
|
</div>
|
|
4657
4629
|
</div>
|
|
4658
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
|
+
|
|
4659
4649
|
<!-- Action Tree Table -->
|
|
4660
|
-
<
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
<ng-template
|
|
4685
|
-
<tr>
|
|
4686
|
-
<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">
|
|
4687
4677
|
<p-checkbox
|
|
4688
4678
|
[ngModel]="selectionMap()[rowData.id]"
|
|
4689
4679
|
[binary]="true"
|
|
@@ -4694,9 +4684,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4694
4684
|
</td>
|
|
4695
4685
|
<td>
|
|
4696
4686
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4697
|
-
<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>
|
|
4698
4697
|
</td>
|
|
4699
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
4698
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
4700
4699
|
<td>
|
|
4701
4700
|
<p-tag
|
|
4702
4701
|
[value]="rowData.actionType"
|
|
@@ -4705,12 +4704,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4705
4704
|
"
|
|
4706
4705
|
/>
|
|
4707
4706
|
</td>
|
|
4708
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
4707
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4709
4708
|
</tr>
|
|
4710
4709
|
</ng-template>
|
|
4711
|
-
<ng-template
|
|
4710
|
+
<ng-template #emptymessage>
|
|
4712
4711
|
<tr>
|
|
4713
|
-
<td colspan="5" class="text-center p-4">
|
|
4712
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4714
4713
|
@if (loading()) {
|
|
4715
4714
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
4716
4715
|
} @else {
|
|
@@ -4719,24 +4718,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4719
4718
|
</td>
|
|
4720
4719
|
</tr>
|
|
4721
4720
|
</ng-template>
|
|
4722
|
-
|
|
4721
|
+
</p-treeTable>
|
|
4722
|
+
</div>
|
|
4723
4723
|
</div>
|
|
4724
4724
|
|
|
4725
4725
|
<!-- Change Summary -->
|
|
4726
4726
|
@if (hasChanges()) {
|
|
4727
|
-
<div class="
|
|
4728
|
-
<div class="flex
|
|
4729
|
-
<i class="pi pi-info-circle text-
|
|
4730
|
-
<
|
|
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>
|
|
4731
4731
|
</div>
|
|
4732
|
-
<div class="
|
|
4732
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4733
4733
|
@if (pendingAdd().length > 0) {
|
|
4734
|
-
<div
|
|
4735
|
-
<div class="flex
|
|
4734
|
+
<div>
|
|
4735
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4736
4736
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4737
|
-
<strong class="text-sm"
|
|
4738
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4739
|
-
>
|
|
4737
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4740
4738
|
</div>
|
|
4741
4739
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4742
4740
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4746,12 +4744,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4746
4744
|
</div>
|
|
4747
4745
|
}
|
|
4748
4746
|
@if (pendingRemove().length > 0) {
|
|
4749
|
-
<div
|
|
4750
|
-
<div class="flex
|
|
4747
|
+
<div>
|
|
4748
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4751
4749
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4752
|
-
<strong class="text-sm"
|
|
4753
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4754
|
-
>
|
|
4750
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4755
4751
|
</div>
|
|
4756
4752
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4757
4753
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4766,23 +4762,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4766
4762
|
}
|
|
4767
4763
|
|
|
4768
4764
|
@if (!loading() && actions().length === 0) {
|
|
4769
|
-
<div class="surface-card p-5 border
|
|
4770
|
-
<i
|
|
4771
|
-
|
|
4772
|
-
style="font-size: 3rem; display: block;"
|
|
4773
|
-
></i>
|
|
4774
|
-
<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">
|
|
4775
4768
|
No actions available for this user.
|
|
4776
4769
|
</p>
|
|
4777
4770
|
</div>
|
|
4778
4771
|
}
|
|
4779
4772
|
}
|
|
4780
4773
|
</div>
|
|
4781
|
-
`, 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"] }]
|
|
4782
4775
|
}], ctorParameters: () => [] });
|
|
4783
4776
|
|
|
4784
4777
|
// Logic Builder Component
|
|
4785
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
|
+
|
|
4786
4860
|
/**
|
|
4787
4861
|
* IAM Routes Configuration
|
|
4788
4862
|
*
|
|
@@ -4796,7 +4870,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4796
4870
|
const IAM_ROUTES = [
|
|
4797
4871
|
{
|
|
4798
4872
|
path: '',
|
|
4799
|
-
loadComponent: () => import('./flusys-ng-iam-iam-container.component-
|
|
4873
|
+
loadComponent: () => import('./flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs').then((m) => m.IamContainerComponent),
|
|
4800
4874
|
children: [
|
|
4801
4875
|
// Actions Management
|
|
4802
4876
|
{
|
|
@@ -4804,15 +4878,15 @@ const IAM_ROUTES = [
|
|
|
4804
4878
|
children: [
|
|
4805
4879
|
{
|
|
4806
4880
|
path: '',
|
|
4807
|
-
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),
|
|
4808
4882
|
},
|
|
4809
4883
|
{
|
|
4810
4884
|
path: 'new',
|
|
4811
|
-
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),
|
|
4812
4886
|
},
|
|
4813
4887
|
{
|
|
4814
4888
|
path: ':id',
|
|
4815
|
-
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),
|
|
4816
4890
|
},
|
|
4817
4891
|
],
|
|
4818
4892
|
},
|
|
@@ -4822,22 +4896,22 @@ const IAM_ROUTES = [
|
|
|
4822
4896
|
children: [
|
|
4823
4897
|
{
|
|
4824
4898
|
path: '',
|
|
4825
|
-
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),
|
|
4826
4900
|
},
|
|
4827
4901
|
{
|
|
4828
4902
|
path: 'new',
|
|
4829
|
-
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),
|
|
4830
4904
|
},
|
|
4831
4905
|
{
|
|
4832
4906
|
path: ':id',
|
|
4833
|
-
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),
|
|
4834
4908
|
},
|
|
4835
4909
|
],
|
|
4836
4910
|
},
|
|
4837
4911
|
// Permissions Management (User permission assignment)
|
|
4838
4912
|
{
|
|
4839
4913
|
path: 'permissions',
|
|
4840
|
-
loadComponent: () => import('./flusys-ng-iam-permission-page.component-
|
|
4914
|
+
loadComponent: () => import('./flusys-ng-iam-permission-page.component-CmxOBJPu.mjs').then((m) => m.PermissionPageComponent),
|
|
4841
4915
|
},
|
|
4842
4916
|
// Default redirect to actions
|
|
4843
4917
|
{
|
|
@@ -4855,5 +4929,5 @@ const IAM_ROUTES = [
|
|
|
4855
4929
|
* Generated bundle index. Do not edit.
|
|
4856
4930
|
*/
|
|
4857
4931
|
|
|
4858
|
-
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 };
|
|
4859
|
-
//# 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
|