@flusys/ng-iam 1.0.0-rc → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-CVN8sV-c.mjs} +11 -61
  3. package/fesm2022/flusys-ng-iam-action-form-page.component-CVN8sV-c.mjs.map +1 -0
  4. package/fesm2022/{flusys-ng-iam-action-list-page.component-Daf93zpS.mjs → flusys-ng-iam-action-list-page.component-CQ6RazN0.mjs} +19 -46
  5. package/fesm2022/flusys-ng-iam-action-list-page.component-CQ6RazN0.mjs.map +1 -0
  6. package/fesm2022/{flusys-ng-iam-flusys-ng-iam-BPIpfrjN.mjs → flusys-ng-iam-flusys-ng-iam-DrGHlTiz.mjs} +251 -813
  7. package/fesm2022/flusys-ng-iam-flusys-ng-iam-DrGHlTiz.mjs.map +1 -0
  8. package/fesm2022/{flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs → flusys-ng-iam-iam-container.component-BToYxEej.mjs} +2 -2
  9. package/fesm2022/flusys-ng-iam-iam-container.component-BToYxEej.mjs.map +1 -0
  10. package/fesm2022/{flusys-ng-iam-permission-page.component-CmxOBJPu.mjs → flusys-ng-iam-permission-page.component-BS7xXmsn.mjs} +8 -43
  11. package/fesm2022/flusys-ng-iam-permission-page.component-BS7xXmsn.mjs.map +1 -0
  12. package/fesm2022/{flusys-ng-iam-role-form-page.component-ByNueI1a.mjs → flusys-ng-iam-role-form-page.component-BjPwXkip.mjs} +2 -16
  13. package/fesm2022/flusys-ng-iam-role-form-page.component-BjPwXkip.mjs.map +1 -0
  14. package/fesm2022/{flusys-ng-iam-role-list-page.component-CFly5KnH.mjs → flusys-ng-iam-role-list-page.component-Cz-jk-R_.mjs} +3 -20
  15. package/fesm2022/flusys-ng-iam-role-list-page.component-Cz-jk-R_.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 -444
  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,7 +1,7 @@
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';
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
5
  import { APP_CONFIG, 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';
@@ -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,81 +29,40 @@ 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('iam/roles', inject(HttpClient));
60
38
  }
61
39
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
62
40
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleApiService, providedIn: 'root' });
63
41
  }
64
42
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", 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
50
  const http = inject(HttpClient);
80
51
  super('iam/actions', http);
81
52
  }
82
- /**
83
- * Get actions for permission assignment
84
- * POST /iam/actions/tree-for-permission
85
- * Returns actions filtered by company whitelist if enabled
86
- */
53
+ /** Get actions filtered by company whitelist for permission assignment */
87
54
  getActionsForPermission() {
88
55
  return this.http.post(`${this.appConfig.apiBaseUrl}/iam/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
66
  return this.http.post(`${this.appConfig.apiBaseUrl}/iam/actions/tree`, body);
112
67
  }
113
68
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -115,48 +70,24 @@ class ActionApiService extends ApiResourceService {
115
70
  }
116
71
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionApiService, decorators: [{
117
72
  type: Injectable,
118
- args: [{
119
- providedIn: 'root',
120
- }]
73
+ args: [{ providedIn: 'root' }]
121
74
  }], ctorParameters: () => [] });
122
75
 
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
76
  function extractRequiredActionIds(logic) {
131
77
  const actionIds = new Set();
132
- if (!logic) {
133
- return actionIds;
78
+ if (logic) {
79
+ collectActionIds(logic, actionIds);
134
80
  }
135
- collectActionIds(logic, actionIds);
136
81
  return actionIds;
137
82
  }
138
- /**
139
- * Recursively collect action IDs from logic tree
140
- */
141
83
  function collectActionIds(node, actionIds) {
142
84
  if (node.type === 'action' && node.actionId) {
143
85
  actionIds.add(node.actionId);
144
86
  }
145
87
  else if (node.type === 'group' && node.children) {
146
- for (const child of node.children) {
147
- collectActionIds(child, actionIds);
148
- }
88
+ node.children.forEach((child) => collectActionIds(child, actionIds));
149
89
  }
150
90
  }
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
91
  function validateActionPrerequisites(action, selectedActionIds, allActions) {
161
92
  if (!action.permissionLogic) {
162
93
  return { valid: true, missingActions: [] };
@@ -168,20 +99,12 @@ function validateActionPrerequisites(action, selectedActionIds, allActions) {
168
99
  const missingActions = allActions.filter((a) => a.id && result.missingActionIds.has(a.id));
169
100
  return { valid: false, missingActions };
170
101
  }
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
102
  function evaluateLogicNodeWithMissing(node, selectedActionIds) {
177
103
  if (node.type === 'action' && node.actionId) {
178
104
  const valid = selectedActionIds.has(node.actionId);
179
- return {
180
- valid,
181
- missingActionIds: valid ? new Set() : new Set([node.actionId]),
182
- };
105
+ return { valid, missingActionIds: valid ? new Set() : new Set([node.actionId]) };
183
106
  }
184
- if (node.type === 'group' && node.children && node.children.length > 0) {
107
+ if (node.type === 'group' && node.children?.length) {
185
108
  const operator = node.operator || 'AND';
186
109
  if (operator === 'AND') {
187
110
  const missingIds = new Set();
@@ -195,109 +118,28 @@ function evaluateLogicNodeWithMissing(node, selectedActionIds) {
195
118
  }
196
119
  return { valid: allValid, missingActionIds: missingIds };
197
120
  }
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
- }
121
+ // OR operator
122
+ for (const child of node.children) {
123
+ const childResult = evaluateLogicNodeWithMissing(child, selectedActionIds);
124
+ if (childResult.valid) {
125
+ return { valid: true, missingActionIds: new Set() };
204
126
  }
205
- const firstChildResult = evaluateLogicNodeWithMissing(node.children[0], selectedActionIds);
206
- return {
207
- valid: false,
208
- missingActionIds: firstChildResult.missingActionIds,
209
- };
210
127
  }
128
+ const firstChildResult = evaluateLogicNodeWithMissing(node.children[0], selectedActionIds);
129
+ return { valid: false, missingActionIds: firstChildResult.missingActionIds };
211
130
  }
212
131
  return { valid: true, missingActionIds: new Set() };
213
132
  }
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
133
 
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
- */
134
+ /** Shared service for smart dependency management across action selectors */
281
135
  class ActionPermissionLogicService {
282
136
  confirmationService = inject(ConfirmationService);
283
137
  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
- */
138
+ /** Handle checking an action with prerequisite validation (recursive deep scan) */
297
139
  handleCheck(action, currentSelection, allActions, onUpdate, onCancel) {
298
140
  // Validate prerequisites with RECURSIVE DEEP SCAN
299
141
  if (action.permissionLogic) {
300
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
142
+ const selectedActionIds = this.getSelectedIds(currentSelection);
301
143
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
302
144
  if (!validationResult.valid) {
303
145
  // Store previous state
@@ -318,14 +160,7 @@ class ActionPermissionLogicService {
318
160
  selMap[action.id] = true;
319
161
  onUpdate(selMap);
320
162
  }
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
- */
163
+ /** Handle unchecking an action with dependency detection */
329
164
  handleUncheck(action, currentSelection, allActions, onUpdate) {
330
165
  const affectedActions = this.findActionsDependingOn(action.id, currentSelection, allActions);
331
166
  if (affectedActions.length > 0) {
@@ -337,46 +172,28 @@ class ActionPermissionLogicService {
337
172
  selMap[action.id] = false;
338
173
  onUpdate(selMap);
339
174
  }
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
- */
175
+ /** Check if an action has unmet prerequisites */
348
176
  hasUnmetPrerequisites(action, currentSelection, allActions) {
349
177
  const isSelected = currentSelection[action.id];
350
178
  if (!isSelected || !action.permissionLogic) {
351
179
  return false;
352
180
  }
353
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id] && id !== action.id));
181
+ const selectedActionIds = this.getSelectedIds(currentSelection);
182
+ selectedActionIds.delete(action.id);
354
183
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
355
184
  return !validationResult.valid;
356
185
  }
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
- */
186
+ /** Get all selected actions that have unmet prerequisites */
364
187
  getActionsWithUnmetPrerequisites(currentSelection, allActions) {
365
188
  const selectedActions = allActions.filter((a) => currentSelection[a.id]);
366
189
  return selectedActions.filter((action) => this.hasUnmetPrerequisites(action, currentSelection, allActions));
367
190
  }
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
- */
191
+ /** Show validation error dialog with auto-fix options */
376
192
  showValidationErrorDialog(invalidActions, currentSelection, allActions, onUpdate) {
377
193
  const errorList = invalidActions
378
194
  .map((action) => {
379
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id] && id !== action.id));
195
+ const selectedActionIds = this.getSelectedIds(currentSelection);
196
+ selectedActionIds.delete(action.id);
380
197
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
381
198
  const sanitizedActionName = this.sanitizeHtml(action.name ?? 'Unknown');
382
199
  const missingNames = validationResult.missingActions
@@ -418,19 +235,12 @@ class ActionPermissionLogicService {
418
235
  },
419
236
  });
420
237
  }
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
- */
238
+ /** Get prerequisite description for tooltip display */
429
239
  getPrerequisiteTooltip(action, currentSelection, allActions) {
430
240
  if (!action.permissionLogic) {
431
241
  return '';
432
242
  }
433
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
243
+ const selectedActionIds = this.getSelectedIds(currentSelection);
434
244
  const validationResult = validateActionPrerequisites(action, selectedActionIds, allActions);
435
245
  if (validationResult.valid) {
436
246
  return '[OK] Prerequisites Satisfied\n\nAll required actions are already selected.\nYou can safely add this action.';
@@ -442,23 +252,16 @@ class ActionPermissionLogicService {
442
252
  const hint = '\n\n💡 Click to auto-select required actions';
443
253
  return `${header}\n\n${logicTree}${hint}`;
444
254
  }
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();
255
+ /** Build dynamic logic tree message with AND/OR operators and nesting */
256
+ buildLogicMessage(logic, _missingActions, allActions, currentSelection) {
257
+ const selectedIds = currentSelection ? this.getSelectedIds(currentSelection) : new Set();
458
258
  const actionMap = new Map(allActions.map((a) => [a.id, a]));
459
259
  return this.formatLogicNode(logic, selectedIds, actionMap, 0);
460
260
  }
461
- // ==================== Private Methods ====================
261
+ /** Extract selected action IDs from selection map */
262
+ getSelectedIds(currentSelection) {
263
+ return new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
264
+ }
462
265
  sanitizeHtml(text) {
463
266
  return text
464
267
  .replace(/&/g, '&')
@@ -467,24 +270,7 @@ class ActionPermissionLogicService {
467
270
  .replace(/"/g, '"')
468
271
  .replace(/'/g, ''');
469
272
  }
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
- */
273
+ /** Recursively collect ALL missing prerequisites at all dependency levels */
488
274
  getAllMissingPrerequisitesRecursive(action, currentSelection, allActions) {
489
275
  if (!action.id)
490
276
  return [];
@@ -504,7 +290,7 @@ class ActionPermissionLogicService {
504
290
  }
505
291
  visited.add(targetAction.id);
506
292
  stack.add(targetAction.id);
507
- const selectedActionIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
293
+ const selectedActionIds = this.getSelectedIds(currentSelection);
508
294
  const result = validateActionPrerequisites(targetAction, selectedActionIds, allActions);
509
295
  if (!result.valid) {
510
296
  result.missingActions.forEach((missingAction) => {
@@ -565,7 +351,7 @@ class ActionPermissionLogicService {
565
351
  },
566
352
  });
567
353
  }
568
- showDependencyDialog(action, affectedActions, currentSelection, allActions, onUpdate) {
354
+ showDependencyDialog(action, affectedActions, currentSelection, _allActions, onUpdate) {
569
355
  if (!action.id)
570
356
  return;
571
357
  const alternatives = this.findAlternatives(action.id, affectedActions, currentSelection);
@@ -688,7 +474,7 @@ class ActionPermissionLogicService {
688
474
  calculateSmartSelection(logic, missingActions, currentSelection, allActions) {
689
475
  const missingIds = new Set(missingActions.map((a) => a.id));
690
476
  const actionMap = new Map(allActions.map((a) => [a.id, a]));
691
- const selectedIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
477
+ const selectedIds = this.getSelectedIds(currentSelection);
692
478
  const requiredIds = this.findRequiredActionIds(logic, missingIds, selectedIds);
693
479
  return Array.from(requiredIds)
694
480
  .map((id) => actionMap.get(id))
@@ -761,12 +547,10 @@ class ActionPermissionLogicService {
761
547
  }
762
548
  return `${indent}<em style="color: #9ca3af;">Invalid logic node</em>`;
763
549
  }
764
- /**
765
- * Build clean text-based logic tree for tooltips
766
- */
550
+ /** Build clean text-based logic tree for tooltips */
767
551
  buildTooltipLogicTree(node, currentSelection, allActions, depth) {
768
552
  const indent = ' '.repeat(depth);
769
- const selectedIds = new Set(Object.keys(currentSelection).filter((id) => currentSelection[id]));
553
+ const selectedIds = this.getSelectedIds(currentSelection);
770
554
  const actionMap = new Map(allActions.map((a) => [a.id, a]));
771
555
  if (node.type === 'action' && node.actionId) {
772
556
  const action = actionMap.get(node.actionId);
@@ -801,91 +585,34 @@ class ActionPermissionLogicService {
801
585
  }
802
586
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ActionPermissionLogicService, decorators: [{
803
587
  type: Injectable,
804
- args: [{
805
- providedIn: 'root',
806
- }]
588
+ args: [{ providedIn: 'root' }]
807
589
  }] });
808
590
 
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
591
  class PermissionApiService extends BaseApiService {
821
592
  constructor() {
822
593
  super('iam');
823
594
  }
824
- // =============================================================================
825
- // User → Action (Direct Permissions)
826
- // =============================================================================
827
- /**
828
- * Assign/remove actions directly to/from user
829
- * POST /permissions/user-actions/assign
830
- */
831
595
  assignUserActions(data) {
832
596
  return this.http.post(this.getUrl('permissions/user-actions/assign'), data);
833
597
  }
834
- /**
835
- * Get user's direct action permissions
836
- * POST /iam/permissions/get-user-actions
837
- */
838
598
  getUserActions(userId, query) {
839
599
  return this.http.post(this.getUrl('permissions/get-user-actions'), { userId, branchId: query?.branchId });
840
600
  }
841
- // =============================================================================
842
- // User → Role (Role Assignments)
843
- // =============================================================================
844
- /**
845
- * Assign/remove roles to/from user
846
- * POST /permissions/user-roles/assign
847
- */
848
601
  assignUserRoles(data) {
849
602
  return this.http.post(this.getUrl('permissions/user-roles/assign'), data);
850
603
  }
851
- /**
852
- * Get user's role assignments
853
- * POST /iam/permissions/get-user-roles
854
- */
855
604
  getUserRoles(userId, query) {
856
605
  return this.http.post(this.getUrl('permissions/get-user-roles'), { userId, branchId: query?.branchId });
857
606
  }
858
- // =============================================================================
859
- // Role → Action (Role Permissions)
860
- // =============================================================================
861
- /**
862
- * Assign/remove actions to/from role
863
- * POST /permissions/role-actions/assign
864
- */
865
607
  assignRoleActions(data) {
866
608
  return this.http.post(this.getUrl('permissions/role-actions/assign'), data);
867
609
  }
868
- /**
869
- * Get role's action permissions
870
- * POST /iam/permissions/get-role-actions
871
- */
872
- getRoleActions(roleId, query) {
610
+ getRoleActions(roleId) {
873
611
  return this.http.post(this.getUrl('permissions/get-role-actions'), { roleId });
874
612
  }
875
- // =============================================================================
876
- // Company → Action (Company Whitelisting)
877
- // =============================================================================
878
- /**
879
- * Assign/remove actions to/from company (whitelisting)
880
- * POST /permissions/company-actions/assign
881
- */
882
613
  assignCompanyActions(data) {
883
614
  return this.http.post(this.getUrl('permissions/company-actions/assign'), data);
884
615
  }
885
- /**
886
- * Get company's whitelisted actions
887
- * POST /iam/permissions/get-company-actions
888
- */
889
616
  getCompanyActions(companyId) {
890
617
  return this.http.post(this.getUrl('permissions/get-company-actions'), { companyId });
891
618
  }
@@ -894,9 +621,7 @@ class PermissionApiService extends BaseApiService {
894
621
  }
895
622
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionApiService, decorators: [{
896
623
  type: Injectable,
897
- args: [{
898
- providedIn: 'root',
899
- }]
624
+ args: [{ providedIn: 'root' }]
900
625
  }], ctorParameters: () => [] });
901
626
 
902
627
  /**
@@ -931,47 +656,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
931
656
  }]
932
657
  }], ctorParameters: () => [] });
933
658
 
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
659
  class PermissionStateService {
956
660
  permissionApi = inject(MyPermissionsApiService);
957
661
  permissionValidator = inject(PermissionValidatorService);
958
- // Permission state
959
662
  _permissions = signal(null, ...(ngDevMode ? [{ debugName: "_permissions" }] : []));
960
663
  permissions = this._permissions.asReadonly();
961
- // Loading state
962
664
  _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
963
665
  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
666
  loadPermissions(dto) {
970
667
  this._isLoading.set(true);
971
668
  return this.permissionApi.getMyPermissions(dto).pipe(tap((response) => {
972
669
  this._permissions.set(response.data ?? null);
973
670
  this._isLoading.set(false);
974
- // Update shared permission validator
975
671
  const actionCodes = response.data?.frontendActions.map((a) => a.code) ?? [];
976
672
  this.permissionValidator.setPermissions(actionCodes);
977
673
  }), catchError(() => {
@@ -981,14 +677,8 @@ class PermissionStateService {
981
677
  return of(void 0);
982
678
  }), map(() => void 0));
983
679
  }
984
- /**
985
- * Check if permissions are loaded
986
- *
987
- * @returns true if permissions are loaded
988
- */
989
680
  isLoaded() {
990
- return (this._permissions() !== null &&
991
- this.permissionValidator.isPermissionsLoaded());
681
+ return this._permissions() !== null && this.permissionValidator.isPermissionsLoaded();
992
682
  }
993
683
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
994
684
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionStateService, providedIn: 'root' });
@@ -1012,26 +702,28 @@ function toLogicNode(node) {
1012
702
  }
1013
703
  function toBuilderNode(node) {
1014
704
  if (node.type === 'action') {
1015
- return { id: crypto.randomUUID(), type: 'action', actionId: node.actionId };
705
+ return createActionNode(node.actionId);
1016
706
  }
1017
707
  return {
1018
- id: crypto.randomUUID(),
1019
- type: 'group',
1020
- operator: node.operator,
708
+ ...createGroupNode(node.operator),
1021
709
  children: node.children?.map(toBuilderNode) ?? [],
1022
710
  };
1023
711
  }
712
+ function createGroupNode(operator = 'AND') {
713
+ return { id: crypto.randomUUID(), type: 'group', operator, children: [] };
714
+ }
715
+ function createActionNode(actionId = '') {
716
+ return { id: crypto.randomUUID(), type: 'action', actionId };
717
+ }
1024
718
  /** Visual builder for AND/OR permission logic trees */
1025
719
  class LogicBuilderComponent {
1026
720
  logic = input(null, ...(ngDevMode ? [{ debugName: "logic" }] : []));
1027
721
  actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
1028
722
  logicChange = output();
1029
- availableActions = computed(() => this.actions(), ...(ngDevMode ? [{ debugName: "availableActions" }] : []));
1030
723
  /** Internal builder tree state (private writable + public readonly pattern) */
1031
724
  _builderTree = signal(null, ...(ngDevMode ? [{ debugName: "_builderTree" }] : []));
1032
725
  builderLogic = this._builderTree.asReadonly();
1033
726
  constructor() {
1034
- // Sync builderTree with logic input changes
1035
727
  effect(() => {
1036
728
  const logic = this.logic();
1037
729
  if (!logic) {
@@ -1043,40 +735,24 @@ class LogicBuilderComponent {
1043
735
  }, { allowSignalWrites: true });
1044
736
  }
1045
737
  initializeLogic() {
1046
- this._builderTree.set({
1047
- id: crypto.randomUUID(),
1048
- type: 'group',
1049
- operator: 'AND',
1050
- children: [],
1051
- });
1052
- this.emitChange();
738
+ this.updateTreeAndEmit(createGroupNode());
1053
739
  }
1054
740
  clearLogic() {
1055
741
  this._builderTree.set(null);
1056
742
  this.logicChange.emit(null);
1057
743
  }
1058
744
  toggleOperator(nodeId) {
1059
- const tree = this._builderTree();
1060
- if (!tree)
1061
- return;
1062
- this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
745
+ this.updateNode(nodeId, (node) => ({
1063
746
  ...node,
1064
747
  operator: node.operator === 'AND' ? 'OR' : 'AND',
1065
- })));
1066
- this.emitChange();
748
+ }));
1067
749
  }
1068
750
  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) => ({
751
+ const newNode = type === 'group' ? createGroupNode() : createActionNode();
752
+ this.updateNode(parentId, (node) => ({
1076
753
  ...node,
1077
754
  children: [...(node.children || []), newNode],
1078
- })));
1079
- this.emitChange();
755
+ }));
1080
756
  }
1081
757
  removeNode(nodeId) {
1082
758
  const tree = this._builderTree();
@@ -1086,48 +762,42 @@ class LogicBuilderComponent {
1086
762
  this.clearLogic();
1087
763
  return;
1088
764
  }
1089
- this._builderTree.set(this.removeNodeFromTree(tree, nodeId));
1090
- this.emitChange();
765
+ this.updateTreeAndEmit(this.removeNodeFromTree(tree, nodeId));
1091
766
  }
1092
767
  updateActionId(nodeId, actionId) {
768
+ this.updateNode(nodeId, (node) => ({ ...node, actionId }));
769
+ }
770
+ /** Updates a node in the tree and emits the change */
771
+ updateNode(nodeId, updater) {
1093
772
  const tree = this._builderTree();
1094
773
  if (!tree)
1095
774
  return;
1096
- this._builderTree.set(this.updateNodeInTree(tree, nodeId, (node) => ({
1097
- ...node,
1098
- actionId,
1099
- })));
1100
- this.emitChange();
775
+ this.updateTreeAndEmit(this.updateNodeInTree(tree, nodeId, updater));
1101
776
  }
1102
- emitChange() {
1103
- const tree = this._builderTree();
1104
- if (!tree) {
1105
- this.logicChange.emit(null);
1106
- return;
1107
- }
777
+ /** Sets the tree and emits the change */
778
+ updateTreeAndEmit(tree) {
779
+ this._builderTree.set(tree);
1108
780
  this.logicChange.emit(toLogicNode(tree));
1109
781
  }
1110
782
  updateNodeInTree(node, targetId, updater) {
1111
783
  if (node.id === targetId)
1112
784
  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;
785
+ if (!node.children)
786
+ return node;
787
+ return {
788
+ ...node,
789
+ children: node.children.map((child) => this.updateNodeInTree(child, targetId, updater)),
790
+ };
1120
791
  }
1121
792
  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;
793
+ if (!node.children)
794
+ return node;
795
+ return {
796
+ ...node,
797
+ children: node.children
798
+ .filter((child) => child.id !== targetId)
799
+ .map((child) => this.removeNodeFromTree(child, targetId)),
800
+ };
1131
801
  }
1132
802
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1133
803
  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: `
@@ -1190,8 +860,8 @@ class LogicBuilderComponent {
1190
860
  class="action-select flex-1 p-2 border border-surface rounded text-sm"
1191
861
  [ngModel]="node.actionId"
1192
862
  (ngModelChange)="updateActionId(node.id, $event)">
1193
- <option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
1194
- @for (action of availableActions(); track action.id) {
863
+ <option [value]="null">Select Action... ({{ actions().length }} available)</option>
864
+ @for (action of actions(); track action.id) {
1195
865
  <option [ngValue]="action.id">{{ action.name }}</option>
1196
866
  }
1197
867
  </select>
@@ -1245,7 +915,7 @@ class LogicBuilderComponent {
1245
915
  }
1246
916
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LogicBuilderComponent, decorators: [{
1247
917
  type: Component,
1248
- args: [{ selector: 'lib-logic-builder', standalone: true, imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
918
+ args: [{ selector: 'lib-logic-builder', imports: [AngularModule, PrimeModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1249
919
  <div class="logic-builder">
1250
920
  <div class="flex justify-between items-center mb-3">
1251
921
  <h4 class="text-sm font-semibold m-0">Permission Logic</h4>
@@ -1305,8 +975,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1305
975
  class="action-select flex-1 p-2 border border-surface rounded text-sm"
1306
976
  [ngModel]="node.actionId"
1307
977
  (ngModelChange)="updateActionId(node.id, $event)">
1308
- <option [value]="null">Select Action... ({{ availableActions().length }} available)</option>
1309
- @for (action of availableActions(); track action.id) {
978
+ <option [value]="null">Select Action... ({{ actions().length }} available)</option>
979
+ @for (action of actions(); track action.id) {
1310
980
  <option [ngValue]="action.id">{{ action.name }}</option>
1311
981
  }
1312
982
  </select>
@@ -1359,102 +1029,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1359
1029
  `, 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
1030
  }], 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
1031
 
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
1032
  function flattenTree(tree) {
1386
- if (!tree?.length) {
1033
+ if (!tree?.length)
1387
1034
  return [];
1388
- }
1389
1035
  const result = [];
1390
1036
  const flatten = (nodes) => {
1391
1037
  for (const node of nodes) {
1392
1038
  result.push(node);
1393
- if (node.children?.length) {
1039
+ if (node.children?.length)
1394
1040
  flatten(node.children);
1395
- }
1396
1041
  }
1397
1042
  };
1398
1043
  flatten(tree);
1399
1044
  return result;
1400
1045
  }
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
1046
  function buildTreeFromFlat(flatList) {
1445
- if (!flatList?.length) {
1047
+ if (!flatList?.length)
1446
1048
  return [];
1447
- }
1448
- // Create map for O(1) lookup
1449
1049
  const itemMap = new Map();
1450
1050
  const rootItems = [];
1451
- // First pass: Create all nodes with empty children arrays
1452
1051
  flatList.forEach((item) => {
1453
- if (item.id) {
1052
+ if (item.id)
1454
1053
  itemMap.set(item.id, { ...item, children: [] });
1455
- }
1456
1054
  });
1457
- // Second pass: Build parent-child relationships
1458
1055
  flatList.forEach((item) => {
1459
1056
  if (!item.id)
1460
1057
  return;
@@ -1462,51 +1059,23 @@ function buildTreeFromFlat(flatList) {
1462
1059
  if (!node)
1463
1060
  return;
1464
1061
  if (!item.parentId) {
1465
- // Root level item
1466
1062
  rootItems.push(node);
1467
1063
  }
1468
1064
  else {
1469
- // Child item - add to parent's children
1470
1065
  const parent = itemMap.get(item.parentId);
1471
1066
  if (parent) {
1472
1067
  parent.children.push(node);
1473
1068
  }
1474
1069
  else {
1475
- // Parent not found - treat as root
1476
1070
  rootItems.push(node);
1477
1071
  }
1478
1072
  }
1479
1073
  });
1480
1074
  return rootItems;
1481
1075
  }
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
1076
  function convertActionToTreeNode(actions) {
1507
- if (!actions?.length) {
1077
+ if (!actions?.length)
1508
1078
  return [];
1509
- }
1510
1079
  const convert = (action) => {
1511
1080
  const { children, ...actionData } = action;
1512
1081
  return {
@@ -1606,17 +1175,14 @@ class RoleActionSelectorComponent {
1606
1175
  const selMap = this.selectionMap();
1607
1176
  const allActions = this.actions();
1608
1177
  const unmetSet = new Set();
1609
- allActions.forEach((action) => {
1610
- if (action.id &&
1611
- this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
1178
+ for (const action of allActions) {
1179
+ if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
1612
1180
  unmetSet.add(action.id);
1613
1181
  }
1614
- });
1182
+ }
1615
1183
  return unmetSet;
1616
1184
  }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
1617
- invalidActionsCount = computed(() => {
1618
- return this.actionsWithUnmetPrerequisites().size;
1619
- }, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1185
+ invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1620
1186
  // Computed - Pending Changes
1621
1187
  pendingAdd = computed(() => {
1622
1188
  const current = this.selectionMap();
@@ -1701,13 +1267,12 @@ class RoleActionSelectorComponent {
1701
1267
  const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
1702
1268
  // Build selection map
1703
1269
  const selMap = {};
1704
- flatActions.forEach((action) => {
1270
+ for (const action of flatActions) {
1705
1271
  if (action.id) {
1706
1272
  selMap[action.id] = assignedIds.has(action.id);
1707
1273
  }
1708
- });
1709
- this._selectionMap.set(selMap);
1710
- this._initialSelection.set({ ...selMap });
1274
+ }
1275
+ this.applySelection(selMap);
1711
1276
  }
1712
1277
  finally {
1713
1278
  if (!signal.aborted) {
@@ -1756,35 +1321,20 @@ class RoleActionSelectorComponent {
1756
1321
  this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
1757
1322
  }
1758
1323
  }
1759
- /**
1760
- * Toggle all actions
1761
- */
1762
1324
  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);
1325
+ this.setAllSelection(!this.allSelected());
1769
1326
  }
1770
- /**
1771
- * Select all actions
1772
- */
1773
1327
  selectAll() {
1774
- const selMap = {};
1775
- this.actions().forEach((action) => {
1776
- selMap[action.id] = true;
1777
- });
1778
- this._selectionMap.set(selMap);
1328
+ this.setAllSelection(true);
1779
1329
  }
1780
- /**
1781
- * Deselect all actions
1782
- */
1783
1330
  deselectAll() {
1331
+ this.setAllSelection(false);
1332
+ }
1333
+ setAllSelection(value) {
1784
1334
  const selMap = {};
1785
- this.actions().forEach((action) => {
1786
- selMap[action.id] = false;
1787
- });
1335
+ for (const action of this.actions()) {
1336
+ selMap[action.id] = value;
1337
+ }
1788
1338
  this._selectionMap.set(selMap);
1789
1339
  }
1790
1340
  /**
@@ -1801,19 +1351,7 @@ class RoleActionSelectorComponent {
1801
1351
  return;
1802
1352
  }
1803
1353
  // 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
- });
1354
+ const items = this.buildPayloadItems();
1817
1355
  if (items.length === 0)
1818
1356
  return;
1819
1357
  this.saving.set(true);
@@ -1837,9 +1375,20 @@ class RoleActionSelectorComponent {
1837
1375
  this.saving.set(false);
1838
1376
  }
1839
1377
  }
1840
- /**
1841
- * Reset component state
1842
- */
1378
+ applySelection(selMap) {
1379
+ this._selectionMap.set(selMap);
1380
+ this._initialSelection.set({ ...selMap });
1381
+ }
1382
+ buildPayloadItems() {
1383
+ const items = [];
1384
+ for (const action of this.pendingAdd()) {
1385
+ items.push({ id: action.id, action: 'add' });
1386
+ }
1387
+ for (const action of this.pendingRemove()) {
1388
+ items.push({ id: action.id, action: 'remove' });
1389
+ }
1390
+ return items;
1391
+ }
1843
1392
  resetState() {
1844
1393
  this._actionsTree.set([]);
1845
1394
  this._actions.set([]);
@@ -2072,7 +1621,7 @@ class RoleActionSelectorComponent {
2072
1621
  }
2073
1622
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
2074
1623
  type: Component,
2075
- args: [{ selector: 'flusys-role-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
1624
+ args: [{ selector: 'flusys-role-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2076
1625
  <div class="role-action-selector">
2077
1626
  <!-- Role Selector -->
2078
1627
  <div class="mb-4">
@@ -2384,17 +1933,14 @@ class CompanyActionSelectorComponent {
2384
1933
  const selMap = this.selectionMap();
2385
1934
  const allActions = this.actions();
2386
1935
  const unmetSet = new Set();
2387
- allActions.forEach((action) => {
2388
- if (action.id &&
2389
- this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
1936
+ for (const action of allActions) {
1937
+ if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
2390
1938
  unmetSet.add(action.id);
2391
1939
  }
2392
- });
1940
+ }
2393
1941
  return unmetSet;
2394
1942
  }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
2395
- invalidActionsCount = computed(() => {
2396
- return this.actionsWithUnmetPrerequisites().size;
2397
- }, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1943
+ invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
2398
1944
  // Computed - Pending Changes
2399
1945
  pendingAdd = computed(() => {
2400
1946
  const current = this.selectionMap();
@@ -2478,13 +2024,7 @@ class CompanyActionSelectorComponent {
2478
2024
  if (signal.aborted)
2479
2025
  return;
2480
2026
  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
- });
2027
+ const selMap = this.buildSelectionMap(actionsList, (action) => assignedIds.has(action.id));
2488
2028
  this._selectionMap.set(selMap);
2489
2029
  this._initialSelection.set({ ...selMap });
2490
2030
  }
@@ -2535,36 +2075,17 @@ class CompanyActionSelectorComponent {
2535
2075
  this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
2536
2076
  }
2537
2077
  }
2538
- /**
2539
- * Toggle all actions
2540
- */
2541
2078
  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);
2079
+ this.setAllSelection(!this.allSelected());
2548
2080
  }
2549
- /**
2550
- * Select all actions
2551
- */
2552
2081
  selectAll() {
2553
- const selMap = {};
2554
- this.actions().forEach((action) => {
2555
- selMap[action.id] = true;
2556
- });
2557
- this._selectionMap.set(selMap);
2082
+ this.setAllSelection(true);
2558
2083
  }
2559
- /**
2560
- * Deselect all actions
2561
- */
2562
2084
  deselectAll() {
2563
- const selMap = {};
2564
- this.actions().forEach((action) => {
2565
- selMap[action.id] = false;
2566
- });
2567
- this._selectionMap.set(selMap);
2085
+ this.setAllSelection(false);
2086
+ }
2087
+ setAllSelection(value) {
2088
+ this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
2568
2089
  }
2569
2090
  /**
2570
2091
  * Save changes to backend
@@ -2579,20 +2100,7 @@ class CompanyActionSelectorComponent {
2579
2100
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
2580
2101
  return;
2581
2102
  }
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
- });
2103
+ const items = this.buildPayloadItems();
2596
2104
  if (items.length === 0)
2597
2105
  return;
2598
2106
  this.saving.set(true);
@@ -2670,15 +2178,29 @@ class CompanyActionSelectorComponent {
2670
2178
  },
2671
2179
  });
2672
2180
  }
2673
- /**
2674
- * Reset component state
2675
- */
2676
2181
  resetState() {
2677
2182
  this._actionsTree.set([]);
2678
2183
  this._actions.set([]);
2679
2184
  this._selectionMap.set({});
2680
2185
  this._initialSelection.set({});
2681
2186
  }
2187
+ buildSelectionMap(actions, predicate) {
2188
+ const selMap = {};
2189
+ for (const action of actions) {
2190
+ selMap[action.id] = predicate(action);
2191
+ }
2192
+ return selMap;
2193
+ }
2194
+ buildPayloadItems() {
2195
+ const items = [];
2196
+ for (const action of this.pendingAdd()) {
2197
+ items.push({ id: action.id, action: 'add' });
2198
+ }
2199
+ for (const action of this.pendingRemove()) {
2200
+ items.push({ id: action.id, action: 'remove' });
2201
+ }
2202
+ return items;
2203
+ }
2682
2204
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2683
2205
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", ngImport: i0, template: `
2684
2206
  <div class="company-action-selector">
@@ -2899,7 +2421,7 @@ class CompanyActionSelectorComponent {
2899
2421
  }
2900
2422
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
2901
2423
  type: Component,
2902
- args: [{ selector: 'flusys-company-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2424
+ args: [{ selector: 'flusys-company-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective], template: `
2903
2425
  <div class="company-action-selector">
2904
2426
  <!-- Company Selector -->
2905
2427
  <div class="mb-4">
@@ -3157,9 +2679,6 @@ class UserRoleSelectorComponent {
3157
2679
  roleApi = inject(RoleApiService);
3158
2680
  permissionApi = inject(PermissionApiService);
3159
2681
  messageService = inject(MessageService);
3160
- destroyRef = inject(DestroyRef);
3161
- // AbortController for data loading
3162
- loadDataAbortController = null;
3163
2682
  // State - User/Branch Selection
3164
2683
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3165
2684
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
@@ -3220,10 +2739,6 @@ class UserRoleSelectorComponent {
3220
2739
  return this.hasChanges() && !this.saving();
3221
2740
  }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
3222
2741
  constructor() {
3223
- // Cleanup on destroy
3224
- this.destroyRef.onDestroy(() => {
3225
- this.loadDataAbortController?.abort();
3226
- });
3227
2742
  // Effect: Load user branches and data when user changes
3228
2743
  effect(() => {
3229
2744
  const userId = this.selectedUserId();
@@ -3260,9 +2775,7 @@ class UserRoleSelectorComponent {
3260
2775
  this.branches.set([]);
3261
2776
  return;
3262
2777
  }
3263
- const response = await this.userPermissionProvider
3264
- .getUserBranchPermissions(userId)
3265
- .toPromise();
2778
+ const response = await firstValueFrom(this.userPermissionProvider.getUserBranchPermissions(userId));
3266
2779
  const userBranches = response?.data?.map((ub) => ({
3267
2780
  id: ub.branchId,
3268
2781
  name: ub.branchName,
@@ -3285,20 +2798,16 @@ class UserRoleSelectorComponent {
3285
2798
  this.loading.set(true);
3286
2799
  try {
3287
2800
  // Load all roles
3288
- const rolesResponse = await this.roleApi
3289
- .getAll('', {
2801
+ const rolesResponse = await firstValueFrom(this.roleApi.getAll('', {
3290
2802
  pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
3291
- })
3292
- .toPromise();
2803
+ }));
3293
2804
  const rolesList = rolesResponse?.data ?? [];
3294
2805
  this._roles.set(rolesList);
3295
2806
  // Load existing user-role assignments
3296
2807
  const query = isCompanyFeatureEnabled(this.appConfig) && branchId
3297
2808
  ? { branchId }
3298
2809
  : undefined;
3299
- const assignedResponse = await this.permissionApi
3300
- .getUserRoles(userId, query)
3301
- .toPromise();
2810
+ const assignedResponse = await firstValueFrom(this.permissionApi.getUserRoles(userId, query));
3302
2811
  const assignedIds = new Set((assignedResponse?.data || []).map((r) => r.roleId));
3303
2812
  // Build selection map
3304
2813
  const selMap = {};
@@ -3338,30 +2847,27 @@ class UserRoleSelectorComponent {
3338
2847
  * Toggle all roles
3339
2848
  */
3340
2849
  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);
2850
+ this.setAllSelections(!this.allSelected());
3347
2851
  }
3348
2852
  /**
3349
2853
  * Select all roles
3350
2854
  */
3351
2855
  selectAll() {
3352
- const selMap = {};
3353
- this.roles().forEach((role) => {
3354
- selMap[role.id] = true;
3355
- });
3356
- this._selectionMap.set(selMap);
2856
+ this.setAllSelections(true);
3357
2857
  }
3358
2858
  /**
3359
2859
  * Deselect all roles
3360
2860
  */
3361
2861
  deselectAll() {
2862
+ this.setAllSelections(false);
2863
+ }
2864
+ /**
2865
+ * Set all role selections to a given value
2866
+ */
2867
+ setAllSelections(value) {
3362
2868
  const selMap = {};
3363
2869
  this.roles().forEach((role) => {
3364
- selMap[role.id] = false;
2870
+ selMap[role.id] = value;
3365
2871
  });
3366
2872
  this._selectionMap.set(selMap);
3367
2873
  }
@@ -3404,9 +2910,7 @@ class UserRoleSelectorComponent {
3404
2910
  payload.branchId = branchId;
3405
2911
  }
3406
2912
  }
3407
- const response = await this.permissionApi
3408
- .assignUserRoles(payload)
3409
- .toPromise();
2913
+ const response = await firstValueFrom(this.permissionApi.assignUserRoles(payload));
3410
2914
  this.messageService.add({
3411
2915
  severity: 'success',
3412
2916
  summary: 'Success',
@@ -3654,11 +3158,11 @@ class UserRoleSelectorComponent {
3654
3158
  }
3655
3159
  }
3656
3160
  </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 });
3161
+ `, 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
3162
  }
3659
3163
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
3660
3164
  type: Component,
3661
- args: [{ selector: 'flusys-user-role-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3165
+ args: [{ selector: 'flusys-user-role-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3662
3166
  <div class="user-role-selector">
3663
3167
  <!-- User and Branch Selectors -->
3664
3168
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -3925,9 +3429,6 @@ class UserActionSelectorComponent {
3925
3429
  permissionApi = inject(PermissionApiService);
3926
3430
  permissionLogic = inject(ActionPermissionLogicService);
3927
3431
  messageService = inject(MessageService);
3928
- destroyRef = inject(DestroyRef);
3929
- // AbortController for data loading
3930
- loadDataAbortController = null;
3931
3432
  // State - User/Branch Selection
3932
3433
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3933
3434
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
@@ -3946,8 +3447,10 @@ class UserActionSelectorComponent {
3946
3447
  selectionMap = this._selectionMap.asReadonly();
3947
3448
  _initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
3948
3449
  initialSelection = this._initialSelection.asReadonly();
3450
+ // Computed - Company Feature Flag (cached)
3451
+ isCompanyFeatureActive = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "isCompanyFeatureActive" }] : []));
3949
3452
  // Computed - UI Flags
3950
- showBranchSelector = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "showBranchSelector" }] : []));
3453
+ showBranchSelector = this.isCompanyFeatureActive;
3951
3454
  filteredBranches = computed(() => {
3952
3455
  const currentCompanyId = this.companyContext.currentCompanyInfo()?.id || undefined;
3953
3456
  if (!currentCompanyId)
@@ -4009,15 +3512,11 @@ class UserActionSelectorComponent {
4009
3512
  return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
4010
3513
  }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
4011
3514
  constructor() {
4012
- // Cleanup on destroy
4013
- this.destroyRef.onDestroy(() => {
4014
- this.loadDataAbortController?.abort();
4015
- });
4016
3515
  // Effect: Load user branches and data when user changes
4017
3516
  effect(() => {
4018
3517
  const userId = this.selectedUserId();
4019
3518
  if (userId) {
4020
- if (isCompanyFeatureEnabled(this.appConfig)) {
3519
+ if (this.isCompanyFeatureActive()) {
4021
3520
  this.loadUserBranches(userId);
4022
3521
  }
4023
3522
  this.loadData();
@@ -4032,9 +3531,7 @@ class UserActionSelectorComponent {
4032
3531
  effect(() => {
4033
3532
  const userId = this.selectedUserId();
4034
3533
  const branchId = this.selectedBranchId();
4035
- if (isCompanyFeatureEnabled(this.appConfig) &&
4036
- userId &&
4037
- branchId !== undefined) {
3534
+ if (this.isCompanyFeatureActive() && userId && branchId !== undefined) {
4038
3535
  this.loadData();
4039
3536
  }
4040
3537
  });
@@ -4043,15 +3540,13 @@ class UserActionSelectorComponent {
4043
3540
  * Load user's permitted branches
4044
3541
  */
4045
3542
  async loadUserBranches(userId) {
3543
+ const companyId = this.companyContext.currentCompanyInfo()?.id;
3544
+ if (!companyId) {
3545
+ this.branches.set([]);
3546
+ return;
3547
+ }
4046
3548
  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();
3549
+ const response = await firstValueFrom(this.userPermissionProvider.getUserBranchPermissions(userId));
4055
3550
  const userBranches = response?.data?.map((ub) => ({
4056
3551
  id: ub.branchId,
4057
3552
  name: ub.branchName,
@@ -4073,30 +3568,15 @@ class UserActionSelectorComponent {
4073
3568
  const branchId = this.selectedBranchId();
4074
3569
  this.loading.set(true);
4075
3570
  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)
3571
+ const actionsResponse = await firstValueFrom(this.actionApi.getActionsForPermission());
4081
3572
  const flatActions = actionsResponse?.data ?? [];
4082
- // Build tree structure from flat list with parentId
4083
3573
  const actionsTree = buildTreeFromFlat(flatActions);
4084
- // Store both representations
4085
3574
  this._actionsTree.set(actionsTree);
4086
3575
  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();
3576
+ const query = this.isCompanyFeatureActive() && branchId ? { branchId } : undefined;
3577
+ const assignedResponse = await firstValueFrom(this.permissionApi.getUserActions(userId, query));
4094
3578
  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
- });
3579
+ const selMap = this.buildSelectionMap(flatActions, (action) => assignedIds.has(action.id));
4100
3580
  this._selectionMap.set(selMap);
4101
3581
  this._initialSelection.set({ ...selMap });
4102
3582
  }
@@ -4148,48 +3628,25 @@ class UserActionSelectorComponent {
4148
3628
  this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
4149
3629
  }
4150
3630
  }
4151
- /**
4152
- * Toggle all actions
4153
- */
4154
3631
  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);
3632
+ this.setAllActions(!this.allSelected());
4161
3633
  }
4162
- /**
4163
- * Select all actions
4164
- */
4165
3634
  selectAll() {
4166
- const selMap = {};
4167
- this.actions().forEach((action) => {
4168
- selMap[action.id] = true;
4169
- });
4170
- this._selectionMap.set(selMap);
3635
+ this.setAllActions(true);
4171
3636
  }
4172
- /**
4173
- * Deselect all actions
4174
- */
4175
3637
  deselectAll() {
4176
- const selMap = {};
4177
- this.actions().forEach((action) => {
4178
- selMap[action.id] = false;
4179
- });
4180
- this._selectionMap.set(selMap);
3638
+ this.setAllActions(false);
3639
+ }
3640
+ setAllActions(value) {
3641
+ this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
4181
3642
  }
4182
- /**
4183
- * Save changes to backend
4184
- */
4185
3643
  async saveChanges() {
4186
3644
  const userId = this.selectedUserId();
4187
3645
  if (!userId)
4188
3646
  return;
4189
3647
  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) {
3648
+ const companyId = this.companyContext.currentCompanyInfo()?.id;
3649
+ if (this.isCompanyFeatureActive() && !companyId) {
4193
3650
  this.messageService.add({
4194
3651
  severity: 'warn',
4195
3652
  summary: 'Company Required',
@@ -4197,49 +3654,30 @@ class UserActionSelectorComponent {
4197
3654
  });
4198
3655
  return;
4199
3656
  }
4200
- // Pre-save validation
4201
3657
  const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
4202
3658
  if (invalidActions.length > 0) {
4203
3659
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
4204
3660
  return;
4205
3661
  }
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
- });
3662
+ const items = this.buildPermissionItems();
4220
3663
  if (items.length === 0)
4221
3664
  return;
4222
3665
  this.saving.set(true);
4223
3666
  try {
4224
3667
  const payload = { userId, items };
4225
- if (isCompanyFeatureEnabled(this.appConfig)) {
4226
- if (companyId) {
3668
+ if (this.isCompanyFeatureActive()) {
3669
+ if (companyId)
4227
3670
  payload.companyId = companyId;
4228
- }
4229
- if (branchId) {
3671
+ if (branchId)
4230
3672
  payload.branchId = branchId;
4231
- }
4232
3673
  }
4233
- const response = await this.permissionApi
4234
- .assignUserActions(payload)
4235
- .toPromise();
3674
+ const response = await firstValueFrom(this.permissionApi.assignUserActions(payload));
4236
3675
  this.messageService.add({
4237
3676
  severity: 'success',
4238
3677
  summary: 'Success',
4239
3678
  detail: response?.data?.message ||
4240
3679
  'User action permissions updated successfully',
4241
3680
  });
4242
- // Update baseline
4243
3681
  this._initialSelection.set({ ...this.selectionMap() });
4244
3682
  }
4245
3683
  catch {
@@ -4249,15 +3687,29 @@ class UserActionSelectorComponent {
4249
3687
  this.saving.set(false);
4250
3688
  }
4251
3689
  }
4252
- /**
4253
- * Reset component state
4254
- */
4255
3690
  resetState() {
4256
3691
  this._actionsTree.set([]);
4257
3692
  this._actions.set([]);
4258
3693
  this._selectionMap.set({});
4259
3694
  this._initialSelection.set({});
4260
3695
  }
3696
+ buildSelectionMap(actions, predicate) {
3697
+ const selMap = {};
3698
+ actions.forEach((action) => {
3699
+ selMap[action.id] = predicate(action);
3700
+ });
3701
+ return selMap;
3702
+ }
3703
+ buildPermissionItems() {
3704
+ const items = [];
3705
+ this.pendingAdd().forEach((action) => {
3706
+ items.push({ id: action.id, action: 'add' });
3707
+ });
3708
+ this.pendingRemove().forEach((action) => {
3709
+ items.push({ id: action.id, action: 'remove' });
3710
+ });
3711
+ return items;
3712
+ }
4261
3713
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4262
3714
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
4263
3715
  <div class="user-action-selector">
@@ -4513,11 +3965,11 @@ class UserActionSelectorComponent {
4513
3965
  }
4514
3966
  }
4515
3967
  </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 });
3968
+ `, 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
3969
  }
4518
3970
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
4519
3971
  type: Component,
4520
- args: [{ selector: 'flusys-user-action-selector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
3972
+ args: [{ selector: 'flusys-user-action-selector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent], template: `
4521
3973
  <div class="user-action-selector">
4522
3974
  <!-- User and Branch Selectors -->
4523
3975
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -4828,90 +4280,76 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4828
4280
  type: Injectable
4829
4281
  }] });
4830
4282
 
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
- */
4283
+ /** Registers IAM provider adapters for ng-shared interfaces */
4850
4284
  function provideIamProviders() {
4851
4285
  return [
4852
- // Profile permission provider (for auth profile page)
4853
- {
4854
- provide: PROFILE_PERMISSION_PROVIDER,
4855
- useClass: ProfilePermissionProviderAdapter,
4856
- },
4286
+ { provide: PROFILE_PERMISSION_PROVIDER, useClass: ProfilePermissionProviderAdapter },
4857
4287
  ];
4858
4288
  }
4859
4289
 
4860
4290
  /**
4861
4291
  * IAM Routes Configuration
4862
4292
  *
4863
- * Identity and Access Management routing
4864
- * - Actions: Permission actions (always visible)
4293
+ * Identity and Access Management routing with permission guards.
4294
+ * - Actions: Permission actions management
4865
4295
  * - 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.
4296
+ * - Permissions: User permission assignments
4869
4297
  */
4870
4298
  const IAM_ROUTES = [
4871
4299
  {
4872
4300
  path: '',
4873
- loadComponent: () => import('./flusys-ng-iam-iam-container.component-Bn4kQtxW.mjs').then((m) => m.IamContainerComponent),
4301
+ loadComponent: () => import('./flusys-ng-iam-iam-container.component-BToYxEej.mjs').then((m) => m.IamContainerComponent),
4874
4302
  children: [
4875
4303
  // Actions Management
4876
4304
  {
4877
4305
  path: 'actions',
4306
+ canActivate: [permissionGuard(ACTION_PERMISSIONS.READ)],
4878
4307
  children: [
4879
4308
  {
4880
4309
  path: '',
4881
- loadComponent: () => import('./flusys-ng-iam-action-list-page.component-Daf93zpS.mjs').then((m) => m.ActionListPageComponent),
4310
+ loadComponent: () => import('./flusys-ng-iam-action-list-page.component-CQ6RazN0.mjs').then((m) => m.ActionListPageComponent),
4882
4311
  },
4883
4312
  {
4884
4313
  path: 'new',
4885
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
4314
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-CVN8sV-c.mjs').then((m) => m.ActionFormPageComponent),
4886
4315
  },
4887
4316
  {
4888
4317
  path: ':id',
4889
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-C_BRrrWW.mjs').then((m) => m.ActionFormPageComponent),
4318
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-CVN8sV-c.mjs').then((m) => m.ActionFormPageComponent),
4890
4319
  },
4891
4320
  ],
4892
4321
  },
4893
4322
  // Roles Management
4894
4323
  {
4895
4324
  path: 'roles',
4325
+ canActivate: [permissionGuard(ROLE_PERMISSIONS.READ)],
4896
4326
  children: [
4897
4327
  {
4898
4328
  path: '',
4899
- loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CFly5KnH.mjs').then((m) => m.RoleListPageComponent),
4329
+ loadComponent: () => import('./flusys-ng-iam-role-list-page.component-Cz-jk-R_.mjs').then((m) => m.RoleListPageComponent),
4900
4330
  },
4901
4331
  {
4902
4332
  path: 'new',
4903
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
4333
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-BjPwXkip.mjs').then((m) => m.RoleFormPageComponent),
4904
4334
  },
4905
4335
  {
4906
4336
  path: ':id',
4907
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-ByNueI1a.mjs').then((m) => m.RoleFormPageComponent),
4337
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-BjPwXkip.mjs').then((m) => m.RoleFormPageComponent),
4908
4338
  },
4909
4339
  ],
4910
4340
  },
4911
- // Permissions Management (User permission assignment)
4341
+ // Permissions Management (requires any permission tab access)
4912
4342
  {
4913
4343
  path: 'permissions',
4914
- loadComponent: () => import('./flusys-ng-iam-permission-page.component-CmxOBJPu.mjs').then((m) => m.PermissionPageComponent),
4344
+ canActivate: [
4345
+ anyPermissionGuard([
4346
+ ROLE_ACTION_PERMISSIONS.READ,
4347
+ USER_ROLE_PERMISSIONS.READ,
4348
+ USER_ACTION_PERMISSIONS.READ,
4349
+ COMPANY_ACTION_PERMISSIONS.READ,
4350
+ ]),
4351
+ ],
4352
+ loadComponent: () => import('./flusys-ng-iam-permission-page.component-BS7xXmsn.mjs').then((m) => m.PermissionPageComponent),
4915
4353
  },
4916
4354
  // Default redirect to actions
4917
4355
  {
@@ -4930,4 +4368,4 @@ const IAM_ROUTES = [
4930
4368
  */
4931
4369
 
4932
4370
  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
4371
+ //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-DrGHlTiz.mjs.map