@flusys/ng-iam 1.0.0-rc → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/README.md +1 -1
  2. package/fesm2022/{flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs → flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs} +14 -64
  3. package/fesm2022/flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs.map +1 -0
  4. package/fesm2022/{flusys-ng-iam-action-list-page.component-Daf93zpS.mjs → flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs} +22 -49
  5. package/fesm2022/flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs.map +1 -0
  6. package/fesm2022/{flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs → flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs} +295 -859
  7. package/fesm2022/flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs.map +1 -0
  8. package/fesm2022/{flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs → flusys-ng-iam-iam-container.component-UYJjqYV9.mjs} +5 -5
  9. package/fesm2022/flusys-ng-iam-iam-container.component-UYJjqYV9.mjs.map +1 -0
  10. package/fesm2022/{flusys-ng-iam-permission-page.component-CmxOBJPu.mjs → flusys-ng-iam-permission-page.component-DcgT7L3_.mjs} +11 -46
  11. package/fesm2022/flusys-ng-iam-permission-page.component-DcgT7L3_.mjs.map +1 -0
  12. package/fesm2022/{flusys-ng-iam-role-form-page.component-ByNueI1a.mjs → flusys-ng-iam-role-form-page.component-D_AAEay2.mjs} +5 -19
  13. package/fesm2022/flusys-ng-iam-role-form-page.component-D_AAEay2.mjs.map +1 -0
  14. package/fesm2022/{flusys-ng-iam-role-list-page.component-CFly5KnH.mjs → flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs} +6 -23
  15. package/fesm2022/flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs.map +1 -0
  16. package/fesm2022/flusys-ng-iam.mjs +1 -1
  17. package/package.json +11 -11
  18. package/types/flusys-ng-iam.d.ts +46 -445
  19. package/fesm2022/flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs.map +0 -1
  20. package/fesm2022/flusys-ng-iam-action-list-page.component-Daf93zpS.mjs.map +0 -1
  21. package/fesm2022/flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs.map +0 -1
  22. package/fesm2022/flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs.map +0 -1
  23. package/fesm2022/flusys-ng-iam-permission-page.component-CmxOBJPu.mjs.map +0 -1
  24. package/fesm2022/flusys-ng-iam-role-form-page.component-ByNueI1a.mjs.map +0 -1
  25. package/fesm2022/flusys-ng-iam-role-list-page.component-CFly5KnH.mjs.map +0 -1
@@ -1,8 +1,8 @@
1
- import * as i0 from '@angular/core';
2
- import { inject, Injectable, signal, input, output, computed, effect, ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
3
1
  import { HttpClient } from '@angular/common/http';
4
- import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, ROLE_ACTION_PERMISSIONS, HasPermissionDirective, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, USER_ROLE_PERMISSIONS, USER_PERMISSION_PROVIDER, UserSelectComponent, USER_ACTION_PERMISSIONS, PROFILE_PERMISSION_PROVIDER } from '@flusys/ng-shared';
5
- import { APP_CONFIG, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
2
+ import * as i0 from '@angular/core';
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';
6
6
  import { ConfirmationService, MessageService } from 'primeng/api';
7
7
  import { of, firstValueFrom, map as map$1 } from 'rxjs';
8
8
  import { tap, catchError, map } from 'rxjs/operators';
@@ -19,11 +19,7 @@ import * as i7 from 'primeng/treetable';
19
19
  import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
20
20
  import * as i5$1 from 'primeng/table';
21
21
 
22
- // ==================== ENUMS ====================
23
- /**
24
- * Action Type - determines how action is used
25
- * CRITICAL: Must match backend ActionType enum exactly
26
- */
22
+ /** Must match backend ActionType enum */
27
23
  var ActionType;
28
24
  (function (ActionType) {
29
25
  ActionType["BACKEND"] = "backend";
@@ -33,130 +29,66 @@ var ActionType;
33
29
 
34
30
  // Company interfaces
35
31
 
36
- /**
37
- * Pagination Constants
38
- *
39
- * Standard pagination limits for IAM components.
40
- * Prevents excessive data loading and potential DoS.
41
- */
42
- /**
43
- * Maximum items to fetch for dropdown lists
44
- * Used for: companies, roles, users, branches
45
- *
46
- * Security: Prevents memory exhaustion from loading excessive records
47
- */
32
+ /** Maximum items for dropdown lists (companies, roles, users, branches) */
48
33
  const MAX_DROPDOWN_ITEMS = 100;
49
34
 
50
- /**
51
- * Role API Service
52
- * Handles role CRUD operations
53
- * Endpoint: POST /iam/roles/*
54
- * Conditional: Only available in RBAC/FULL mode
55
- */
56
35
  class RoleApiService extends ApiResourceService {
57
36
  constructor() {
58
- const http = inject(HttpClient);
59
- super('iam/roles', http);
37
+ super('roles', inject(HttpClient), 'iam');
60
38
  }
61
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
62
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, providedIn: 'root' });
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' });
63
41
  }
64
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, decorators: [{
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleApiService, decorators: [{
65
43
  type: Injectable,
66
- args: [{
67
- providedIn: 'root',
68
- }]
44
+ args: [{ providedIn: 'root' }]
69
45
  }], ctorParameters: () => [] });
70
46
 
71
- /**
72
- * Action API Service
73
- * Handles action CRUD operations
74
- * Endpoint: POST /iam/actions/*
75
- */
76
47
  class ActionApiService extends ApiResourceService {
77
48
  appConfig = inject(APP_CONFIG);
78
49
  constructor() {
79
- const http = inject(HttpClient);
80
- super('iam/actions', http);
50
+ super('actions', inject(HttpClient), 'iam');
81
51
  }
82
- /**
83
- * Get actions for permission assignment
84
- * POST /iam/actions/tree-for-permission
85
- * Returns actions filtered by company whitelist if enabled
86
- */
52
+ /** Get actions filtered by company whitelist for permission assignment */
87
53
  getActionsForPermission() {
88
- return this.http.post(`${this.appConfig.apiBaseUrl}/iam/actions/tree-for-permission`, {});
54
+ const iamBaseUrl = getServiceUrl(this.appConfig, 'iam');
55
+ return this.http.post(`${iamBaseUrl}/actions/tree-for-permission`, {});
89
56
  }
90
- /**
91
- * Get actions in hierarchical tree structure
92
- * POST /iam/actions/tree
93
- * Returns all actions organized in parent-child tree
94
- *
95
- * @param search - Optional search term (name or code)
96
- * @param isActive - Optional filter by active status
97
- * @param withDeleted - Include deleted actions (default: false)
98
- * @returns Observable of action tree response
99
- */
57
+ /** Get actions in hierarchical tree structure */
100
58
  getTree(search, isActive, withDeleted) {
101
59
  const body = {};
102
- if (search?.trim()) {
60
+ if (search?.trim())
103
61
  body.search = search.trim();
104
- }
105
- if (isActive !== undefined) {
62
+ if (isActive !== undefined)
106
63
  body.isActive = isActive;
107
- }
108
- if (withDeleted !== undefined) {
64
+ if (withDeleted !== undefined)
109
65
  body.withDeleted = withDeleted;
110
- }
111
- return this.http.post(`${this.appConfig.apiBaseUrl}/iam/actions/tree`, body);
66
+ const iamBaseUrl = getServiceUrl(this.appConfig, 'iam');
67
+ return this.http.post(`${iamBaseUrl}/actions/tree`, body);
112
68
  }
113
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
114
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, providedIn: 'root' });
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' });
115
71
  }
116
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, decorators: [{
72
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionApiService, decorators: [{
117
73
  type: Injectable,
118
- args: [{
119
- providedIn: 'root',
120
- }]
74
+ args: [{ providedIn: 'root' }]
121
75
  }], ctorParameters: () => [] });
122
76
 
123
- /**
124
- * Extract all required action IDs from a permission logic tree
125
- * This is useful for prerequisite validation
126
- *
127
- * @param logic - Permission logic to analyze
128
- * @returns Set of action IDs that are required
129
- */
130
77
  function extractRequiredActionIds(logic) {
131
78
  const actionIds = new Set();
132
- if (!logic) {
133
- return actionIds;
79
+ if (logic) {
80
+ collectActionIds(logic, actionIds);
134
81
  }
135
- collectActionIds(logic, actionIds);
136
82
  return actionIds;
137
83
  }
138
- /**
139
- * Recursively collect action IDs from logic tree
140
- */
141
84
  function collectActionIds(node, actionIds) {
142
85
  if (node.type === 'action' && node.actionId) {
143
86
  actionIds.add(node.actionId);
144
87
  }
145
88
  else if (node.type === 'group' && node.children) {
146
- for (const child of node.children) {
147
- collectActionIds(child, actionIds);
148
- }
89
+ node.children.forEach((child) => collectActionIds(child, actionIds));
149
90
  }
150
91
  }
151
- /**
152
- * Validate if an action's prerequisites are satisfied
153
- * Respects AND/OR logic operators in permission tree
154
- *
155
- * @param action - Action to validate
156
- * @param selectedActionIds - Set of currently selected action IDs
157
- * @param allActions - All available actions (to look up missing ones)
158
- * @returns Validation result with missing actions
159
- */
160
92
  function validateActionPrerequisites(action, selectedActionIds, allActions) {
161
93
  if (!action.permissionLogic) {
162
94
  return { valid: true, missingActions: [] };
@@ -168,20 +100,12 @@ function validateActionPrerequisites(action, selectedActionIds, allActions) {
168
100
  const missingActions = allActions.filter((a) => a.id && result.missingActionIds.has(a.id));
169
101
  return { valid: false, missingActions };
170
102
  }
171
- /**
172
- * Recursively evaluate logic node respecting AND/OR operators
173
- * Returns validation result with missing action IDs (for prerequisite dialogs)
174
- * Note: This differs from evaluateLogicNode in ng-shared which returns boolean only
175
- */
176
103
  function evaluateLogicNodeWithMissing(node, selectedActionIds) {
177
104
  if (node.type === 'action' && node.actionId) {
178
105
  const valid = selectedActionIds.has(node.actionId);
179
- return {
180
- valid,
181
- missingActionIds: valid ? new Set() : new Set([node.actionId]),
182
- };
106
+ return { valid, missingActionIds: valid ? new Set() : new Set([node.actionId]) };
183
107
  }
184
- if (node.type === 'group' && node.children && node.children.length > 0) {
108
+ if (node.type === 'group' && node.children?.length) {
185
109
  const operator = node.operator || 'AND';
186
110
  if (operator === 'AND') {
187
111
  const missingIds = new Set();
@@ -195,109 +119,28 @@ function evaluateLogicNodeWithMissing(node, selectedActionIds) {
195
119
  }
196
120
  return { valid: allValid, missingActionIds: missingIds };
197
121
  }
198
- else {
199
- for (const child of node.children) {
200
- const childResult = evaluateLogicNodeWithMissing(child, selectedActionIds);
201
- if (childResult.valid) {
202
- return { valid: true, missingActionIds: new Set() };
203
- }
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() };
204
127
  }
205
- const firstChildResult = evaluateLogicNodeWithMissing(node.children[0], selectedActionIds);
206
- return {
207
- valid: false,
208
- missingActionIds: firstChildResult.missingActionIds,
209
- };
210
128
  }
129
+ const firstChildResult = evaluateLogicNodeWithMissing(node.children[0], selectedActionIds);
130
+ return { valid: false, missingActionIds: firstChildResult.missingActionIds };
211
131
  }
212
132
  return { valid: true, missingActionIds: new Set() };
213
133
  }
214
- /**
215
- * Get human-readable prerequisite description
216
- *
217
- * @param logic - Permission logic to describe
218
- * @param allActions - All available actions for name lookup
219
- * @returns Human-readable string describing prerequisites
220
- */
221
- function describePrerequisites(logic, allActions) {
222
- if (!logic) {
223
- return 'None';
224
- }
225
- const requiredIds = extractRequiredActionIds(logic);
226
- if (requiredIds.size === 0) {
227
- return 'None';
228
- }
229
- const actionMap = new Map(allActions.map((a) => [a.id, a]));
230
- const names = Array.from(requiredIds)
231
- .map((id) => actionMap.get(id)?.name || id)
232
- .filter(Boolean);
233
- if (names.length === 0) {
234
- return 'Unknown prerequisites';
235
- }
236
- if (names.length === 1) {
237
- return names[0];
238
- }
239
- return names.join(', ');
240
- }
241
134
 
242
- /**
243
- * Action Permission Logic Service
244
- *
245
- * Shared service for handling smart dependency management across all action selectors:
246
- * - Company-Action Selector
247
- * - Role-Action Selector
248
- * - User-Action Selector
249
- *
250
- * **Core Features:**
251
- * - Smart auto-selection (AND/OR optimization)
252
- * - Dependency detection and management
253
- * - Alternative suggestion for OR logic
254
- * - Visual formatting of permission logic trees
255
- * - Prerequisite validation
256
- *
257
- * @example
258
- * constructor() {
259
- * this.permissionLogic = inject(ActionPermissionLogicService);
260
- * }
261
- *
262
- * onActionToggle(action: IAction, newValue: boolean) {
263
- * if (!newValue) {
264
- * this.permissionLogic.handleUncheck(
265
- * action,
266
- * this.selectionMap(),
267
- * this.actions(),
268
- * (newMap) => this.selectionMap.set(newMap)
269
- * );
270
- * } else {
271
- * this.permissionLogic.handleCheck(
272
- * action,
273
- * this.selectionMap(),
274
- * this.actions(),
275
- * (newMap) => this.selectionMap.set(newMap),
276
- * (previousState) => this.selectionMap.set(previousState)
277
- * );
278
- * }
279
- * }
280
- */
135
+ /** Shared service for smart dependency management across action selectors */
281
136
  class ActionPermissionLogicService {
282
137
  confirmationService = inject(ConfirmationService);
283
138
  messageService = inject(MessageService);
284
- /**
285
- * Handle checking an action with prerequisite validation
286
- *
287
- * Uses recursive deep scan to find ALL missing prerequisites at all levels,
288
- * not just direct dependencies. This ensures cascading dependencies are
289
- * resolved in a single step.
290
- *
291
- * @param action - Action being checked
292
- * @param currentSelection - Current selection map
293
- * @param allActions - All available actions
294
- * @param onUpdate - Callback to update selection
295
- * @param onCancel - Callback when user cancels
296
- */
139
+ /** Handle checking an action with prerequisite validation (recursive deep scan) */
297
140
  handleCheck(action, currentSelection, allActions, onUpdate, onCancel) {
298
141
  // Validate prerequisites with RECURSIVE DEEP SCAN
299
142
  if (action.permissionLogic) {
300
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
143
+ const selectedActionIds = this.getSelectedIds(currentSelection);
301
144
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
302
145
  if (!validationResult.valid) {
303
146
  // Store previous state
@@ -318,14 +161,7 @@ class ActionPermissionLogicService {
318
161
  selMap[action.id] = true;
319
162
  onUpdate(selMap);
320
163
  }
321
- /**
322
- * Handle unchecking an action with dependency detection
323
- *
324
- * @param action - Action being unchecked
325
- * @param currentSelection - Current selection map
326
- * @param allActions - All available actions
327
- * @param onUpdate - Callback to update selection
328
- */
164
+ /** Handle unchecking an action with dependency detection */
329
165
  handleUncheck(action, currentSelection, allActions, onUpdate) {
330
166
  const affectedActions = this.findActionsDependingOn(action.id, currentSelection, allActions);
331
167
  if (affectedActions.length > 0) {
@@ -337,46 +173,28 @@ class ActionPermissionLogicService {
337
173
  selMap[action.id] = false;
338
174
  onUpdate(selMap);
339
175
  }
340
- /**
341
- * Check if an action has unmet prerequisites
342
- *
343
- * @param action - Action to check
344
- * @param currentSelection - Current selection map
345
- * @param allActions - All available actions
346
- * @returns True if action has unmet prerequisites
347
- */
176
+ /** Check if an action has unmet prerequisites */
348
177
  hasUnmetPrerequisites(action, currentSelection, allActions) {
349
178
  const isSelected = currentSelection[action.id];
350
179
  if (!isSelected || !action.permissionLogic) {
351
180
  return false;
352
181
  }
353
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id] && id !== action.id));
182
+ const selectedActionIds = this.getSelectedIds(currentSelection);
183
+ selectedActionIds.delete(action.id);
354
184
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
355
185
  return !validationResult.valid;
356
186
  }
357
- /**
358
- * Get all selected actions that have unmet prerequisites
359
- *
360
- * @param currentSelection - Current selection map
361
- * @param allActions - All available actions
362
- * @returns Array of actions with unmet prerequisites
363
- */
187
+ /** Get all selected actions that have unmet prerequisites */
364
188
  getActionsWithUnmetPrerequisites(currentSelection, allActions) {
365
189
  const selectedActions = allActions.filter((a) => currentSelection[a.id]);
366
190
  return selectedActions.filter((action) => this.hasUnmetPrerequisites(action, currentSelection, allActions));
367
191
  }
368
- /**
369
- * Show validation error dialog with auto-fix options
370
- *
371
- * @param invalidActions - Actions with unmet prerequisites
372
- * @param currentSelection - Current selection map
373
- * @param allActions - All available actions
374
- * @param onUpdate - Callback to update selection
375
- */
192
+ /** Show validation error dialog with auto-fix options */
376
193
  showValidationErrorDialog(invalidActions, currentSelection, allActions, onUpdate) {
377
194
  const errorList = invalidActions
378
195
  .map((action) => {
379
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id] && id !== action.id));
196
+ const selectedActionIds = this.getSelectedIds(currentSelection);
197
+ selectedActionIds.delete(action.id);
380
198
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
381
199
  const sanitizedActionName = this.sanitizeHtml(action.name ?? 'Unknown');
382
200
  const missingNames = validationResult.missingActions
@@ -418,19 +236,12 @@ class ActionPermissionLogicService {
418
236
  },
419
237
  });
420
238
  }
421
- /**
422
- * Get prerequisite description for tooltip display
423
- *
424
- * @param action - Action to get prerequisites for
425
- * @param currentSelection - Current selection map
426
- * @param allActions - All available actions
427
- * @returns Plain text prerequisite description
428
- */
239
+ /** Get prerequisite description for tooltip display */
429
240
  getPrerequisiteTooltip(action, currentSelection, allActions) {
430
241
  if (!action.permissionLogic) {
431
242
  return '';
432
243
  }
433
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
244
+ const selectedActionIds = this.getSelectedIds(currentSelection);
434
245
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
435
246
  if (validationResult.valid) {
436
247
  return '[OK] Prerequisites Satisfied\n\nAll required actions are already selected.\nYou can safely add this action.';
@@ -442,23 +253,16 @@ class ActionPermissionLogicService {
442
253
  const hint = '\n\n💡 Click to auto-select required actions';
443
254
  return `${header}\n\n${logicTree}${hint}`;
444
255
  }
445
- /**
446
- * Build dynamic logic tree message with AND/OR operators and nesting
447
- *
448
- * @param logic - Permission logic tree
449
- * @param missingActions - Actions that are missing
450
- * @param allActions - All available actions
451
- * @param currentSelection - Current selection map for accurate status
452
- * @returns HTML formatted logic tree
453
- */
454
- buildLogicMessage(logic, missingActions, allActions, currentSelection) {
455
- const selectedIds = currentSelection
456
- ? new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]))
457
- : 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();
458
259
  const actionMap = new Map(allActions.map((a) => [a.id, a]));
459
260
  return this.formatLogicNode(logic, selectedIds, actionMap, 0);
460
261
  }
461
- // ==================== Private Methods ====================
262
+ /** Extract selected action IDs from selection map */
263
+ getSelectedIds(currentSelection) {
264
+ return new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
265
+ }
462
266
  sanitizeHtml(text) {
463
267
  return text
464
268
  .replace(/&/g, '&')
@@ -467,24 +271,7 @@ class ActionPermissionLogicService {
467
271
  .replace(/"/g, '"')
468
272
  .replace(/'/g, ''');
469
273
  }
470
- /**
471
- * Recursively collect ALL missing prerequisites at all dependency levels
472
- *
473
- * This prevents cascading prerequisite dialogs by finding the complete
474
- * dependency chain upfront.
475
- *
476
- * **Example:**
477
- * - Action 4 requires Action 3
478
- * - Action 3 requires Action 2
479
- * - Action 2 requires Action 1
480
- *
481
- * Instead of showing 3 separate dialogs, this returns: [Action 3, Action 2, Action 1]
482
- *
483
- * @param action - Starting action to check
484
- * @param currentSelection - Current selection map
485
- * @param allActions - All available actions
486
- * @returns Complete set of missing prerequisites across all levels
487
- */
274
+ /** Recursively collect ALL missing prerequisites at all dependency levels */
488
275
  getAllMissingPrerequisitesRecursive(action, currentSelection, allActions) {
489
276
  if (!action.id)
490
277
  return [];
@@ -504,7 +291,7 @@ class ActionPermissionLogicService {
504
291
  }
505
292
  visited.add(targetAction.id);
506
293
  stack.add(targetAction.id);
507
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
294
+ const selectedActionIds = this.getSelectedIds(currentSelection);
508
295
  const result = validateActionPrerequisites(targetAction, selectedActionIds, allActions);
509
296
  if (!result.valid) {
510
297
  result.missingActions.forEach((missingAction) => {
@@ -565,7 +352,7 @@ class ActionPermissionLogicService {
565
352
  },
566
353
  });
567
354
  }
568
- showDependencyDialog(action, affectedActions, currentSelection, allActions, onUpdate) {
355
+ showDependencyDialog(action, affectedActions, currentSelection, _allActions, onUpdate) {
569
356
  if (!action.id)
570
357
  return;
571
358
  const alternatives = this.findAlternatives(action.id, affectedActions, currentSelection);
@@ -688,7 +475,7 @@ class ActionPermissionLogicService {
688
475
  calculateSmartSelection(logic, missingActions, currentSelection, allActions) {
689
476
  const missingIds = new Set(missingActions.map((a) => a.id));
690
477
  const actionMap = new Map(allActions.map((a) => [a.id, a]));
691
- const selectedIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
478
+ const selectedIds = this.getSelectedIds(currentSelection);
692
479
  const requiredIds = this.findRequiredActionIds(logic, missingIds, selectedIds);
693
480
  return Array.from(requiredIds)
694
481
  .map((id) => actionMap.get(id))
@@ -761,12 +548,10 @@ class ActionPermissionLogicService {
761
548
  }
762
549
  return `${indent}<em style="color: #9ca3af;">Invalid logic node</em>`;
763
550
  }
764
- /**
765
- * Build clean text-based logic tree for tooltips
766
- */
551
+ /** Build clean text-based logic tree for tooltips */
767
552
  buildTooltipLogicTree(node, currentSelection, allActions, depth) {
768
553
  const indent = ' '.repeat(depth);
769
- const selectedIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
554
+ const selectedIds = this.getSelectedIds(currentSelection);
770
555
  const actionMap = new Map(allActions.map((a) => [a.id, a]));
771
556
  if (node.type === 'action' && node.actionId) {
772
557
  const action = actionMap.get(node.actionId);
@@ -796,107 +581,48 @@ class ActionPermissionLogicService {
796
581
  }
797
582
  return `${indent}(invalid)`;
798
583
  }
799
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
800
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, providedIn: 'root' });
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' });
801
586
  }
802
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, decorators: [{
587
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ActionPermissionLogicService, decorators: [{
803
588
  type: Injectable,
804
- args: [{
805
- providedIn: 'root',
806
- }]
589
+ args: [{ providedIn: 'root' }]
807
590
  }] });
808
591
 
809
- /**
810
- * Consolidated Permission API Service
811
- * Handles all permission-related operations in one service
812
- * Supports:
813
- * - User → Action (direct permissions)
814
- * - User → Role (role assignments)
815
- * - Role → Action (role permissions)
816
- * - Company → Action (company whitelisting)
817
- *
818
- * Endpoint: POST /permissions/*
819
- */
820
592
  class PermissionApiService extends BaseApiService {
821
593
  constructor() {
822
594
  super('iam');
823
595
  }
824
- // =============================================================================
825
- // User → Action (Direct Permissions)
826
- // =============================================================================
827
- /**
828
- * Assign/remove actions directly to/from user
829
- * POST /permissions/user-actions/assign
830
- */
831
596
  assignUserActions(data) {
832
597
  return this.http.post(this.getUrl('permissions/user-actions/assign'), data);
833
598
  }
834
- /**
835
- * Get user's direct action permissions
836
- * POST /iam/permissions/get-user-actions
837
- */
838
599
  getUserActions(userId, query) {
839
600
  return this.http.post(this.getUrl('permissions/get-user-actions'), { userId, branchId: query?.branchId });
840
601
  }
841
- // =============================================================================
842
- // User → Role (Role Assignments)
843
- // =============================================================================
844
- /**
845
- * Assign/remove roles to/from user
846
- * POST /permissions/user-roles/assign
847
- */
848
602
  assignUserRoles(data) {
849
603
  return this.http.post(this.getUrl('permissions/user-roles/assign'), data);
850
604
  }
851
- /**
852
- * Get user's role assignments
853
- * POST /iam/permissions/get-user-roles
854
- */
855
605
  getUserRoles(userId, query) {
856
606
  return this.http.post(this.getUrl('permissions/get-user-roles'), { userId, branchId: query?.branchId });
857
607
  }
858
- // =============================================================================
859
- // Role → Action (Role Permissions)
860
- // =============================================================================
861
- /**
862
- * Assign/remove actions to/from role
863
- * POST /permissions/role-actions/assign
864
- */
865
608
  assignRoleActions(data) {
866
609
  return this.http.post(this.getUrl('permissions/role-actions/assign'), data);
867
610
  }
868
- /**
869
- * Get role's action permissions
870
- * POST /iam/permissions/get-role-actions
871
- */
872
- getRoleActions(roleId, query) {
611
+ getRoleActions(roleId) {
873
612
  return this.http.post(this.getUrl('permissions/get-role-actions'), { roleId });
874
613
  }
875
- // =============================================================================
876
- // Company → Action (Company Whitelisting)
877
- // =============================================================================
878
- /**
879
- * Assign/remove actions to/from company (whitelisting)
880
- * POST /permissions/company-actions/assign
881
- */
882
614
  assignCompanyActions(data) {
883
615
  return this.http.post(this.getUrl('permissions/company-actions/assign'), data);
884
616
  }
885
- /**
886
- * Get company's whitelisted actions
887
- * POST /iam/permissions/get-company-actions
888
- */
889
617
  getCompanyActions(companyId) {
890
618
  return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
891
619
  }
892
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
893
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, providedIn: 'root' });
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' });
894
622
  }
895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, decorators: [{
623
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionApiService, decorators: [{
896
624
  type: Injectable,
897
- args: [{
898
- providedIn: 'root',
899
- }]
625
+ args: [{ providedIn: 'root' }]
900
626
  }], ctorParameters: () => [] });
901
627
 
902
628
  /**
@@ -908,10 +634,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
908
634
  class MyPermissionsApiService {
909
635
  http = inject(HttpClient);
910
636
  appConfig = inject(APP_CONFIG);
911
- baseUrl;
912
- constructor() {
913
- this.baseUrl = `${this.appConfig.apiBaseUrl}/iam/permissions`;
914
- }
637
+ baseUrl = `${getServiceUrl(this.appConfig, 'iam')}/permissions`;
915
638
  /**
916
639
  * Get current user's complete permissions
917
640
  * POST /iam/permissions/my-permissions
@@ -921,57 +644,28 @@ class MyPermissionsApiService {
921
644
  getMyPermissions(dto = {}) {
922
645
  return this.http.post(`${this.baseUrl}/my-permissions`, dto);
923
646
  }
924
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MyPermissionsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
925
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MyPermissionsApiService, providedIn: 'root' });
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' });
926
649
  }
927
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MyPermissionsApiService, decorators: [{
650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: MyPermissionsApiService, decorators: [{
928
651
  type: Injectable,
929
652
  args: [{
930
653
  providedIn: 'root',
931
654
  }]
932
- }], ctorParameters: () => [] });
655
+ }] });
933
656
 
934
- /**
935
- * Permission State Service
936
- * Manages user permissions state and provides permission checking methods
937
- *
938
- * Uses shared PermissionValidatorService for centralized permission checking.
939
- *
940
- * @example
941
- * ```typescript
942
- * // In component
943
- * readonly permissionState = inject(PermissionStateService);
944
- *
945
- * ngOnInit() {
946
- * this.permissionState.loadPermissions();
947
- * }
948
- *
949
- * // Check permission
950
- * if (this.permissionState.hasAction('user.create')) {
951
- * // Show create button
952
- * }
953
- * ```
954
- */
955
657
  class PermissionStateService {
956
658
  permissionApi = inject(MyPermissionsApiService);
957
659
  permissionValidator = inject(PermissionValidatorService);
958
- // Permission state
959
660
  _permissions = signal(null, ...(ngDevMode ? [{ debugName: "_permissions" }] : []));
960
661
  permissions = this._permissions.asReadonly();
961
- // Loading state
962
662
  _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
963
663
  isLoading = this._isLoading.asReadonly();
964
- /**
965
- * Load current user's permissions from API
966
- * Call this on app initialization or after login
967
- * Returns Observable for reactive composition
968
- */
969
664
  loadPermissions(dto) {
970
665
  this._isLoading.set(true);
971
666
  return this.permissionApi.getMyPermissions(dto).pipe(tap((response) => {
972
667
  this._permissions.set(response.data ?? null);
973
668
  this._isLoading.set(false);
974
- // Update shared permission validator
975
669
  const actionCodes = response.data?.frontendActions.map((a) => a.code) ?? [];
976
670
  this.permissionValidator.setPermissions(actionCodes);
977
671
  }), catchError(() => {
@@ -981,19 +675,13 @@ class PermissionStateService {
981
675
  return of(void 0);
982
676
  }), map(() => void 0));
983
677
  }
984
- /**
985
- * Check if permissions are loaded
986
- *
987
- * @returns true if permissions are loaded
988
- */
989
678
  isLoaded() {
990
- return (this._permissions() !== null &&
991
- this.permissionValidator.isPermissionsLoaded());
679
+ return this._permissions() !== null && this.permissionValidator.isPermissionsLoaded();
992
680
  }
993
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
994
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, providedIn: 'root' });
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' });
995
683
  }
996
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, decorators: [{
684
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PermissionStateService, decorators: [{
997
685
  type: Injectable,
998
686
  args: [{ providedIn: 'root' }]
999
687
  }] });
@@ -1012,26 +700,28 @@ function toLogicNode(node) {
1012
700
  }
1013
701
  function toBuilderNode(node) {
1014
702
  if (node.type === 'action') {
1015
- return { id: crypto.randomUUID(), type: 'action', actionId: node.actionId };
703
+ return createActionNode(node.actionId);
1016
704
  }
1017
705
  return {
1018
- id: crypto.randomUUID(),
1019
- type: 'group',
1020
- operator: node.operator,
706
+ ...createGroupNode(node.operator),
1021
707
  children: node.children?.map(toBuilderNode) ?? [],
1022
708
  };
1023
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
+ }
1024
716
  /** Visual builder for AND/OR permission logic trees */
1025
717
  class LogicBuilderComponent {
1026
718
  logic = input(null, ...(ngDevMode ? [{ debugName: "logic" }] : []));
1027
719
  actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
1028
720
  logicChange = output();
1029
- availableActions = computed(() => this.actions(), ...(ngDevMode ? [{ debugName: "availableActions" }] : []));
1030
721
  /** Internal builder tree state (private writable + public readonly pattern) */
1031
722
  _builderTree = signal(null, ...(ngDevMode ? [{ debugName: "_builderTree" }] : []));
1032
723
  builderLogic = this._builderTree.asReadonly();
1033
724
  constructor() {
1034
- // Sync builderTree with logic input changes
1035
725
  effect(() => {
1036
726
  const logic = this.logic();
1037
727
  if (!logic) {
@@ -1043,40 +733,24 @@ class LogicBuilderComponent {
1043
733
  }, { allowSignalWrites: true });
1044
734
  }
1045
735
  initializeLogic() {
1046
- this._builderTree.set({
1047
- id: crypto.randomUUID(),
1048
- type: 'group',
1049
- operator: 'AND',
1050
- children: [],
1051
- });
1052
- this.emitChange();
736
+ this.updateTreeAndEmit(createGroupNode());
1053
737
  }
1054
738
  clearLogic() {
1055
739
  this._builderTree.set(null);
1056
740
  this.logicChange.emit(null);
1057
741
  }
1058
742
  toggleOperator(nodeId) {
1059
- const tree = this._builderTree();
1060
- if (!tree)
1061
- return;
1062
- this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
743
+ this.updateNode(nodeId, (node) => ({
1063
744
  ...node,
1064
745
  operator: node.operator === 'AND' ? 'OR' : 'AND',
1065
- })));
1066
- this.emitChange();
746
+ }));
1067
747
  }
1068
748
  addChildNode(parentId, type) {
1069
- const tree = this._builderTree();
1070
- if (!tree)
1071
- return;
1072
- const newNode = type === 'group'
1073
- ? { id: crypto.randomUUID(), type: 'group', operator: 'AND', children: [] }
1074
- : { id: crypto.randomUUID(), type: 'action', actionId: '' };
1075
- this._builderTree.set(this.updateNodeInTree(tree, parentId, (node) => ({
749
+ const newNode = type === 'group' ? createGroupNode() : createActionNode();
750
+ this.updateNode(parentId, (node) => ({
1076
751
  ...node,
1077
752
  children: [...(node.children || []), newNode],
1078
- })));
1079
- this.emitChange();
753
+ }));
1080
754
  }
1081
755
  removeNode(nodeId) {
1082
756
  const tree = this._builderTree();
@@ -1086,51 +760,45 @@ class LogicBuilderComponent {
1086
760
  this.clearLogic();
1087
761
  return;
1088
762
  }
1089
- this._builderTree.set(this.removeNodeFromTree(tree, nodeId));
1090
- this.emitChange();
763
+ this.updateTreeAndEmit(this.removeNodeFromTree(tree, nodeId));
1091
764
  }
1092
765
  updateActionId(nodeId, actionId) {
766
+ this.updateNode(nodeId, (node) => ({ ...node, actionId }));
767
+ }
768
+ /** Updates a node in the tree and emits the change */
769
+ updateNode(nodeId, updater) {
1093
770
  const tree = this._builderTree();
1094
771
  if (!tree)
1095
772
  return;
1096
- this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
1097
- ...node,
1098
- actionId,
1099
- })));
1100
- this.emitChange();
773
+ this.updateTreeAndEmit(this.updateNodeInTree(tree, nodeId, updater));
1101
774
  }
1102
- emitChange() {
1103
- const tree = this._builderTree();
1104
- if (!tree) {
1105
- this.logicChange.emit(null);
1106
- return;
1107
- }
775
+ /** Sets the tree and emits the change */
776
+ updateTreeAndEmit(tree) {
777
+ this._builderTree.set(tree);
1108
778
  this.logicChange.emit(toLogicNode(tree));
1109
779
  }
1110
780
  updateNodeInTree(node, targetId, updater) {
1111
781
  if (node.id === targetId)
1112
782
  return updater(node);
1113
- if (node.children) {
1114
- return {
1115
- ...node,
1116
- children: node.children.map((child) => this.updateNodeInTree(child, targetId, updater)),
1117
- };
1118
- }
1119
- 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
+ };
1120
789
  }
1121
790
  removeNodeFromTree(node, targetId) {
1122
- if (node.children) {
1123
- return {
1124
- ...node,
1125
- children: node.children
1126
- .filter((child) => child.id !== targetId)
1127
- .map((child) => this.removeNodeFromTree(child, targetId)),
1128
- };
1129
- }
1130
- 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
+ };
1131
799
  }
1132
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1133
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: LogicBuilderComponent, isStandalone: true, selector: "lib-logic-builder", inputs: { logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logicChange: "logicChange" }, ngImport: i0, template: `
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: `
1134
802
  <div class="logic-builder">
1135
803
  <div class="flex justify-between items-center mb-3">
1136
804
  <h4 class="text-sm font-semibold m-0">Permission Logic</h4>
@@ -1190,8 +858,8 @@ class LogicBuilderComponent {
1190
858
  class="action-select flex-1 p-2 border border-surface rounded text-sm"
1191
859
  [ngModel]="node.actionId"
1192
860
  (ngModelChange)="updateActionId(node.id, $event)">
1193
- <option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
1194
- @for (action of availableActions(); track action.id) {
861
+ <option [value]="null">Select Action... ({{ actions().length }} available)</option>
862
+ @for (action of actions(); track action.id) {
1195
863
  <option [ngValue]="action.id">{{ action.name }}</option>
1196
864
  }
1197
865
  </select>
@@ -1243,9 +911,9 @@ class LogicBuilderComponent {
1243
911
  </ng-template>
1244
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 });
1245
913
  }
1246
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, decorators: [{
914
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LogicBuilderComponent, decorators: [{
1247
915
  type: Component,
1248
- args: [{ selector: 'lib-logic-builder', standalone: true, imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
916
+ args: [{ selector: 'lib-logic-builder', imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1249
917
  <div class="logic-builder">
1250
918
  <div class="flex justify-between items-center mb-3">
1251
919
  <h4 class="text-sm font-semibold m-0">Permission Logic</h4>
@@ -1305,8 +973,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1305
973
  class="action-select flex-1 p-2 border border-surface rounded text-sm"
1306
974
  [ngModel]="node.actionId"
1307
975
  (ngModelChange)="updateActionId(node.id, $event)">
1308
- <option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
1309
- @for (action of availableActions(); track action.id) {
976
+ <option [value]="null">Select Action... ({{ actions().length }} available)</option>
977
+ @for (action of actions(); track action.id) {
1310
978
  <option [ngValue]="action.id">{{ action.name }}</option>
1311
979
  }
1312
980
  </select>
@@ -1359,102 +1027,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1359
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"] }]
1360
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"] }] } });
1361
1029
 
1362
- /**
1363
- * Tree Utility Functions
1364
- * Shared utilities for working with hierarchical tree structures
1365
- */
1366
- /**
1367
- * Flattens a hierarchical tree structure into a flat array
1368
- *
1369
- * @template T - Type of tree node (must have optional children array)
1370
- * @param tree - Array of tree nodes to flatten
1371
- * @returns Flat array containing all nodes from the tree
1372
- *
1373
- * @example
1374
- * ```typescript
1375
- * const tree = [
1376
- * { id: '1', name: 'Parent', children: [
1377
- * { id: '2', name: 'Child 1' },
1378
- * { id: '3', name: 'Child 2' }
1379
- * ]}
1380
- * ];
1381
- * const flat = flattenTree(tree);
1382
- * // Returns: [{ id: '1', ... }, { id: '2', ... }, { id: '3', ... }]
1383
- * ```
1384
- */
1385
1030
  function flattenTree(tree) {
1386
- if (!tree?.length) {
1031
+ if (!tree?.length)
1387
1032
  return [];
1388
- }
1389
1033
  const result = [];
1390
1034
  const flatten = (nodes) => {
1391
1035
  for (const node of nodes) {
1392
1036
  result.push(node);
1393
- if (node.children?.length) {
1037
+ if (node.children?.length)
1394
1038
  flatten(node.children);
1395
- }
1396
1039
  }
1397
1040
  };
1398
1041
  flatten(tree);
1399
1042
  return result;
1400
1043
  }
1401
- /**
1402
- * Builds a map of items by their ID for quick lookup
1403
- *
1404
- * @template T - Type of item (must have id property)
1405
- * @param items - Array of items to map
1406
- * @returns Map with item IDs as keys and items as values
1407
- *
1408
- * @example
1409
- * ```typescript
1410
- * const actions = [{ id: '1', name: 'Action 1' }, { id: '2', name: 'Action 2' }];
1411
- * const map = buildItemMap(actions);
1412
- * const action = map.get('1'); // { id: '1', name: 'Action 1' }
1413
- * ```
1414
- */
1415
- function buildItemMap(items) {
1416
- if (!items?.length) {
1417
- return new Map();
1418
- }
1419
- return new Map(items
1420
- .filter((item) => item?.id !== undefined && item?.id !== null)
1421
- .map((item) => [String(item.id), item]));
1422
- }
1423
- /**
1424
- * Build tree structure from flat list using parentId
1425
- *
1426
- * Converts flat array with parentId references into hierarchical tree structure.
1427
- * Used when backend returns flat list but tree structure is needed for display.
1428
- *
1429
- * @template T - Type of node (must have id and optional parentId)
1430
- * @param flatList - Flat array of nodes with parentId references
1431
- * @returns Hierarchical tree with children arrays
1432
- *
1433
- * @example
1434
- * ```typescript
1435
- * const flat = [
1436
- * { id: '1', name: 'Parent', parentId: null },
1437
- * { id: '2', name: 'Child 1', parentId: '1' },
1438
- * { id: '3', name: 'Child 2', parentId: '1' }
1439
- * ];
1440
- * const tree = buildTreeFromFlat(flat);
1441
- * // Returns: [{ id: '1', name: 'Parent', children: [child1, child2] }]
1442
- * ```
1443
- */
1444
1044
  function buildTreeFromFlat(flatList) {
1445
- if (!flatList?.length) {
1045
+ if (!flatList?.length)
1446
1046
  return [];
1447
- }
1448
- // Create map for O(1) lookup
1449
1047
  const itemMap = new Map();
1450
1048
  const rootItems = [];
1451
- // First pass: Create all nodes with empty children arrays
1452
1049
  flatList.forEach((item) => {
1453
- if (item.id) {
1050
+ if (item.id)
1454
1051
  itemMap.set(item.id, { ...item, children: [] });
1455
- }
1456
1052
  });
1457
- // Second pass: Build parent-child relationships
1458
1053
  flatList.forEach((item) => {
1459
1054
  if (!item.id)
1460
1055
  return;
@@ -1462,51 +1057,23 @@ function buildTreeFromFlat(flatList) {
1462
1057
  if (!node)
1463
1058
  return;
1464
1059
  if (!item.parentId) {
1465
- // Root level item
1466
1060
  rootItems.push(node);
1467
1061
  }
1468
1062
  else {
1469
- // Child item - add to parent's children
1470
1063
  const parent = itemMap.get(item.parentId);
1471
1064
  if (parent) {
1472
1065
  parent.children.push(node);
1473
1066
  }
1474
1067
  else {
1475
- // Parent not found - treat as root
1476
1068
  rootItems.push(node);
1477
1069
  }
1478
1070
  }
1479
1071
  });
1480
1072
  return rootItems;
1481
1073
  }
1482
- /**
1483
- * Convert ActionTreeDto to PrimeNG TreeNode format
1484
- *
1485
- * Transforms hierarchical action data from backend into PrimeNG TreeTable format.
1486
- * Recursively processes children and sets leaf/expanded properties.
1487
- *
1488
- * @param actions - Array of action tree DTOs from backend
1489
- * @returns Array of TreeNodes for PrimeNG TreeTable
1490
- *
1491
- * @example
1492
- * ```typescript
1493
- * const actionsTree = [
1494
- * {
1495
- * id: '1',
1496
- * name: 'User Management',
1497
- * children: [
1498
- * { id: '2', name: 'Create User', children: [] }
1499
- * ]
1500
- * }
1501
- * ];
1502
- * const treeNodes = convertActionToTreeNode(actionsTree);
1503
- * // Use with: <p-treeTable [value]="treeNodes">
1504
- * ```
1505
- */
1506
1074
  function convertActionToTreeNode(actions) {
1507
- if (!actions?.length) {
1075
+ if (!actions?.length)
1508
1076
  return [];
1509
- }
1510
1077
  const convert = (action) => {
1511
1078
  const { children, ...actionData } = action;
1512
1079
  return {
@@ -1606,17 +1173,14 @@ class RoleActionSelectorComponent {
1606
1173
  const selMap = this.selectionMap();
1607
1174
  const allActions = this.actions();
1608
1175
  const unmetSet = new Set();
1609
- allActions.forEach((action) => {
1610
- if (action.id &&
1611
- this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
1176
+ for (const action of allActions) {
1177
+ if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
1612
1178
  unmetSet.add(action.id);
1613
1179
  }
1614
- });
1180
+ }
1615
1181
  return unmetSet;
1616
1182
  }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
1617
- invalidActionsCount = computed(() => {
1618
- return this.actionsWithUnmetPrerequisites().size;
1619
- }, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1183
+ invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1620
1184
  // Computed - Pending Changes
1621
1185
  pendingAdd = computed(() => {
1622
1186
  const current = this.selectionMap();
@@ -1701,13 +1265,12 @@ class RoleActionSelectorComponent {
1701
1265
  const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
1702
1266
  // Build selection map
1703
1267
  const selMap = {};
1704
- flatActions.forEach((action) => {
1268
+ for (const action of flatActions) {
1705
1269
  if (action.id) {
1706
1270
  selMap[action.id] = assignedIds.has(action.id);
1707
1271
  }
1708
- });
1709
- this._selectionMap.set(selMap);
1710
- this._initialSelection.set({ ...selMap });
1272
+ }
1273
+ this.applySelection(selMap);
1711
1274
  }
1712
1275
  finally {
1713
1276
  if (!signal.aborted) {
@@ -1756,35 +1319,20 @@ class RoleActionSelectorComponent {
1756
1319
  this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
1757
1320
  }
1758
1321
  }
1759
- /**
1760
- * Toggle all actions
1761
- */
1762
1322
  toggleAll() {
1763
- const newValue = !this.allSelected();
1764
- const selMap = {};
1765
- this.actions().forEach((action) => {
1766
- selMap[action.id] = newValue;
1767
- });
1768
- this._selectionMap.set(selMap);
1323
+ this.setAllSelection(!this.allSelected());
1769
1324
  }
1770
- /**
1771
- * Select all actions
1772
- */
1773
1325
  selectAll() {
1774
- const selMap = {};
1775
- this.actions().forEach((action) => {
1776
- selMap[action.id] = true;
1777
- });
1778
- this._selectionMap.set(selMap);
1326
+ this.setAllSelection(true);
1779
1327
  }
1780
- /**
1781
- * Deselect all actions
1782
- */
1783
1328
  deselectAll() {
1329
+ this.setAllSelection(false);
1330
+ }
1331
+ setAllSelection(value) {
1784
1332
  const selMap = {};
1785
- this.actions().forEach((action) => {
1786
- selMap[action.id] = false;
1787
- });
1333
+ for (const action of this.actions()) {
1334
+ selMap[action.id] = value;
1335
+ }
1788
1336
  this._selectionMap.set(selMap);
1789
1337
  }
1790
1338
  /**
@@ -1801,19 +1349,7 @@ class RoleActionSelectorComponent {
1801
1349
  return;
1802
1350
  }
1803
1351
  // Build payload
1804
- const items = [];
1805
- this.pendingAdd().forEach((action) => {
1806
- items.push({
1807
- id: action.id,
1808
- action: 'add',
1809
- });
1810
- });
1811
- this.pendingRemove().forEach((action) => {
1812
- items.push({
1813
- id: action.id,
1814
- action: 'remove',
1815
- });
1816
- });
1352
+ const items = this.buildPayloadItems();
1817
1353
  if (items.length === 0)
1818
1354
  return;
1819
1355
  this.saving.set(true);
@@ -1837,17 +1373,28 @@ class RoleActionSelectorComponent {
1837
1373
  this.saving.set(false);
1838
1374
  }
1839
1375
  }
1840
- /**
1841
- * Reset component state
1842
- */
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
+ }
1843
1390
  resetState() {
1844
1391
  this._actionsTree.set([]);
1845
1392
  this._actions.set([]);
1846
1393
  this._selectionMap.set({});
1847
1394
  this._initialSelection.set({});
1848
1395
  }
1849
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1850
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", ngImport: i0, template: `
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: `
1851
1398
  <div class="role-action-selector">
1852
1399
  <!-- Role Selector -->
1853
1400
  <div class="mb-4">
@@ -2070,9 +1617,9 @@ class RoleActionSelectorComponent {
2070
1617
  </div>
2071
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 });
2072
1619
  }
2073
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
1620
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
2074
1621
  type: Component,
2075
- args: [{ selector: 'flusys-role-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
1622
+ args: [{ selector: 'flusys-role-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2076
1623
  <div class="role-action-selector">
2077
1624
  <!-- Role Selector -->
2078
1625
  <div class="mb-4">
@@ -2384,17 +1931,14 @@ class CompanyActionSelectorComponent {
2384
1931
  const selMap = this.selectionMap();
2385
1932
  const allActions = this.actions();
2386
1933
  const unmetSet = new Set();
2387
- allActions.forEach((action) => {
2388
- if (action.id &&
2389
- this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
1934
+ for (const action of allActions) {
1935
+ if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
2390
1936
  unmetSet.add(action.id);
2391
1937
  }
2392
- });
1938
+ }
2393
1939
  return unmetSet;
2394
1940
  }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
2395
- invalidActionsCount = computed(() => {
2396
- return this.actionsWithUnmetPrerequisites().size;
2397
- }, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1941
+ invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
2398
1942
  // Computed - Pending Changes
2399
1943
  pendingAdd = computed(() => {
2400
1944
  const current = this.selectionMap();
@@ -2478,13 +2022,7 @@ class CompanyActionSelectorComponent {
2478
2022
  if (signal.aborted)
2479
2023
  return;
2480
2024
  const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
2481
- // Build selection map
2482
- const selMap = {};
2483
- actionsList.forEach((action) => {
2484
- if (action.id) {
2485
- selMap[action.id] = assignedIds.has(action.id);
2486
- }
2487
- });
2025
+ const selMap = this.buildSelectionMap(actionsList, (action) => assignedIds.has(action.id));
2488
2026
  this._selectionMap.set(selMap);
2489
2027
  this._initialSelection.set({ ...selMap });
2490
2028
  }
@@ -2535,36 +2073,17 @@ class CompanyActionSelectorComponent {
2535
2073
  this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
2536
2074
  }
2537
2075
  }
2538
- /**
2539
- * Toggle all actions
2540
- */
2541
2076
  toggleAll() {
2542
- const newValue = !this.allSelected();
2543
- const selMap = {};
2544
- this.actions().forEach((action) => {
2545
- selMap[action.id] = newValue;
2546
- });
2547
- this._selectionMap.set(selMap);
2077
+ this.setAllSelection(!this.allSelected());
2548
2078
  }
2549
- /**
2550
- * Select all actions
2551
- */
2552
2079
  selectAll() {
2553
- const selMap = {};
2554
- this.actions().forEach((action) => {
2555
- selMap[action.id] = true;
2556
- });
2557
- this._selectionMap.set(selMap);
2080
+ this.setAllSelection(true);
2558
2081
  }
2559
- /**
2560
- * Deselect all actions
2561
- */
2562
2082
  deselectAll() {
2563
- const selMap = {};
2564
- this.actions().forEach((action) => {
2565
- selMap[action.id] = false;
2566
- });
2567
- this._selectionMap.set(selMap);
2083
+ this.setAllSelection(false);
2084
+ }
2085
+ setAllSelection(value) {
2086
+ this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
2568
2087
  }
2569
2088
  /**
2570
2089
  * Save changes to backend
@@ -2579,20 +2098,7 @@ class CompanyActionSelectorComponent {
2579
2098
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
2580
2099
  return;
2581
2100
  }
2582
- // Build payload
2583
- const items = [];
2584
- this.pendingAdd().forEach((action) => {
2585
- items.push({
2586
- id: action.id,
2587
- action: 'add',
2588
- });
2589
- });
2590
- this.pendingRemove().forEach((action) => {
2591
- items.push({
2592
- id: action.id,
2593
- action: 'remove',
2594
- });
2595
- });
2101
+ const items = this.buildPayloadItems();
2596
2102
  if (items.length === 0)
2597
2103
  return;
2598
2104
  this.saving.set(true);
@@ -2670,17 +2176,31 @@ class CompanyActionSelectorComponent {
2670
2176
  },
2671
2177
  });
2672
2178
  }
2673
- /**
2674
- * Reset component state
2675
- */
2676
2179
  resetState() {
2677
2180
  this._actionsTree.set([]);
2678
2181
  this._actions.set([]);
2679
2182
  this._selectionMap.set({});
2680
2183
  this._initialSelection.set({});
2681
2184
  }
2682
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2683
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", ngImport: i0, template: `
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: `
2684
2204
  <div class="company-action-selector">
2685
2205
  <!-- Company Selector -->
2686
2206
  <div class="mb-4">
@@ -2897,9 +2417,9 @@ class CompanyActionSelectorComponent {
2897
2417
  </div>
2898
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 });
2899
2419
  }
2900
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
2420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
2901
2421
  type: Component,
2902
- args: [{ selector: 'flusys-company-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2422
+ args: [{ selector: 'flusys-company-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2903
2423
  <div class="company-action-selector">
2904
2424
  <!-- Company Selector -->
2905
2425
  <div class="mb-4">
@@ -3157,9 +2677,6 @@ class UserRoleSelectorComponent {
3157
2677
  roleApi = inject(RoleApiService);
3158
2678
  permissionApi = inject(PermissionApiService);
3159
2679
  messageService = inject(MessageService);
3160
- destroyRef = inject(DestroyRef);
3161
- // AbortController for data loading
3162
- loadDataAbortController = null;
3163
2680
  // State - User/Branch Selection
3164
2681
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3165
2682
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
@@ -3220,10 +2737,6 @@ class UserRoleSelectorComponent {
3220
2737
  return this.hasChanges() && !this.saving();
3221
2738
  }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
3222
2739
  constructor() {
3223
- // Cleanup on destroy
3224
- this.destroyRef.onDestroy(() => {
3225
- this.loadDataAbortController?.abort();
3226
- });
3227
2740
  // Effect: Load user branches and data when user changes
3228
2741
  effect(() => {
3229
2742
  const userId = this.selectedUserId();
@@ -3260,9 +2773,7 @@ class UserRoleSelectorComponent {
3260
2773
  this.branches.set([]);
3261
2774
  return;
3262
2775
  }
3263
- const response = await this.userPermissionProvider
3264
- .getUserBranchPermissions(userId)
3265
- .toPromise();
2776
+ const response = await firstValueFrom(this.userPermissionProvider.getUserBranchPermissions(userId));
3266
2777
  const userBranches = response?.data?.map((ub) => ({
3267
2778
  id: ub.branchId,
3268
2779
  name: ub.branchName,
@@ -3285,20 +2796,16 @@ class UserRoleSelectorComponent {
3285
2796
  this.loading.set(true);
3286
2797
  try {
3287
2798
  // Load all roles
3288
- const rolesResponse = await this.roleApi
3289
- .getAll('', {
2799
+ const rolesResponse = await firstValueFrom(this.roleApi.getAll('', {
3290
2800
  pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
3291
- })
3292
- .toPromise();
2801
+ }));
3293
2802
  const rolesList = rolesResponse?.data ?? [];
3294
2803
  this._roles.set(rolesList);
3295
2804
  // Load existing user-role assignments
3296
2805
  const query = isCompanyFeatureEnabled(this.appConfig) && branchId
3297
2806
  ? { branchId }
3298
2807
  : undefined;
3299
- const assignedResponse = await this.permissionApi
3300
- .getUserRoles(userId, query)
3301
- .toPromise();
2808
+ const assignedResponse = await firstValueFrom(this.permissionApi.getUserRoles(userId, query));
3302
2809
  const assignedIds = new Set((assignedResponse?.data || []).map((r) => r.roleId));
3303
2810
  // Build selection map
3304
2811
  const selMap = {};
@@ -3338,30 +2845,27 @@ class UserRoleSelectorComponent {
3338
2845
  * Toggle all roles
3339
2846
  */
3340
2847
  toggleAll() {
3341
- const newValue = !this.allSelected();
3342
- const selMap = {};
3343
- this.roles().forEach((role) => {
3344
- selMap[role.id] = newValue;
3345
- });
3346
- this._selectionMap.set(selMap);
2848
+ this.setAllSelections(!this.allSelected());
3347
2849
  }
3348
2850
  /**
3349
2851
  * Select all roles
3350
2852
  */
3351
2853
  selectAll() {
3352
- const selMap = {};
3353
- this.roles().forEach((role) => {
3354
- selMap[role.id] = true;
3355
- });
3356
- this._selectionMap.set(selMap);
2854
+ this.setAllSelections(true);
3357
2855
  }
3358
2856
  /**
3359
2857
  * Deselect all roles
3360
2858
  */
3361
2859
  deselectAll() {
2860
+ this.setAllSelections(false);
2861
+ }
2862
+ /**
2863
+ * Set all role selections to a given value
2864
+ */
2865
+ setAllSelections(value) {
3362
2866
  const selMap = {};
3363
2867
  this.roles().forEach((role) => {
3364
- selMap[role.id] = false;
2868
+ selMap[role.id] = value;
3365
2869
  });
3366
2870
  this._selectionMap.set(selMap);
3367
2871
  }
@@ -3404,9 +2908,7 @@ class UserRoleSelectorComponent {
3404
2908
  payload.branchId = branchId;
3405
2909
  }
3406
2910
  }
3407
- const response = await this.permissionApi
3408
- .assignUserRoles(payload)
3409
- .toPromise();
2911
+ const response = await firstValueFrom(this.permissionApi.assignUserRoles(payload));
3410
2912
  this.messageService.add({
3411
2913
  severity: 'success',
3412
2914
  summary: 'Success',
@@ -3431,8 +2933,8 @@ class UserRoleSelectorComponent {
3431
2933
  this._selectionMap.set({});
3432
2934
  this._initialSelection.set({});
3433
2935
  }
3434
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3435
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserRoleSelectorComponent, isStandalone: true, selector: "flusys-user-role-selector", ngImport: i0, template: `
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: `
3436
2938
  <div class="user-role-selector">
3437
2939
  <!-- User and Branch Selectors -->
3438
2940
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -3654,11 +3156,11 @@ class UserRoleSelectorComponent {
3654
3156
  }
3655
3157
  }
3656
3158
  </div>
3657
- `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
3658
3160
  }
3659
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
3161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
3660
3162
  type: Component,
3661
- args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3163
+ args: [{ selector: 'flusys-user-role-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3662
3164
  <div class="user-role-selector">
3663
3165
  <!-- User and Branch Selectors -->
3664
3166
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -3925,9 +3427,6 @@ class UserActionSelectorComponent {
3925
3427
  permissionApi = inject(PermissionApiService);
3926
3428
  permissionLogic = inject(ActionPermissionLogicService);
3927
3429
  messageService = inject(MessageService);
3928
- destroyRef = inject(DestroyRef);
3929
- // AbortController for data loading
3930
- loadDataAbortController = null;
3931
3430
  // State - User/Branch Selection
3932
3431
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3933
3432
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
@@ -3946,8 +3445,10 @@ class UserActionSelectorComponent {
3946
3445
  selectionMap = this._selectionMap.asReadonly();
3947
3446
  _initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
3948
3447
  initialSelection = this._initialSelection.asReadonly();
3448
+ // Computed - Company Feature Flag (cached)
3449
+ isCompanyFeatureActive = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "isCompanyFeatureActive" }] : []));
3949
3450
  // Computed - UI Flags
3950
- showBranchSelector = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "showBranchSelector" }] : []));
3451
+ showBranchSelector = this.isCompanyFeatureActive;
3951
3452
  filteredBranches = computed(() => {
3952
3453
  const currentCompanyId = this.companyContext.currentCompanyInfo()?.id || undefined;
3953
3454
  if (!currentCompanyId)
@@ -4009,15 +3510,11 @@ class UserActionSelectorComponent {
4009
3510
  return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
4010
3511
  }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
4011
3512
  constructor() {
4012
- // Cleanup on destroy
4013
- this.destroyRef.onDestroy(() => {
4014
- this.loadDataAbortController?.abort();
4015
- });
4016
3513
  // Effect: Load user branches and data when user changes
4017
3514
  effect(() => {
4018
3515
  const userId = this.selectedUserId();
4019
3516
  if (userId) {
4020
- if (isCompanyFeatureEnabled(this.appConfig)) {
3517
+ if (this.isCompanyFeatureActive()) {
4021
3518
  this.loadUserBranches(userId);
4022
3519
  }
4023
3520
  this.loadData();
@@ -4032,9 +3529,7 @@ class UserActionSelectorComponent {
4032
3529
  effect(() => {
4033
3530
  const userId = this.selectedUserId();
4034
3531
  const branchId = this.selectedBranchId();
4035
- if (isCompanyFeatureEnabled(this.appConfig) &&
4036
- userId &&
4037
- branchId !== undefined) {
3532
+ if (this.isCompanyFeatureActive() && userId && branchId !== undefined) {
4038
3533
  this.loadData();
4039
3534
  }
4040
3535
  });
@@ -4043,15 +3538,13 @@ class UserActionSelectorComponent {
4043
3538
  * Load user's permitted branches
4044
3539
  */
4045
3540
  async loadUserBranches(userId) {
3541
+ const companyId = this.companyContext.currentCompanyInfo()?.id;
3542
+ if (!companyId) {
3543
+ this.branches.set([]);
3544
+ return;
3545
+ }
4046
3546
  try {
4047
- const companyId = this.companyContext.currentCompanyInfo()?.id || undefined;
4048
- if (!companyId) {
4049
- this.branches.set([]);
4050
- return;
4051
- }
4052
- const response = await this.userPermissionProvider
4053
- .getUserBranchPermissions(userId)
4054
- .toPromise();
3547
+ const response = await firstValueFrom(this.userPermissionProvider.getUserBranchPermissions(userId));
4055
3548
  const userBranches = response?.data?.map((ub) => ({
4056
3549
  id: ub.branchId,
4057
3550
  name: ub.branchName,
@@ -4073,30 +3566,15 @@ class UserActionSelectorComponent {
4073
3566
  const branchId = this.selectedBranchId();
4074
3567
  this.loading.set(true);
4075
3568
  try {
4076
- // Load actions filtered by company whitelist
4077
- const actionsResponse = await this.actionApi
4078
- .getActionsForPermission()
4079
- .toPromise();
4080
- // Get flat list (filtered by company if enabled)
3569
+ const actionsResponse = await firstValueFrom(this.actionApi.getActionsForPermission());
4081
3570
  const flatActions = actionsResponse?.data ?? [];
4082
- // Build tree structure from flat list with parentId
4083
3571
  const actionsTree = buildTreeFromFlat(flatActions);
4084
- // Store both representations
4085
3572
  this._actionsTree.set(actionsTree);
4086
3573
  this._actions.set(flatActions);
4087
- // Load existing user-action assignments
4088
- const query = isCompanyFeatureEnabled(this.appConfig) && branchId
4089
- ? { branchId }
4090
- : undefined;
4091
- const assignedResponse = await this.permissionApi
4092
- .getUserActions(userId, query)
4093
- .toPromise();
3574
+ const query = this.isCompanyFeatureActive() && branchId ? { branchId } : undefined;
3575
+ const assignedResponse = await firstValueFrom(this.permissionApi.getUserActions(userId, query));
4094
3576
  const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
4095
- // Build selection map
4096
- const selMap = {};
4097
- flatActions.forEach((action) => {
4098
- selMap[action.id] = assignedIds.has(action.id);
4099
- });
3577
+ const selMap = this.buildSelectionMap(flatActions, (action) => assignedIds.has(action.id));
4100
3578
  this._selectionMap.set(selMap);
4101
3579
  this._initialSelection.set({ ...selMap });
4102
3580
  }
@@ -4148,48 +3626,25 @@ class UserActionSelectorComponent {
4148
3626
  this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
4149
3627
  }
4150
3628
  }
4151
- /**
4152
- * Toggle all actions
4153
- */
4154
3629
  toggleAll() {
4155
- const newValue = !this.allSelected();
4156
- const selMap = {};
4157
- this.actions().forEach((action) => {
4158
- selMap[action.id] = newValue;
4159
- });
4160
- this._selectionMap.set(selMap);
3630
+ this.setAllActions(!this.allSelected());
4161
3631
  }
4162
- /**
4163
- * Select all actions
4164
- */
4165
3632
  selectAll() {
4166
- const selMap = {};
4167
- this.actions().forEach((action) => {
4168
- selMap[action.id] = true;
4169
- });
4170
- this._selectionMap.set(selMap);
3633
+ this.setAllActions(true);
4171
3634
  }
4172
- /**
4173
- * Deselect all actions
4174
- */
4175
3635
  deselectAll() {
4176
- const selMap = {};
4177
- this.actions().forEach((action) => {
4178
- selMap[action.id] = false;
4179
- });
4180
- this._selectionMap.set(selMap);
3636
+ this.setAllActions(false);
3637
+ }
3638
+ setAllActions(value) {
3639
+ this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
4181
3640
  }
4182
- /**
4183
- * Save changes to backend
4184
- */
4185
3641
  async saveChanges() {
4186
3642
  const userId = this.selectedUserId();
4187
3643
  if (!userId)
4188
3644
  return;
4189
3645
  const branchId = this.selectedBranchId();
4190
- const companyId = this.companyContext.currentCompanyInfo()?.id || undefined;
4191
- // Validate company context if feature enabled
4192
- if (isCompanyFeatureEnabled(this.appConfig) && !companyId) {
3646
+ const companyId = this.companyContext.currentCompanyInfo()?.id;
3647
+ if (this.isCompanyFeatureActive() && !companyId) {
4193
3648
  this.messageService.add({
4194
3649
  severity: 'warn',
4195
3650
  summary: 'Company Required',
@@ -4197,49 +3652,30 @@ class UserActionSelectorComponent {
4197
3652
  });
4198
3653
  return;
4199
3654
  }
4200
- // Pre-save validation
4201
3655
  const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
4202
3656
  if (invalidActions.length > 0) {
4203
3657
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
4204
3658
  return;
4205
3659
  }
4206
- // Build payload
4207
- const items = [];
4208
- this.pendingAdd().forEach((action) => {
4209
- items.push({
4210
- id: action.id,
4211
- action: 'add',
4212
- });
4213
- });
4214
- this.pendingRemove().forEach((action) => {
4215
- items.push({
4216
- id: action.id,
4217
- action: 'remove',
4218
- });
4219
- });
3660
+ const items = this.buildPermissionItems();
4220
3661
  if (items.length === 0)
4221
3662
  return;
4222
3663
  this.saving.set(true);
4223
3664
  try {
4224
3665
  const payload = { userId, items };
4225
- if (isCompanyFeatureEnabled(this.appConfig)) {
4226
- if (companyId) {
3666
+ if (this.isCompanyFeatureActive()) {
3667
+ if (companyId)
4227
3668
  payload.companyId = companyId;
4228
- }
4229
- if (branchId) {
3669
+ if (branchId)
4230
3670
  payload.branchId = branchId;
4231
- }
4232
3671
  }
4233
- const response = await this.permissionApi
4234
- .assignUserActions(payload)
4235
- .toPromise();
3672
+ const response = await firstValueFrom(this.permissionApi.assignUserActions(payload));
4236
3673
  this.messageService.add({
4237
3674
  severity: 'success',
4238
3675
  summary: 'Success',
4239
3676
  detail: response?.data?.message ||
4240
3677
  'User action permissions updated successfully',
4241
3678
  });
4242
- // Update baseline
4243
3679
  this._initialSelection.set({ ...this.selectionMap() });
4244
3680
  }
4245
3681
  catch {
@@ -4249,17 +3685,31 @@ class UserActionSelectorComponent {
4249
3685
  this.saving.set(false);
4250
3686
  }
4251
3687
  }
4252
- /**
4253
- * Reset component state
4254
- */
4255
3688
  resetState() {
4256
3689
  this._actionsTree.set([]);
4257
3690
  this._actions.set([]);
4258
3691
  this._selectionMap.set({});
4259
3692
  this._initialSelection.set({});
4260
3693
  }
4261
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4262
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
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: `
4263
3713
  <div class="user-action-selector">
4264
3714
  <!-- User and Branch Selectors -->
4265
3715
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -4513,11 +3963,11 @@ class UserActionSelectorComponent {
4513
3963
  }
4514
3964
  }
4515
3965
  </div>
4516
- `, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["loadUsers", "placeHolder", "isEditMode", "filterActive", "additionalFilters", "pageSize", "value"], outputs: ["valueChange", "userSelected", "onError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
4517
3967
  }
4518
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
3968
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
4519
3969
  type: Component,
4520
- args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3970
+ args: [{ selector: 'flusys-user-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
4521
3971
  <div class="user-action-selector">
4522
3972
  <!-- User and Branch Selectors -->
4523
3973
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -4821,97 +4271,83 @@ class ProfilePermissionProviderAdapter {
4821
4271
  })),
4822
4272
  })));
4823
4273
  }
4824
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePermissionProviderAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4825
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePermissionProviderAdapter });
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 });
4826
4276
  }
4827
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePermissionProviderAdapter, decorators: [{
4277
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfilePermissionProviderAdapter, decorators: [{
4828
4278
  type: Injectable
4829
4279
  }] });
4830
4280
 
4831
- /**
4832
- * Provide IAM Provider Adapters
4833
- *
4834
- * Registers IAM implementations for provider interfaces from ng-shared.
4835
- * This allows ng-auth profile page to display permissions without direct dependencies.
4836
- *
4837
- * @example
4838
- * // In app.config.ts
4839
- * import { provideIamProviders } from '@flusys/ng-iam';
4840
- *
4841
- * export const appConfig: ApplicationConfig = {
4842
- * providers: [
4843
- * ...provideIamProviders(),
4844
- * // ... other providers
4845
- * ]
4846
- * };
4847
- *
4848
- * @returns Array of Angular providers
4849
- */
4281
+ /** Registers IAM provider adapters for ng-shared interfaces */
4850
4282
  function provideIamProviders() {
4851
4283
  return [
4852
- // Profile permission provider (for auth profile page)
4853
- {
4854
- provide: PROFILE_PERMISSION_PROVIDER,
4855
- useClass: ProfilePermissionProviderAdapter,
4856
- },
4284
+ { provide: PROFILE_PERMISSION_PROVIDER, useClass: ProfilePermissionProviderAdapter },
4857
4285
  ];
4858
4286
  }
4859
4287
 
4860
4288
  /**
4861
4289
  * IAM Routes Configuration
4862
4290
  *
4863
- * Identity and Access Management routing
4864
- * - Actions: Permission actions (always visible)
4291
+ * Identity and Access Management routing with permission guards.
4292
+ * - Actions: Permission actions management
4865
4293
  * - Roles: Role management (conditional on RBAC/FULL mode)
4866
- * - Permissions: User permission assignments (always visible)
4867
- *
4868
- * All routes are protected by permission guards to prevent direct URL access.
4294
+ * - Permissions: User permission assignments
4869
4295
  */
4870
4296
  const IAM_ROUTES = [
4871
4297
  {
4872
4298
  path: '',
4873
- loadComponent: () => import('./flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs').then((m) => m.IamContainerComponent),
4299
+ loadComponent: () => import('./flusys-ng-iam-iam-container.component-UYJjqYV9.mjs').then((m) => m.IamContainerComponent),
4874
4300
  children: [
4875
4301
  // Actions Management
4876
4302
  {
4877
4303
  path: 'actions',
4304
+ canActivate: [permissionGuard(ACTION_PERMISSIONS.READ)],
4878
4305
  children: [
4879
4306
  {
4880
4307
  path: '',
4881
- loadComponent: () => import('./flusys-ng-iam-action-list-page.component-Daf93zpS.mjs').then((m) => m.ActionListPageComponent),
4308
+ loadComponent: () => import('./flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs').then((m) => m.ActionListPageComponent),
4882
4309
  },
4883
4310
  {
4884
4311
  path: 'new',
4885
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
4312
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs').then((m) => m.ActionFormPageComponent),
4886
4313
  },
4887
4314
  {
4888
4315
  path: ':id',
4889
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
4316
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs').then((m) => m.ActionFormPageComponent),
4890
4317
  },
4891
4318
  ],
4892
4319
  },
4893
4320
  // Roles Management
4894
4321
  {
4895
4322
  path: 'roles',
4323
+ canActivate: [permissionGuard(ROLE_PERMISSIONS.READ)],
4896
4324
  children: [
4897
4325
  {
4898
4326
  path: '',
4899
- loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CFly5KnH.mjs').then((m) => m.RoleListPageComponent),
4327
+ loadComponent: () => import('./flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs').then((m) => m.RoleListPageComponent),
4900
4328
  },
4901
4329
  {
4902
4330
  path: 'new',
4903
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
4331
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-D_AAEay2.mjs').then((m) => m.RoleFormPageComponent),
4904
4332
  },
4905
4333
  {
4906
4334
  path: ':id',
4907
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
4335
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-D_AAEay2.mjs').then((m) => m.RoleFormPageComponent),
4908
4336
  },
4909
4337
  ],
4910
4338
  },
4911
- // Permissions Management (User permission assignment)
4339
+ // Permissions Management (requires any permission tab access)
4912
4340
  {
4913
4341
  path: 'permissions',
4914
- loadComponent: () => import('./flusys-ng-iam-permission-page.component-CmxOBJPu.mjs').then((m) => m.PermissionPageComponent),
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),
4915
4351
  },
4916
4352
  // Default redirect to actions
4917
4353
  {
@@ -4930,4 +4366,4 @@ const IAM_ROUTES = [
4930
4366
  */
4931
4367
 
4932
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 };
4933
- //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs.map
4369
+ //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs.map