@flusys/ng-iam 1.1.0-beta → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -25
- package/fesm2022/flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs +389 -0
- package/fesm2022/flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs +262 -0
- package/fesm2022/flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-flusys-ng-iam-BjdM-Vgz.mjs → flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs} +1046 -1617
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-iam-container.component-UYJjqYV9.mjs +92 -0
- package/fesm2022/flusys-ng-iam-iam-container.component-UYJjqYV9.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-permission-page.component-DcgT7L3_.mjs +137 -0
- package/fesm2022/flusys-ng-iam-permission-page.component-DcgT7L3_.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-role-form-page.component-Ctigzpon.mjs → flusys-ng-iam-role-form-page.component-D_AAEay2.mjs} +109 -151
- package/fesm2022/flusys-ng-iam-role-form-page.component-D_AAEay2.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs +299 -0
- package/fesm2022/flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam.mjs +1 -1
- package/package.json +10 -10
- package/types/flusys-ng-iam.d.ts +75 -455
- package/fesm2022/flusys-ng-iam-action-form-page.component-DBJzC5GS.mjs +0 -467
- package/fesm2022/flusys-ng-iam-action-form-page.component-DBJzC5GS.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-action-list-page.component-Dfts0JCt.mjs +0 -281
- package/fesm2022/flusys-ng-iam-action-list-page.component-Dfts0JCt.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-BjdM-Vgz.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-cDrwUAQ_.mjs +0 -143
- package/fesm2022/flusys-ng-iam-permission-page.component-cDrwUAQ_.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-form-page.component-Ctigzpon.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-list-page.component-BF-Z_TQK.mjs +0 -266
- package/fesm2022/flusys-ng-iam-role-list-page.component-BF-Z_TQK.mjs.map +0 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { HttpClient } from '@angular/common/http';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, signal, input, output,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { APP_CONFIG, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
|
|
6
|
-
import * as i2$1 from 'primeng/api';
|
|
3
|
+
import { inject, Injectable, signal, input, output, effect, ChangeDetectionStrategy, Component, DestroyRef, computed } from '@angular/core';
|
|
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, permissionGuard, ACTION_PERMISSIONS, ROLE_PERMISSIONS, anyPermissionGuard } from '@flusys/ng-shared';
|
|
5
|
+
import { APP_CONFIG, getServiceUrl, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
|
|
7
6
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
|
8
7
|
import { of, firstValueFrom, map as map$1 } from 'rxjs';
|
|
9
8
|
import { tap, catchError, map } from 'rxjs/operators';
|
|
@@ -12,20 +11,15 @@ import { CommonModule } from '@angular/common';
|
|
|
12
11
|
import * as i1$1 from '@angular/forms';
|
|
13
12
|
import { FormsModule } from '@angular/forms';
|
|
14
13
|
import * as i2 from 'primeng/button';
|
|
15
|
-
import
|
|
16
|
-
import * as
|
|
17
|
-
import * as i4 from 'primeng/
|
|
18
|
-
import * as i5 from 'primeng/
|
|
19
|
-
import * as
|
|
20
|
-
import * as i8 from 'primeng/treetable';
|
|
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
|
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Action Type - determines how action is used
|
|
27
|
-
* CRITICAL: Must match backend ActionType enum exactly
|
|
28
|
-
*/
|
|
22
|
+
/** Must match backend ActionType enum */
|
|
29
23
|
var ActionType;
|
|
30
24
|
(function (ActionType) {
|
|
31
25
|
ActionType["BACKEND"] = "backend";
|
|
@@ -35,130 +29,66 @@ var ActionType;
|
|
|
35
29
|
|
|
36
30
|
// Company interfaces
|
|
37
31
|
|
|
38
|
-
/**
|
|
39
|
-
* Pagination Constants
|
|
40
|
-
*
|
|
41
|
-
* Standard pagination limits for IAM components.
|
|
42
|
-
* Prevents excessive data loading and potential DoS.
|
|
43
|
-
*/
|
|
44
|
-
/**
|
|
45
|
-
* Maximum items to fetch for dropdown lists
|
|
46
|
-
* Used for: companies, roles, users, branches
|
|
47
|
-
*
|
|
48
|
-
* Security: Prevents memory exhaustion from loading excessive records
|
|
49
|
-
*/
|
|
32
|
+
/** Maximum items for dropdown lists (companies, roles, users, branches) */
|
|
50
33
|
const MAX_DROPDOWN_ITEMS = 100;
|
|
51
34
|
|
|
52
|
-
/**
|
|
53
|
-
* Role API Service
|
|
54
|
-
* Handles role CRUD operations
|
|
55
|
-
* Endpoint: POST /iam/roles/*
|
|
56
|
-
* Conditional: Only available in RBAC/FULL mode
|
|
57
|
-
*/
|
|
58
35
|
class RoleApiService extends ApiResourceService {
|
|
59
36
|
constructor() {
|
|
60
|
-
|
|
61
|
-
super('iam/roles', http);
|
|
37
|
+
super('roles', inject(HttpClient), 'iam');
|
|
62
38
|
}
|
|
63
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
64
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
39
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
40
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleApiService, providedIn: 'root' });
|
|
65
41
|
}
|
|
66
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
42
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleApiService, decorators: [{
|
|
67
43
|
type: Injectable,
|
|
68
|
-
args: [{
|
|
69
|
-
providedIn: 'root',
|
|
70
|
-
}]
|
|
44
|
+
args: [{ providedIn: 'root' }]
|
|
71
45
|
}], ctorParameters: () => [] });
|
|
72
46
|
|
|
73
|
-
/**
|
|
74
|
-
* Action API Service
|
|
75
|
-
* Handles action CRUD operations
|
|
76
|
-
* Endpoint: POST /iam/actions/*
|
|
77
|
-
*/
|
|
78
47
|
class ActionApiService extends ApiResourceService {
|
|
79
48
|
appConfig = inject(APP_CONFIG);
|
|
80
49
|
constructor() {
|
|
81
|
-
|
|
82
|
-
super('iam/actions', http);
|
|
50
|
+
super('actions', inject(HttpClient), 'iam');
|
|
83
51
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Get actions for permission assignment
|
|
86
|
-
* GET /iam/actions/tree-for-permission
|
|
87
|
-
* Returns actions filtered by company whitelist if enabled
|
|
88
|
-
*/
|
|
52
|
+
/** Get actions filtered by company whitelist for permission assignment */
|
|
89
53
|
getActionsForPermission() {
|
|
90
|
-
|
|
54
|
+
const iamBaseUrl = getServiceUrl(this.appConfig, 'iam');
|
|
55
|
+
return this.http.post(`${iamBaseUrl}/actions/tree-for-permission`, {});
|
|
91
56
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Get actions in hierarchical tree structure
|
|
94
|
-
* POST /iam/actions/tree
|
|
95
|
-
* Returns all actions organized in parent-child tree
|
|
96
|
-
*
|
|
97
|
-
* @param search - Optional search term (name or code)
|
|
98
|
-
* @param isActive - Optional filter by active status
|
|
99
|
-
* @param withDeleted - Include deleted actions (default: false)
|
|
100
|
-
* @returns Observable of action tree response
|
|
101
|
-
*/
|
|
57
|
+
/** Get actions in hierarchical tree structure */
|
|
102
58
|
getTree(search, isActive, withDeleted) {
|
|
103
59
|
const body = {};
|
|
104
|
-
if (search?.trim())
|
|
60
|
+
if (search?.trim())
|
|
105
61
|
body.search = search.trim();
|
|
106
|
-
|
|
107
|
-
if (isActive !== undefined) {
|
|
62
|
+
if (isActive !== undefined)
|
|
108
63
|
body.isActive = isActive;
|
|
109
|
-
|
|
110
|
-
if (withDeleted !== undefined) {
|
|
64
|
+
if (withDeleted !== undefined)
|
|
111
65
|
body.withDeleted = withDeleted;
|
|
112
|
-
|
|
113
|
-
return this.http.post(`${
|
|
66
|
+
const iamBaseUrl = getServiceUrl(this.appConfig, 'iam');
|
|
67
|
+
return this.http.post(`${iamBaseUrl}/actions/tree`, body);
|
|
114
68
|
}
|
|
115
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
116
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
69
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
70
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionApiService, providedIn: 'root' });
|
|
117
71
|
}
|
|
118
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
72
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionApiService, decorators: [{
|
|
119
73
|
type: Injectable,
|
|
120
|
-
args: [{
|
|
121
|
-
providedIn: 'root',
|
|
122
|
-
}]
|
|
74
|
+
args: [{ providedIn: 'root' }]
|
|
123
75
|
}], ctorParameters: () => [] });
|
|
124
76
|
|
|
125
|
-
/**
|
|
126
|
-
* Extract all required action IDs from a permission logic tree
|
|
127
|
-
* This is useful for prerequisite validation
|
|
128
|
-
*
|
|
129
|
-
* @param logic - Permission logic to analyze
|
|
130
|
-
* @returns Set of action IDs that are required
|
|
131
|
-
*/
|
|
132
77
|
function extractRequiredActionIds(logic) {
|
|
133
78
|
const actionIds = new Set();
|
|
134
|
-
if (
|
|
135
|
-
|
|
79
|
+
if (logic) {
|
|
80
|
+
collectActionIds(logic, actionIds);
|
|
136
81
|
}
|
|
137
|
-
collectActionIds(logic, actionIds);
|
|
138
82
|
return actionIds;
|
|
139
83
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Recursively collect action IDs from logic tree
|
|
142
|
-
*/
|
|
143
84
|
function collectActionIds(node, actionIds) {
|
|
144
85
|
if (node.type === 'action' && node.actionId) {
|
|
145
86
|
actionIds.add(node.actionId);
|
|
146
87
|
}
|
|
147
88
|
else if (node.type === 'group' && node.children) {
|
|
148
|
-
|
|
149
|
-
collectActionIds(child, actionIds);
|
|
150
|
-
}
|
|
89
|
+
node.children.forEach((child) => collectActionIds(child, actionIds));
|
|
151
90
|
}
|
|
152
91
|
}
|
|
153
|
-
/**
|
|
154
|
-
* Validate if an action's prerequisites are satisfied
|
|
155
|
-
* Respects AND/OR logic operators in permission tree
|
|
156
|
-
*
|
|
157
|
-
* @param action - Action to validate
|
|
158
|
-
* @param selectedActionIds - Set of currently selected action IDs
|
|
159
|
-
* @param allActions - All available actions (to look up missing ones)
|
|
160
|
-
* @returns Validation result with missing actions
|
|
161
|
-
*/
|
|
162
92
|
function validateActionPrerequisites(action, selectedActionIds, allActions) {
|
|
163
93
|
if (!action.permissionLogic) {
|
|
164
94
|
return { valid: true, missingActions: [] };
|
|
@@ -170,20 +100,12 @@ function validateActionPrerequisites(action, selectedActionIds, allActions) {
|
|
|
170
100
|
const missingActions = allActions.filter((a) => a.id && result.missingActionIds.has(a.id));
|
|
171
101
|
return { valid: false, missingActions };
|
|
172
102
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Recursively evaluate logic node respecting AND/OR operators
|
|
175
|
-
* Returns validation result with missing action IDs (for prerequisite dialogs)
|
|
176
|
-
* Note: This differs from evaluateLogicNode in ng-shared which returns boolean only
|
|
177
|
-
*/
|
|
178
103
|
function evaluateLogicNodeWithMissing(node, selectedActionIds) {
|
|
179
104
|
if (node.type === 'action' && node.actionId) {
|
|
180
105
|
const valid = selectedActionIds.has(node.actionId);
|
|
181
|
-
return {
|
|
182
|
-
valid,
|
|
183
|
-
missingActionIds: valid ? new Set() : new Set([node.actionId]),
|
|
184
|
-
};
|
|
106
|
+
return { valid, missingActionIds: valid ? new Set() : new Set([node.actionId]) };
|
|
185
107
|
}
|
|
186
|
-
if (node.type === 'group' && node.children
|
|
108
|
+
if (node.type === 'group' && node.children?.length) {
|
|
187
109
|
const operator = node.operator || 'AND';
|
|
188
110
|
if (operator === 'AND') {
|
|
189
111
|
const missingIds = new Set();
|
|
@@ -197,109 +119,28 @@ function evaluateLogicNodeWithMissing(node, selectedActionIds) {
|
|
|
197
119
|
}
|
|
198
120
|
return { valid: allValid, missingActionIds: missingIds };
|
|
199
121
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
122
|
+
// OR operator
|
|
123
|
+
for (const child of node.children) {
|
|
124
|
+
const childResult = evaluateLogicNodeWithMissing(child, selectedActionIds);
|
|
125
|
+
if (childResult.valid) {
|
|
126
|
+
return { valid: true, missingActionIds: new Set() };
|
|
206
127
|
}
|
|
207
|
-
const firstChildResult = evaluateLogicNodeWithMissing(node.children[0], selectedActionIds);
|
|
208
|
-
return {
|
|
209
|
-
valid: false,
|
|
210
|
-
missingActionIds: firstChildResult.missingActionIds,
|
|
211
|
-
};
|
|
212
128
|
}
|
|
129
|
+
const firstChildResult = evaluateLogicNodeWithMissing(node.children[0], selectedActionIds);
|
|
130
|
+
return { valid: false, missingActionIds: firstChildResult.missingActionIds };
|
|
213
131
|
}
|
|
214
132
|
return { valid: true, missingActionIds: new Set() };
|
|
215
133
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Get human-readable prerequisite description
|
|
218
|
-
*
|
|
219
|
-
* @param logic - Permission logic to describe
|
|
220
|
-
* @param allActions - All available actions for name lookup
|
|
221
|
-
* @returns Human-readable string describing prerequisites
|
|
222
|
-
*/
|
|
223
|
-
function describePrerequisites(logic, allActions) {
|
|
224
|
-
if (!logic) {
|
|
225
|
-
return 'None';
|
|
226
|
-
}
|
|
227
|
-
const requiredIds = extractRequiredActionIds(logic);
|
|
228
|
-
if (requiredIds.size === 0) {
|
|
229
|
-
return 'None';
|
|
230
|
-
}
|
|
231
|
-
const actionMap = new Map(allActions.map((a) => [a.id, a]));
|
|
232
|
-
const names = Array.from(requiredIds)
|
|
233
|
-
.map((id) => actionMap.get(id)?.name || id)
|
|
234
|
-
.filter(Boolean);
|
|
235
|
-
if (names.length === 0) {
|
|
236
|
-
return 'Unknown prerequisites';
|
|
237
|
-
}
|
|
238
|
-
if (names.length === 1) {
|
|
239
|
-
return names[0];
|
|
240
|
-
}
|
|
241
|
-
return names.join(', ');
|
|
242
|
-
}
|
|
243
134
|
|
|
244
|
-
/**
|
|
245
|
-
* Action Permission Logic Service
|
|
246
|
-
*
|
|
247
|
-
* Shared service for handling smart dependency management across all action selectors:
|
|
248
|
-
* - Company-Action Selector
|
|
249
|
-
* - Role-Action Selector
|
|
250
|
-
* - User-Action Selector
|
|
251
|
-
*
|
|
252
|
-
* **Core Features:**
|
|
253
|
-
* - Smart auto-selection (AND/OR optimization)
|
|
254
|
-
* - Dependency detection and management
|
|
255
|
-
* - Alternative suggestion for OR logic
|
|
256
|
-
* - Visual formatting of permission logic trees
|
|
257
|
-
* - Prerequisite validation
|
|
258
|
-
*
|
|
259
|
-
* @example
|
|
260
|
-
* constructor() {
|
|
261
|
-
* this.permissionLogic = inject(ActionPermissionLogicService);
|
|
262
|
-
* }
|
|
263
|
-
*
|
|
264
|
-
* onActionToggle(action: IAction, newValue: boolean) {
|
|
265
|
-
* if (!newValue) {
|
|
266
|
-
* this.permissionLogic.handleUncheck(
|
|
267
|
-
* action,
|
|
268
|
-
* this.selectionMap(),
|
|
269
|
-
* this.actions(),
|
|
270
|
-
* (newMap) => this.selectionMap.set(newMap)
|
|
271
|
-
* );
|
|
272
|
-
* } else {
|
|
273
|
-
* this.permissionLogic.handleCheck(
|
|
274
|
-
* action,
|
|
275
|
-
* this.selectionMap(),
|
|
276
|
-
* this.actions(),
|
|
277
|
-
* (newMap) => this.selectionMap.set(newMap),
|
|
278
|
-
* (previousState) => this.selectionMap.set(previousState)
|
|
279
|
-
* );
|
|
280
|
-
* }
|
|
281
|
-
* }
|
|
282
|
-
*/
|
|
135
|
+
/** Shared service for smart dependency management across action selectors */
|
|
283
136
|
class ActionPermissionLogicService {
|
|
284
137
|
confirmationService = inject(ConfirmationService);
|
|
285
138
|
messageService = inject(MessageService);
|
|
286
|
-
/**
|
|
287
|
-
* Handle checking an action with prerequisite validation
|
|
288
|
-
*
|
|
289
|
-
* Uses recursive deep scan to find ALL missing prerequisites at all levels,
|
|
290
|
-
* not just direct dependencies. This ensures cascading dependencies are
|
|
291
|
-
* resolved in a single step.
|
|
292
|
-
*
|
|
293
|
-
* @param action - Action being checked
|
|
294
|
-
* @param currentSelection - Current selection map
|
|
295
|
-
* @param allActions - All available actions
|
|
296
|
-
* @param onUpdate - Callback to update selection
|
|
297
|
-
* @param onCancel - Callback when user cancels
|
|
298
|
-
*/
|
|
139
|
+
/** Handle checking an action with prerequisite validation (recursive deep scan) */
|
|
299
140
|
handleCheck(action, currentSelection, allActions, onUpdate, onCancel) {
|
|
300
141
|
// Validate prerequisites with RECURSIVE DEEP SCAN
|
|
301
142
|
if (action.permissionLogic) {
|
|
302
|
-
const selectedActionIds =
|
|
143
|
+
const selectedActionIds = this.getSelectedIds(currentSelection);
|
|
303
144
|
const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
|
|
304
145
|
if (!validationResult.valid) {
|
|
305
146
|
// Store previous state
|
|
@@ -320,14 +161,7 @@ class ActionPermissionLogicService {
|
|
|
320
161
|
selMap[action.id] = true;
|
|
321
162
|
onUpdate(selMap);
|
|
322
163
|
}
|
|
323
|
-
/**
|
|
324
|
-
* Handle unchecking an action with dependency detection
|
|
325
|
-
*
|
|
326
|
-
* @param action - Action being unchecked
|
|
327
|
-
* @param currentSelection - Current selection map
|
|
328
|
-
* @param allActions - All available actions
|
|
329
|
-
* @param onUpdate - Callback to update selection
|
|
330
|
-
*/
|
|
164
|
+
/** Handle unchecking an action with dependency detection */
|
|
331
165
|
handleUncheck(action, currentSelection, allActions, onUpdate) {
|
|
332
166
|
const affectedActions = this.findActionsDependingOn(action.id, currentSelection, allActions);
|
|
333
167
|
if (affectedActions.length > 0) {
|
|
@@ -339,46 +173,28 @@ class ActionPermissionLogicService {
|
|
|
339
173
|
selMap[action.id] = false;
|
|
340
174
|
onUpdate(selMap);
|
|
341
175
|
}
|
|
342
|
-
/**
|
|
343
|
-
* Check if an action has unmet prerequisites
|
|
344
|
-
*
|
|
345
|
-
* @param action - Action to check
|
|
346
|
-
* @param currentSelection - Current selection map
|
|
347
|
-
* @param allActions - All available actions
|
|
348
|
-
* @returns True if action has unmet prerequisites
|
|
349
|
-
*/
|
|
176
|
+
/** Check if an action has unmet prerequisites */
|
|
350
177
|
hasUnmetPrerequisites(action, currentSelection, allActions) {
|
|
351
178
|
const isSelected = currentSelection[action.id];
|
|
352
179
|
if (!isSelected || !action.permissionLogic) {
|
|
353
180
|
return false;
|
|
354
181
|
}
|
|
355
|
-
const selectedActionIds =
|
|
182
|
+
const selectedActionIds = this.getSelectedIds(currentSelection);
|
|
183
|
+
selectedActionIds.delete(action.id);
|
|
356
184
|
const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
|
|
357
185
|
return !validationResult.valid;
|
|
358
186
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Get all selected actions that have unmet prerequisites
|
|
361
|
-
*
|
|
362
|
-
* @param currentSelection - Current selection map
|
|
363
|
-
* @param allActions - All available actions
|
|
364
|
-
* @returns Array of actions with unmet prerequisites
|
|
365
|
-
*/
|
|
187
|
+
/** Get all selected actions that have unmet prerequisites */
|
|
366
188
|
getActionsWithUnmetPrerequisites(currentSelection, allActions) {
|
|
367
189
|
const selectedActions = allActions.filter((a) => currentSelection[a.id]);
|
|
368
190
|
return selectedActions.filter((action) => this.hasUnmetPrerequisites(action, currentSelection, allActions));
|
|
369
191
|
}
|
|
370
|
-
/**
|
|
371
|
-
* Show validation error dialog with auto-fix options
|
|
372
|
-
*
|
|
373
|
-
* @param invalidActions - Actions with unmet prerequisites
|
|
374
|
-
* @param currentSelection - Current selection map
|
|
375
|
-
* @param allActions - All available actions
|
|
376
|
-
* @param onUpdate - Callback to update selection
|
|
377
|
-
*/
|
|
192
|
+
/** Show validation error dialog with auto-fix options */
|
|
378
193
|
showValidationErrorDialog(invalidActions, currentSelection, allActions, onUpdate) {
|
|
379
194
|
const errorList = invalidActions
|
|
380
195
|
.map((action) => {
|
|
381
|
-
const selectedActionIds =
|
|
196
|
+
const selectedActionIds = this.getSelectedIds(currentSelection);
|
|
197
|
+
selectedActionIds.delete(action.id);
|
|
382
198
|
const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
|
|
383
199
|
const sanitizedActionName = this.sanitizeHtml(action.name ?? 'Unknown');
|
|
384
200
|
const missingNames = validationResult.missingActions
|
|
@@ -420,19 +236,12 @@ class ActionPermissionLogicService {
|
|
|
420
236
|
},
|
|
421
237
|
});
|
|
422
238
|
}
|
|
423
|
-
/**
|
|
424
|
-
* Get prerequisite description for tooltip display
|
|
425
|
-
*
|
|
426
|
-
* @param action - Action to get prerequisites for
|
|
427
|
-
* @param currentSelection - Current selection map
|
|
428
|
-
* @param allActions - All available actions
|
|
429
|
-
* @returns Plain text prerequisite description
|
|
430
|
-
*/
|
|
239
|
+
/** Get prerequisite description for tooltip display */
|
|
431
240
|
getPrerequisiteTooltip(action, currentSelection, allActions) {
|
|
432
241
|
if (!action.permissionLogic) {
|
|
433
242
|
return '';
|
|
434
243
|
}
|
|
435
|
-
const selectedActionIds =
|
|
244
|
+
const selectedActionIds = this.getSelectedIds(currentSelection);
|
|
436
245
|
const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
|
|
437
246
|
if (validationResult.valid) {
|
|
438
247
|
return '[OK] Prerequisites Satisfied\n\nAll required actions are already selected.\nYou can safely add this action.';
|
|
@@ -444,23 +253,16 @@ class ActionPermissionLogicService {
|
|
|
444
253
|
const hint = '\n\n💡 Click to auto-select required actions';
|
|
445
254
|
return `${header}\n\n${logicTree}${hint}`;
|
|
446
255
|
}
|
|
447
|
-
/**
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
* @param logic - Permission logic tree
|
|
451
|
-
* @param missingActions - Actions that are missing
|
|
452
|
-
* @param allActions - All available actions
|
|
453
|
-
* @param currentSelection - Current selection map for accurate status
|
|
454
|
-
* @returns HTML formatted logic tree
|
|
455
|
-
*/
|
|
456
|
-
buildLogicMessage(logic, missingActions, allActions, currentSelection) {
|
|
457
|
-
const selectedIds = currentSelection
|
|
458
|
-
? new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]))
|
|
459
|
-
: new Set();
|
|
256
|
+
/** Build dynamic logic tree message with AND/OR operators and nesting */
|
|
257
|
+
buildLogicMessage(logic, _missingActions, allActions, currentSelection) {
|
|
258
|
+
const selectedIds = currentSelection ? this.getSelectedIds(currentSelection) : new Set();
|
|
460
259
|
const actionMap = new Map(allActions.map((a) => [a.id, a]));
|
|
461
260
|
return this.formatLogicNode(logic, selectedIds, actionMap, 0);
|
|
462
261
|
}
|
|
463
|
-
|
|
262
|
+
/** Extract selected action IDs from selection map */
|
|
263
|
+
getSelectedIds(currentSelection) {
|
|
264
|
+
return new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
|
|
265
|
+
}
|
|
464
266
|
sanitizeHtml(text) {
|
|
465
267
|
return text
|
|
466
268
|
.replace(/&/g, '&')
|
|
@@ -469,24 +271,7 @@ class ActionPermissionLogicService {
|
|
|
469
271
|
.replace(/"/g, '"')
|
|
470
272
|
.replace(/'/g, ''');
|
|
471
273
|
}
|
|
472
|
-
/**
|
|
473
|
-
* Recursively collect ALL missing prerequisites at all dependency levels
|
|
474
|
-
*
|
|
475
|
-
* This prevents cascading prerequisite dialogs by finding the complete
|
|
476
|
-
* dependency chain upfront.
|
|
477
|
-
*
|
|
478
|
-
* **Example:**
|
|
479
|
-
* - Action 4 requires Action 3
|
|
480
|
-
* - Action 3 requires Action 2
|
|
481
|
-
* - Action 2 requires Action 1
|
|
482
|
-
*
|
|
483
|
-
* Instead of showing 3 separate dialogs, this returns: [Action 3, Action 2, Action 1]
|
|
484
|
-
*
|
|
485
|
-
* @param action - Starting action to check
|
|
486
|
-
* @param currentSelection - Current selection map
|
|
487
|
-
* @param allActions - All available actions
|
|
488
|
-
* @returns Complete set of missing prerequisites across all levels
|
|
489
|
-
*/
|
|
274
|
+
/** Recursively collect ALL missing prerequisites at all dependency levels */
|
|
490
275
|
getAllMissingPrerequisitesRecursive(action, currentSelection, allActions) {
|
|
491
276
|
if (!action.id)
|
|
492
277
|
return [];
|
|
@@ -506,7 +291,7 @@ class ActionPermissionLogicService {
|
|
|
506
291
|
}
|
|
507
292
|
visited.add(targetAction.id);
|
|
508
293
|
stack.add(targetAction.id);
|
|
509
|
-
const selectedActionIds =
|
|
294
|
+
const selectedActionIds = this.getSelectedIds(currentSelection);
|
|
510
295
|
const result = validateActionPrerequisites(targetAction, selectedActionIds, allActions);
|
|
511
296
|
if (!result.valid) {
|
|
512
297
|
result.missingActions.forEach((missingAction) => {
|
|
@@ -567,7 +352,7 @@ class ActionPermissionLogicService {
|
|
|
567
352
|
},
|
|
568
353
|
});
|
|
569
354
|
}
|
|
570
|
-
showDependencyDialog(action, affectedActions, currentSelection,
|
|
355
|
+
showDependencyDialog(action, affectedActions, currentSelection, _allActions, onUpdate) {
|
|
571
356
|
if (!action.id)
|
|
572
357
|
return;
|
|
573
358
|
const alternatives = this.findAlternatives(action.id, affectedActions, currentSelection);
|
|
@@ -690,7 +475,7 @@ class ActionPermissionLogicService {
|
|
|
690
475
|
calculateSmartSelection(logic, missingActions, currentSelection, allActions) {
|
|
691
476
|
const missingIds = new Set(missingActions.map((a) => a.id));
|
|
692
477
|
const actionMap = new Map(allActions.map((a) => [a.id, a]));
|
|
693
|
-
const selectedIds =
|
|
478
|
+
const selectedIds = this.getSelectedIds(currentSelection);
|
|
694
479
|
const requiredIds = this.findRequiredActionIds(logic, missingIds, selectedIds);
|
|
695
480
|
return Array.from(requiredIds)
|
|
696
481
|
.map((id) => actionMap.get(id))
|
|
@@ -763,12 +548,10 @@ class ActionPermissionLogicService {
|
|
|
763
548
|
}
|
|
764
549
|
return `${indent}<em style="color: #9ca3af;">Invalid logic node</em>`;
|
|
765
550
|
}
|
|
766
|
-
/**
|
|
767
|
-
* Build clean text-based logic tree for tooltips
|
|
768
|
-
*/
|
|
551
|
+
/** Build clean text-based logic tree for tooltips */
|
|
769
552
|
buildTooltipLogicTree(node, currentSelection, allActions, depth) {
|
|
770
553
|
const indent = ' '.repeat(depth);
|
|
771
|
-
const selectedIds =
|
|
554
|
+
const selectedIds = this.getSelectedIds(currentSelection);
|
|
772
555
|
const actionMap = new Map(allActions.map((a) => [a.id, a]));
|
|
773
556
|
if (node.type === 'action' && node.actionId) {
|
|
774
557
|
const action = actionMap.get(node.actionId);
|
|
@@ -798,115 +581,48 @@ class ActionPermissionLogicService {
|
|
|
798
581
|
}
|
|
799
582
|
return `${indent}(invalid)`;
|
|
800
583
|
}
|
|
801
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
802
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
584
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionPermissionLogicService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
585
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionPermissionLogicService, providedIn: 'root' });
|
|
803
586
|
}
|
|
804
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
587
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionPermissionLogicService, decorators: [{
|
|
805
588
|
type: Injectable,
|
|
806
|
-
args: [{
|
|
807
|
-
providedIn: 'root',
|
|
808
|
-
}]
|
|
589
|
+
args: [{ providedIn: 'root' }]
|
|
809
590
|
}] });
|
|
810
591
|
|
|
811
|
-
/**
|
|
812
|
-
* Consolidated Permission API Service
|
|
813
|
-
* Handles all permission-related operations in one service
|
|
814
|
-
* Supports:
|
|
815
|
-
* - User → Action (direct permissions)
|
|
816
|
-
* - User → Role (role assignments)
|
|
817
|
-
* - Role → Action (role permissions)
|
|
818
|
-
* - Company → Action (company whitelisting)
|
|
819
|
-
*
|
|
820
|
-
* Endpoint: POST /permissions/*
|
|
821
|
-
*/
|
|
822
592
|
class PermissionApiService extends BaseApiService {
|
|
823
593
|
constructor() {
|
|
824
594
|
super('iam');
|
|
825
595
|
}
|
|
826
|
-
// =============================================================================
|
|
827
|
-
// User → Action (Direct Permissions)
|
|
828
|
-
// =============================================================================
|
|
829
|
-
/**
|
|
830
|
-
* Assign/remove actions directly to/from user
|
|
831
|
-
* POST /permissions/user-actions/assign
|
|
832
|
-
*/
|
|
833
596
|
assignUserActions(data) {
|
|
834
597
|
return this.http.post(this.getUrl('permissions/user-actions/assign'), data);
|
|
835
598
|
}
|
|
836
|
-
/**
|
|
837
|
-
* Get user's direct action permissions
|
|
838
|
-
* GET /permissions/user-actions/:userId
|
|
839
|
-
*/
|
|
840
599
|
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 });
|
|
600
|
+
return this.http.post(this.getUrl('permissions/get-user-actions'), { userId, branchId: query?.branchId });
|
|
846
601
|
}
|
|
847
|
-
// =============================================================================
|
|
848
|
-
// User → Role (Role Assignments)
|
|
849
|
-
// =============================================================================
|
|
850
|
-
/**
|
|
851
|
-
* Assign/remove roles to/from user
|
|
852
|
-
* POST /permissions/user-roles/assign
|
|
853
|
-
*/
|
|
854
602
|
assignUserRoles(data) {
|
|
855
603
|
return this.http.post(this.getUrl('permissions/user-roles/assign'), data);
|
|
856
604
|
}
|
|
857
|
-
/**
|
|
858
|
-
* Get user's role assignments
|
|
859
|
-
* GET /permissions/user-roles/:userId
|
|
860
|
-
*/
|
|
861
605
|
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 });
|
|
606
|
+
return this.http.post(this.getUrl('permissions/get-user-roles'), { userId, branchId: query?.branchId });
|
|
867
607
|
}
|
|
868
|
-
// =============================================================================
|
|
869
|
-
// Role → Action (Role Permissions)
|
|
870
|
-
// =============================================================================
|
|
871
|
-
/**
|
|
872
|
-
* Assign/remove actions to/from role
|
|
873
|
-
* POST /permissions/role-actions/assign
|
|
874
|
-
*/
|
|
875
608
|
assignRoleActions(data) {
|
|
876
609
|
return this.http.post(this.getUrl('permissions/role-actions/assign'), data);
|
|
877
610
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
* GET /permissions/role-actions/:roleId
|
|
881
|
-
*/
|
|
882
|
-
getRoleActions(roleId, query) {
|
|
883
|
-
return this.http.get(this.getUrl(`permissions/role-actions/${roleId}`));
|
|
611
|
+
getRoleActions(roleId) {
|
|
612
|
+
return this.http.post(this.getUrl('permissions/get-role-actions'), { roleId });
|
|
884
613
|
}
|
|
885
|
-
// =============================================================================
|
|
886
|
-
// Company → Action (Company Whitelisting)
|
|
887
|
-
// =============================================================================
|
|
888
|
-
/**
|
|
889
|
-
* Assign/remove actions to/from company (whitelisting)
|
|
890
|
-
* POST /permissions/company-actions/assign
|
|
891
|
-
*/
|
|
892
614
|
assignCompanyActions(data) {
|
|
893
615
|
return this.http.post(this.getUrl('permissions/company-actions/assign'), data);
|
|
894
616
|
}
|
|
895
|
-
/**
|
|
896
|
-
* Get company's whitelisted actions
|
|
897
|
-
* GET /permissions/company-actions/:companyId
|
|
898
|
-
*/
|
|
899
617
|
getCompanyActions(companyId) {
|
|
900
|
-
return this.http.
|
|
618
|
+
return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
|
|
901
619
|
}
|
|
902
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
903
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
620
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
621
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionApiService, providedIn: 'root' });
|
|
904
622
|
}
|
|
905
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
623
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionApiService, decorators: [{
|
|
906
624
|
type: Injectable,
|
|
907
|
-
args: [{
|
|
908
|
-
providedIn: 'root',
|
|
909
|
-
}]
|
|
625
|
+
args: [{ providedIn: 'root' }]
|
|
910
626
|
}], ctorParameters: () => [] });
|
|
911
627
|
|
|
912
628
|
/**
|
|
@@ -918,10 +634,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
918
634
|
class MyPermissionsApiService {
|
|
919
635
|
http = inject(HttpClient);
|
|
920
636
|
appConfig = inject(APP_CONFIG);
|
|
921
|
-
baseUrl
|
|
922
|
-
constructor() {
|
|
923
|
-
this.baseUrl = `${this.appConfig.apiBaseUrl}/iam/permissions`;
|
|
924
|
-
}
|
|
637
|
+
baseUrl = `${getServiceUrl(this.appConfig, 'iam')}/permissions`;
|
|
925
638
|
/**
|
|
926
639
|
* Get current user's complete permissions
|
|
927
640
|
* POST /iam/permissions/my-permissions
|
|
@@ -931,57 +644,28 @@ class MyPermissionsApiService {
|
|
|
931
644
|
getMyPermissions(dto = {}) {
|
|
932
645
|
return this.http.post(`${this.baseUrl}/my-permissions`, dto);
|
|
933
646
|
}
|
|
934
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
935
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
647
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: MyPermissionsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
648
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: MyPermissionsApiService, providedIn: 'root' });
|
|
936
649
|
}
|
|
937
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
650
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: MyPermissionsApiService, decorators: [{
|
|
938
651
|
type: Injectable,
|
|
939
652
|
args: [{
|
|
940
653
|
providedIn: 'root',
|
|
941
654
|
}]
|
|
942
|
-
}]
|
|
655
|
+
}] });
|
|
943
656
|
|
|
944
|
-
/**
|
|
945
|
-
* Permission State Service
|
|
946
|
-
* Manages user permissions state and provides permission checking methods
|
|
947
|
-
*
|
|
948
|
-
* Uses shared PermissionValidatorService for centralized permission checking.
|
|
949
|
-
*
|
|
950
|
-
* @example
|
|
951
|
-
* ```typescript
|
|
952
|
-
* // In component
|
|
953
|
-
* readonly permissionState = inject(PermissionStateService);
|
|
954
|
-
*
|
|
955
|
-
* ngOnInit() {
|
|
956
|
-
* this.permissionState.loadPermissions();
|
|
957
|
-
* }
|
|
958
|
-
*
|
|
959
|
-
* // Check permission
|
|
960
|
-
* if (this.permissionState.hasAction('user.create')) {
|
|
961
|
-
* // Show create button
|
|
962
|
-
* }
|
|
963
|
-
* ```
|
|
964
|
-
*/
|
|
965
657
|
class PermissionStateService {
|
|
966
658
|
permissionApi = inject(MyPermissionsApiService);
|
|
967
659
|
permissionValidator = inject(PermissionValidatorService);
|
|
968
|
-
// Permission state
|
|
969
660
|
_permissions = signal(null, ...(ngDevMode ? [{ debugName: "_permissions" }] : []));
|
|
970
661
|
permissions = this._permissions.asReadonly();
|
|
971
|
-
// Loading state
|
|
972
662
|
_isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
|
|
973
663
|
isLoading = this._isLoading.asReadonly();
|
|
974
|
-
/**
|
|
975
|
-
* Load current user's permissions from API
|
|
976
|
-
* Call this on app initialization or after login
|
|
977
|
-
* Returns Observable for reactive composition
|
|
978
|
-
*/
|
|
979
664
|
loadPermissions(dto) {
|
|
980
665
|
this._isLoading.set(true);
|
|
981
666
|
return this.permissionApi.getMyPermissions(dto).pipe(tap((response) => {
|
|
982
667
|
this._permissions.set(response.data ?? null);
|
|
983
668
|
this._isLoading.set(false);
|
|
984
|
-
// Update shared permission validator
|
|
985
669
|
const actionCodes = response.data?.frontendActions.map((a) => a.code) ?? [];
|
|
986
670
|
this.permissionValidator.setPermissions(actionCodes);
|
|
987
671
|
}), catchError(() => {
|
|
@@ -991,19 +675,13 @@ class PermissionStateService {
|
|
|
991
675
|
return of(void 0);
|
|
992
676
|
}), map(() => void 0));
|
|
993
677
|
}
|
|
994
|
-
/**
|
|
995
|
-
* Check if permissions are loaded
|
|
996
|
-
*
|
|
997
|
-
* @returns true if permissions are loaded
|
|
998
|
-
*/
|
|
999
678
|
isLoaded() {
|
|
1000
|
-
return
|
|
1001
|
-
this.permissionValidator.isPermissionsLoaded());
|
|
679
|
+
return this._permissions() !== null && this.permissionValidator.isPermissionsLoaded();
|
|
1002
680
|
}
|
|
1003
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1004
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
682
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionStateService, providedIn: 'root' });
|
|
1005
683
|
}
|
|
1006
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionStateService, decorators: [{
|
|
1007
685
|
type: Injectable,
|
|
1008
686
|
args: [{ providedIn: 'root' }]
|
|
1009
687
|
}] });
|
|
@@ -1022,120 +700,108 @@ function toLogicNode(node) {
|
|
|
1022
700
|
}
|
|
1023
701
|
function toBuilderNode(node) {
|
|
1024
702
|
if (node.type === 'action') {
|
|
1025
|
-
return
|
|
703
|
+
return createActionNode(node.actionId);
|
|
1026
704
|
}
|
|
1027
705
|
return {
|
|
1028
|
-
|
|
1029
|
-
type: 'group',
|
|
1030
|
-
operator: node.operator,
|
|
706
|
+
...createGroupNode(node.operator),
|
|
1031
707
|
children: node.children?.map(toBuilderNode) ?? [],
|
|
1032
708
|
};
|
|
1033
709
|
}
|
|
710
|
+
function createGroupNode(operator = 'AND') {
|
|
711
|
+
return { id: crypto.randomUUID(), type: 'group', operator, children: [] };
|
|
712
|
+
}
|
|
713
|
+
function createActionNode(actionId = '') {
|
|
714
|
+
return { id: crypto.randomUUID(), type: 'action', actionId };
|
|
715
|
+
}
|
|
1034
716
|
/** Visual builder for AND/OR permission logic trees */
|
|
1035
717
|
class LogicBuilderComponent {
|
|
1036
718
|
logic = input(null, ...(ngDevMode ? [{ debugName: "logic" }] : []));
|
|
1037
719
|
actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
|
|
1038
720
|
logicChange = output();
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
builderLogic =
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
721
|
+
/** Internal builder tree state (private writable + public readonly pattern) */
|
|
722
|
+
_builderTree = signal(null, ...(ngDevMode ? [{ debugName: "_builderTree" }] : []));
|
|
723
|
+
builderLogic = this._builderTree.asReadonly();
|
|
724
|
+
constructor() {
|
|
725
|
+
effect(() => {
|
|
726
|
+
const logic = this.logic();
|
|
727
|
+
if (!logic) {
|
|
728
|
+
this._builderTree.set(null);
|
|
729
|
+
}
|
|
730
|
+
else if (!this._builderTree()) {
|
|
731
|
+
this._builderTree.set(toBuilderNode(logic));
|
|
732
|
+
}
|
|
733
|
+
}, { allowSignalWrites: true });
|
|
734
|
+
}
|
|
1052
735
|
initializeLogic() {
|
|
1053
|
-
this.
|
|
1054
|
-
id: crypto.randomUUID(),
|
|
1055
|
-
type: 'group',
|
|
1056
|
-
operator: 'AND',
|
|
1057
|
-
children: [],
|
|
1058
|
-
};
|
|
1059
|
-
this.emitChange();
|
|
736
|
+
this.updateTreeAndEmit(createGroupNode());
|
|
1060
737
|
}
|
|
1061
738
|
clearLogic() {
|
|
1062
|
-
this.
|
|
739
|
+
this._builderTree.set(null);
|
|
1063
740
|
this.logicChange.emit(null);
|
|
1064
741
|
}
|
|
1065
742
|
toggleOperator(nodeId) {
|
|
1066
|
-
|
|
1067
|
-
return;
|
|
1068
|
-
this.builderTree = this.updateNodeInTree(this.builderTree, nodeId, (node) => ({
|
|
743
|
+
this.updateNode(nodeId, (node) => ({
|
|
1069
744
|
...node,
|
|
1070
745
|
operator: node.operator === 'AND' ? 'OR' : 'AND',
|
|
1071
746
|
}));
|
|
1072
|
-
this.emitChange();
|
|
1073
747
|
}
|
|
1074
748
|
addChildNode(parentId, type) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const newNode = type === 'group'
|
|
1078
|
-
? { id: crypto.randomUUID(), type: 'group', operator: 'AND', children: [] }
|
|
1079
|
-
: { id: crypto.randomUUID(), type: 'action', actionId: '' };
|
|
1080
|
-
this.builderTree = this.updateNodeInTree(this.builderTree, parentId, (node) => ({
|
|
749
|
+
const newNode = type === 'group' ? createGroupNode() : createActionNode();
|
|
750
|
+
this.updateNode(parentId, (node) => ({
|
|
1081
751
|
...node,
|
|
1082
752
|
children: [...(node.children || []), newNode],
|
|
1083
753
|
}));
|
|
1084
|
-
this.emitChange();
|
|
1085
754
|
}
|
|
1086
755
|
removeNode(nodeId) {
|
|
1087
|
-
|
|
756
|
+
const tree = this._builderTree();
|
|
757
|
+
if (!tree)
|
|
1088
758
|
return;
|
|
1089
|
-
if (
|
|
759
|
+
if (tree.id === nodeId) {
|
|
1090
760
|
this.clearLogic();
|
|
1091
761
|
return;
|
|
1092
762
|
}
|
|
1093
|
-
this.
|
|
1094
|
-
this.emitChange();
|
|
763
|
+
this.updateTreeAndEmit(this.removeNodeFromTree(tree, nodeId));
|
|
1095
764
|
}
|
|
1096
765
|
updateActionId(nodeId, actionId) {
|
|
1097
|
-
|
|
1098
|
-
return;
|
|
1099
|
-
this.builderTree = this.updateNodeInTree(this.builderTree, nodeId, (node) => ({
|
|
1100
|
-
...node,
|
|
1101
|
-
actionId,
|
|
1102
|
-
}));
|
|
1103
|
-
this.emitChange();
|
|
766
|
+
this.updateNode(nodeId, (node) => ({ ...node, actionId }));
|
|
1104
767
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
768
|
+
/** Updates a node in the tree and emits the change */
|
|
769
|
+
updateNode(nodeId, updater) {
|
|
770
|
+
const tree = this._builderTree();
|
|
771
|
+
if (!tree)
|
|
1108
772
|
return;
|
|
1109
|
-
|
|
1110
|
-
|
|
773
|
+
this.updateTreeAndEmit(this.updateNodeInTree(tree, nodeId, updater));
|
|
774
|
+
}
|
|
775
|
+
/** Sets the tree and emits the change */
|
|
776
|
+
updateTreeAndEmit(tree) {
|
|
777
|
+
this._builderTree.set(tree);
|
|
778
|
+
this.logicChange.emit(toLogicNode(tree));
|
|
1111
779
|
}
|
|
1112
780
|
updateNodeInTree(node, targetId, updater) {
|
|
1113
781
|
if (node.id === targetId)
|
|
1114
782
|
return updater(node);
|
|
1115
|
-
if (node.children)
|
|
1116
|
-
return
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
}
|
|
1121
|
-
return node;
|
|
783
|
+
if (!node.children)
|
|
784
|
+
return node;
|
|
785
|
+
return {
|
|
786
|
+
...node,
|
|
787
|
+
children: node.children.map((child) => this.updateNodeInTree(child, targetId, updater)),
|
|
788
|
+
};
|
|
1122
789
|
}
|
|
1123
790
|
removeNodeFromTree(node, targetId) {
|
|
1124
|
-
if (node.children)
|
|
1125
|
-
return
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
return node;
|
|
791
|
+
if (!node.children)
|
|
792
|
+
return node;
|
|
793
|
+
return {
|
|
794
|
+
...node,
|
|
795
|
+
children: node.children
|
|
796
|
+
.filter((child) => child.id !== targetId)
|
|
797
|
+
.map((child) => this.removeNodeFromTree(child, targetId)),
|
|
798
|
+
};
|
|
1133
799
|
}
|
|
1134
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1135
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
800
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LogicBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
801
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", 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
802
|
<div class="logic-builder">
|
|
1137
803
|
<div class="flex justify-between items-center mb-3">
|
|
1138
|
-
<h4 class="text-sm font-semibold">Permission Logic</h4>
|
|
804
|
+
<h4 class="text-sm font-semibold m-0">Permission Logic</h4>
|
|
1139
805
|
@if (!builderLogic()) {
|
|
1140
806
|
<p-button
|
|
1141
807
|
label="Add Logic"
|
|
@@ -1154,13 +820,13 @@ class LogicBuilderComponent {
|
|
|
1154
820
|
</div>
|
|
1155
821
|
|
|
1156
822
|
@if (builderLogic()) {
|
|
1157
|
-
<div class="border rounded p-3 bg-
|
|
1158
|
-
<div class="mb-3 text-sm text-
|
|
823
|
+
<div class="border border-surface rounded p-3 bg-surface-50">
|
|
824
|
+
<div class="mb-3 text-sm text-muted-color">
|
|
1159
825
|
Define permission requirements using AND/OR logic with actions
|
|
1160
826
|
</div>
|
|
1161
827
|
|
|
1162
828
|
<!-- Root Node -->
|
|
1163
|
-
<div class="
|
|
829
|
+
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1164
830
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
|
|
1165
831
|
</div>
|
|
1166
832
|
</div>
|
|
@@ -1169,11 +835,11 @@ class LogicBuilderComponent {
|
|
|
1169
835
|
|
|
1170
836
|
<!-- Recursive Node Template -->
|
|
1171
837
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1172
|
-
<div class="
|
|
1173
|
-
<div class="
|
|
838
|
+
<div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
|
|
839
|
+
<div class="flex items-center gap-3 mb-2">
|
|
1174
840
|
<span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
|
|
1175
841
|
|
|
1176
|
-
<div class="
|
|
842
|
+
<div class="flex-1 flex items-center gap-2">
|
|
1177
843
|
@if (node.type === 'group') {
|
|
1178
844
|
<!-- Group: show operator toggle -->
|
|
1179
845
|
<span
|
|
@@ -1183,24 +849,24 @@ class LogicBuilderComponent {
|
|
|
1183
849
|
title="Click to toggle">
|
|
1184
850
|
{{ node.operator }}
|
|
1185
851
|
</span>
|
|
1186
|
-
<span class="text-
|
|
852
|
+
<span class="text-muted-color text-xs">({{ node.children?.length || 0 }} conditions)</span>
|
|
1187
853
|
}
|
|
1188
854
|
|
|
1189
855
|
@if (node.type === 'action') {
|
|
1190
856
|
<!-- Action: show action selector -->
|
|
1191
857
|
<select
|
|
1192
|
-
class="flex-1"
|
|
858
|
+
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1193
859
|
[ngModel]="node.actionId"
|
|
1194
860
|
(ngModelChange)="updateActionId(node.id, $event)">
|
|
1195
|
-
<option [value]="null">Select Action... ({{
|
|
1196
|
-
@for (action of
|
|
861
|
+
<option [value]="null">Select Action... ({{ actions().length }} available)</option>
|
|
862
|
+
@for (action of actions(); track action.id) {
|
|
1197
863
|
<option [ngValue]="action.id">{{ action.name }}</option>
|
|
1198
864
|
}
|
|
1199
865
|
</select>
|
|
1200
866
|
}
|
|
1201
867
|
</div>
|
|
1202
868
|
|
|
1203
|
-
<div class="
|
|
869
|
+
<div class="flex gap-1">
|
|
1204
870
|
<p-button
|
|
1205
871
|
icon="pi pi-trash"
|
|
1206
872
|
[text]="true"
|
|
@@ -1213,7 +879,7 @@ class LogicBuilderComponent {
|
|
|
1213
879
|
|
|
1214
880
|
<!-- Children for group nodes -->
|
|
1215
881
|
@if (node.type === 'group' && node.children && node.children.length > 0) {
|
|
1216
|
-
<div class="
|
|
882
|
+
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1217
883
|
@for (child of node.children; track child.id) {
|
|
1218
884
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
1219
885
|
}
|
|
@@ -1222,9 +888,9 @@ class LogicBuilderComponent {
|
|
|
1222
888
|
|
|
1223
889
|
<!-- Add child buttons for group nodes -->
|
|
1224
890
|
@if (node.type === 'group') {
|
|
1225
|
-
<div class="
|
|
1226
|
-
<div class="text-xs font-semibold text-
|
|
1227
|
-
<div class="
|
|
891
|
+
<div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
|
|
892
|
+
<div class="text-xs font-semibold text-muted-color mb-2">Add Condition:</div>
|
|
893
|
+
<div class="flex gap-2 flex-wrap">
|
|
1228
894
|
<p-button
|
|
1229
895
|
label="Group"
|
|
1230
896
|
icon="pi pi-sitemap"
|
|
@@ -1243,14 +909,14 @@ class LogicBuilderComponent {
|
|
|
1243
909
|
}
|
|
1244
910
|
</div>
|
|
1245
911
|
</ng-template>
|
|
1246
|
-
`, isInline: true, styles: [":host{display:block}.
|
|
912
|
+
`, 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
913
|
}
|
|
1248
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
914
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LogicBuilderComponent, decorators: [{
|
|
1249
915
|
type: Component,
|
|
1250
|
-
args: [{ selector: 'lib-logic-builder',
|
|
916
|
+
args: [{ selector: 'lib-logic-builder', imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1251
917
|
<div class="logic-builder">
|
|
1252
918
|
<div class="flex justify-between items-center mb-3">
|
|
1253
|
-
<h4 class="text-sm font-semibold">Permission Logic</h4>
|
|
919
|
+
<h4 class="text-sm font-semibold m-0">Permission Logic</h4>
|
|
1254
920
|
@if (!builderLogic()) {
|
|
1255
921
|
<p-button
|
|
1256
922
|
label="Add Logic"
|
|
@@ -1269,13 +935,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1269
935
|
</div>
|
|
1270
936
|
|
|
1271
937
|
@if (builderLogic()) {
|
|
1272
|
-
<div class="border rounded p-3 bg-
|
|
1273
|
-
<div class="mb-3 text-sm text-
|
|
938
|
+
<div class="border border-surface rounded p-3 bg-surface-50">
|
|
939
|
+
<div class="mb-3 text-sm text-muted-color">
|
|
1274
940
|
Define permission requirements using AND/OR logic with actions
|
|
1275
941
|
</div>
|
|
1276
942
|
|
|
1277
943
|
<!-- Root Node -->
|
|
1278
|
-
<div class="
|
|
944
|
+
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1279
945
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
|
|
1280
946
|
</div>
|
|
1281
947
|
</div>
|
|
@@ -1284,11 +950,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1284
950
|
|
|
1285
951
|
<!-- Recursive Node Template -->
|
|
1286
952
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1287
|
-
<div class="
|
|
1288
|
-
<div class="
|
|
953
|
+
<div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
|
|
954
|
+
<div class="flex items-center gap-3 mb-2">
|
|
1289
955
|
<span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
|
|
1290
956
|
|
|
1291
|
-
<div class="
|
|
957
|
+
<div class="flex-1 flex items-center gap-2">
|
|
1292
958
|
@if (node.type === 'group') {
|
|
1293
959
|
<!-- Group: show operator toggle -->
|
|
1294
960
|
<span
|
|
@@ -1298,24 +964,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1298
964
|
title="Click to toggle">
|
|
1299
965
|
{{ node.operator }}
|
|
1300
966
|
</span>
|
|
1301
|
-
<span class="text-
|
|
967
|
+
<span class="text-muted-color text-xs">({{ node.children?.length || 0 }} conditions)</span>
|
|
1302
968
|
}
|
|
1303
969
|
|
|
1304
970
|
@if (node.type === 'action') {
|
|
1305
971
|
<!-- Action: show action selector -->
|
|
1306
972
|
<select
|
|
1307
|
-
class="flex-1"
|
|
973
|
+
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1308
974
|
[ngModel]="node.actionId"
|
|
1309
975
|
(ngModelChange)="updateActionId(node.id, $event)">
|
|
1310
|
-
<option [value]="null">Select Action... ({{
|
|
1311
|
-
@for (action of
|
|
976
|
+
<option [value]="null">Select Action... ({{ actions().length }} available)</option>
|
|
977
|
+
@for (action of actions(); track action.id) {
|
|
1312
978
|
<option [ngValue]="action.id">{{ action.name }}</option>
|
|
1313
979
|
}
|
|
1314
980
|
</select>
|
|
1315
981
|
}
|
|
1316
982
|
</div>
|
|
1317
983
|
|
|
1318
|
-
<div class="
|
|
984
|
+
<div class="flex gap-1">
|
|
1319
985
|
<p-button
|
|
1320
986
|
icon="pi pi-trash"
|
|
1321
987
|
[text]="true"
|
|
@@ -1328,7 +994,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1328
994
|
|
|
1329
995
|
<!-- Children for group nodes -->
|
|
1330
996
|
@if (node.type === 'group' && node.children && node.children.length > 0) {
|
|
1331
|
-
<div class="
|
|
997
|
+
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1332
998
|
@for (child of node.children; track child.id) {
|
|
1333
999
|
<ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
1334
1000
|
}
|
|
@@ -1337,9 +1003,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1337
1003
|
|
|
1338
1004
|
<!-- Add child buttons for group nodes -->
|
|
1339
1005
|
@if (node.type === 'group') {
|
|
1340
|
-
<div class="
|
|
1341
|
-
<div class="text-xs font-semibold text-
|
|
1342
|
-
<div class="
|
|
1006
|
+
<div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
|
|
1007
|
+
<div class="text-xs font-semibold text-muted-color mb-2">Add Condition:</div>
|
|
1008
|
+
<div class="flex gap-2 flex-wrap">
|
|
1343
1009
|
<p-button
|
|
1344
1010
|
label="Group"
|
|
1345
1011
|
icon="pi pi-sitemap"
|
|
@@ -1358,105 +1024,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1358
1024
|
}
|
|
1359
1025
|
</div>
|
|
1360
1026
|
</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"] }] } });
|
|
1027
|
+
`, 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"] }]
|
|
1028
|
+
}], 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
1029
|
|
|
1364
|
-
/**
|
|
1365
|
-
* Tree Utility Functions
|
|
1366
|
-
* Shared utilities for working with hierarchical tree structures
|
|
1367
|
-
*/
|
|
1368
|
-
/**
|
|
1369
|
-
* Flattens a hierarchical tree structure into a flat array
|
|
1370
|
-
*
|
|
1371
|
-
* @template T - Type of tree node (must have optional children array)
|
|
1372
|
-
* @param tree - Array of tree nodes to flatten
|
|
1373
|
-
* @returns Flat array containing all nodes from the tree
|
|
1374
|
-
*
|
|
1375
|
-
* @example
|
|
1376
|
-
* ```typescript
|
|
1377
|
-
* const tree = [
|
|
1378
|
-
* { id: '1', name: 'Parent', children: [
|
|
1379
|
-
* { id: '2', name: 'Child 1' },
|
|
1380
|
-
* { id: '3', name: 'Child 2' }
|
|
1381
|
-
* ]}
|
|
1382
|
-
* ];
|
|
1383
|
-
* const flat = flattenTree(tree);
|
|
1384
|
-
* // Returns: [{ id: '1', ... }, { id: '2', ... }, { id: '3', ... }]
|
|
1385
|
-
* ```
|
|
1386
|
-
*/
|
|
1387
1030
|
function flattenTree(tree) {
|
|
1388
|
-
if (!tree?.length)
|
|
1031
|
+
if (!tree?.length)
|
|
1389
1032
|
return [];
|
|
1390
|
-
}
|
|
1391
1033
|
const result = [];
|
|
1392
1034
|
const flatten = (nodes) => {
|
|
1393
1035
|
for (const node of nodes) {
|
|
1394
1036
|
result.push(node);
|
|
1395
|
-
if (node.children?.length)
|
|
1037
|
+
if (node.children?.length)
|
|
1396
1038
|
flatten(node.children);
|
|
1397
|
-
}
|
|
1398
1039
|
}
|
|
1399
1040
|
};
|
|
1400
1041
|
flatten(tree);
|
|
1401
1042
|
return result;
|
|
1402
1043
|
}
|
|
1403
|
-
/**
|
|
1404
|
-
* Builds a map of items by their ID for quick lookup
|
|
1405
|
-
*
|
|
1406
|
-
* @template T - Type of item (must have id property)
|
|
1407
|
-
* @param items - Array of items to map
|
|
1408
|
-
* @returns Map with item IDs as keys and items as values
|
|
1409
|
-
*
|
|
1410
|
-
* @example
|
|
1411
|
-
* ```typescript
|
|
1412
|
-
* const actions = [{ id: '1', name: 'Action 1' }, { id: '2', name: 'Action 2' }];
|
|
1413
|
-
* const map = buildItemMap(actions);
|
|
1414
|
-
* const action = map.get('1'); // { id: '1', name: 'Action 1' }
|
|
1415
|
-
* ```
|
|
1416
|
-
*/
|
|
1417
|
-
function buildItemMap(items) {
|
|
1418
|
-
if (!items?.length) {
|
|
1419
|
-
return new Map();
|
|
1420
|
-
}
|
|
1421
|
-
return new Map(items
|
|
1422
|
-
.filter((item) => item?.id !== undefined && item?.id !== null)
|
|
1423
|
-
.map((item) => [String(item.id), item]));
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Build tree structure from flat list using parentId
|
|
1427
|
-
*
|
|
1428
|
-
* Converts flat array with parentId references into hierarchical tree structure.
|
|
1429
|
-
* Used when backend returns flat list but tree structure is needed for display.
|
|
1430
|
-
*
|
|
1431
|
-
* @template T - Type of node (must have id and optional parentId)
|
|
1432
|
-
* @param flatList - Flat array of nodes with parentId references
|
|
1433
|
-
* @returns Hierarchical tree with children arrays
|
|
1434
|
-
*
|
|
1435
|
-
* @example
|
|
1436
|
-
* ```typescript
|
|
1437
|
-
* const flat = [
|
|
1438
|
-
* { id: '1', name: 'Parent', parentId: null },
|
|
1439
|
-
* { id: '2', name: 'Child 1', parentId: '1' },
|
|
1440
|
-
* { id: '3', name: 'Child 2', parentId: '1' }
|
|
1441
|
-
* ];
|
|
1442
|
-
* const tree = buildTreeFromFlat(flat);
|
|
1443
|
-
* // Returns: [{ id: '1', name: 'Parent', children: [child1, child2] }]
|
|
1444
|
-
* ```
|
|
1445
|
-
*/
|
|
1446
1044
|
function buildTreeFromFlat(flatList) {
|
|
1447
|
-
if (!flatList?.length)
|
|
1045
|
+
if (!flatList?.length)
|
|
1448
1046
|
return [];
|
|
1449
|
-
}
|
|
1450
|
-
// Create map for O(1) lookup
|
|
1451
1047
|
const itemMap = new Map();
|
|
1452
1048
|
const rootItems = [];
|
|
1453
|
-
// First pass: Create all nodes with empty children arrays
|
|
1454
1049
|
flatList.forEach((item) => {
|
|
1455
|
-
if (item.id)
|
|
1050
|
+
if (item.id)
|
|
1456
1051
|
itemMap.set(item.id, { ...item, children: [] });
|
|
1457
|
-
}
|
|
1458
1052
|
});
|
|
1459
|
-
// Second pass: Build parent-child relationships
|
|
1460
1053
|
flatList.forEach((item) => {
|
|
1461
1054
|
if (!item.id)
|
|
1462
1055
|
return;
|
|
@@ -1464,51 +1057,23 @@ function buildTreeFromFlat(flatList) {
|
|
|
1464
1057
|
if (!node)
|
|
1465
1058
|
return;
|
|
1466
1059
|
if (!item.parentId) {
|
|
1467
|
-
// Root level item
|
|
1468
1060
|
rootItems.push(node);
|
|
1469
1061
|
}
|
|
1470
1062
|
else {
|
|
1471
|
-
// Child item - add to parent's children
|
|
1472
1063
|
const parent = itemMap.get(item.parentId);
|
|
1473
1064
|
if (parent) {
|
|
1474
1065
|
parent.children.push(node);
|
|
1475
1066
|
}
|
|
1476
1067
|
else {
|
|
1477
|
-
// Parent not found - treat as root
|
|
1478
1068
|
rootItems.push(node);
|
|
1479
1069
|
}
|
|
1480
1070
|
}
|
|
1481
1071
|
});
|
|
1482
1072
|
return rootItems;
|
|
1483
1073
|
}
|
|
1484
|
-
/**
|
|
1485
|
-
* Convert ActionTreeDto to PrimeNG TreeNode format
|
|
1486
|
-
*
|
|
1487
|
-
* Transforms hierarchical action data from backend into PrimeNG TreeTable format.
|
|
1488
|
-
* Recursively processes children and sets leaf/expanded properties.
|
|
1489
|
-
*
|
|
1490
|
-
* @param actions - Array of action tree DTOs from backend
|
|
1491
|
-
* @returns Array of TreeNodes for PrimeNG TreeTable
|
|
1492
|
-
*
|
|
1493
|
-
* @example
|
|
1494
|
-
* ```typescript
|
|
1495
|
-
* const actionsTree = [
|
|
1496
|
-
* {
|
|
1497
|
-
* id: '1',
|
|
1498
|
-
* name: 'User Management',
|
|
1499
|
-
* children: [
|
|
1500
|
-
* { id: '2', name: 'Create User', children: [] }
|
|
1501
|
-
* ]
|
|
1502
|
-
* }
|
|
1503
|
-
* ];
|
|
1504
|
-
* const treeNodes = convertActionToTreeNode(actionsTree);
|
|
1505
|
-
* // Use with: <p-treeTable [value]="treeNodes">
|
|
1506
|
-
* ```
|
|
1507
|
-
*/
|
|
1508
1074
|
function convertActionToTreeNode(actions) {
|
|
1509
|
-
if (!actions?.length)
|
|
1075
|
+
if (!actions?.length)
|
|
1510
1076
|
return [];
|
|
1511
|
-
}
|
|
1512
1077
|
const convert = (action) => {
|
|
1513
1078
|
const { children, ...actionData } = action;
|
|
1514
1079
|
return {
|
|
@@ -1556,7 +1121,10 @@ function convertActionToTreeNode(actions) {
|
|
|
1556
1121
|
* ```
|
|
1557
1122
|
*/
|
|
1558
1123
|
class RoleActionSelectorComponent {
|
|
1124
|
+
// Permission constants for template
|
|
1125
|
+
ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
|
|
1559
1126
|
// Dependencies
|
|
1127
|
+
destroyRef = inject(DestroyRef);
|
|
1560
1128
|
roleApi = inject(RoleApiService);
|
|
1561
1129
|
actionApi = inject(ActionApiService);
|
|
1562
1130
|
permissionApi = inject(PermissionApiService);
|
|
@@ -1605,17 +1173,14 @@ class RoleActionSelectorComponent {
|
|
|
1605
1173
|
const selMap = this.selectionMap();
|
|
1606
1174
|
const allActions = this.actions();
|
|
1607
1175
|
const unmetSet = new Set();
|
|
1608
|
-
|
|
1609
|
-
if (action.id &&
|
|
1610
|
-
this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
|
|
1176
|
+
for (const action of allActions) {
|
|
1177
|
+
if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
|
|
1611
1178
|
unmetSet.add(action.id);
|
|
1612
1179
|
}
|
|
1613
|
-
}
|
|
1180
|
+
}
|
|
1614
1181
|
return unmetSet;
|
|
1615
1182
|
}, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
|
|
1616
|
-
invalidActionsCount = computed(() => {
|
|
1617
|
-
return this.actionsWithUnmetPrerequisites().size;
|
|
1618
|
-
}, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
1183
|
+
invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
1619
1184
|
// Computed - Pending Changes
|
|
1620
1185
|
pendingAdd = computed(() => {
|
|
1621
1186
|
const current = this.selectionMap();
|
|
@@ -1637,6 +1202,10 @@ class RoleActionSelectorComponent {
|
|
|
1637
1202
|
loadDataAbortController = null;
|
|
1638
1203
|
constructor() {
|
|
1639
1204
|
this.loadRoles();
|
|
1205
|
+
// Cleanup on destroy
|
|
1206
|
+
this.destroyRef.onDestroy(() => {
|
|
1207
|
+
this.loadDataAbortController?.abort();
|
|
1208
|
+
});
|
|
1640
1209
|
// Effect: Load data when role selection changes
|
|
1641
1210
|
effect(() => {
|
|
1642
1211
|
const roleId = this.selectedRoleId();
|
|
@@ -1646,11 +1215,7 @@ class RoleActionSelectorComponent {
|
|
|
1646
1215
|
this.loadDataAbortController = new AbortController();
|
|
1647
1216
|
this.onRoleChange(this.loadDataAbortController.signal).catch((err) => {
|
|
1648
1217
|
if (err.name !== 'AbortError') {
|
|
1649
|
-
|
|
1650
|
-
severity: 'error',
|
|
1651
|
-
summary: 'Error',
|
|
1652
|
-
detail: 'Failed to load role permissions. Please refresh.',
|
|
1653
|
-
});
|
|
1218
|
+
// Error toast handled by global interceptor
|
|
1654
1219
|
this.loading.set(false);
|
|
1655
1220
|
}
|
|
1656
1221
|
});
|
|
@@ -1660,9 +1225,6 @@ class RoleActionSelectorComponent {
|
|
|
1660
1225
|
}
|
|
1661
1226
|
});
|
|
1662
1227
|
}
|
|
1663
|
-
ngOnDestroy() {
|
|
1664
|
-
this.loadDataAbortController?.abort();
|
|
1665
|
-
}
|
|
1666
1228
|
/**
|
|
1667
1229
|
* Load roles from API
|
|
1668
1230
|
*/
|
|
@@ -1674,11 +1236,7 @@ class RoleActionSelectorComponent {
|
|
|
1674
1236
|
this.roles.set(response?.data ?? []);
|
|
1675
1237
|
}
|
|
1676
1238
|
catch {
|
|
1677
|
-
|
|
1678
|
-
severity: 'error',
|
|
1679
|
-
summary: 'Error',
|
|
1680
|
-
detail: 'Failed to load roles',
|
|
1681
|
-
});
|
|
1239
|
+
// Error toast handled by global interceptor
|
|
1682
1240
|
}
|
|
1683
1241
|
}
|
|
1684
1242
|
/**
|
|
@@ -1707,13 +1265,12 @@ class RoleActionSelectorComponent {
|
|
|
1707
1265
|
const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
|
|
1708
1266
|
// Build selection map
|
|
1709
1267
|
const selMap = {};
|
|
1710
|
-
|
|
1268
|
+
for (const action of flatActions) {
|
|
1711
1269
|
if (action.id) {
|
|
1712
1270
|
selMap[action.id] = assignedIds.has(action.id);
|
|
1713
1271
|
}
|
|
1714
|
-
}
|
|
1715
|
-
this.
|
|
1716
|
-
this._initialSelection.set({ ...selMap });
|
|
1272
|
+
}
|
|
1273
|
+
this.applySelection(selMap);
|
|
1717
1274
|
}
|
|
1718
1275
|
finally {
|
|
1719
1276
|
if (!signal.aborted) {
|
|
@@ -1762,35 +1319,20 @@ class RoleActionSelectorComponent {
|
|
|
1762
1319
|
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
1763
1320
|
}
|
|
1764
1321
|
}
|
|
1765
|
-
/**
|
|
1766
|
-
* Toggle all actions
|
|
1767
|
-
*/
|
|
1768
1322
|
toggleAll() {
|
|
1769
|
-
|
|
1770
|
-
const selMap = {};
|
|
1771
|
-
this.actions().forEach((action) => {
|
|
1772
|
-
selMap[action.id] = newValue;
|
|
1773
|
-
});
|
|
1774
|
-
this._selectionMap.set(selMap);
|
|
1323
|
+
this.setAllSelection(!this.allSelected());
|
|
1775
1324
|
}
|
|
1776
|
-
/**
|
|
1777
|
-
* Select all actions
|
|
1778
|
-
*/
|
|
1779
1325
|
selectAll() {
|
|
1780
|
-
|
|
1781
|
-
this.actions().forEach((action) => {
|
|
1782
|
-
selMap[action.id] = true;
|
|
1783
|
-
});
|
|
1784
|
-
this._selectionMap.set(selMap);
|
|
1326
|
+
this.setAllSelection(true);
|
|
1785
1327
|
}
|
|
1786
|
-
/**
|
|
1787
|
-
* Deselect all actions
|
|
1788
|
-
*/
|
|
1789
1328
|
deselectAll() {
|
|
1329
|
+
this.setAllSelection(false);
|
|
1330
|
+
}
|
|
1331
|
+
setAllSelection(value) {
|
|
1790
1332
|
const selMap = {};
|
|
1791
|
-
this.actions()
|
|
1792
|
-
selMap[action.id] =
|
|
1793
|
-
}
|
|
1333
|
+
for (const action of this.actions()) {
|
|
1334
|
+
selMap[action.id] = value;
|
|
1335
|
+
}
|
|
1794
1336
|
this._selectionMap.set(selMap);
|
|
1795
1337
|
}
|
|
1796
1338
|
/**
|
|
@@ -1807,19 +1349,7 @@ class RoleActionSelectorComponent {
|
|
|
1807
1349
|
return;
|
|
1808
1350
|
}
|
|
1809
1351
|
// Build payload
|
|
1810
|
-
const items =
|
|
1811
|
-
this.pendingAdd().forEach((action) => {
|
|
1812
|
-
items.push({
|
|
1813
|
-
id: action.id,
|
|
1814
|
-
action: 'add',
|
|
1815
|
-
});
|
|
1816
|
-
});
|
|
1817
|
-
this.pendingRemove().forEach((action) => {
|
|
1818
|
-
items.push({
|
|
1819
|
-
id: action.id,
|
|
1820
|
-
action: 'remove',
|
|
1821
|
-
});
|
|
1822
|
-
});
|
|
1352
|
+
const items = this.buildPayloadItems();
|
|
1823
1353
|
if (items.length === 0)
|
|
1824
1354
|
return;
|
|
1825
1355
|
this.saving.set(true);
|
|
@@ -1836,37 +1366,44 @@ class RoleActionSelectorComponent {
|
|
|
1836
1366
|
// Update baseline
|
|
1837
1367
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
1838
1368
|
}
|
|
1839
|
-
catch
|
|
1840
|
-
|
|
1841
|
-
this.messageService.add({
|
|
1842
|
-
severity: 'error',
|
|
1843
|
-
summary: 'Error',
|
|
1844
|
-
detail: error?.error?.message || 'Failed to update role permissions',
|
|
1845
|
-
});
|
|
1369
|
+
catch {
|
|
1370
|
+
// Error toast handled by global interceptor
|
|
1846
1371
|
}
|
|
1847
1372
|
finally {
|
|
1848
1373
|
this.saving.set(false);
|
|
1849
1374
|
}
|
|
1850
1375
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1376
|
+
applySelection(selMap) {
|
|
1377
|
+
this._selectionMap.set(selMap);
|
|
1378
|
+
this._initialSelection.set({ ...selMap });
|
|
1379
|
+
}
|
|
1380
|
+
buildPayloadItems() {
|
|
1381
|
+
const items = [];
|
|
1382
|
+
for (const action of this.pendingAdd()) {
|
|
1383
|
+
items.push({ id: action.id, action: 'add' });
|
|
1384
|
+
}
|
|
1385
|
+
for (const action of this.pendingRemove()) {
|
|
1386
|
+
items.push({ id: action.id, action: 'remove' });
|
|
1387
|
+
}
|
|
1388
|
+
return items;
|
|
1389
|
+
}
|
|
1854
1390
|
resetState() {
|
|
1855
1391
|
this._actionsTree.set([]);
|
|
1856
1392
|
this._actions.set([]);
|
|
1857
1393
|
this._selectionMap.set({});
|
|
1858
1394
|
this._initialSelection.set({});
|
|
1859
1395
|
}
|
|
1860
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1861
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
1396
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1397
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", ngImport: i0, template: `
|
|
1862
1398
|
<div class="role-action-selector">
|
|
1863
1399
|
<!-- Role Selector -->
|
|
1864
1400
|
<div class="mb-4">
|
|
1865
|
-
<div class="grid">
|
|
1866
|
-
<div
|
|
1401
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
1402
|
+
<div>
|
|
1867
1403
|
<label class="block font-semibold mb-2">Select Role</label>
|
|
1868
1404
|
<p-select
|
|
1869
|
-
[
|
|
1405
|
+
[ngModel]="selectedRoleId()"
|
|
1406
|
+
(ngModelChange)="selectedRoleId.set($event)"
|
|
1870
1407
|
[options]="roles()"
|
|
1871
1408
|
optionLabel="name"
|
|
1872
1409
|
optionValue="id"
|
|
@@ -1883,24 +1420,21 @@ class RoleActionSelectorComponent {
|
|
|
1883
1420
|
<!-- Loading State -->
|
|
1884
1421
|
@if (loading()) {
|
|
1885
1422
|
<div
|
|
1886
|
-
class="surface-card p-5 border
|
|
1423
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
1887
1424
|
>
|
|
1888
|
-
<i
|
|
1889
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
1890
|
-
style="font-size: 3rem"
|
|
1891
|
-
></i>
|
|
1425
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
1892
1426
|
</div>
|
|
1893
1427
|
}
|
|
1894
1428
|
|
|
1895
1429
|
<!-- Action List -->
|
|
1896
1430
|
@if (!loading() && actions().length > 0) {
|
|
1897
|
-
<div class="surface-card p-4 border
|
|
1431
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
1898
1432
|
<div
|
|
1899
|
-
class="flex flex-
|
|
1433
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
1900
1434
|
>
|
|
1901
1435
|
<div>
|
|
1902
1436
|
<h5 class="m-0 mb-1">Action Permissions</h5>
|
|
1903
|
-
<p class="text-sm text-color
|
|
1437
|
+
<p class="text-sm text-muted-color m-0">
|
|
1904
1438
|
{{ actions().length }} actions available
|
|
1905
1439
|
</p>
|
|
1906
1440
|
</div>
|
|
@@ -1922,6 +1456,7 @@ class RoleActionSelectorComponent {
|
|
|
1922
1456
|
(onClick)="deselectAll()"
|
|
1923
1457
|
/>
|
|
1924
1458
|
<p-button
|
|
1459
|
+
*hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
|
|
1925
1460
|
label="Save Changes"
|
|
1926
1461
|
icon="pi pi-save"
|
|
1927
1462
|
[disabled]="!canSave()"
|
|
@@ -1935,14 +1470,12 @@ class RoleActionSelectorComponent {
|
|
|
1935
1470
|
|
|
1936
1471
|
<!-- Validation Warning -->
|
|
1937
1472
|
@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">
|
|
1473
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
1474
|
+
<div class="flex items-start gap-2">
|
|
1475
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
1476
|
+
<div class="flex-1">
|
|
1477
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
1478
|
+
<p class="text-sm mt-1 mb-0">
|
|
1946
1479
|
{{ invalidActionsCount() }} selected
|
|
1947
1480
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
1948
1481
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -1954,33 +1487,33 @@ class RoleActionSelectorComponent {
|
|
|
1954
1487
|
}
|
|
1955
1488
|
|
|
1956
1489
|
<!-- 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
|
|
1490
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
1491
|
+
<p-treeTable
|
|
1492
|
+
[value]="treeNodes()"
|
|
1493
|
+
dataKey="id"
|
|
1494
|
+
styleClass="p-treetable-sm"
|
|
1495
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
1496
|
+
>
|
|
1497
|
+
<ng-template #header>
|
|
1498
|
+
<tr>
|
|
1499
|
+
<th class="w-12">
|
|
1500
|
+
<p-checkbox
|
|
1501
|
+
[ngModel]="allSelected()"
|
|
1502
|
+
[binary]="true"
|
|
1503
|
+
(ngModelChange)="toggleAll()"
|
|
1504
|
+
pTooltip="Select/Deselect All"
|
|
1505
|
+
tooltipPosition="top"
|
|
1506
|
+
/>
|
|
1507
|
+
</th>
|
|
1508
|
+
<th>Name</th>
|
|
1509
|
+
<th class="hidden md:table-cell">Code</th>
|
|
1510
|
+
<th>Type</th>
|
|
1511
|
+
<th class="hidden lg:table-cell">Requirements</th>
|
|
1512
|
+
</tr>
|
|
1513
|
+
</ng-template>
|
|
1514
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
1515
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
1516
|
+
<td class="w-12">
|
|
1984
1517
|
<p-checkbox
|
|
1985
1518
|
[ngModel]="selectionMap()[rowData.id]"
|
|
1986
1519
|
[binary]="true"
|
|
@@ -1991,7 +1524,7 @@ class RoleActionSelectorComponent {
|
|
|
1991
1524
|
</td>
|
|
1992
1525
|
<td>
|
|
1993
1526
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
1994
|
-
<span class="inline-flex
|
|
1527
|
+
<span class="inline-flex items-center gap-2">
|
|
1995
1528
|
{{ rowData.name }}
|
|
1996
1529
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
1997
1530
|
<i
|
|
@@ -2002,7 +1535,7 @@ class RoleActionSelectorComponent {
|
|
|
2002
1535
|
}
|
|
2003
1536
|
</span>
|
|
2004
1537
|
</td>
|
|
2005
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
1538
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2006
1539
|
<td>
|
|
2007
1540
|
<p-tag
|
|
2008
1541
|
[value]="rowData.actionType"
|
|
@@ -2011,42 +1544,41 @@ class RoleActionSelectorComponent {
|
|
|
2011
1544
|
"
|
|
2012
1545
|
/>
|
|
2013
1546
|
</td>
|
|
2014
|
-
<td>
|
|
1547
|
+
<td class="hidden lg:table-cell">
|
|
2015
1548
|
@if (rowData.permissionLogic) {
|
|
2016
|
-
<span class="text-sm text-muted">Has prerequisites</span>
|
|
1549
|
+
<span class="text-sm text-muted-color">Has prerequisites</span>
|
|
2017
1550
|
} @else {
|
|
2018
|
-
<span class="text-muted">-</span>
|
|
1551
|
+
<span class="text-muted-color">-</span>
|
|
2019
1552
|
}
|
|
2020
1553
|
</td>
|
|
2021
1554
|
</tr>
|
|
2022
1555
|
</ng-template>
|
|
2023
|
-
<ng-template
|
|
1556
|
+
<ng-template #emptymessage>
|
|
2024
1557
|
<tr>
|
|
2025
|
-
<td colspan="5" class="text-center p-4">
|
|
1558
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2026
1559
|
@if (loading()) {
|
|
2027
1560
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2028
1561
|
} @else { No actions available. }
|
|
2029
1562
|
</td>
|
|
2030
1563
|
</tr>
|
|
2031
1564
|
</ng-template>
|
|
2032
|
-
|
|
1565
|
+
</p-treeTable>
|
|
1566
|
+
</div>
|
|
2033
1567
|
</div>
|
|
2034
1568
|
|
|
2035
1569
|
<!-- Change Summary -->
|
|
2036
1570
|
@if (hasChanges()) {
|
|
2037
|
-
<div class="
|
|
2038
|
-
<div class="flex
|
|
2039
|
-
<i class="pi pi-info-circle text-
|
|
2040
|
-
<
|
|
1571
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
1572
|
+
<div class="flex items-center gap-2 mb-3">
|
|
1573
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
1574
|
+
<span class="font-bold">Pending Changes</span>
|
|
2041
1575
|
</div>
|
|
2042
|
-
<div class="
|
|
1576
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2043
1577
|
@if (pendingAdd().length > 0) {
|
|
2044
|
-
<div
|
|
2045
|
-
<div class="flex
|
|
1578
|
+
<div>
|
|
1579
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2046
1580
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2047
|
-
<strong class="text-sm"
|
|
2048
|
-
>To Add ({{ pendingAdd().length }})</strong
|
|
2049
|
-
>
|
|
1581
|
+
<strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
|
|
2050
1582
|
</div>
|
|
2051
1583
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2052
1584
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2056,12 +1588,10 @@ class RoleActionSelectorComponent {
|
|
|
2056
1588
|
</div>
|
|
2057
1589
|
}
|
|
2058
1590
|
@if (pendingRemove().length > 0) {
|
|
2059
|
-
<div
|
|
2060
|
-
<div class="flex
|
|
2061
|
-
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2062
|
-
<strong class="text-sm"
|
|
2063
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
2064
|
-
>
|
|
1591
|
+
<div>
|
|
1592
|
+
<div class="flex items-center gap-2 mb-2">
|
|
1593
|
+
<i class="pi pi-minus-circle text-red-500"></i>
|
|
1594
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2065
1595
|
</div>
|
|
2066
1596
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2067
1597
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2076,31 +1606,29 @@ class RoleActionSelectorComponent {
|
|
|
2076
1606
|
}
|
|
2077
1607
|
|
|
2078
1608
|
@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">
|
|
1609
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
1610
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
1611
|
+
<p class="text-muted-color m-0">
|
|
2085
1612
|
No actions available for this role.
|
|
2086
1613
|
</p>
|
|
2087
1614
|
</div>
|
|
2088
1615
|
}
|
|
2089
1616
|
}
|
|
2090
1617
|
</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: "
|
|
1618
|
+
`, 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
1619
|
}
|
|
2093
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1620
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
|
|
2094
1621
|
type: Component,
|
|
2095
|
-
args: [{ selector: 'flusys-role-action-selector',
|
|
1622
|
+
args: [{ selector: 'flusys-role-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
|
|
2096
1623
|
<div class="role-action-selector">
|
|
2097
1624
|
<!-- Role Selector -->
|
|
2098
1625
|
<div class="mb-4">
|
|
2099
|
-
<div class="grid">
|
|
2100
|
-
<div
|
|
1626
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
1627
|
+
<div>
|
|
2101
1628
|
<label class="block font-semibold mb-2">Select Role</label>
|
|
2102
1629
|
<p-select
|
|
2103
|
-
[
|
|
1630
|
+
[ngModel]="selectedRoleId()"
|
|
1631
|
+
(ngModelChange)="selectedRoleId.set($event)"
|
|
2104
1632
|
[options]="roles()"
|
|
2105
1633
|
optionLabel="name"
|
|
2106
1634
|
optionValue="id"
|
|
@@ -2117,24 +1645,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2117
1645
|
<!-- Loading State -->
|
|
2118
1646
|
@if (loading()) {
|
|
2119
1647
|
<div
|
|
2120
|
-
class="surface-card p-5 border
|
|
1648
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2121
1649
|
>
|
|
2122
|
-
<i
|
|
2123
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2124
|
-
style="font-size: 3rem"
|
|
2125
|
-
></i>
|
|
1650
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2126
1651
|
</div>
|
|
2127
1652
|
}
|
|
2128
1653
|
|
|
2129
1654
|
<!-- Action List -->
|
|
2130
1655
|
@if (!loading() && actions().length > 0) {
|
|
2131
|
-
<div class="surface-card p-4 border
|
|
1656
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2132
1657
|
<div
|
|
2133
|
-
class="flex flex-
|
|
1658
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2134
1659
|
>
|
|
2135
1660
|
<div>
|
|
2136
1661
|
<h5 class="m-0 mb-1">Action Permissions</h5>
|
|
2137
|
-
<p class="text-sm text-color
|
|
1662
|
+
<p class="text-sm text-muted-color m-0">
|
|
2138
1663
|
{{ actions().length }} actions available
|
|
2139
1664
|
</p>
|
|
2140
1665
|
</div>
|
|
@@ -2156,6 +1681,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2156
1681
|
(onClick)="deselectAll()"
|
|
2157
1682
|
/>
|
|
2158
1683
|
<p-button
|
|
1684
|
+
*hasPermission="ROLE_ACTION_PERMISSIONS.ASSIGN"
|
|
2159
1685
|
label="Save Changes"
|
|
2160
1686
|
icon="pi pi-save"
|
|
2161
1687
|
[disabled]="!canSave()"
|
|
@@ -2169,14 +1695,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2169
1695
|
|
|
2170
1696
|
<!-- Validation Warning -->
|
|
2171
1697
|
@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">
|
|
1698
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
1699
|
+
<div class="flex items-start gap-2">
|
|
1700
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
1701
|
+
<div class="flex-1">
|
|
1702
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
1703
|
+
<p class="text-sm mt-1 mb-0">
|
|
2180
1704
|
{{ invalidActionsCount() }} selected
|
|
2181
1705
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
2182
1706
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -2188,33 +1712,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2188
1712
|
}
|
|
2189
1713
|
|
|
2190
1714
|
<!-- 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
|
|
1715
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
1716
|
+
<p-treeTable
|
|
1717
|
+
[value]="treeNodes()"
|
|
1718
|
+
dataKey="id"
|
|
1719
|
+
styleClass="p-treetable-sm"
|
|
1720
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
1721
|
+
>
|
|
1722
|
+
<ng-template #header>
|
|
1723
|
+
<tr>
|
|
1724
|
+
<th class="w-12">
|
|
1725
|
+
<p-checkbox
|
|
1726
|
+
[ngModel]="allSelected()"
|
|
1727
|
+
[binary]="true"
|
|
1728
|
+
(ngModelChange)="toggleAll()"
|
|
1729
|
+
pTooltip="Select/Deselect All"
|
|
1730
|
+
tooltipPosition="top"
|
|
1731
|
+
/>
|
|
1732
|
+
</th>
|
|
1733
|
+
<th>Name</th>
|
|
1734
|
+
<th class="hidden md:table-cell">Code</th>
|
|
1735
|
+
<th>Type</th>
|
|
1736
|
+
<th class="hidden lg:table-cell">Requirements</th>
|
|
1737
|
+
</tr>
|
|
1738
|
+
</ng-template>
|
|
1739
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
1740
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
1741
|
+
<td class="w-12">
|
|
2218
1742
|
<p-checkbox
|
|
2219
1743
|
[ngModel]="selectionMap()[rowData.id]"
|
|
2220
1744
|
[binary]="true"
|
|
@@ -2225,7 +1749,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2225
1749
|
</td>
|
|
2226
1750
|
<td>
|
|
2227
1751
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2228
|
-
<span class="inline-flex
|
|
1752
|
+
<span class="inline-flex items-center gap-2">
|
|
2229
1753
|
{{ rowData.name }}
|
|
2230
1754
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2231
1755
|
<i
|
|
@@ -2236,7 +1760,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2236
1760
|
}
|
|
2237
1761
|
</span>
|
|
2238
1762
|
</td>
|
|
2239
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
1763
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2240
1764
|
<td>
|
|
2241
1765
|
<p-tag
|
|
2242
1766
|
[value]="rowData.actionType"
|
|
@@ -2245,42 +1769,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2245
1769
|
"
|
|
2246
1770
|
/>
|
|
2247
1771
|
</td>
|
|
2248
|
-
<td>
|
|
1772
|
+
<td class="hidden lg:table-cell">
|
|
2249
1773
|
@if (rowData.permissionLogic) {
|
|
2250
|
-
<span class="text-sm text-muted">Has prerequisites</span>
|
|
1774
|
+
<span class="text-sm text-muted-color">Has prerequisites</span>
|
|
2251
1775
|
} @else {
|
|
2252
|
-
<span class="text-muted">-</span>
|
|
1776
|
+
<span class="text-muted-color">-</span>
|
|
2253
1777
|
}
|
|
2254
1778
|
</td>
|
|
2255
1779
|
</tr>
|
|
2256
1780
|
</ng-template>
|
|
2257
|
-
<ng-template
|
|
1781
|
+
<ng-template #emptymessage>
|
|
2258
1782
|
<tr>
|
|
2259
|
-
<td colspan="5" class="text-center p-4">
|
|
1783
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2260
1784
|
@if (loading()) {
|
|
2261
1785
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2262
1786
|
} @else { No actions available. }
|
|
2263
1787
|
</td>
|
|
2264
1788
|
</tr>
|
|
2265
1789
|
</ng-template>
|
|
2266
|
-
|
|
1790
|
+
</p-treeTable>
|
|
1791
|
+
</div>
|
|
2267
1792
|
</div>
|
|
2268
1793
|
|
|
2269
1794
|
<!-- Change Summary -->
|
|
2270
1795
|
@if (hasChanges()) {
|
|
2271
|
-
<div class="
|
|
2272
|
-
<div class="flex
|
|
2273
|
-
<i class="pi pi-info-circle text-
|
|
2274
|
-
<
|
|
1796
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
1797
|
+
<div class="flex items-center gap-2 mb-3">
|
|
1798
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
1799
|
+
<span class="font-bold">Pending Changes</span>
|
|
2275
1800
|
</div>
|
|
2276
|
-
<div class="
|
|
1801
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2277
1802
|
@if (pendingAdd().length > 0) {
|
|
2278
|
-
<div
|
|
2279
|
-
<div class="flex
|
|
1803
|
+
<div>
|
|
1804
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2280
1805
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2281
|
-
<strong class="text-sm"
|
|
2282
|
-
>To Add ({{ pendingAdd().length }})</strong
|
|
2283
|
-
>
|
|
1806
|
+
<strong class="text-sm">To Add ({{ pendingAdd().length }})</strong>
|
|
2284
1807
|
</div>
|
|
2285
1808
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2286
1809
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2290,12 +1813,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2290
1813
|
</div>
|
|
2291
1814
|
}
|
|
2292
1815
|
@if (pendingRemove().length > 0) {
|
|
2293
|
-
<div
|
|
2294
|
-
<div class="flex
|
|
1816
|
+
<div>
|
|
1817
|
+
<div class="flex items-center gap-2 mb-2">
|
|
2295
1818
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2296
|
-
<strong class="text-sm"
|
|
2297
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
2298
|
-
>
|
|
1819
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2299
1820
|
</div>
|
|
2300
1821
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2301
1822
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2310,19 +1831,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2310
1831
|
}
|
|
2311
1832
|
|
|
2312
1833
|
@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">
|
|
1834
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
1835
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
1836
|
+
<p class="text-muted-color m-0">
|
|
2319
1837
|
No actions available for this role.
|
|
2320
1838
|
</p>
|
|
2321
1839
|
</div>
|
|
2322
1840
|
}
|
|
2323
1841
|
}
|
|
2324
1842
|
</div>
|
|
2325
|
-
`, styles: [":host{display:block}\n"] }]
|
|
1843
|
+
`, 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
1844
|
}], ctorParameters: () => [] });
|
|
2327
1845
|
|
|
2328
1846
|
/**
|
|
@@ -2360,6 +1878,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2360
1878
|
* ```
|
|
2361
1879
|
*/
|
|
2362
1880
|
class CompanyActionSelectorComponent {
|
|
1881
|
+
// Permission constants for template
|
|
1882
|
+
COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
|
|
2363
1883
|
// Dependencies
|
|
2364
1884
|
companyApiProvider = inject(COMPANY_API_PROVIDER);
|
|
2365
1885
|
actionApi = inject(ActionApiService);
|
|
@@ -2367,6 +1887,7 @@ class CompanyActionSelectorComponent {
|
|
|
2367
1887
|
messageService = inject(MessageService);
|
|
2368
1888
|
confirmationService = inject(ConfirmationService);
|
|
2369
1889
|
permissionLogic = inject(ActionPermissionLogicService);
|
|
1890
|
+
destroyRef = inject(DestroyRef);
|
|
2370
1891
|
// State - Company Selection
|
|
2371
1892
|
selectedCompanyId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
|
|
2372
1893
|
companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
|
|
@@ -2410,17 +1931,14 @@ class CompanyActionSelectorComponent {
|
|
|
2410
1931
|
const selMap = this.selectionMap();
|
|
2411
1932
|
const allActions = this.actions();
|
|
2412
1933
|
const unmetSet = new Set();
|
|
2413
|
-
|
|
2414
|
-
if (action.id &&
|
|
2415
|
-
this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
|
|
1934
|
+
for (const action of allActions) {
|
|
1935
|
+
if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
|
|
2416
1936
|
unmetSet.add(action.id);
|
|
2417
1937
|
}
|
|
2418
|
-
}
|
|
1938
|
+
}
|
|
2419
1939
|
return unmetSet;
|
|
2420
1940
|
}, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
|
|
2421
|
-
invalidActionsCount = computed(() => {
|
|
2422
|
-
return this.actionsWithUnmetPrerequisites().size;
|
|
2423
|
-
}, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
1941
|
+
invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
2424
1942
|
// Computed - Pending Changes
|
|
2425
1943
|
pendingAdd = computed(() => {
|
|
2426
1944
|
const current = this.selectionMap();
|
|
@@ -2441,6 +1959,10 @@ class CompanyActionSelectorComponent {
|
|
|
2441
1959
|
// AbortController for data loading
|
|
2442
1960
|
loadDataAbortController = null;
|
|
2443
1961
|
constructor() {
|
|
1962
|
+
// Cleanup on destroy
|
|
1963
|
+
this.destroyRef.onDestroy(() => {
|
|
1964
|
+
this.loadDataAbortController?.abort();
|
|
1965
|
+
});
|
|
2444
1966
|
this.loadCompanies();
|
|
2445
1967
|
// Effect: Load data when company selection changes
|
|
2446
1968
|
effect(() => {
|
|
@@ -2451,11 +1973,7 @@ class CompanyActionSelectorComponent {
|
|
|
2451
1973
|
this.loadDataAbortController = new AbortController();
|
|
2452
1974
|
this.loadData(this.loadDataAbortController.signal).catch((err) => {
|
|
2453
1975
|
if (err.name !== 'AbortError') {
|
|
2454
|
-
|
|
2455
|
-
severity: 'error',
|
|
2456
|
-
summary: 'Error',
|
|
2457
|
-
detail: 'Failed to load company actions. Please refresh.',
|
|
2458
|
-
});
|
|
1976
|
+
// Error toast handled by global interceptor
|
|
2459
1977
|
this.loading.set(false);
|
|
2460
1978
|
}
|
|
2461
1979
|
});
|
|
@@ -2465,9 +1983,6 @@ class CompanyActionSelectorComponent {
|
|
|
2465
1983
|
}
|
|
2466
1984
|
});
|
|
2467
1985
|
}
|
|
2468
|
-
ngOnDestroy() {
|
|
2469
|
-
this.loadDataAbortController?.abort();
|
|
2470
|
-
}
|
|
2471
1986
|
/**
|
|
2472
1987
|
* Load companies from API
|
|
2473
1988
|
*/
|
|
@@ -2480,11 +1995,7 @@ class CompanyActionSelectorComponent {
|
|
|
2480
1995
|
this.companies.set(response?.data ?? []);
|
|
2481
1996
|
}
|
|
2482
1997
|
catch {
|
|
2483
|
-
|
|
2484
|
-
severity: 'error',
|
|
2485
|
-
summary: 'Error',
|
|
2486
|
-
detail: 'Failed to load companies',
|
|
2487
|
-
});
|
|
1998
|
+
// Error toast handled by global interceptor
|
|
2488
1999
|
}
|
|
2489
2000
|
}
|
|
2490
2001
|
/**
|
|
@@ -2511,13 +2022,7 @@ class CompanyActionSelectorComponent {
|
|
|
2511
2022
|
if (signal.aborted)
|
|
2512
2023
|
return;
|
|
2513
2024
|
const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
|
|
2514
|
-
|
|
2515
|
-
const selMap = {};
|
|
2516
|
-
actionsList.forEach((action) => {
|
|
2517
|
-
if (action.id) {
|
|
2518
|
-
selMap[action.id] = assignedIds.has(action.id);
|
|
2519
|
-
}
|
|
2520
|
-
});
|
|
2025
|
+
const selMap = this.buildSelectionMap(actionsList, (action) => assignedIds.has(action.id));
|
|
2521
2026
|
this._selectionMap.set(selMap);
|
|
2522
2027
|
this._initialSelection.set({ ...selMap });
|
|
2523
2028
|
}
|
|
@@ -2568,36 +2073,17 @@ class CompanyActionSelectorComponent {
|
|
|
2568
2073
|
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
2569
2074
|
}
|
|
2570
2075
|
}
|
|
2571
|
-
/**
|
|
2572
|
-
* Toggle all actions
|
|
2573
|
-
*/
|
|
2574
2076
|
toggleAll() {
|
|
2575
|
-
|
|
2576
|
-
const selMap = {};
|
|
2577
|
-
this.actions().forEach((action) => {
|
|
2578
|
-
selMap[action.id] = newValue;
|
|
2579
|
-
});
|
|
2580
|
-
this._selectionMap.set(selMap);
|
|
2077
|
+
this.setAllSelection(!this.allSelected());
|
|
2581
2078
|
}
|
|
2582
|
-
/**
|
|
2583
|
-
* Select all actions
|
|
2584
|
-
*/
|
|
2585
2079
|
selectAll() {
|
|
2586
|
-
|
|
2587
|
-
this.actions().forEach((action) => {
|
|
2588
|
-
selMap[action.id] = true;
|
|
2589
|
-
});
|
|
2590
|
-
this._selectionMap.set(selMap);
|
|
2080
|
+
this.setAllSelection(true);
|
|
2591
2081
|
}
|
|
2592
|
-
/**
|
|
2593
|
-
* Deselect all actions
|
|
2594
|
-
*/
|
|
2595
2082
|
deselectAll() {
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
this._selectionMap.set(selMap);
|
|
2083
|
+
this.setAllSelection(false);
|
|
2084
|
+
}
|
|
2085
|
+
setAllSelection(value) {
|
|
2086
|
+
this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
|
|
2601
2087
|
}
|
|
2602
2088
|
/**
|
|
2603
2089
|
* Save changes to backend
|
|
@@ -2612,20 +2098,7 @@ class CompanyActionSelectorComponent {
|
|
|
2612
2098
|
this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
2613
2099
|
return;
|
|
2614
2100
|
}
|
|
2615
|
-
|
|
2616
|
-
const items = [];
|
|
2617
|
-
this.pendingAdd().forEach((action) => {
|
|
2618
|
-
items.push({
|
|
2619
|
-
id: action.id,
|
|
2620
|
-
action: 'add',
|
|
2621
|
-
});
|
|
2622
|
-
});
|
|
2623
|
-
this.pendingRemove().forEach((action) => {
|
|
2624
|
-
items.push({
|
|
2625
|
-
id: action.id,
|
|
2626
|
-
action: 'remove',
|
|
2627
|
-
});
|
|
2628
|
-
});
|
|
2101
|
+
const items = this.buildPayloadItems();
|
|
2629
2102
|
if (items.length === 0)
|
|
2630
2103
|
return;
|
|
2631
2104
|
this.saving.set(true);
|
|
@@ -2649,13 +2122,8 @@ class CompanyActionSelectorComponent {
|
|
|
2649
2122
|
// Update baseline
|
|
2650
2123
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
2651
2124
|
}
|
|
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
|
-
});
|
|
2125
|
+
catch {
|
|
2126
|
+
// Error toast handled by global interceptor
|
|
2659
2127
|
}
|
|
2660
2128
|
finally {
|
|
2661
2129
|
this.saving.set(false);
|
|
@@ -2708,25 +2176,40 @@ class CompanyActionSelectorComponent {
|
|
|
2708
2176
|
},
|
|
2709
2177
|
});
|
|
2710
2178
|
}
|
|
2711
|
-
/**
|
|
2712
|
-
* Reset component state
|
|
2713
|
-
*/
|
|
2714
2179
|
resetState() {
|
|
2715
2180
|
this._actionsTree.set([]);
|
|
2716
2181
|
this._actions.set([]);
|
|
2717
2182
|
this._selectionMap.set({});
|
|
2718
2183
|
this._initialSelection.set({});
|
|
2719
2184
|
}
|
|
2720
|
-
|
|
2721
|
-
|
|
2185
|
+
buildSelectionMap(actions, predicate) {
|
|
2186
|
+
const selMap = {};
|
|
2187
|
+
for (const action of actions) {
|
|
2188
|
+
selMap[action.id] = predicate(action);
|
|
2189
|
+
}
|
|
2190
|
+
return selMap;
|
|
2191
|
+
}
|
|
2192
|
+
buildPayloadItems() {
|
|
2193
|
+
const items = [];
|
|
2194
|
+
for (const action of this.pendingAdd()) {
|
|
2195
|
+
items.push({ id: action.id, action: 'add' });
|
|
2196
|
+
}
|
|
2197
|
+
for (const action of this.pendingRemove()) {
|
|
2198
|
+
items.push({ id: action.id, action: 'remove' });
|
|
2199
|
+
}
|
|
2200
|
+
return items;
|
|
2201
|
+
}
|
|
2202
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2203
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", ngImport: i0, template: `
|
|
2722
2204
|
<div class="company-action-selector">
|
|
2723
2205
|
<!-- Company Selector -->
|
|
2724
2206
|
<div class="mb-4">
|
|
2725
|
-
<div class="grid">
|
|
2726
|
-
<div
|
|
2207
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2208
|
+
<div>
|
|
2727
2209
|
<label class="block font-semibold mb-2">Select Company</label>
|
|
2728
2210
|
<p-select
|
|
2729
|
-
[
|
|
2211
|
+
[ngModel]="selectedCompanyId()"
|
|
2212
|
+
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2730
2213
|
[options]="companies()"
|
|
2731
2214
|
optionLabel="name"
|
|
2732
2215
|
optionValue="id"
|
|
@@ -2743,24 +2226,21 @@ class CompanyActionSelectorComponent {
|
|
|
2743
2226
|
<!-- Loading State -->
|
|
2744
2227
|
@if (loading()) {
|
|
2745
2228
|
<div
|
|
2746
|
-
class="surface-card p-5 border
|
|
2229
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2747
2230
|
>
|
|
2748
|
-
<i
|
|
2749
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2750
|
-
style="font-size: 3rem"
|
|
2751
|
-
></i>
|
|
2231
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2752
2232
|
</div>
|
|
2753
2233
|
}
|
|
2754
2234
|
|
|
2755
2235
|
<!-- Action List -->
|
|
2756
2236
|
@if (!loading() && actions().length > 0) {
|
|
2757
|
-
<div class="surface-card p-4 border
|
|
2237
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2758
2238
|
<div
|
|
2759
|
-
class="flex flex-
|
|
2239
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2760
2240
|
>
|
|
2761
2241
|
<div>
|
|
2762
2242
|
<h5 class="m-0 mb-1">Action Whitelist</h5>
|
|
2763
|
-
<p class="text-sm text-color
|
|
2243
|
+
<p class="text-sm text-muted-color m-0">
|
|
2764
2244
|
{{ actions().length }} actions available
|
|
2765
2245
|
</p>
|
|
2766
2246
|
</div>
|
|
@@ -2782,6 +2262,7 @@ class CompanyActionSelectorComponent {
|
|
|
2782
2262
|
(onClick)="deselectAll()"
|
|
2783
2263
|
/>
|
|
2784
2264
|
<p-button
|
|
2265
|
+
*hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
|
|
2785
2266
|
label="Save Changes"
|
|
2786
2267
|
icon="pi pi-save"
|
|
2787
2268
|
[disabled]="!canSave()"
|
|
@@ -2795,14 +2276,12 @@ class CompanyActionSelectorComponent {
|
|
|
2795
2276
|
|
|
2796
2277
|
<!-- Validation Warning -->
|
|
2797
2278
|
@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">
|
|
2279
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2280
|
+
<div class="flex items-start gap-2">
|
|
2281
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2282
|
+
<div class="flex-1">
|
|
2283
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
2284
|
+
<p class="text-sm mt-1 mb-0">
|
|
2806
2285
|
{{ invalidActionsCount() }} selected
|
|
2807
2286
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
2808
2287
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -2814,33 +2293,33 @@ class CompanyActionSelectorComponent {
|
|
|
2814
2293
|
}
|
|
2815
2294
|
|
|
2816
2295
|
<!-- 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
|
|
2296
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2297
|
+
<p-treeTable
|
|
2298
|
+
[value]="treeNodes()"
|
|
2299
|
+
dataKey="id"
|
|
2300
|
+
styleClass="p-treetable-sm"
|
|
2301
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2302
|
+
>
|
|
2303
|
+
<ng-template #header>
|
|
2304
|
+
<tr>
|
|
2305
|
+
<th class="w-12">
|
|
2306
|
+
<p-checkbox
|
|
2307
|
+
[ngModel]="allSelected()"
|
|
2308
|
+
[binary]="true"
|
|
2309
|
+
(ngModelChange)="toggleAll()"
|
|
2310
|
+
pTooltip="Select/Deselect All"
|
|
2311
|
+
tooltipPosition="top"
|
|
2312
|
+
/>
|
|
2313
|
+
</th>
|
|
2314
|
+
<th>Name</th>
|
|
2315
|
+
<th class="hidden md:table-cell">Code</th>
|
|
2316
|
+
<th>Type</th>
|
|
2317
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
2318
|
+
</tr>
|
|
2319
|
+
</ng-template>
|
|
2320
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
2321
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
2322
|
+
<td class="w-12">
|
|
2844
2323
|
<p-checkbox
|
|
2845
2324
|
[ngModel]="selectionMap()[rowData.id]"
|
|
2846
2325
|
[binary]="true"
|
|
@@ -2851,7 +2330,7 @@ class CompanyActionSelectorComponent {
|
|
|
2851
2330
|
</td>
|
|
2852
2331
|
<td>
|
|
2853
2332
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2854
|
-
<span class="inline-flex
|
|
2333
|
+
<span class="inline-flex items-center gap-2">
|
|
2855
2334
|
{{ rowData.name }}
|
|
2856
2335
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
2857
2336
|
<i
|
|
@@ -2862,7 +2341,7 @@ class CompanyActionSelectorComponent {
|
|
|
2862
2341
|
}
|
|
2863
2342
|
</span>
|
|
2864
2343
|
</td>
|
|
2865
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
2344
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
2866
2345
|
<td>
|
|
2867
2346
|
<p-tag
|
|
2868
2347
|
[value]="rowData.actionType"
|
|
@@ -2871,36 +2350,35 @@ class CompanyActionSelectorComponent {
|
|
|
2871
2350
|
"
|
|
2872
2351
|
/>
|
|
2873
2352
|
</td>
|
|
2874
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
2353
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
2875
2354
|
</tr>
|
|
2876
2355
|
</ng-template>
|
|
2877
|
-
<ng-template
|
|
2356
|
+
<ng-template #emptymessage>
|
|
2878
2357
|
<tr>
|
|
2879
|
-
<td colspan="5" class="text-center p-4">
|
|
2358
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2880
2359
|
@if (loading()) {
|
|
2881
2360
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
2882
2361
|
} @else { No actions available. }
|
|
2883
2362
|
</td>
|
|
2884
2363
|
</tr>
|
|
2885
2364
|
</ng-template>
|
|
2886
|
-
|
|
2365
|
+
</p-treeTable>
|
|
2366
|
+
</div>
|
|
2887
2367
|
</div>
|
|
2888
2368
|
|
|
2889
2369
|
<!-- Change Summary -->
|
|
2890
2370
|
@if (hasChanges()) {
|
|
2891
|
-
<div class="
|
|
2892
|
-
<div class="flex
|
|
2893
|
-
<i class="pi pi-info-circle text-
|
|
2894
|
-
<
|
|
2371
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2372
|
+
<div class="flex items-center gap-2 mb-3">
|
|
2373
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
2374
|
+
<span class="font-bold">Pending Changes</span>
|
|
2895
2375
|
</div>
|
|
2896
|
-
<div class="
|
|
2376
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2897
2377
|
@if (pendingAdd().length > 0) {
|
|
2898
|
-
<div
|
|
2378
|
+
<div>
|
|
2899
2379
|
<div class="flex items-center gap-2 mb-2">
|
|
2900
2380
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2901
|
-
<strong class="text-sm">
|
|
2902
|
-
To Whitelist ({{ pendingAdd().length }})
|
|
2903
|
-
</strong>
|
|
2381
|
+
<strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
|
|
2904
2382
|
</div>
|
|
2905
2383
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2906
2384
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2909,14 +2387,11 @@ class CompanyActionSelectorComponent {
|
|
|
2909
2387
|
</ul>
|
|
2910
2388
|
</div>
|
|
2911
2389
|
}
|
|
2912
|
-
|
|
2913
2390
|
@if (pendingRemove().length > 0) {
|
|
2914
|
-
<div
|
|
2391
|
+
<div>
|
|
2915
2392
|
<div class="flex items-center gap-2 mb-2">
|
|
2916
2393
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2917
|
-
<strong class="text-sm">
|
|
2918
|
-
To Remove ({{ pendingRemove().length }})
|
|
2919
|
-
</strong>
|
|
2394
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
2920
2395
|
</div>
|
|
2921
2396
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2922
2397
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2931,31 +2406,29 @@ class CompanyActionSelectorComponent {
|
|
|
2931
2406
|
}
|
|
2932
2407
|
|
|
2933
2408
|
@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">
|
|
2409
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2410
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
2411
|
+
<p class="text-muted-color m-0">
|
|
2940
2412
|
No actions available for this company.
|
|
2941
2413
|
</p>
|
|
2942
2414
|
</div>
|
|
2943
2415
|
}
|
|
2944
2416
|
}
|
|
2945
2417
|
</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: "
|
|
2418
|
+
`, 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
2419
|
}
|
|
2948
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
2420
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
|
|
2949
2421
|
type: Component,
|
|
2950
|
-
args: [{ selector: 'flusys-company-action-selector',
|
|
2422
|
+
args: [{ selector: 'flusys-company-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
|
|
2951
2423
|
<div class="company-action-selector">
|
|
2952
2424
|
<!-- Company Selector -->
|
|
2953
2425
|
<div class="mb-4">
|
|
2954
|
-
<div class="grid">
|
|
2955
|
-
<div
|
|
2426
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2427
|
+
<div>
|
|
2956
2428
|
<label class="block font-semibold mb-2">Select Company</label>
|
|
2957
2429
|
<p-select
|
|
2958
|
-
[
|
|
2430
|
+
[ngModel]="selectedCompanyId()"
|
|
2431
|
+
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2959
2432
|
[options]="companies()"
|
|
2960
2433
|
optionLabel="name"
|
|
2961
2434
|
optionValue="id"
|
|
@@ -2972,24 +2445,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2972
2445
|
<!-- Loading State -->
|
|
2973
2446
|
@if (loading()) {
|
|
2974
2447
|
<div
|
|
2975
|
-
class="surface-card p-5 border
|
|
2448
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
2976
2449
|
>
|
|
2977
|
-
<i
|
|
2978
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
2979
|
-
style="font-size: 3rem"
|
|
2980
|
-
></i>
|
|
2450
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
2981
2451
|
</div>
|
|
2982
2452
|
}
|
|
2983
2453
|
|
|
2984
2454
|
<!-- Action List -->
|
|
2985
2455
|
@if (!loading() && actions().length > 0) {
|
|
2986
|
-
<div class="surface-card p-4 border
|
|
2456
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2987
2457
|
<div
|
|
2988
|
-
class="flex flex-
|
|
2458
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2989
2459
|
>
|
|
2990
2460
|
<div>
|
|
2991
2461
|
<h5 class="m-0 mb-1">Action Whitelist</h5>
|
|
2992
|
-
<p class="text-sm text-color
|
|
2462
|
+
<p class="text-sm text-muted-color m-0">
|
|
2993
2463
|
{{ actions().length }} actions available
|
|
2994
2464
|
</p>
|
|
2995
2465
|
</div>
|
|
@@ -3011,6 +2481,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3011
2481
|
(onClick)="deselectAll()"
|
|
3012
2482
|
/>
|
|
3013
2483
|
<p-button
|
|
2484
|
+
*hasPermission="COMPANY_ACTION_PERMISSIONS.ASSIGN"
|
|
3014
2485
|
label="Save Changes"
|
|
3015
2486
|
icon="pi pi-save"
|
|
3016
2487
|
[disabled]="!canSave()"
|
|
@@ -3024,14 +2495,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3024
2495
|
|
|
3025
2496
|
<!-- Validation Warning -->
|
|
3026
2497
|
@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">
|
|
2498
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2499
|
+
<div class="flex items-start gap-2">
|
|
2500
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2501
|
+
<div class="flex-1">
|
|
2502
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
2503
|
+
<p class="text-sm mt-1 mb-0">
|
|
3035
2504
|
{{ invalidActionsCount() }} selected
|
|
3036
2505
|
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
3037
2506
|
unmet prerequisites. Fix before saving or use auto-fix on
|
|
@@ -3043,33 +2512,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3043
2512
|
}
|
|
3044
2513
|
|
|
3045
2514
|
<!-- 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
|
|
2515
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2516
|
+
<p-treeTable
|
|
2517
|
+
[value]="treeNodes()"
|
|
2518
|
+
dataKey="id"
|
|
2519
|
+
styleClass="p-treetable-sm"
|
|
2520
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2521
|
+
>
|
|
2522
|
+
<ng-template #header>
|
|
2523
|
+
<tr>
|
|
2524
|
+
<th class="w-12">
|
|
2525
|
+
<p-checkbox
|
|
2526
|
+
[ngModel]="allSelected()"
|
|
2527
|
+
[binary]="true"
|
|
2528
|
+
(ngModelChange)="toggleAll()"
|
|
2529
|
+
pTooltip="Select/Deselect All"
|
|
2530
|
+
tooltipPosition="top"
|
|
2531
|
+
/>
|
|
2532
|
+
</th>
|
|
2533
|
+
<th>Name</th>
|
|
2534
|
+
<th class="hidden md:table-cell">Code</th>
|
|
2535
|
+
<th>Type</th>
|
|
2536
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
2537
|
+
</tr>
|
|
2538
|
+
</ng-template>
|
|
2539
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
2540
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
2541
|
+
<td class="w-12">
|
|
3073
2542
|
<p-checkbox
|
|
3074
2543
|
[ngModel]="selectionMap()[rowData.id]"
|
|
3075
2544
|
[binary]="true"
|
|
@@ -3080,7 +2549,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3080
2549
|
</td>
|
|
3081
2550
|
<td>
|
|
3082
2551
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
3083
|
-
<span class="inline-flex
|
|
2552
|
+
<span class="inline-flex items-center gap-2">
|
|
3084
2553
|
{{ rowData.name }}
|
|
3085
2554
|
@if (hasUnmetPrerequisites(rowData)) {
|
|
3086
2555
|
<i
|
|
@@ -3091,7 +2560,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3091
2560
|
}
|
|
3092
2561
|
</span>
|
|
3093
2562
|
</td>
|
|
3094
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
2563
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
3095
2564
|
<td>
|
|
3096
2565
|
<p-tag
|
|
3097
2566
|
[value]="rowData.actionType"
|
|
@@ -3100,36 +2569,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3100
2569
|
"
|
|
3101
2570
|
/>
|
|
3102
2571
|
</td>
|
|
3103
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
2572
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
3104
2573
|
</tr>
|
|
3105
2574
|
</ng-template>
|
|
3106
|
-
<ng-template
|
|
2575
|
+
<ng-template #emptymessage>
|
|
3107
2576
|
<tr>
|
|
3108
|
-
<td colspan="5" class="text-center p-4">
|
|
2577
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
3109
2578
|
@if (loading()) {
|
|
3110
2579
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
3111
2580
|
} @else { No actions available. }
|
|
3112
2581
|
</td>
|
|
3113
2582
|
</tr>
|
|
3114
2583
|
</ng-template>
|
|
3115
|
-
|
|
2584
|
+
</p-treeTable>
|
|
2585
|
+
</div>
|
|
3116
2586
|
</div>
|
|
3117
2587
|
|
|
3118
2588
|
<!-- Change Summary -->
|
|
3119
2589
|
@if (hasChanges()) {
|
|
3120
|
-
<div class="
|
|
3121
|
-
<div class="flex
|
|
3122
|
-
<i class="pi pi-info-circle text-
|
|
3123
|
-
<
|
|
2590
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2591
|
+
<div class="flex items-center gap-2 mb-3">
|
|
2592
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
2593
|
+
<span class="font-bold">Pending Changes</span>
|
|
3124
2594
|
</div>
|
|
3125
|
-
<div class="
|
|
2595
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3126
2596
|
@if (pendingAdd().length > 0) {
|
|
3127
|
-
<div
|
|
2597
|
+
<div>
|
|
3128
2598
|
<div class="flex items-center gap-2 mb-2">
|
|
3129
2599
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3130
|
-
<strong class="text-sm">
|
|
3131
|
-
To Whitelist ({{ pendingAdd().length }})
|
|
3132
|
-
</strong>
|
|
2600
|
+
<strong class="text-sm">To Whitelist ({{ pendingAdd().length }})</strong>
|
|
3133
2601
|
</div>
|
|
3134
2602
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3135
2603
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -3138,14 +2606,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3138
2606
|
</ul>
|
|
3139
2607
|
</div>
|
|
3140
2608
|
}
|
|
3141
|
-
|
|
3142
2609
|
@if (pendingRemove().length > 0) {
|
|
3143
|
-
<div
|
|
2610
|
+
<div>
|
|
3144
2611
|
<div class="flex items-center gap-2 mb-2">
|
|
3145
2612
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3146
|
-
<strong class="text-sm">
|
|
3147
|
-
To Remove ({{ pendingRemove().length }})
|
|
3148
|
-
</strong>
|
|
2613
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3149
2614
|
</div>
|
|
3150
2615
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3151
2616
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -3160,19 +2625,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3160
2625
|
}
|
|
3161
2626
|
|
|
3162
2627
|
@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">
|
|
2628
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2629
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
2630
|
+
<p class="text-muted-color m-0">
|
|
3169
2631
|
No actions available for this company.
|
|
3170
2632
|
</p>
|
|
3171
2633
|
</div>
|
|
3172
2634
|
}
|
|
3173
2635
|
}
|
|
3174
2636
|
</div>
|
|
3175
|
-
`, styles: [":host{display:block}\n"] }]
|
|
2637
|
+
`, 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
2638
|
}], ctorParameters: () => [] });
|
|
3177
2639
|
|
|
3178
2640
|
/**
|
|
@@ -3206,6 +2668,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3206
2668
|
* ```
|
|
3207
2669
|
*/
|
|
3208
2670
|
class UserRoleSelectorComponent {
|
|
2671
|
+
// Permission constants for template
|
|
2672
|
+
USER_ROLE_PERMISSIONS = USER_ROLE_PERMISSIONS;
|
|
3209
2673
|
// Dependencies
|
|
3210
2674
|
appConfig = inject(APP_CONFIG);
|
|
3211
2675
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
@@ -3249,7 +2713,11 @@ class UserRoleSelectorComponent {
|
|
|
3249
2713
|
hasChanges = computed(() => {
|
|
3250
2714
|
const current = this.selectionMap();
|
|
3251
2715
|
const initial = this.initialSelection();
|
|
3252
|
-
|
|
2716
|
+
const currentKeys = Object.keys(current);
|
|
2717
|
+
const initialKeys = Object.keys(initial);
|
|
2718
|
+
if (currentKeys.length !== initialKeys.length)
|
|
2719
|
+
return true;
|
|
2720
|
+
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
3253
2721
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
3254
2722
|
// Computed - Pending Changes
|
|
3255
2723
|
pendingAdd = computed(() => {
|
|
@@ -3305,9 +2773,7 @@ class UserRoleSelectorComponent {
|
|
|
3305
2773
|
this.branches.set([]);
|
|
3306
2774
|
return;
|
|
3307
2775
|
}
|
|
3308
|
-
const response = await this.userPermissionProvider
|
|
3309
|
-
.getUserBranchPermissions(userId)
|
|
3310
|
-
.toPromise();
|
|
2776
|
+
const response = await firstValueFrom(this.userPermissionProvider.getUserBranchPermissions(userId));
|
|
3311
2777
|
const userBranches = response?.data?.map((ub) => ({
|
|
3312
2778
|
id: ub.branchId,
|
|
3313
2779
|
name: ub.branchName,
|
|
@@ -3316,11 +2782,7 @@ class UserRoleSelectorComponent {
|
|
|
3316
2782
|
this.branches.set(userBranches);
|
|
3317
2783
|
}
|
|
3318
2784
|
catch {
|
|
3319
|
-
|
|
3320
|
-
severity: 'error',
|
|
3321
|
-
summary: 'Error',
|
|
3322
|
-
detail: 'Failed to load user permitted branches',
|
|
3323
|
-
});
|
|
2785
|
+
// Error toast handled by global interceptor
|
|
3324
2786
|
}
|
|
3325
2787
|
}
|
|
3326
2788
|
/**
|
|
@@ -3334,20 +2796,16 @@ class UserRoleSelectorComponent {
|
|
|
3334
2796
|
this.loading.set(true);
|
|
3335
2797
|
try {
|
|
3336
2798
|
// Load all roles
|
|
3337
|
-
const rolesResponse = await this.roleApi
|
|
3338
|
-
.getAll('', {
|
|
2799
|
+
const rolesResponse = await firstValueFrom(this.roleApi.getAll('', {
|
|
3339
2800
|
pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
|
|
3340
|
-
})
|
|
3341
|
-
.toPromise();
|
|
2801
|
+
}));
|
|
3342
2802
|
const rolesList = rolesResponse?.data ?? [];
|
|
3343
2803
|
this._roles.set(rolesList);
|
|
3344
2804
|
// Load existing user-role assignments
|
|
3345
2805
|
const query = isCompanyFeatureEnabled(this.appConfig) && branchId
|
|
3346
2806
|
? { branchId }
|
|
3347
2807
|
: undefined;
|
|
3348
|
-
const assignedResponse = await this.permissionApi
|
|
3349
|
-
.getUserRoles(userId, query)
|
|
3350
|
-
.toPromise();
|
|
2808
|
+
const assignedResponse = await firstValueFrom(this.permissionApi.getUserRoles(userId, query));
|
|
3351
2809
|
const assignedIds = new Set((assignedResponse?.data || []).map((r) => r.roleId));
|
|
3352
2810
|
// Build selection map
|
|
3353
2811
|
const selMap = {};
|
|
@@ -3358,11 +2816,7 @@ class UserRoleSelectorComponent {
|
|
|
3358
2816
|
this._initialSelection.set({ ...selMap });
|
|
3359
2817
|
}
|
|
3360
2818
|
catch {
|
|
3361
|
-
|
|
3362
|
-
severity: 'error',
|
|
3363
|
-
summary: 'Error',
|
|
3364
|
-
detail: 'Failed to load user role assignments',
|
|
3365
|
-
});
|
|
2819
|
+
// Error toast handled by global interceptor
|
|
3366
2820
|
}
|
|
3367
2821
|
finally {
|
|
3368
2822
|
this.loading.set(false);
|
|
@@ -3391,30 +2845,27 @@ class UserRoleSelectorComponent {
|
|
|
3391
2845
|
* Toggle all roles
|
|
3392
2846
|
*/
|
|
3393
2847
|
toggleAll() {
|
|
3394
|
-
|
|
3395
|
-
const selMap = {};
|
|
3396
|
-
this.roles().forEach((role) => {
|
|
3397
|
-
selMap[role.id] = newValue;
|
|
3398
|
-
});
|
|
3399
|
-
this._selectionMap.set(selMap);
|
|
2848
|
+
this.setAllSelections(!this.allSelected());
|
|
3400
2849
|
}
|
|
3401
2850
|
/**
|
|
3402
2851
|
* Select all roles
|
|
3403
2852
|
*/
|
|
3404
2853
|
selectAll() {
|
|
3405
|
-
|
|
3406
|
-
this.roles().forEach((role) => {
|
|
3407
|
-
selMap[role.id] = true;
|
|
3408
|
-
});
|
|
3409
|
-
this._selectionMap.set(selMap);
|
|
2854
|
+
this.setAllSelections(true);
|
|
3410
2855
|
}
|
|
3411
2856
|
/**
|
|
3412
2857
|
* Deselect all roles
|
|
3413
2858
|
*/
|
|
3414
2859
|
deselectAll() {
|
|
2860
|
+
this.setAllSelections(false);
|
|
2861
|
+
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Set all role selections to a given value
|
|
2864
|
+
*/
|
|
2865
|
+
setAllSelections(value) {
|
|
3415
2866
|
const selMap = {};
|
|
3416
2867
|
this.roles().forEach((role) => {
|
|
3417
|
-
selMap[role.id] =
|
|
2868
|
+
selMap[role.id] = value;
|
|
3418
2869
|
});
|
|
3419
2870
|
this._selectionMap.set(selMap);
|
|
3420
2871
|
}
|
|
@@ -3457,9 +2908,7 @@ class UserRoleSelectorComponent {
|
|
|
3457
2908
|
payload.branchId = branchId;
|
|
3458
2909
|
}
|
|
3459
2910
|
}
|
|
3460
|
-
const response = await this.permissionApi
|
|
3461
|
-
.assignUserRoles(payload)
|
|
3462
|
-
.toPromise();
|
|
2911
|
+
const response = await firstValueFrom(this.permissionApi.assignUserRoles(payload));
|
|
3463
2912
|
this.messageService.add({
|
|
3464
2913
|
severity: 'success',
|
|
3465
2914
|
summary: 'Success',
|
|
@@ -3469,13 +2918,8 @@ class UserRoleSelectorComponent {
|
|
|
3469
2918
|
// Update baseline
|
|
3470
2919
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
3471
2920
|
}
|
|
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
|
-
});
|
|
2921
|
+
catch {
|
|
2922
|
+
// Error toast handled by global interceptor
|
|
3479
2923
|
}
|
|
3480
2924
|
finally {
|
|
3481
2925
|
this.saving.set(false);
|
|
@@ -3489,32 +2933,34 @@ class UserRoleSelectorComponent {
|
|
|
3489
2933
|
this._selectionMap.set({});
|
|
3490
2934
|
this._initialSelection.set({});
|
|
3491
2935
|
}
|
|
3492
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
3493
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
2936
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserRoleSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2937
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: UserRoleSelectorComponent, isStandalone: true, selector: "flusys-user-role-selector", ngImport: i0, template: `
|
|
3494
2938
|
<div class="user-role-selector">
|
|
3495
2939
|
<!-- 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
|
|
2940
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
2941
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
2942
|
+
<div>
|
|
2943
|
+
<label class="block font-semibold mb-2">
|
|
3500
2944
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
3501
2945
|
Select User
|
|
3502
2946
|
</label>
|
|
3503
2947
|
<lib-user-select
|
|
3504
|
-
[
|
|
2948
|
+
[value]="selectedUserId()"
|
|
2949
|
+
(valueChange)="selectedUserId.set($event)"
|
|
3505
2950
|
[isEditMode]="true"
|
|
3506
2951
|
placeHolder="Select a user"
|
|
3507
2952
|
/>
|
|
3508
2953
|
</div>
|
|
3509
2954
|
|
|
3510
2955
|
@if (showBranchSelector()) {
|
|
3511
|
-
<div
|
|
3512
|
-
<label class="block font-semibold mb-2
|
|
2956
|
+
<div>
|
|
2957
|
+
<label class="block font-semibold mb-2">
|
|
3513
2958
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
3514
2959
|
Select Branch
|
|
3515
2960
|
</label>
|
|
3516
2961
|
<p-select
|
|
3517
|
-
[
|
|
2962
|
+
[ngModel]="selectedBranchId()"
|
|
2963
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
3518
2964
|
[options]="filteredBranches()"
|
|
3519
2965
|
optionLabel="name"
|
|
3520
2966
|
optionValue="id"
|
|
@@ -3525,20 +2971,20 @@ class UserRoleSelectorComponent {
|
|
|
3525
2971
|
>
|
|
3526
2972
|
<ng-template #selectedItem let-branch>
|
|
3527
2973
|
@if (branch) {
|
|
3528
|
-
<div class="flex
|
|
2974
|
+
<div class="flex items-center gap-2">
|
|
3529
2975
|
<i class="pi pi-building text-primary"></i>
|
|
3530
2976
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
3531
2977
|
</div>
|
|
3532
2978
|
}
|
|
3533
2979
|
</ng-template>
|
|
3534
2980
|
<ng-template #item let-branch>
|
|
3535
|
-
<div class="flex
|
|
3536
|
-
<i class="pi pi-building text-color
|
|
2981
|
+
<div class="flex items-center gap-2">
|
|
2982
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
3537
2983
|
<span>{{ branch.name }}</span>
|
|
3538
2984
|
</div>
|
|
3539
2985
|
</ng-template>
|
|
3540
2986
|
</p-select>
|
|
3541
|
-
<small class="text-color
|
|
2987
|
+
<small class="text-muted-color block mt-1">
|
|
3542
2988
|
{{ filteredBranches().length }} permitted branch{{
|
|
3543
2989
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
3544
2990
|
}}
|
|
@@ -3553,24 +2999,21 @@ class UserRoleSelectorComponent {
|
|
|
3553
2999
|
<!-- Loading State -->
|
|
3554
3000
|
@if (loading()) {
|
|
3555
3001
|
<div
|
|
3556
|
-
class="surface-card p-5 border
|
|
3002
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
3557
3003
|
>
|
|
3558
|
-
<i
|
|
3559
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
3560
|
-
style="font-size: 3rem"
|
|
3561
|
-
></i>
|
|
3004
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3562
3005
|
</div>
|
|
3563
3006
|
}
|
|
3564
3007
|
|
|
3565
3008
|
<!-- Role List -->
|
|
3566
3009
|
@if (!loading() && roles().length > 0) {
|
|
3567
|
-
<div class="surface-card p-4 border
|
|
3010
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3568
3011
|
<div
|
|
3569
|
-
class="flex flex-
|
|
3012
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3570
3013
|
>
|
|
3571
3014
|
<div>
|
|
3572
3015
|
<h5 class="m-0 mb-1">Role Assignments</h5>
|
|
3573
|
-
<p class="text-sm text-color
|
|
3016
|
+
<p class="text-sm text-muted-color m-0">
|
|
3574
3017
|
{{ roles().length }} roles available
|
|
3575
3018
|
</p>
|
|
3576
3019
|
</div>
|
|
@@ -3592,6 +3035,7 @@ class UserRoleSelectorComponent {
|
|
|
3592
3035
|
(onClick)="deselectAll()"
|
|
3593
3036
|
/>
|
|
3594
3037
|
<p-button
|
|
3038
|
+
*hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
|
|
3595
3039
|
label="Save Changes"
|
|
3596
3040
|
icon="pi pi-save"
|
|
3597
3041
|
[disabled]="!canSave()"
|
|
@@ -3604,77 +3048,78 @@ class UserRoleSelectorComponent {
|
|
|
3604
3048
|
</div>
|
|
3605
3049
|
|
|
3606
3050
|
<!-- 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
|
-
|
|
3051
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
3052
|
+
<p-table
|
|
3053
|
+
[value]="roles()"
|
|
3054
|
+
[rows]="10"
|
|
3055
|
+
[paginator]="roles().length > 10"
|
|
3056
|
+
[rowsPerPageOptions]="[10, 20, 50]"
|
|
3057
|
+
[globalFilterFields]="['name', 'code', 'description']"
|
|
3058
|
+
[showCurrentPageReport]="true"
|
|
3059
|
+
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
|
|
3060
|
+
styleClass="p-datatable-sm"
|
|
3061
|
+
[tableStyle]="{ 'min-width': '35rem' }"
|
|
3062
|
+
>
|
|
3063
|
+
<ng-template #header>
|
|
3064
|
+
<tr>
|
|
3065
|
+
<th style="width: 3rem">
|
|
3066
|
+
<p-checkbox
|
|
3067
|
+
[ngModel]="allSelected()"
|
|
3068
|
+
[binary]="true"
|
|
3069
|
+
(ngModelChange)="toggleAll()"
|
|
3070
|
+
pTooltip="Select/Deselect All"
|
|
3071
|
+
tooltipPosition="top"
|
|
3072
|
+
/>
|
|
3073
|
+
</th>
|
|
3074
|
+
<th>Name</th>
|
|
3075
|
+
<th class="hidden sm:table-cell">Code</th>
|
|
3076
|
+
<th class="hidden md:table-cell">Description</th>
|
|
3077
|
+
</tr>
|
|
3078
|
+
</ng-template>
|
|
3079
|
+
<ng-template #body let-role>
|
|
3080
|
+
<tr>
|
|
3081
|
+
<td>
|
|
3082
|
+
<p-checkbox
|
|
3083
|
+
[ngModel]="selectionMap()[role.id]"
|
|
3084
|
+
[binary]="true"
|
|
3085
|
+
(ngModelChange)="onRoleToggle(role, $event)"
|
|
3086
|
+
[pTooltip]="getTooltip(role)"
|
|
3087
|
+
tooltipPosition="top"
|
|
3088
|
+
/>
|
|
3089
|
+
</td>
|
|
3090
|
+
<td>{{ role.name }}</td>
|
|
3091
|
+
<td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
|
|
3092
|
+
<td class="hidden md:table-cell">{{ role.description || '-' }}</td>
|
|
3093
|
+
</tr>
|
|
3094
|
+
</ng-template>
|
|
3095
|
+
<ng-template #emptymessage>
|
|
3096
|
+
<tr>
|
|
3097
|
+
<td colspan="4" class="text-center p-4 text-muted-color">
|
|
3098
|
+
@if (loading()) {
|
|
3099
|
+
<i class="pi pi-spin pi-spinner"></i> Loading roles...
|
|
3100
|
+
} @else {
|
|
3101
|
+
No roles available.
|
|
3102
|
+
}
|
|
3103
|
+
</td>
|
|
3104
|
+
</tr>
|
|
3105
|
+
</ng-template>
|
|
3106
|
+
</p-table>
|
|
3107
|
+
</div>
|
|
3661
3108
|
</div>
|
|
3662
3109
|
|
|
3663
3110
|
<!-- Change Summary -->
|
|
3664
3111
|
@if (hasChanges()) {
|
|
3665
|
-
<div class="
|
|
3666
|
-
<div class="flex
|
|
3667
|
-
<i class="pi pi-info-circle text-
|
|
3668
|
-
<
|
|
3112
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3113
|
+
<div class="flex items-center gap-2 mb-3">
|
|
3114
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
3115
|
+
<span class="font-bold">Pending Changes</span>
|
|
3669
3116
|
</div>
|
|
3670
|
-
<div class="
|
|
3117
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3671
3118
|
@if (pendingAdd().length > 0) {
|
|
3672
|
-
<div
|
|
3673
|
-
<div class="flex
|
|
3119
|
+
<div>
|
|
3120
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3674
3121
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3675
|
-
<strong class="text-sm"
|
|
3676
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
3677
|
-
>
|
|
3122
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
3678
3123
|
</div>
|
|
3679
3124
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3680
3125
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3684,12 +3129,10 @@ class UserRoleSelectorComponent {
|
|
|
3684
3129
|
</div>
|
|
3685
3130
|
}
|
|
3686
3131
|
@if (pendingRemove().length > 0) {
|
|
3687
|
-
<div
|
|
3688
|
-
<div class="flex
|
|
3132
|
+
<div>
|
|
3133
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3689
3134
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3690
|
-
<strong class="text-sm"
|
|
3691
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
3692
|
-
>
|
|
3135
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3693
3136
|
</div>
|
|
3694
3137
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3695
3138
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3704,47 +3147,46 @@ class UserRoleSelectorComponent {
|
|
|
3704
3147
|
}
|
|
3705
3148
|
|
|
3706
3149
|
@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">
|
|
3150
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3151
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
3152
|
+
<p class="text-muted-color m-0">
|
|
3713
3153
|
No roles available for this user.
|
|
3714
3154
|
</p>
|
|
3715
3155
|
</div>
|
|
3716
3156
|
}
|
|
3717
3157
|
}
|
|
3718
3158
|
</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: 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:
|
|
3159
|
+
`, 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: ["value"], outputs: ["valueChange", "userSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3720
3160
|
}
|
|
3721
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
3161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
|
|
3722
3162
|
type: Component,
|
|
3723
|
-
args: [{ selector: 'flusys-user-role-selector',
|
|
3163
|
+
args: [{ selector: 'flusys-user-role-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
|
|
3724
3164
|
<div class="user-role-selector">
|
|
3725
3165
|
<!-- 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
|
|
3166
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
3167
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
3168
|
+
<div>
|
|
3169
|
+
<label class="block font-semibold mb-2">
|
|
3730
3170
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
3731
3171
|
Select User
|
|
3732
3172
|
</label>
|
|
3733
3173
|
<lib-user-select
|
|
3734
|
-
[
|
|
3174
|
+
[value]="selectedUserId()"
|
|
3175
|
+
(valueChange)="selectedUserId.set($event)"
|
|
3735
3176
|
[isEditMode]="true"
|
|
3736
3177
|
placeHolder="Select a user"
|
|
3737
3178
|
/>
|
|
3738
3179
|
</div>
|
|
3739
3180
|
|
|
3740
3181
|
@if (showBranchSelector()) {
|
|
3741
|
-
<div
|
|
3742
|
-
<label class="block font-semibold mb-2
|
|
3182
|
+
<div>
|
|
3183
|
+
<label class="block font-semibold mb-2">
|
|
3743
3184
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
3744
3185
|
Select Branch
|
|
3745
3186
|
</label>
|
|
3746
3187
|
<p-select
|
|
3747
|
-
[
|
|
3188
|
+
[ngModel]="selectedBranchId()"
|
|
3189
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
3748
3190
|
[options]="filteredBranches()"
|
|
3749
3191
|
optionLabel="name"
|
|
3750
3192
|
optionValue="id"
|
|
@@ -3755,20 +3197,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3755
3197
|
>
|
|
3756
3198
|
<ng-template #selectedItem let-branch>
|
|
3757
3199
|
@if (branch) {
|
|
3758
|
-
<div class="flex
|
|
3200
|
+
<div class="flex items-center gap-2">
|
|
3759
3201
|
<i class="pi pi-building text-primary"></i>
|
|
3760
3202
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
3761
3203
|
</div>
|
|
3762
3204
|
}
|
|
3763
3205
|
</ng-template>
|
|
3764
3206
|
<ng-template #item let-branch>
|
|
3765
|
-
<div class="flex
|
|
3766
|
-
<i class="pi pi-building text-color
|
|
3207
|
+
<div class="flex items-center gap-2">
|
|
3208
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
3767
3209
|
<span>{{ branch.name }}</span>
|
|
3768
3210
|
</div>
|
|
3769
3211
|
</ng-template>
|
|
3770
3212
|
</p-select>
|
|
3771
|
-
<small class="text-color
|
|
3213
|
+
<small class="text-muted-color block mt-1">
|
|
3772
3214
|
{{ filteredBranches().length }} permitted branch{{
|
|
3773
3215
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
3774
3216
|
}}
|
|
@@ -3783,24 +3225,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3783
3225
|
<!-- Loading State -->
|
|
3784
3226
|
@if (loading()) {
|
|
3785
3227
|
<div
|
|
3786
|
-
class="surface-card p-5 border
|
|
3228
|
+
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
3787
3229
|
>
|
|
3788
|
-
<i
|
|
3789
|
-
class="pi pi-spin pi-spinner text-primary"
|
|
3790
|
-
style="font-size: 3rem"
|
|
3791
|
-
></i>
|
|
3230
|
+
<i class="pi pi-spin pi-spinner text-primary text-5xl"></i>
|
|
3792
3231
|
</div>
|
|
3793
3232
|
}
|
|
3794
3233
|
|
|
3795
3234
|
<!-- Role List -->
|
|
3796
3235
|
@if (!loading() && roles().length > 0) {
|
|
3797
|
-
<div class="surface-card p-4 border
|
|
3236
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
3798
3237
|
<div
|
|
3799
|
-
class="flex flex-
|
|
3238
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3800
3239
|
>
|
|
3801
3240
|
<div>
|
|
3802
3241
|
<h5 class="m-0 mb-1">Role Assignments</h5>
|
|
3803
|
-
<p class="text-sm text-color
|
|
3242
|
+
<p class="text-sm text-muted-color m-0">
|
|
3804
3243
|
{{ roles().length }} roles available
|
|
3805
3244
|
</p>
|
|
3806
3245
|
</div>
|
|
@@ -3822,6 +3261,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3822
3261
|
(onClick)="deselectAll()"
|
|
3823
3262
|
/>
|
|
3824
3263
|
<p-button
|
|
3264
|
+
*hasPermission="USER_ROLE_PERMISSIONS.ASSIGN"
|
|
3825
3265
|
label="Save Changes"
|
|
3826
3266
|
icon="pi pi-save"
|
|
3827
3267
|
[disabled]="!canSave()"
|
|
@@ -3834,77 +3274,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3834
3274
|
</div>
|
|
3835
3275
|
|
|
3836
3276
|
<!-- 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
|
-
|
|
3277
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
3278
|
+
<p-table
|
|
3279
|
+
[value]="roles()"
|
|
3280
|
+
[rows]="10"
|
|
3281
|
+
[paginator]="roles().length > 10"
|
|
3282
|
+
[rowsPerPageOptions]="[10, 20, 50]"
|
|
3283
|
+
[globalFilterFields]="['name', 'code', 'description']"
|
|
3284
|
+
[showCurrentPageReport]="true"
|
|
3285
|
+
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} roles"
|
|
3286
|
+
styleClass="p-datatable-sm"
|
|
3287
|
+
[tableStyle]="{ 'min-width': '35rem' }"
|
|
3288
|
+
>
|
|
3289
|
+
<ng-template #header>
|
|
3290
|
+
<tr>
|
|
3291
|
+
<th style="width: 3rem">
|
|
3292
|
+
<p-checkbox
|
|
3293
|
+
[ngModel]="allSelected()"
|
|
3294
|
+
[binary]="true"
|
|
3295
|
+
(ngModelChange)="toggleAll()"
|
|
3296
|
+
pTooltip="Select/Deselect All"
|
|
3297
|
+
tooltipPosition="top"
|
|
3298
|
+
/>
|
|
3299
|
+
</th>
|
|
3300
|
+
<th>Name</th>
|
|
3301
|
+
<th class="hidden sm:table-cell">Code</th>
|
|
3302
|
+
<th class="hidden md:table-cell">Description</th>
|
|
3303
|
+
</tr>
|
|
3304
|
+
</ng-template>
|
|
3305
|
+
<ng-template #body let-role>
|
|
3306
|
+
<tr>
|
|
3307
|
+
<td>
|
|
3308
|
+
<p-checkbox
|
|
3309
|
+
[ngModel]="selectionMap()[role.id]"
|
|
3310
|
+
[binary]="true"
|
|
3311
|
+
(ngModelChange)="onRoleToggle(role, $event)"
|
|
3312
|
+
[pTooltip]="getTooltip(role)"
|
|
3313
|
+
tooltipPosition="top"
|
|
3314
|
+
/>
|
|
3315
|
+
</td>
|
|
3316
|
+
<td>{{ role.name }}</td>
|
|
3317
|
+
<td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
|
|
3318
|
+
<td class="hidden md:table-cell">{{ role.description || '-' }}</td>
|
|
3319
|
+
</tr>
|
|
3320
|
+
</ng-template>
|
|
3321
|
+
<ng-template #emptymessage>
|
|
3322
|
+
<tr>
|
|
3323
|
+
<td colspan="4" class="text-center p-4 text-muted-color">
|
|
3324
|
+
@if (loading()) {
|
|
3325
|
+
<i class="pi pi-spin pi-spinner"></i> Loading roles...
|
|
3326
|
+
} @else {
|
|
3327
|
+
No roles available.
|
|
3328
|
+
}
|
|
3329
|
+
</td>
|
|
3330
|
+
</tr>
|
|
3331
|
+
</ng-template>
|
|
3332
|
+
</p-table>
|
|
3333
|
+
</div>
|
|
3891
3334
|
</div>
|
|
3892
3335
|
|
|
3893
3336
|
<!-- Change Summary -->
|
|
3894
3337
|
@if (hasChanges()) {
|
|
3895
|
-
<div class="
|
|
3896
|
-
<div class="flex
|
|
3897
|
-
<i class="pi pi-info-circle text-
|
|
3898
|
-
<
|
|
3338
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3339
|
+
<div class="flex items-center gap-2 mb-3">
|
|
3340
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
3341
|
+
<span class="font-bold">Pending Changes</span>
|
|
3899
3342
|
</div>
|
|
3900
|
-
<div class="
|
|
3343
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3901
3344
|
@if (pendingAdd().length > 0) {
|
|
3902
|
-
<div
|
|
3903
|
-
<div class="flex
|
|
3345
|
+
<div>
|
|
3346
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3904
3347
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3905
|
-
<strong class="text-sm"
|
|
3906
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
3907
|
-
>
|
|
3348
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
3908
3349
|
</div>
|
|
3909
3350
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3910
3351
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3914,12 +3355,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3914
3355
|
</div>
|
|
3915
3356
|
}
|
|
3916
3357
|
@if (pendingRemove().length > 0) {
|
|
3917
|
-
<div
|
|
3918
|
-
<div class="flex
|
|
3358
|
+
<div>
|
|
3359
|
+
<div class="flex items-center gap-2 mb-2">
|
|
3919
3360
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3920
|
-
<strong class="text-sm"
|
|
3921
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
3922
|
-
>
|
|
3361
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
3923
3362
|
</div>
|
|
3924
3363
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3925
3364
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3934,12 +3373,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3934
3373
|
}
|
|
3935
3374
|
|
|
3936
3375
|
@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">
|
|
3376
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3377
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
3378
|
+
<p class="text-muted-color m-0">
|
|
3943
3379
|
No roles available for this user.
|
|
3944
3380
|
</p>
|
|
3945
3381
|
</div>
|
|
@@ -3981,6 +3417,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
3981
3417
|
* ```
|
|
3982
3418
|
*/
|
|
3983
3419
|
class UserActionSelectorComponent {
|
|
3420
|
+
// Permission constants for template
|
|
3421
|
+
USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
|
|
3984
3422
|
// Dependencies
|
|
3985
3423
|
appConfig = inject(APP_CONFIG);
|
|
3986
3424
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
@@ -4007,8 +3445,10 @@ class UserActionSelectorComponent {
|
|
|
4007
3445
|
selectionMap = this._selectionMap.asReadonly();
|
|
4008
3446
|
_initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
|
|
4009
3447
|
initialSelection = this._initialSelection.asReadonly();
|
|
3448
|
+
// Computed - Company Feature Flag (cached)
|
|
3449
|
+
isCompanyFeatureActive = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "isCompanyFeatureActive" }] : []));
|
|
4010
3450
|
// Computed - UI Flags
|
|
4011
|
-
showBranchSelector =
|
|
3451
|
+
showBranchSelector = this.isCompanyFeatureActive;
|
|
4012
3452
|
filteredBranches = computed(() => {
|
|
4013
3453
|
const currentCompanyId = this.companyContext.currentCompanyInfo()?.id || undefined;
|
|
4014
3454
|
if (!currentCompanyId)
|
|
@@ -4030,7 +3470,11 @@ class UserActionSelectorComponent {
|
|
|
4030
3470
|
hasChanges = computed(() => {
|
|
4031
3471
|
const current = this.selectionMap();
|
|
4032
3472
|
const initial = this.initialSelection();
|
|
4033
|
-
|
|
3473
|
+
const currentKeys = Object.keys(current);
|
|
3474
|
+
const initialKeys = Object.keys(initial);
|
|
3475
|
+
if (currentKeys.length !== initialKeys.length)
|
|
3476
|
+
return true;
|
|
3477
|
+
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
4034
3478
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
4035
3479
|
// Computed - Validation
|
|
4036
3480
|
actionsWithUnmetPrerequisites = computed(() => {
|
|
@@ -4070,7 +3514,7 @@ class UserActionSelectorComponent {
|
|
|
4070
3514
|
effect(() => {
|
|
4071
3515
|
const userId = this.selectedUserId();
|
|
4072
3516
|
if (userId) {
|
|
4073
|
-
if (
|
|
3517
|
+
if (this.isCompanyFeatureActive()) {
|
|
4074
3518
|
this.loadUserBranches(userId);
|
|
4075
3519
|
}
|
|
4076
3520
|
this.loadData();
|
|
@@ -4085,9 +3529,7 @@ class UserActionSelectorComponent {
|
|
|
4085
3529
|
effect(() => {
|
|
4086
3530
|
const userId = this.selectedUserId();
|
|
4087
3531
|
const branchId = this.selectedBranchId();
|
|
4088
|
-
if (
|
|
4089
|
-
userId &&
|
|
4090
|
-
branchId !== undefined) {
|
|
3532
|
+
if (this.isCompanyFeatureActive() && userId && branchId !== undefined) {
|
|
4091
3533
|
this.loadData();
|
|
4092
3534
|
}
|
|
4093
3535
|
});
|
|
@@ -4096,15 +3538,13 @@ class UserActionSelectorComponent {
|
|
|
4096
3538
|
* Load user's permitted branches
|
|
4097
3539
|
*/
|
|
4098
3540
|
async loadUserBranches(userId) {
|
|
3541
|
+
const companyId = this.companyContext.currentCompanyInfo()?.id;
|
|
3542
|
+
if (!companyId) {
|
|
3543
|
+
this.branches.set([]);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
4099
3546
|
try {
|
|
4100
|
-
const
|
|
4101
|
-
if (!companyId) {
|
|
4102
|
-
this.branches.set([]);
|
|
4103
|
-
return;
|
|
4104
|
-
}
|
|
4105
|
-
const response = await this.userPermissionProvider
|
|
4106
|
-
.getUserBranchPermissions(userId)
|
|
4107
|
-
.toPromise();
|
|
3547
|
+
const response = await firstValueFrom(this.userPermissionProvider.getUserBranchPermissions(userId));
|
|
4108
3548
|
const userBranches = response?.data?.map((ub) => ({
|
|
4109
3549
|
id: ub.branchId,
|
|
4110
3550
|
name: ub.branchName,
|
|
@@ -4113,11 +3553,7 @@ class UserActionSelectorComponent {
|
|
|
4113
3553
|
this.branches.set(userBranches);
|
|
4114
3554
|
}
|
|
4115
3555
|
catch {
|
|
4116
|
-
|
|
4117
|
-
severity: 'error',
|
|
4118
|
-
summary: 'Error',
|
|
4119
|
-
detail: 'Failed to load user permitted branches',
|
|
4120
|
-
});
|
|
3556
|
+
// Error toast handled by global interceptor
|
|
4121
3557
|
}
|
|
4122
3558
|
}
|
|
4123
3559
|
/**
|
|
@@ -4130,39 +3566,20 @@ class UserActionSelectorComponent {
|
|
|
4130
3566
|
const branchId = this.selectedBranchId();
|
|
4131
3567
|
this.loading.set(true);
|
|
4132
3568
|
try {
|
|
4133
|
-
|
|
4134
|
-
const actionsResponse = await this.actionApi
|
|
4135
|
-
.getActionsForPermission()
|
|
4136
|
-
.toPromise();
|
|
4137
|
-
// Get flat list (filtered by company if enabled)
|
|
3569
|
+
const actionsResponse = await firstValueFrom(this.actionApi.getActionsForPermission());
|
|
4138
3570
|
const flatActions = actionsResponse?.data ?? [];
|
|
4139
|
-
// Build tree structure from flat list with parentId
|
|
4140
3571
|
const actionsTree = buildTreeFromFlat(flatActions);
|
|
4141
|
-
// Store both representations
|
|
4142
3572
|
this._actionsTree.set(actionsTree);
|
|
4143
3573
|
this._actions.set(flatActions);
|
|
4144
|
-
|
|
4145
|
-
const
|
|
4146
|
-
? { branchId }
|
|
4147
|
-
: undefined;
|
|
4148
|
-
const assignedResponse = await this.permissionApi
|
|
4149
|
-
.getUserActions(userId, query)
|
|
4150
|
-
.toPromise();
|
|
3574
|
+
const query = this.isCompanyFeatureActive() && branchId ? { branchId } : undefined;
|
|
3575
|
+
const assignedResponse = await firstValueFrom(this.permissionApi.getUserActions(userId, query));
|
|
4151
3576
|
const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
|
|
4152
|
-
|
|
4153
|
-
const selMap = {};
|
|
4154
|
-
flatActions.forEach((action) => {
|
|
4155
|
-
selMap[action.id] = assignedIds.has(action.id);
|
|
4156
|
-
});
|
|
3577
|
+
const selMap = this.buildSelectionMap(flatActions, (action) => assignedIds.has(action.id));
|
|
4157
3578
|
this._selectionMap.set(selMap);
|
|
4158
3579
|
this._initialSelection.set({ ...selMap });
|
|
4159
3580
|
}
|
|
4160
3581
|
catch {
|
|
4161
|
-
|
|
4162
|
-
severity: 'error',
|
|
4163
|
-
summary: 'Error',
|
|
4164
|
-
detail: 'Failed to load user action permissions',
|
|
4165
|
-
});
|
|
3582
|
+
// Error toast handled by global interceptor
|
|
4166
3583
|
}
|
|
4167
3584
|
finally {
|
|
4168
3585
|
this.loading.set(false);
|
|
@@ -4197,55 +3614,37 @@ class UserActionSelectorComponent {
|
|
|
4197
3614
|
: false;
|
|
4198
3615
|
}
|
|
4199
3616
|
/**
|
|
4200
|
-
* Handle action
|
|
3617
|
+
* Handle action toggle with dependency management
|
|
4201
3618
|
*/
|
|
4202
3619
|
onActionToggle(action, newValue) {
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
3620
|
+
if (!newValue) {
|
|
3621
|
+
// Unchecking - validate dependencies
|
|
3622
|
+
this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
3623
|
+
}
|
|
3624
|
+
else {
|
|
3625
|
+
// Checking - validate prerequisites
|
|
3626
|
+
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
3627
|
+
}
|
|
4206
3628
|
}
|
|
4207
|
-
/**
|
|
4208
|
-
* Toggle all actions
|
|
4209
|
-
*/
|
|
4210
3629
|
toggleAll() {
|
|
4211
|
-
|
|
4212
|
-
const selMap = {};
|
|
4213
|
-
this.actions().forEach((action) => {
|
|
4214
|
-
selMap[action.id] = newValue;
|
|
4215
|
-
});
|
|
4216
|
-
this._selectionMap.set(selMap);
|
|
3630
|
+
this.setAllActions(!this.allSelected());
|
|
4217
3631
|
}
|
|
4218
|
-
/**
|
|
4219
|
-
* Select all actions
|
|
4220
|
-
*/
|
|
4221
3632
|
selectAll() {
|
|
4222
|
-
|
|
4223
|
-
this.actions().forEach((action) => {
|
|
4224
|
-
selMap[action.id] = true;
|
|
4225
|
-
});
|
|
4226
|
-
this._selectionMap.set(selMap);
|
|
3633
|
+
this.setAllActions(true);
|
|
4227
3634
|
}
|
|
4228
|
-
/**
|
|
4229
|
-
* Deselect all actions
|
|
4230
|
-
*/
|
|
4231
3635
|
deselectAll() {
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
this._selectionMap.set(selMap);
|
|
3636
|
+
this.setAllActions(false);
|
|
3637
|
+
}
|
|
3638
|
+
setAllActions(value) {
|
|
3639
|
+
this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
|
|
4237
3640
|
}
|
|
4238
|
-
/**
|
|
4239
|
-
* Save changes to backend
|
|
4240
|
-
*/
|
|
4241
3641
|
async saveChanges() {
|
|
4242
3642
|
const userId = this.selectedUserId();
|
|
4243
3643
|
if (!userId)
|
|
4244
3644
|
return;
|
|
4245
3645
|
const branchId = this.selectedBranchId();
|
|
4246
|
-
const companyId = this.companyContext.currentCompanyInfo()?.id
|
|
4247
|
-
|
|
4248
|
-
if (isCompanyFeatureEnabled(this.appConfig) && !companyId) {
|
|
3646
|
+
const companyId = this.companyContext.currentCompanyInfo()?.id;
|
|
3647
|
+
if (this.isCompanyFeatureActive() && !companyId) {
|
|
4249
3648
|
this.messageService.add({
|
|
4250
3649
|
severity: 'warn',
|
|
4251
3650
|
summary: 'Company Required',
|
|
@@ -4253,92 +3652,90 @@ class UserActionSelectorComponent {
|
|
|
4253
3652
|
});
|
|
4254
3653
|
return;
|
|
4255
3654
|
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
});
|
|
4263
|
-
});
|
|
4264
|
-
this.pendingRemove().forEach((action) => {
|
|
4265
|
-
items.push({
|
|
4266
|
-
id: action.id,
|
|
4267
|
-
action: 'remove',
|
|
4268
|
-
});
|
|
4269
|
-
});
|
|
3655
|
+
const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
|
|
3656
|
+
if (invalidActions.length > 0) {
|
|
3657
|
+
this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
const items = this.buildPermissionItems();
|
|
4270
3661
|
if (items.length === 0)
|
|
4271
3662
|
return;
|
|
4272
3663
|
this.saving.set(true);
|
|
4273
3664
|
try {
|
|
4274
3665
|
const payload = { userId, items };
|
|
4275
|
-
if (
|
|
4276
|
-
if (companyId)
|
|
3666
|
+
if (this.isCompanyFeatureActive()) {
|
|
3667
|
+
if (companyId)
|
|
4277
3668
|
payload.companyId = companyId;
|
|
4278
|
-
|
|
4279
|
-
if (branchId) {
|
|
3669
|
+
if (branchId)
|
|
4280
3670
|
payload.branchId = branchId;
|
|
4281
|
-
}
|
|
4282
3671
|
}
|
|
4283
|
-
const response = await this.permissionApi
|
|
4284
|
-
.assignUserActions(payload)
|
|
4285
|
-
.toPromise();
|
|
3672
|
+
const response = await firstValueFrom(this.permissionApi.assignUserActions(payload));
|
|
4286
3673
|
this.messageService.add({
|
|
4287
3674
|
severity: 'success',
|
|
4288
3675
|
summary: 'Success',
|
|
4289
3676
|
detail: response?.data?.message ||
|
|
4290
3677
|
'User action permissions updated successfully',
|
|
4291
3678
|
});
|
|
4292
|
-
// Update baseline
|
|
4293
3679
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
4294
3680
|
}
|
|
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
|
-
});
|
|
3681
|
+
catch {
|
|
3682
|
+
// Error toast handled by global interceptor
|
|
4302
3683
|
}
|
|
4303
3684
|
finally {
|
|
4304
3685
|
this.saving.set(false);
|
|
4305
3686
|
}
|
|
4306
3687
|
}
|
|
4307
|
-
/**
|
|
4308
|
-
* Reset component state
|
|
4309
|
-
*/
|
|
4310
3688
|
resetState() {
|
|
4311
3689
|
this._actionsTree.set([]);
|
|
4312
3690
|
this._actions.set([]);
|
|
4313
3691
|
this._selectionMap.set({});
|
|
4314
3692
|
this._initialSelection.set({});
|
|
4315
3693
|
}
|
|
4316
|
-
|
|
4317
|
-
|
|
3694
|
+
buildSelectionMap(actions, predicate) {
|
|
3695
|
+
const selMap = {};
|
|
3696
|
+
actions.forEach((action) => {
|
|
3697
|
+
selMap[action.id] = predicate(action);
|
|
3698
|
+
});
|
|
3699
|
+
return selMap;
|
|
3700
|
+
}
|
|
3701
|
+
buildPermissionItems() {
|
|
3702
|
+
const items = [];
|
|
3703
|
+
this.pendingAdd().forEach((action) => {
|
|
3704
|
+
items.push({ id: action.id, action: 'add' });
|
|
3705
|
+
});
|
|
3706
|
+
this.pendingRemove().forEach((action) => {
|
|
3707
|
+
items.push({ id: action.id, action: 'remove' });
|
|
3708
|
+
});
|
|
3709
|
+
return items;
|
|
3710
|
+
}
|
|
3711
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3712
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
|
|
4318
3713
|
<div class="user-action-selector">
|
|
4319
3714
|
<!-- 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
|
|
3715
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
3716
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
3717
|
+
<div>
|
|
3718
|
+
<label class="block font-semibold mb-2">
|
|
4324
3719
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
4325
3720
|
Select User
|
|
4326
3721
|
</label>
|
|
4327
3722
|
<lib-user-select
|
|
4328
|
-
[
|
|
3723
|
+
[value]="selectedUserId()"
|
|
3724
|
+
(valueChange)="selectedUserId.set($event)"
|
|
4329
3725
|
[isEditMode]="true"
|
|
4330
3726
|
placeHolder="Select a user"
|
|
4331
3727
|
/>
|
|
4332
3728
|
</div>
|
|
4333
3729
|
|
|
4334
3730
|
@if (showBranchSelector()) {
|
|
4335
|
-
<div
|
|
4336
|
-
<label class="block font-semibold mb-2
|
|
3731
|
+
<div>
|
|
3732
|
+
<label class="block font-semibold mb-2">
|
|
4337
3733
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
4338
3734
|
Select Branch
|
|
4339
3735
|
</label>
|
|
4340
3736
|
<p-select
|
|
4341
|
-
[
|
|
3737
|
+
[ngModel]="selectedBranchId()"
|
|
3738
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
4342
3739
|
[options]="filteredBranches()"
|
|
4343
3740
|
optionLabel="name"
|
|
4344
3741
|
optionValue="id"
|
|
@@ -4349,20 +3746,20 @@ class UserActionSelectorComponent {
|
|
|
4349
3746
|
>
|
|
4350
3747
|
<ng-template #selectedItem let-branch>
|
|
4351
3748
|
@if (branch) {
|
|
4352
|
-
<div class="flex
|
|
3749
|
+
<div class="flex items-center gap-2">
|
|
4353
3750
|
<i class="pi pi-building text-primary"></i>
|
|
4354
3751
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
4355
3752
|
</div>
|
|
4356
3753
|
}
|
|
4357
3754
|
</ng-template>
|
|
4358
3755
|
<ng-template #item let-branch>
|
|
4359
|
-
<div class="flex
|
|
4360
|
-
<i class="pi pi-building text-color
|
|
3756
|
+
<div class="flex items-center gap-2">
|
|
3757
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
4361
3758
|
<span>{{ branch.name }}</span>
|
|
4362
3759
|
</div>
|
|
4363
3760
|
</ng-template>
|
|
4364
3761
|
</p-select>
|
|
4365
|
-
<small class="text-color
|
|
3762
|
+
<small class="text-muted-color block mt-1">
|
|
4366
3763
|
{{ filteredBranches().length }} permitted branch{{
|
|
4367
3764
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
4368
3765
|
}}
|
|
@@ -4376,20 +3773,20 @@ class UserActionSelectorComponent {
|
|
|
4376
3773
|
@if (selectedUserId()) {
|
|
4377
3774
|
<!-- Loading State -->
|
|
4378
3775
|
@if (loading()) {
|
|
4379
|
-
<div class="flex justify-
|
|
4380
|
-
<i class="pi pi-spin pi-spinner
|
|
3776
|
+
<div class="flex justify-center p-5">
|
|
3777
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
4381
3778
|
</div>
|
|
4382
3779
|
}
|
|
4383
3780
|
|
|
4384
3781
|
<!-- Action List -->
|
|
4385
3782
|
@if (!loading() && actions().length > 0) {
|
|
4386
|
-
<div class="surface-card p-4 border
|
|
3783
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4387
3784
|
<div
|
|
4388
|
-
class="flex flex-
|
|
3785
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4389
3786
|
>
|
|
4390
3787
|
<div>
|
|
4391
3788
|
<h5 class="m-0 mb-1">Direct Action Permissions</h5>
|
|
4392
|
-
<p class="text-sm text-color
|
|
3789
|
+
<p class="text-sm text-muted-color m-0">
|
|
4393
3790
|
{{ actions().length }} actions available
|
|
4394
3791
|
</p>
|
|
4395
3792
|
</div>
|
|
@@ -4411,6 +3808,7 @@ class UserActionSelectorComponent {
|
|
|
4411
3808
|
(onClick)="deselectAll()"
|
|
4412
3809
|
/>
|
|
4413
3810
|
<p-button
|
|
3811
|
+
*hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
|
|
4414
3812
|
label="Save Changes"
|
|
4415
3813
|
icon="pi pi-save"
|
|
4416
3814
|
[disabled]="!canSave()"
|
|
@@ -4422,34 +3820,52 @@ class UserActionSelectorComponent {
|
|
|
4422
3820
|
</div>
|
|
4423
3821
|
</div>
|
|
4424
3822
|
|
|
3823
|
+
<!-- Validation Warning -->
|
|
3824
|
+
@if (invalidActionsCount() > 0) {
|
|
3825
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
3826
|
+
<div class="flex items-start gap-2">
|
|
3827
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
3828
|
+
<div class="flex-1">
|
|
3829
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
3830
|
+
<p class="text-sm mt-1 mb-0">
|
|
3831
|
+
{{ invalidActionsCount() }} selected
|
|
3832
|
+
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
3833
|
+
unmet prerequisites. Fix before saving or use auto-fix on
|
|
3834
|
+
save.
|
|
3835
|
+
</p>
|
|
3836
|
+
</div>
|
|
3837
|
+
</div>
|
|
3838
|
+
</div>
|
|
3839
|
+
}
|
|
3840
|
+
|
|
4425
3841
|
<!-- 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
|
|
3842
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
3843
|
+
<p-treeTable
|
|
3844
|
+
[value]="treeNodes()"
|
|
3845
|
+
dataKey="id"
|
|
3846
|
+
styleClass="p-treetable-sm"
|
|
3847
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
3848
|
+
>
|
|
3849
|
+
<ng-template #header>
|
|
3850
|
+
<tr>
|
|
3851
|
+
<th class="w-12">
|
|
3852
|
+
<p-checkbox
|
|
3853
|
+
[ngModel]="allSelected()"
|
|
3854
|
+
[binary]="true"
|
|
3855
|
+
(ngModelChange)="toggleAll()"
|
|
3856
|
+
pTooltip="Select/Deselect All"
|
|
3857
|
+
tooltipPosition="top"
|
|
3858
|
+
/>
|
|
3859
|
+
</th>
|
|
3860
|
+
<th>Name</th>
|
|
3861
|
+
<th class="hidden md:table-cell">Code</th>
|
|
3862
|
+
<th>Type</th>
|
|
3863
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
3864
|
+
</tr>
|
|
3865
|
+
</ng-template>
|
|
3866
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
3867
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
3868
|
+
<td class="w-12">
|
|
4453
3869
|
<p-checkbox
|
|
4454
3870
|
[ngModel]="selectionMap()[rowData.id]"
|
|
4455
3871
|
[binary]="true"
|
|
@@ -4460,9 +3876,18 @@ class UserActionSelectorComponent {
|
|
|
4460
3876
|
</td>
|
|
4461
3877
|
<td>
|
|
4462
3878
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4463
|
-
<span
|
|
3879
|
+
<span class="inline-flex items-center gap-2">
|
|
3880
|
+
{{ rowData.name }}
|
|
3881
|
+
@if (hasUnmetPrerequisites(rowData)) {
|
|
3882
|
+
<i
|
|
3883
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
3884
|
+
pTooltip="This action has unmet prerequisites and will fail validation on save"
|
|
3885
|
+
tooltipPosition="top"
|
|
3886
|
+
></i>
|
|
3887
|
+
}
|
|
3888
|
+
</span>
|
|
4464
3889
|
</td>
|
|
4465
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
3890
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
4466
3891
|
<td>
|
|
4467
3892
|
<p-tag
|
|
4468
3893
|
[value]="rowData.actionType"
|
|
@@ -4471,12 +3896,12 @@ class UserActionSelectorComponent {
|
|
|
4471
3896
|
"
|
|
4472
3897
|
/>
|
|
4473
3898
|
</td>
|
|
4474
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
3899
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4475
3900
|
</tr>
|
|
4476
3901
|
</ng-template>
|
|
4477
|
-
<ng-template
|
|
3902
|
+
<ng-template #emptymessage>
|
|
4478
3903
|
<tr>
|
|
4479
|
-
<td colspan="5" class="text-center p-4">
|
|
3904
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4480
3905
|
@if (loading()) {
|
|
4481
3906
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
4482
3907
|
} @else {
|
|
@@ -4485,24 +3910,23 @@ class UserActionSelectorComponent {
|
|
|
4485
3910
|
</td>
|
|
4486
3911
|
</tr>
|
|
4487
3912
|
</ng-template>
|
|
4488
|
-
|
|
3913
|
+
</p-treeTable>
|
|
3914
|
+
</div>
|
|
4489
3915
|
</div>
|
|
4490
3916
|
|
|
4491
3917
|
<!-- Change Summary -->
|
|
4492
3918
|
@if (hasChanges()) {
|
|
4493
|
-
<div class="
|
|
4494
|
-
<div class="flex
|
|
4495
|
-
<i class="pi pi-info-circle text-
|
|
4496
|
-
<
|
|
3919
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3920
|
+
<div class="flex items-center gap-2 mb-3">
|
|
3921
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
3922
|
+
<span class="font-bold">Pending Changes</span>
|
|
4497
3923
|
</div>
|
|
4498
|
-
<div class="
|
|
3924
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4499
3925
|
@if (pendingAdd().length > 0) {
|
|
4500
|
-
<div
|
|
4501
|
-
<div class="flex
|
|
3926
|
+
<div>
|
|
3927
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4502
3928
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4503
|
-
<strong class="text-sm"
|
|
4504
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4505
|
-
>
|
|
3929
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4506
3930
|
</div>
|
|
4507
3931
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4508
3932
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4512,12 +3936,10 @@ class UserActionSelectorComponent {
|
|
|
4512
3936
|
</div>
|
|
4513
3937
|
}
|
|
4514
3938
|
@if (pendingRemove().length > 0) {
|
|
4515
|
-
<div
|
|
4516
|
-
<div class="flex
|
|
3939
|
+
<div>
|
|
3940
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4517
3941
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4518
|
-
<strong class="text-sm"
|
|
4519
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4520
|
-
>
|
|
3942
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4521
3943
|
</div>
|
|
4522
3944
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4523
3945
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4532,47 +3954,46 @@ class UserActionSelectorComponent {
|
|
|
4532
3954
|
}
|
|
4533
3955
|
|
|
4534
3956
|
@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">
|
|
3957
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3958
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
3959
|
+
<p class="text-muted-color m-0">
|
|
4541
3960
|
No actions available for this user.
|
|
4542
3961
|
</p>
|
|
4543
3962
|
</div>
|
|
4544
3963
|
}
|
|
4545
3964
|
}
|
|
4546
3965
|
</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: "
|
|
3966
|
+
`, 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: ["value"], outputs: ["valueChange", "userSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4548
3967
|
}
|
|
4549
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
3968
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
|
|
4550
3969
|
type: Component,
|
|
4551
|
-
args: [{ selector: 'flusys-user-action-selector',
|
|
3970
|
+
args: [{ selector: 'flusys-user-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
|
|
4552
3971
|
<div class="user-action-selector">
|
|
4553
3972
|
<!-- 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
|
|
3973
|
+
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
3974
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
3975
|
+
<div>
|
|
3976
|
+
<label class="block font-semibold mb-2">
|
|
4558
3977
|
<i class="pi pi-user mr-2 text-primary"></i>
|
|
4559
3978
|
Select User
|
|
4560
3979
|
</label>
|
|
4561
3980
|
<lib-user-select
|
|
4562
|
-
[
|
|
3981
|
+
[value]="selectedUserId()"
|
|
3982
|
+
(valueChange)="selectedUserId.set($event)"
|
|
4563
3983
|
[isEditMode]="true"
|
|
4564
3984
|
placeHolder="Select a user"
|
|
4565
3985
|
/>
|
|
4566
3986
|
</div>
|
|
4567
3987
|
|
|
4568
3988
|
@if (showBranchSelector()) {
|
|
4569
|
-
<div
|
|
4570
|
-
<label class="block font-semibold mb-2
|
|
3989
|
+
<div>
|
|
3990
|
+
<label class="block font-semibold mb-2">
|
|
4571
3991
|
<i class="pi pi-building mr-2 text-primary"></i>
|
|
4572
3992
|
Select Branch
|
|
4573
3993
|
</label>
|
|
4574
3994
|
<p-select
|
|
4575
|
-
[
|
|
3995
|
+
[ngModel]="selectedBranchId()"
|
|
3996
|
+
(ngModelChange)="selectedBranchId.set($event)"
|
|
4576
3997
|
[options]="filteredBranches()"
|
|
4577
3998
|
optionLabel="name"
|
|
4578
3999
|
optionValue="id"
|
|
@@ -4583,20 +4004,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4583
4004
|
>
|
|
4584
4005
|
<ng-template #selectedItem let-branch>
|
|
4585
4006
|
@if (branch) {
|
|
4586
|
-
<div class="flex
|
|
4007
|
+
<div class="flex items-center gap-2">
|
|
4587
4008
|
<i class="pi pi-building text-primary"></i>
|
|
4588
4009
|
<span class="font-semibold">{{ branch.name }}</span>
|
|
4589
4010
|
</div>
|
|
4590
4011
|
}
|
|
4591
4012
|
</ng-template>
|
|
4592
4013
|
<ng-template #item let-branch>
|
|
4593
|
-
<div class="flex
|
|
4594
|
-
<i class="pi pi-building text-color
|
|
4014
|
+
<div class="flex items-center gap-2">
|
|
4015
|
+
<i class="pi pi-building text-muted-color"></i>
|
|
4595
4016
|
<span>{{ branch.name }}</span>
|
|
4596
4017
|
</div>
|
|
4597
4018
|
</ng-template>
|
|
4598
4019
|
</p-select>
|
|
4599
|
-
<small class="text-color
|
|
4020
|
+
<small class="text-muted-color block mt-1">
|
|
4600
4021
|
{{ filteredBranches().length }} permitted branch{{
|
|
4601
4022
|
filteredBranches().length !== 1 ? 'es' : ''
|
|
4602
4023
|
}}
|
|
@@ -4610,20 +4031,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4610
4031
|
@if (selectedUserId()) {
|
|
4611
4032
|
<!-- Loading State -->
|
|
4612
4033
|
@if (loading()) {
|
|
4613
|
-
<div class="flex justify-
|
|
4614
|
-
<i class="pi pi-spin pi-spinner
|
|
4034
|
+
<div class="flex justify-center p-5">
|
|
4035
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
4615
4036
|
</div>
|
|
4616
4037
|
}
|
|
4617
4038
|
|
|
4618
4039
|
<!-- Action List -->
|
|
4619
4040
|
@if (!loading() && actions().length > 0) {
|
|
4620
|
-
<div class="surface-card p-4 border
|
|
4041
|
+
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4621
4042
|
<div
|
|
4622
|
-
class="flex flex-
|
|
4043
|
+
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4623
4044
|
>
|
|
4624
4045
|
<div>
|
|
4625
4046
|
<h5 class="m-0 mb-1">Direct Action Permissions</h5>
|
|
4626
|
-
<p class="text-sm text-color
|
|
4047
|
+
<p class="text-sm text-muted-color m-0">
|
|
4627
4048
|
{{ actions().length }} actions available
|
|
4628
4049
|
</p>
|
|
4629
4050
|
</div>
|
|
@@ -4645,6 +4066,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4645
4066
|
(onClick)="deselectAll()"
|
|
4646
4067
|
/>
|
|
4647
4068
|
<p-button
|
|
4069
|
+
*hasPermission="USER_ACTION_PERMISSIONS.ASSIGN"
|
|
4648
4070
|
label="Save Changes"
|
|
4649
4071
|
icon="pi pi-save"
|
|
4650
4072
|
[disabled]="!canSave()"
|
|
@@ -4656,34 +4078,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4656
4078
|
</div>
|
|
4657
4079
|
</div>
|
|
4658
4080
|
|
|
4081
|
+
<!-- Validation Warning -->
|
|
4082
|
+
@if (invalidActionsCount() > 0) {
|
|
4083
|
+
<div class="validation-warning rounded-border p-3 mb-4">
|
|
4084
|
+
<div class="flex items-start gap-2">
|
|
4085
|
+
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
4086
|
+
<div class="flex-1">
|
|
4087
|
+
<span class="font-semibold">Validation Warning:</span>
|
|
4088
|
+
<p class="text-sm mt-1 mb-0">
|
|
4089
|
+
{{ invalidActionsCount() }} selected
|
|
4090
|
+
action{{ invalidActionsCount() > 1 ? 's have' : ' has' }}
|
|
4091
|
+
unmet prerequisites. Fix before saving or use auto-fix on
|
|
4092
|
+
save.
|
|
4093
|
+
</p>
|
|
4094
|
+
</div>
|
|
4095
|
+
</div>
|
|
4096
|
+
</div>
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4659
4099
|
<!-- 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
|
|
4100
|
+
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
4101
|
+
<p-treeTable
|
|
4102
|
+
[value]="treeNodes()"
|
|
4103
|
+
dataKey="id"
|
|
4104
|
+
styleClass="p-treetable-sm"
|
|
4105
|
+
[tableStyle]="{ 'min-width': '50rem' }"
|
|
4106
|
+
>
|
|
4107
|
+
<ng-template #header>
|
|
4108
|
+
<tr>
|
|
4109
|
+
<th class="w-12">
|
|
4110
|
+
<p-checkbox
|
|
4111
|
+
[ngModel]="allSelected()"
|
|
4112
|
+
[binary]="true"
|
|
4113
|
+
(ngModelChange)="toggleAll()"
|
|
4114
|
+
pTooltip="Select/Deselect All"
|
|
4115
|
+
tooltipPosition="top"
|
|
4116
|
+
/>
|
|
4117
|
+
</th>
|
|
4118
|
+
<th>Name</th>
|
|
4119
|
+
<th class="hidden md:table-cell">Code</th>
|
|
4120
|
+
<th>Type</th>
|
|
4121
|
+
<th class="hidden lg:table-cell">Description</th>
|
|
4122
|
+
</tr>
|
|
4123
|
+
</ng-template>
|
|
4124
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
4125
|
+
<tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
|
|
4126
|
+
<td class="w-12">
|
|
4687
4127
|
<p-checkbox
|
|
4688
4128
|
[ngModel]="selectionMap()[rowData.id]"
|
|
4689
4129
|
[binary]="true"
|
|
@@ -4694,9 +4134,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4694
4134
|
</td>
|
|
4695
4135
|
<td>
|
|
4696
4136
|
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4697
|
-
<span
|
|
4137
|
+
<span class="inline-flex items-center gap-2">
|
|
4138
|
+
{{ rowData.name }}
|
|
4139
|
+
@if (hasUnmetPrerequisites(rowData)) {
|
|
4140
|
+
<i
|
|
4141
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
4142
|
+
pTooltip="This action has unmet prerequisites and will fail validation on save"
|
|
4143
|
+
tooltipPosition="top"
|
|
4144
|
+
></i>
|
|
4145
|
+
}
|
|
4146
|
+
</span>
|
|
4698
4147
|
</td>
|
|
4699
|
-
<td>{{ rowData.code || '-' }}</td>
|
|
4148
|
+
<td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
|
|
4700
4149
|
<td>
|
|
4701
4150
|
<p-tag
|
|
4702
4151
|
[value]="rowData.actionType"
|
|
@@ -4705,12 +4154,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4705
4154
|
"
|
|
4706
4155
|
/>
|
|
4707
4156
|
</td>
|
|
4708
|
-
<td>{{ rowData.description || '-' }}</td>
|
|
4157
|
+
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4709
4158
|
</tr>
|
|
4710
4159
|
</ng-template>
|
|
4711
|
-
<ng-template
|
|
4160
|
+
<ng-template #emptymessage>
|
|
4712
4161
|
<tr>
|
|
4713
|
-
<td colspan="5" class="text-center p-4">
|
|
4162
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4714
4163
|
@if (loading()) {
|
|
4715
4164
|
<i class="pi pi-spin pi-spinner"></i> Loading actions...
|
|
4716
4165
|
} @else {
|
|
@@ -4719,24 +4168,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4719
4168
|
</td>
|
|
4720
4169
|
</tr>
|
|
4721
4170
|
</ng-template>
|
|
4722
|
-
|
|
4171
|
+
</p-treeTable>
|
|
4172
|
+
</div>
|
|
4723
4173
|
</div>
|
|
4724
4174
|
|
|
4725
4175
|
<!-- Change Summary -->
|
|
4726
4176
|
@if (hasChanges()) {
|
|
4727
|
-
<div class="
|
|
4728
|
-
<div class="flex
|
|
4729
|
-
<i class="pi pi-info-circle text-
|
|
4730
|
-
<
|
|
4177
|
+
<div class="border border-surface rounded-border p-3 mt-4">
|
|
4178
|
+
<div class="flex items-center gap-2 mb-3">
|
|
4179
|
+
<i class="pi pi-info-circle text-primary"></i>
|
|
4180
|
+
<span class="font-bold">Pending Changes</span>
|
|
4731
4181
|
</div>
|
|
4732
|
-
<div class="
|
|
4182
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4733
4183
|
@if (pendingAdd().length > 0) {
|
|
4734
|
-
<div
|
|
4735
|
-
<div class="flex
|
|
4184
|
+
<div>
|
|
4185
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4736
4186
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4737
|
-
<strong class="text-sm"
|
|
4738
|
-
>To Assign ({{ pendingAdd().length }})</strong
|
|
4739
|
-
>
|
|
4187
|
+
<strong class="text-sm">To Assign ({{ pendingAdd().length }})</strong>
|
|
4740
4188
|
</div>
|
|
4741
4189
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4742
4190
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4746,12 +4194,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4746
4194
|
</div>
|
|
4747
4195
|
}
|
|
4748
4196
|
@if (pendingRemove().length > 0) {
|
|
4749
|
-
<div
|
|
4750
|
-
<div class="flex
|
|
4197
|
+
<div>
|
|
4198
|
+
<div class="flex items-center gap-2 mb-2">
|
|
4751
4199
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4752
|
-
<strong class="text-sm"
|
|
4753
|
-
>To Remove ({{ pendingRemove().length }})</strong
|
|
4754
|
-
>
|
|
4200
|
+
<strong class="text-sm">To Remove ({{ pendingRemove().length }})</strong>
|
|
4755
4201
|
</div>
|
|
4756
4202
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4757
4203
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4766,19 +4212,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
4766
4212
|
}
|
|
4767
4213
|
|
|
4768
4214
|
@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">
|
|
4215
|
+
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
4216
|
+
<i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
|
|
4217
|
+
<p class="text-muted-color m-0">
|
|
4775
4218
|
No actions available for this user.
|
|
4776
4219
|
</p>
|
|
4777
4220
|
</div>
|
|
4778
4221
|
}
|
|
4779
4222
|
}
|
|
4780
4223
|
</div>
|
|
4781
|
-
`, styles: [":host{display:block}\n"] }]
|
|
4224
|
+
`, 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
4225
|
}], ctorParameters: () => [] });
|
|
4783
4226
|
|
|
4784
4227
|
// Logic Builder Component
|
|
@@ -4828,97 +4271,83 @@ class ProfilePermissionProviderAdapter {
|
|
|
4828
4271
|
})),
|
|
4829
4272
|
})));
|
|
4830
4273
|
}
|
|
4831
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
4832
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
4274
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfilePermissionProviderAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4275
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfilePermissionProviderAdapter });
|
|
4833
4276
|
}
|
|
4834
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
4277
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfilePermissionProviderAdapter, decorators: [{
|
|
4835
4278
|
type: Injectable
|
|
4836
4279
|
}] });
|
|
4837
4280
|
|
|
4838
|
-
/**
|
|
4839
|
-
* Provide IAM Provider Adapters
|
|
4840
|
-
*
|
|
4841
|
-
* Registers IAM implementations for provider interfaces from ng-shared.
|
|
4842
|
-
* This allows ng-auth profile page to display permissions without direct dependencies.
|
|
4843
|
-
*
|
|
4844
|
-
* @example
|
|
4845
|
-
* // In app.config.ts
|
|
4846
|
-
* import { provideIamProviders } from '@flusys/ng-iam';
|
|
4847
|
-
*
|
|
4848
|
-
* export const appConfig: ApplicationConfig = {
|
|
4849
|
-
* providers: [
|
|
4850
|
-
* ...provideIamProviders(),
|
|
4851
|
-
* // ... other providers
|
|
4852
|
-
* ]
|
|
4853
|
-
* };
|
|
4854
|
-
*
|
|
4855
|
-
* @returns Array of Angular providers
|
|
4856
|
-
*/
|
|
4281
|
+
/** Registers IAM provider adapters for ng-shared interfaces */
|
|
4857
4282
|
function provideIamProviders() {
|
|
4858
4283
|
return [
|
|
4859
|
-
|
|
4860
|
-
{
|
|
4861
|
-
provide: PROFILE_PERMISSION_PROVIDER,
|
|
4862
|
-
useClass: ProfilePermissionProviderAdapter,
|
|
4863
|
-
},
|
|
4284
|
+
{ provide: PROFILE_PERMISSION_PROVIDER, useClass: ProfilePermissionProviderAdapter },
|
|
4864
4285
|
];
|
|
4865
4286
|
}
|
|
4866
4287
|
|
|
4867
4288
|
/**
|
|
4868
4289
|
* IAM Routes Configuration
|
|
4869
4290
|
*
|
|
4870
|
-
* Identity and Access Management routing
|
|
4871
|
-
* - Actions: Permission actions
|
|
4291
|
+
* Identity and Access Management routing with permission guards.
|
|
4292
|
+
* - Actions: Permission actions management
|
|
4872
4293
|
* - Roles: Role management (conditional on RBAC/FULL mode)
|
|
4873
|
-
* - Permissions: User permission assignments
|
|
4874
|
-
*
|
|
4875
|
-
* All routes are protected by permission guards to prevent direct URL access.
|
|
4294
|
+
* - Permissions: User permission assignments
|
|
4876
4295
|
*/
|
|
4877
4296
|
const IAM_ROUTES = [
|
|
4878
4297
|
{
|
|
4879
4298
|
path: '',
|
|
4880
|
-
loadComponent: () => import('./flusys-ng-iam-iam-container.component-
|
|
4299
|
+
loadComponent: () => import('./flusys-ng-iam-iam-container.component-UYJjqYV9.mjs').then((m) => m.IamContainerComponent),
|
|
4881
4300
|
children: [
|
|
4882
4301
|
// Actions Management
|
|
4883
4302
|
{
|
|
4884
4303
|
path: 'actions',
|
|
4304
|
+
canActivate: [permissionGuard(ACTION_PERMISSIONS.READ)],
|
|
4885
4305
|
children: [
|
|
4886
4306
|
{
|
|
4887
4307
|
path: '',
|
|
4888
|
-
loadComponent: () => import('./flusys-ng-iam-action-list-page.component-
|
|
4308
|
+
loadComponent: () => import('./flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs').then((m) => m.ActionListPageComponent),
|
|
4889
4309
|
},
|
|
4890
4310
|
{
|
|
4891
4311
|
path: 'new',
|
|
4892
|
-
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-
|
|
4312
|
+
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs').then((m) => m.ActionFormPageComponent),
|
|
4893
4313
|
},
|
|
4894
4314
|
{
|
|
4895
4315
|
path: ':id',
|
|
4896
|
-
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-
|
|
4316
|
+
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs').then((m) => m.ActionFormPageComponent),
|
|
4897
4317
|
},
|
|
4898
4318
|
],
|
|
4899
4319
|
},
|
|
4900
4320
|
// Roles Management
|
|
4901
4321
|
{
|
|
4902
4322
|
path: 'roles',
|
|
4323
|
+
canActivate: [permissionGuard(ROLE_PERMISSIONS.READ)],
|
|
4903
4324
|
children: [
|
|
4904
4325
|
{
|
|
4905
4326
|
path: '',
|
|
4906
|
-
loadComponent: () => import('./flusys-ng-iam-role-list-page.component-
|
|
4327
|
+
loadComponent: () => import('./flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs').then((m) => m.RoleListPageComponent),
|
|
4907
4328
|
},
|
|
4908
4329
|
{
|
|
4909
4330
|
path: 'new',
|
|
4910
|
-
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-
|
|
4331
|
+
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-D_AAEay2.mjs').then((m) => m.RoleFormPageComponent),
|
|
4911
4332
|
},
|
|
4912
4333
|
{
|
|
4913
4334
|
path: ':id',
|
|
4914
|
-
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-
|
|
4335
|
+
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-D_AAEay2.mjs').then((m) => m.RoleFormPageComponent),
|
|
4915
4336
|
},
|
|
4916
4337
|
],
|
|
4917
4338
|
},
|
|
4918
|
-
// Permissions Management (
|
|
4339
|
+
// Permissions Management (requires any permission tab access)
|
|
4919
4340
|
{
|
|
4920
4341
|
path: 'permissions',
|
|
4921
|
-
|
|
4342
|
+
canActivate: [
|
|
4343
|
+
anyPermissionGuard([
|
|
4344
|
+
ROLE_ACTION_PERMISSIONS.READ,
|
|
4345
|
+
USER_ROLE_PERMISSIONS.READ,
|
|
4346
|
+
USER_ACTION_PERMISSIONS.READ,
|
|
4347
|
+
COMPANY_ACTION_PERMISSIONS.READ,
|
|
4348
|
+
]),
|
|
4349
|
+
],
|
|
4350
|
+
loadComponent: () => import('./flusys-ng-iam-permission-page.component-DcgT7L3_.mjs').then((m) => m.PermissionPageComponent),
|
|
4922
4351
|
},
|
|
4923
4352
|
// Default redirect to actions
|
|
4924
4353
|
{
|
|
@@ -4937,4 +4366,4 @@ const IAM_ROUTES = [
|
|
|
4937
4366
|
*/
|
|
4938
4367
|
|
|
4939
4368
|
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 };
|
|
4940
|
-
//# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-
|
|
4369
|
+
//# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs.map
|