@flusys/ng-iam 4.1.1 → 5.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 (20) hide show
  1. package/README.md +26 -382
  2. package/fesm2022/{flusys-ng-iam-action-form-page.component-DJbMu2aV.mjs → flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs} +14 -26
  3. package/fesm2022/flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs.map +1 -0
  4. package/fesm2022/{flusys-ng-iam-action-list-page.component-DYnYC7Nn.mjs → flusys-ng-iam-action-list-page.component-DRK79zUR.mjs} +4 -4
  5. package/fesm2022/{flusys-ng-iam-action-list-page.component-DYnYC7Nn.mjs.map → flusys-ng-iam-action-list-page.component-DRK79zUR.mjs.map} +1 -1
  6. package/fesm2022/{flusys-ng-iam-flusys-ng-iam-CnVOy_4s.mjs → flusys-ng-iam-flusys-ng-iam-C6I4k78L.mjs} +1140 -1340
  7. package/fesm2022/flusys-ng-iam-flusys-ng-iam-C6I4k78L.mjs.map +1 -0
  8. package/fesm2022/{flusys-ng-iam-permission-page.component-_cLVilgz.mjs → flusys-ng-iam-permission-page.component-CZebeUhC.mjs} +2 -2
  9. package/fesm2022/{flusys-ng-iam-permission-page.component-_cLVilgz.mjs.map → flusys-ng-iam-permission-page.component-CZebeUhC.mjs.map} +1 -1
  10. package/fesm2022/{flusys-ng-iam-role-form-page.component-BuZmko2b.mjs → flusys-ng-iam-role-form-page.component-49dKMKOj.mjs} +11 -20
  11. package/fesm2022/flusys-ng-iam-role-form-page.component-49dKMKOj.mjs.map +1 -0
  12. package/fesm2022/{flusys-ng-iam-role-list-page.component-C7GVYYIn.mjs → flusys-ng-iam-role-list-page.component-CT7CvvHj.mjs} +114 -39
  13. package/fesm2022/flusys-ng-iam-role-list-page.component-CT7CvvHj.mjs.map +1 -0
  14. package/fesm2022/flusys-ng-iam.mjs +1 -1
  15. package/package.json +4 -4
  16. package/types/flusys-ng-iam.d.ts +44 -250
  17. package/fesm2022/flusys-ng-iam-action-form-page.component-DJbMu2aV.mjs.map +0 -1
  18. package/fesm2022/flusys-ng-iam-flusys-ng-iam-CnVOy_4s.mjs.map +0 -1
  19. package/fesm2022/flusys-ng-iam-role-form-page.component-BuZmko2b.mjs.map +0 -1
  20. package/fesm2022/flusys-ng-iam-role-list-page.component-C7GVYYIn.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { HttpClient } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, Injectable, signal, input, output, effect, Component, DestroyRef, computed } from '@angular/core';
3
+ import { inject, Injectable, signal, input, output, effect, Component, computed, DestroyRef } from '@angular/core';
4
4
  import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, TranslatePipe, 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, resolveTranslationModule, SHARED_MESSAGES } from '@flusys/ng-shared';
5
5
  import { APP_CONFIG, getServiceUrl, TRANSLATE_ADAPTER, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
6
6
  import { ConfirmationService, MessageService } from 'primeng/api';
@@ -33,270 +33,153 @@ var ActionType;
33
33
  const MAX_DROPDOWN_ITEMS = 100;
34
34
 
35
35
  const IAM_MESSAGES = {
36
- // Container page
37
- 'iam.title': 'Identity & Access Management',
38
- 'iam.subtitle': 'Manage roles, permissions, and access control',
39
- // Tabs
40
- 'iam.tabs.roles': 'Roles',
41
- 'iam.tabs.actions': 'Actions',
42
- 'iam.tabs.permissions': 'Permissions',
43
- // Administration
44
- 'iam.administration.title': 'Administration',
45
- 'iam.administration.subtitle': 'Manage users, companies, and branches',
46
- // Users
47
- 'iam.user.title': 'Users',
48
- 'iam.user.add': 'Add User',
49
- 'iam.user.new': 'New User',
50
- 'iam.user.create': 'Create User',
51
- 'iam.user.edit': 'Edit User',
52
- 'iam.user.name': 'Name',
53
- 'iam.user.email': 'Email',
54
- 'iam.user.phone': 'Phone',
55
- 'iam.user.password': 'Password',
56
- 'iam.user.password.optional': 'Password (leave blank to keep current)',
57
- 'iam.user.password.placeholder': 'Enter password',
58
- 'iam.user.name.placeholder': 'Enter full name',
59
- 'iam.user.email.placeholder': 'Enter email address',
60
- 'iam.user.phone.placeholder': 'Enter phone number',
61
- 'iam.user.email.verified': 'Email Verified',
62
- 'iam.user.no.users': 'No users found',
63
- 'iam.user.details': 'User Details',
64
- 'iam.user.auto.assign.to': 'Auto-assign to',
65
- 'iam.user.assign.role': 'Assign Role',
66
- 'iam.user.remove.role': 'Remove Role',
67
- 'iam.user.activate': 'Activate User',
68
- 'iam.user.deactivate': 'Deactivate User',
69
- 'iam.user.reset.password': 'Reset Password',
70
- 'iam.user.assign.branch': 'Assign Branch',
71
- 'iam.user.delete.title': 'Delete User',
72
- 'iam.user.delete.success': 'User deleted successfully',
73
- // Roles
74
- 'iam.role.title': 'Roles',
75
- 'iam.role.new': 'New Role',
76
- 'iam.role.create': 'Create Role',
77
- 'iam.role.edit': 'Edit Role',
78
- 'iam.role.name': 'Role Name',
79
- 'iam.role.name.placeholder': 'Enter role name',
80
- 'iam.role.description': 'Role Description',
81
- 'iam.role.description.placeholder': 'Enter role description',
82
- 'iam.role.permissions': 'Permissions',
83
- 'iam.role.assigned.users': 'Assigned Users',
84
- 'iam.role.no.roles': 'No roles found',
85
- 'iam.role.no.roles.assigned': 'No roles assigned',
86
- 'iam.role.delete.title': 'Delete Role',
87
- 'iam.role.delete.success': 'Role deleted successfully',
88
- 'iam.role.create.success': 'Role created successfully',
89
- 'iam.role.update.success': 'Role updated successfully',
36
+ // Action CRUD (API keys)
37
+ 'action.create.success': 'Action created successfully',
38
+ 'action.create.many.success': '{{count}} actions created successfully',
39
+ 'action.get.success': 'Action retrieved successfully',
40
+ 'action.get.by.ids.success': 'Actions retrieved successfully by Ids',
41
+ 'action.get.by.filter.success': 'Actions retrieved successfully by filter',
42
+ 'action.get.all.success': 'Actions retrieved successfully',
43
+ 'action.update.success': 'Action updated successfully',
44
+ 'action.update.many.success': '{{count}} actions updated successfully',
45
+ 'action.delete.success': 'Action deleted successfully',
46
+ 'action.restore.success': 'Action restored successfully',
47
+ // Role CRUD (API keys)
90
48
  'role.create.success': 'Role created successfully',
91
- 'role.update.success': 'Role updated successfully',
92
- 'role.delete.success': 'Role deleted successfully',
49
+ 'role.create.many.success': '{{count}} roles created successfully',
93
50
  'role.get.success': 'Role retrieved successfully',
51
+ 'role.get.by.ids.success': 'Roles retrieved successfully by Ids',
52
+ 'role.get.by.filter.success': 'Roles retrieved successfully by filter',
94
53
  'role.get.all.success': 'Roles retrieved successfully',
95
- // Actions
54
+ 'role.update.success': 'Role updated successfully',
55
+ 'role.update.many.success': '{{count}} roles updated successfully',
56
+ 'role.delete.success': 'Role deleted successfully',
57
+ 'role.restore.success': 'Role restored successfully',
58
+ // PERMISSION_OPERATION_MESSAGES (API keys)
59
+ 'permission.process.success': 'Successfully processed {{total}} items: {{added}} added, {{removed}} removed',
60
+ 'permission.user.required': 'User is required for {{method}}',
61
+ 'permission.already.exists': 'Permission already exists',
62
+ // ROLE_PERMISSION_MESSAGES (API keys)
63
+ 'role.permission.actions.success': 'Role actions retrieved successfully',
64
+ 'role.permission.user.roles.success': 'User roles retrieved successfully',
65
+ // USER_ACTION_PERMISSION_MESSAGES (API keys)
66
+ 'user.action.permission.get.success': 'User action permissions retrieved successfully',
67
+ // COMPANY_ACTION_PERMISSION_MESSAGES (API keys)
68
+ 'company.action.permission.get.success': 'Company action permissions retrieved successfully',
69
+ // MY_PERMISSION_MESSAGES (API keys)
70
+ 'my.permission.get.success': 'Permissions loaded successfully',
71
+ // IAM_MODE_MESSAGES (API keys)
72
+ 'iam.direct.mode.unavailable': 'Direct permission assignment not available in RBAC-only mode',
73
+ 'iam.rbac.mode.unavailable': 'Role-based permission assignment not available in direct-only mode',
74
+ 'iam.role.assignment.unavailable': 'Role assignment not available in direct-only mode',
75
+ // pages/iam-container/iam-container.component.ts
76
+ 'iam.title': 'Identity & Access Management',
77
+ 'iam.subtitle': 'Manage roles, permissions, and access control',
96
78
  'iam.action.title': 'Actions',
79
+ 'iam.role.title': 'Roles',
80
+ 'iam.permission.title': 'Permissions',
81
+ // pages/action/action-list-page.component.ts
97
82
  'iam.action.new': 'New Action',
98
- 'iam.action.create': 'Create Action',
99
- 'iam.action.edit': 'Edit Action',
100
83
  'iam.action.name': 'Action Name',
101
- 'iam.action.name.placeholder': 'Enter action name',
102
84
  'iam.action.code': 'Action Code',
103
- 'iam.action.code.placeholder': 'Enter action code',
104
85
  'iam.action.type': 'Type',
105
86
  'iam.action.no.actions': 'No actions found',
106
- 'iam.action.no.actions.assigned': 'No actions assigned',
107
- 'iam.action.direct.actions': 'Direct Actions',
108
87
  'iam.action.type.backend': 'Backend',
109
88
  'iam.action.type.frontend': 'Frontend',
110
89
  'iam.action.type.both': 'Both',
111
- 'iam.action.type.backend.label': 'Backend Only',
112
- 'iam.action.type.frontend.label': 'Frontend Only',
113
- 'iam.action.type.both.label': 'Backend & Frontend',
114
90
  'iam.action.delete.title': 'Delete Action',
115
- 'iam.action.delete.success': 'Action deleted successfully',
116
- 'iam.action.create.success': 'Action created successfully',
117
- 'iam.action.update.success': 'Action updated successfully',
91
+ // pages/action/action-form-page.component.ts
92
+ 'iam.action.edit': 'Edit Action',
93
+ 'iam.action.name.placeholder': 'Enter action name',
94
+ 'iam.action.code.placeholder': 'Enter action code',
118
95
  'iam.action.parent': 'Parent Action',
119
96
  'iam.action.select.parent': 'Select Parent Action',
120
- // Permissions
121
- 'iam.permission.title': 'Permissions',
122
- 'iam.permission.create': 'Create Permission',
123
- 'iam.permission.edit': 'Edit Permission',
124
- 'iam.permission.name': 'Permission Name',
125
- 'iam.permission.code': 'Permission Code',
126
- 'iam.permission.module': 'Module',
127
- 'iam.permission.action': 'Action',
128
- 'iam.permission.description': 'Description',
97
+ 'iam.action.type.backend.label': 'Backend Only',
98
+ 'iam.action.type.frontend.label': 'Frontend Only',
99
+ 'iam.action.type.both.label': 'Backend & Frontend',
100
+ // pages/role/role-list-page.component.ts
101
+ 'iam.company.title': 'Companies',
102
+ 'iam.role.new': 'New Role',
103
+ 'iam.role.name': 'Role Name',
104
+ 'iam.role.no.roles': 'No roles found',
105
+ 'iam.role.delete.title': 'Delete Role',
106
+ // pages/role/role-form-page.component.ts
107
+ 'iam.role.edit': 'Edit Role',
108
+ 'iam.role.name.placeholder': 'Enter role name',
109
+ // pages/permission/permission-page.component.ts
129
110
  'iam.permission.role.actions': 'Role Actions',
130
111
  'iam.permission.user.roles': 'User Roles',
131
112
  'iam.permission.user.actions': 'User Actions',
132
113
  'iam.permission.company.actions': 'Company Actions',
133
- 'iam.permission.company.permissions': 'Company Permissions',
134
- 'iam.permission.branch.permissions': 'Branch Permissions',
135
- 'iam.permission.my.permissions': 'My Permissions',
136
- 'iam.permission.select.company': 'Select Company',
137
- 'iam.permission.select.company.placeholder': 'Search and select a company',
114
+ // components/logic-builder.component.ts
115
+ 'iam.logic.title': 'Permission Logic',
116
+ 'iam.logic.add.logic': 'Add Logic',
117
+ 'iam.logic.clear.logic': 'Clear Logic',
118
+ 'iam.logic.description': 'Define permission requirements using AND/OR logic with actions',
119
+ 'iam.logic.click.to.toggle': 'Click to toggle',
120
+ 'iam.logic.conditions': '{{count}} conditions',
121
+ 'iam.logic.select.action': 'Select Action...',
122
+ 'iam.logic.actions.available': '{{count}} available',
123
+ 'iam.logic.add.condition': 'Add Condition:',
124
+ 'iam.logic.group': 'Group',
125
+ 'iam.logic.action': 'Action',
126
+ // components/role-action-selector.component.ts
138
127
  'iam.permission.select.role': 'Select Role',
139
128
  'iam.permission.select.role.placeholder': 'Search and select a role',
129
+ 'iam.permission.action.permissions': 'Action Permissions',
130
+ 'iam.permission.actions.available': '{{count}} actions available',
131
+ 'iam.validation.warning.title': 'Validation Warning',
132
+ 'iam.validation.unmet.prerequisites.plural': '{{count}} selected actions have unmet prerequisites. Fix before saving or use auto-fix on save.',
133
+ 'iam.validation.unmet.prerequisites.singular': '{{count}} selected action has unmet prerequisites. Fix before saving or use auto-fix on save.',
134
+ 'iam.permission.requirements': 'Requirements',
135
+ 'iam.validation.unmet.prerequisites.tooltip': 'This action has unmet prerequisites and will fail validation on save',
136
+ 'iam.permission.has.prerequisites': 'Has prerequisites',
137
+ 'iam.permission.no.actions.available': 'No actions available',
138
+ 'iam.permission.no.actions.for.role': 'No actions available for this role',
139
+ 'iam.tooltip.remove.action': 'Click to remove',
140
+ 'iam.tooltip.add.action': 'Click to add (auto-selects required)',
141
+ 'iam.tooltip.assigned.to.role': 'Assigned to role',
142
+ 'iam.tooltip.click.to.remove.role': 'Click to remove',
143
+ 'iam.tooltip.click.to.assign.role': 'Click to assign to role',
144
+ // components/user-action-selector.component.ts
140
145
  'iam.permission.select.user': 'Select User',
141
146
  'iam.permission.select.user.placeholder': 'Search and select a user',
142
147
  'iam.permission.select.branch': 'Select Branch',
143
148
  'iam.permission.select.branch.placeholder': 'Search and select a branch',
144
- 'iam.permission.action.whitelist': 'Action Whitelist',
145
- 'iam.permission.action.permissions': 'Action Permissions',
149
+ 'iam.branch.permitted.count.plural': '{{count}} permitted branches in current company',
150
+ 'iam.branch.permitted.count': '{{count}} permitted branch in current company',
146
151
  'iam.permission.direct.action.permissions': 'Direct Action Permissions',
147
- 'iam.permission.role.assignments': 'Role Assignments',
148
- 'iam.permission.actions.available': '{{count}} actions available',
149
- 'iam.permission.roles.available': '{{count}} roles available',
150
- 'iam.permission.no.actions.available': 'No actions available',
151
- 'iam.permission.no.roles.available': 'No roles available',
152
- 'iam.permission.no.actions.for.company': 'No actions available for this company',
153
- 'iam.permission.no.actions.for.role': 'No actions available for this role',
154
152
  'iam.permission.no.actions.for.user': 'No actions available for this user',
155
- 'iam.permission.no.roles.for.user': 'No roles available for this user',
153
+ 'iam.tooltip.assigned.to.user': 'Assigned to user',
154
+ 'iam.tooltip.click.to.remove.user': 'Click to remove direct permission',
155
+ 'iam.tooltip.click.to.assign.user': 'Click to assign direct permission',
156
156
  'iam.permission.company.required': 'Please select a company first',
157
- 'iam.permission.has.prerequisites': 'Has prerequisites',
158
- 'iam.permission.requirements': 'Requirements',
159
- 'iam.permission.company.actions.updated': 'Company actions updated successfully',
160
- 'iam.permission.role.permissions.updated': 'Role permissions updated successfully',
161
- 'iam.permission.user.actions.updated': 'User actions updated successfully',
162
- 'iam.permission.user.roles.updated': 'User roles updated successfully',
157
+ // components/company-action-selector.component.ts
158
+ 'iam.permission.select.company': 'Select Company',
159
+ 'iam.permission.select.company.placeholder': 'Search and select a company',
160
+ 'iam.permission.action.whitelist': 'Action Whitelist',
161
+ 'iam.permission.no.actions.for.company': 'No actions available for this company',
162
+ 'iam.tooltip.selected': 'Selected',
163
+ 'iam.tooltip.click.to.remove': 'Click to remove from company whitelist',
164
+ 'iam.tooltip.click.to.add': 'Click to add to company whitelist',
163
165
  'iam.permission.requires': 'requires',
164
166
  'iam.permission.prerequisite.validation.failed': 'Prerequisite Validation Failed',
165
167
  'iam.permission.prerequisite.error.message': 'The following actions have unmet prerequisites:',
166
- 'iam.permission.auto.select.prompt': 'Would you like to auto-select the required actions?',
167
168
  'iam.permission.auto.select.required': 'Auto-select Required',
168
169
  'iam.permission.actions.selected': 'Actions Selected',
169
170
  'iam.permission.auto.selected.prerequisites': 'Required prerequisites have been auto-selected. Click Save to apply changes.',
170
171
  'iam.permission.changes.reverted': 'Changes Reverted',
171
172
  'iam.permission.selection.reverted': 'Selection has been reverted to the initial state.',
172
- 'permission.create.success': 'Permission created successfully',
173
- 'permission.update.success': 'Permission updated successfully',
174
- 'permission.delete.success': 'Permission deleted successfully',
175
- 'permission.get.success': 'Permission retrieved successfully',
176
- 'permission.get.all.success': 'Permissions retrieved successfully',
177
- 'permission.process.success': 'Successfully processed {{total}} items: {{added}} added, {{removed}} removed',
178
- 'permission.user.required': 'User is required for {{method}}',
179
- 'permission.already.exists': 'Permission already exists',
180
- // Access control
181
- 'iam.access.denied': 'Access Denied',
182
- 'iam.no.permission': 'You do not have permission to perform this action.',
183
- 'iam.contact.admin': 'Please contact your administrator for access.',
184
- 'iam.insufficient.permissions': 'Insufficient permissions',
185
- // Company
186
- 'iam.company.title': 'Companies',
187
- 'iam.company.add': 'Add Company',
188
- 'iam.company.new': 'New Company',
189
- 'iam.company.create': 'Create Company',
190
- 'iam.company.edit': 'Edit Company',
191
- 'iam.company.select': 'Select Company',
192
- 'iam.company.name': 'Company Name',
193
- 'iam.company.name.placeholder': 'Enter company name',
194
- 'iam.company.slug': 'Company Slug',
195
- 'iam.company.slug.placeholder': 'Enter company slug',
196
- 'iam.company.code': 'Company Code',
197
- 'iam.company.code.placeholder': 'Enter company code',
198
- 'iam.company.details': 'Company Details',
199
- 'iam.company.address': 'Address',
200
- 'iam.company.address.placeholder': 'Enter company address',
201
- 'iam.company.phone': 'Phone',
202
- 'iam.company.phone.placeholder': 'Enter phone number',
203
- 'iam.company.email': 'Email',
204
- 'iam.company.email.placeholder': 'Enter email address',
205
- 'iam.company.website': 'Website',
206
- 'iam.company.website.placeholder': 'Enter website URL',
207
- 'iam.company.no.companies': 'No companies found',
208
- 'iam.company.delete.title': 'Delete Company',
209
- 'iam.company.delete.success': 'Company deleted successfully',
210
- 'iam.company.slug.required': 'Company slug is required',
211
- // Branch
212
- 'iam.branch.title': 'Branches',
213
- 'iam.branch.add': 'Add Branch',
214
- 'iam.branch.new': 'New Branch',
215
- 'iam.branch.create': 'Create Branch',
216
- 'iam.branch.edit': 'Edit Branch',
217
- 'iam.branch.name': 'Branch Name',
218
- 'iam.branch.name.placeholder': 'Enter branch name',
219
- 'iam.branch.slug': 'Branch Slug',
220
- 'iam.branch.slug.placeholder': 'Enter branch slug',
221
- 'iam.branch.code': 'Branch Code',
222
- 'iam.branch.code.placeholder': 'Enter branch code',
223
- 'iam.branch.company': 'Company',
224
- 'iam.branch.no.branches': 'No branches found',
225
- 'iam.branch.select.company.first': 'Please select a company first',
226
- 'iam.branch.view.branches': 'View Branches',
227
- 'iam.branch.delete.title': 'Delete Branch',
228
- 'iam.branch.delete.success': 'Branch deleted successfully',
229
- 'iam.branch.slug.required': 'Branch slug is required',
230
- // Action CRUD messages (API keys)
231
- 'action.create.success': 'Action created successfully',
232
- 'action.create.many.success': '{{count}} actions created successfully',
233
- 'action.get.success': 'Action retrieved successfully',
234
- 'action.get.all.success': 'Actions retrieved successfully',
235
- 'action.update.success': 'Action updated successfully',
236
- 'action.update.many.success': '{{count}} actions updated successfully',
237
- 'action.delete.success': 'Action deleted successfully',
238
- 'action.restore.success': 'Action restored successfully',
239
- 'action.not.found': 'Action not found',
240
- // Role CRUD messages (API keys)
241
- 'role.create.many.success': '{{count}} roles created successfully',
242
- 'role.update.many.success': '{{count}} roles updated successfully',
243
- 'role.restore.success': 'Role restored successfully',
244
- 'role.not.found': 'Role not found',
245
- // Role permission messages (API keys)
246
- 'role.permission.get.success': 'Role permissions retrieved successfully',
247
- 'role.permission.assign.success': 'Role permissions assigned successfully',
248
- 'role.permission.actions.success': 'Role actions retrieved successfully',
249
- 'role.permission.users.success': 'Role users retrieved successfully',
250
- 'role.permission.user.roles.success': 'User roles retrieved successfully',
251
- // User action permission messages (API keys)
252
- 'user.action.permission.get.success': 'User action permissions retrieved successfully',
253
- 'user.action.permission.assign.success': 'User action permissions assigned successfully',
254
- 'user.action.permission.revoke.success': 'User action permissions revoked successfully',
255
- // Company action permission messages (API keys)
256
- 'company.action.permission.get.success': 'Company action permissions retrieved successfully',
257
- 'company.action.permission.assign.success': 'Company action permissions assigned successfully',
258
- 'company.action.permission.revoke.success': 'Company action permissions revoked successfully',
259
- // My permission messages (API keys)
260
- 'my.permission.get.success': 'Permissions loaded successfully',
261
- // IAM mode messages (API keys)
262
- 'iam.direct.mode.unavailable': 'Direct permission assignment not available in RBAC-only mode',
263
- 'iam.rbac.mode.unavailable': 'Role-based permission assignment not available in direct-only mode',
264
- 'iam.role.assignment.unavailable': 'Role assignment not available in direct-only mode',
265
- // Validation warnings
266
- 'iam.validation.warning.title': 'Validation Warning',
267
- 'iam.validation.unmet.prerequisites.singular': '{{count}} selected action has unmet prerequisites. Fix before saving or use auto-fix on save.',
268
- 'iam.validation.unmet.prerequisites.plural': '{{count}} selected actions have unmet prerequisites. Fix before saving or use auto-fix on save.',
269
- 'iam.validation.unmet.prerequisites.tooltip': 'This action has unmet prerequisites and will fail validation on save',
270
- // Branch selector
271
- 'iam.branch.permitted.count': '{{count}} permitted branch in current company',
272
- 'iam.branch.permitted.count.plural': '{{count}} permitted branches in current company',
273
- // Tooltips
274
- 'iam.tooltip.remove.action': 'Click to remove',
275
- 'iam.tooltip.add.action': 'Click to add (auto-selects required)',
276
- 'iam.tooltip.selected': 'Selected',
277
- 'iam.tooltip.click.to.remove': 'Click to remove from company whitelist',
278
- 'iam.tooltip.click.to.add': 'Click to add to company whitelist',
279
- 'iam.tooltip.assigned.to.role': 'Assigned to role',
280
- 'iam.tooltip.click.to.remove.role': 'Click to remove',
281
- 'iam.tooltip.click.to.assign.role': 'Click to assign to role',
282
- 'iam.tooltip.assigned.to.user': 'Assigned to user',
283
- 'iam.tooltip.click.to.remove.user': 'Click to remove direct permission',
284
- 'iam.tooltip.click.to.assign.user': 'Click to assign direct permission',
173
+ // components/user-role-selector.component.ts
174
+ 'iam.permission.role.assignments': 'Role Assignments',
175
+ 'iam.permission.roles.available': '{{count}} roles available',
176
+ 'iam.pagination.roles.template': 'Showing {first} to {last} of {totalRecords} roles',
177
+ 'iam.permission.no.roles.available': 'No roles available',
178
+ 'iam.permission.no.roles.for.user': 'No roles available for this user',
285
179
  'iam.tooltip.click.to.remove.role.from.user': 'Click to remove role',
286
180
  'iam.tooltip.click.to.assign.role.to.user': 'Click to assign role to user',
287
- // Logic builder
288
- 'iam.logic.title': 'Permission Logic',
289
- 'iam.logic.add.logic': 'Add Logic',
290
- 'iam.logic.clear.logic': 'Clear Logic',
291
- 'iam.logic.description': 'Define permission requirements using AND/OR logic with actions',
292
- 'iam.logic.conditions': '{{count}} conditions',
293
- 'iam.logic.select.action': 'Select Action...',
294
- 'iam.logic.actions.available': '{{count}} available',
295
- 'iam.logic.remove': 'Remove',
296
- 'iam.logic.add.condition': 'Add Condition:',
297
- 'iam.logic.group': 'Group',
298
- 'iam.logic.action': 'Action',
299
- // Permission logic dialogs
181
+ // services/action-permission-logic.service.ts
182
+ 'iam.logic.unknown.action': 'Unknown Action',
300
183
  'iam.logic.validation.failed': 'Validation Failed',
301
184
  'iam.logic.validation.failed.message': 'The following actions have unmet prerequisites:',
302
185
  'iam.logic.validation.failed.prompt': 'Would you like to:',
@@ -305,47 +188,39 @@ const IAM_MESSAGES = {
305
188
  'iam.logic.removed.actions.detail': 'Removed {{count}} action(s) with unmet prerequisites. You can now save.',
306
189
  'iam.logic.save.cancelled': 'Save Cancelled',
307
190
  'iam.logic.fix.prerequisites.manually': 'Please fix the prerequisites manually before saving',
308
- 'iam.logic.missing.prerequisites': 'Missing Prerequisites',
309
- 'iam.logic.requires.conditions': 'requires the following conditions to be satisfied:',
310
- 'iam.logic.auto.select.prompt': 'Auto-select will choose {{count}} action(s){{suffix}}.',
311
- 'iam.logic.minimum.required': ' (minimum required)',
312
- 'iam.logic.would.you.continue': 'Would you like to continue?',
191
+ 'iam.logic.prerequisites.satisfied': '[OK] Prerequisites Satisfied',
192
+ 'iam.logic.all.required.selected': 'All required actions are already selected.\nYou can safely add this action.',
193
+ 'iam.logic.prerequisites.required': 'Prerequisites Required ({{count}} missing)',
194
+ 'iam.logic.click.to.auto.select': 'Click to auto-select required actions',
313
195
  'iam.logic.select.action.label': 'Select Action',
314
196
  'iam.logic.auto.select.actions': 'Auto-select Actions',
315
- 'iam.logic.actions.selected.summary': 'Actions Selected',
316
197
  'iam.logic.action.selected.detail': 'Action selected successfully (prerequisites already satisfied)',
317
198
  'iam.logic.auto.selected.detail': 'Automatically selected {{count}} action(s) including prerequisites',
199
+ 'iam.logic.minimum.required': ' (minimum required)',
200
+ 'iam.logic.auto.select.prompt': 'Auto-select will choose {{count}} action(s){{suffix}}.',
201
+ 'iam.logic.missing.prerequisites': 'Missing Prerequisites',
202
+ 'iam.logic.requires.conditions': 'requires the following conditions to be satisfied:',
203
+ 'iam.logic.would.you.continue': 'Would you like to continue?',
204
+ 'iam.logic.actions.selected.summary': 'Actions Selected',
318
205
  'iam.logic.selection.cancelled': 'Selection Cancelled',
319
206
  'iam.logic.action.not.added': 'Action not added to whitelist',
320
- 'iam.logic.dependency.warning': 'Dependency Warning',
321
207
  'iam.logic.required.by.actions': 'is required by the following action(s):',
322
208
  'iam.logic.alternatives.available': 'Alternative options available:',
323
209
  'iam.logic.switch.to.alternatives': 'Would you like to automatically switch to alternatives?',
324
210
  'iam.logic.remove.dependents': 'Removing this will also remove the dependent action(s).',
211
+ 'iam.logic.dependency.warning': 'Dependency Warning',
325
212
  'iam.logic.use.alternatives': 'Use Alternatives',
326
213
  'iam.logic.remove.all': 'Remove All',
327
214
  'iam.logic.alternatives.applied': 'Alternatives Applied',
328
- 'iam.logic.switched.to.alternatives': 'Switched to alternative actions to maintain dependencies',
215
+ 'iam.logic.switched.to.alternatives': 'Automatically switched to alternative action(s)',
329
216
  'iam.logic.actions.removed': 'Actions Removed',
330
217
  'iam.logic.removed.with.dependents': 'Removed {{name}} and {{count}} dependent action(s)',
331
218
  'iam.logic.cancelled': 'Cancelled',
332
219
  'iam.logic.no.changes.made': 'No changes made',
333
- 'iam.logic.prerequisites.satisfied': '[OK] Prerequisites Satisfied',
334
- 'iam.logic.all.required.selected': 'All required actions are already selected.\nYou can safely add this action.',
335
- 'iam.logic.prerequisites.required': 'Prerequisites Required ({{count}} missing)',
336
- 'iam.logic.click.to.auto.select': 'Click to auto-select required actions',
337
220
  'iam.logic.satisfied': '(satisfied)',
338
221
  'iam.logic.missing': '(missing)',
339
- 'iam.logic.unknown.action': 'Unknown Action',
340
222
  'iam.logic.invalid.node': 'Invalid logic node',
341
223
  'iam.logic.invalid': '(invalid)',
342
- 'iam.logic.click.to.toggle': 'Click to toggle',
343
- // Pagination templates
344
- 'iam.pagination.roles.template': 'Showing {first} to {last} of {totalRecords} roles',
345
- 'iam.pagination.actions.template': 'Showing {first} to {last} of {totalRecords} actions',
346
- 'iam.pagination.users.template': 'Showing {first} to {last} of {totalRecords} users',
347
- 'iam.pagination.companies.template': 'Showing {first} to {last} of {totalRecords} companies',
348
- 'iam.pagination.branches.template': 'Showing {first} to {last} of {totalRecords} branches',
349
224
  };
350
225
 
351
226
  class RoleApiService extends ApiResourceService {
@@ -1121,13 +996,16 @@ class LogicBuilderComponent {
1121
996
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: LogicBuilderComponent, isStandalone: true, selector: "lib-logic-builder", inputs: { logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logicChange: "logicChange" }, ngImport: i0, template: `
1122
997
  <div class="logic-builder">
1123
998
  <div class="flex justify-between items-center mb-3">
1124
- <h4 class="text-sm font-semibold m-0">{{ 'iam.logic.title' | translate }}</h4>
999
+ <h4 class="text-sm font-semibold m-0">
1000
+ {{ 'iam.logic.title' | translate }}
1001
+ </h4>
1125
1002
  @if (!builderLogic()) {
1126
1003
  <p-button
1127
1004
  [label]="'iam.logic.add.logic' | translate"
1128
1005
  icon="pi pi-plus"
1129
1006
  size="small"
1130
- (onClick)="initializeLogic()" />
1007
+ (onClick)="initializeLogic()"
1008
+ />
1131
1009
  } @else {
1132
1010
  <p-button
1133
1011
  [label]="'iam.logic.clear.logic' | translate"
@@ -1135,7 +1013,8 @@ class LogicBuilderComponent {
1135
1013
  size="small"
1136
1014
  severity="danger"
1137
1015
  [outlined]="true"
1138
- (onClick)="clearLogic()" />
1016
+ (onClick)="clearLogic()"
1017
+ />
1139
1018
  }
1140
1019
  </div>
1141
1020
 
@@ -1147,7 +1026,12 @@ class LogicBuilderComponent {
1147
1026
 
1148
1027
  <!-- Root Node -->
1149
1028
  <div class="text-sm bg-surface-0 rounded border border-surface">
1150
- <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
1029
+ <ng-container
1030
+ *ngTemplateOutlet="
1031
+ nodeTemplate;
1032
+ context: { $implicit: builderLogic()!, depth: 0 }
1033
+ "
1034
+ ></ng-container>
1151
1035
  </div>
1152
1036
  </div>
1153
1037
  }
@@ -1155,9 +1039,14 @@ class LogicBuilderComponent {
1155
1039
 
1156
1040
  <!-- Recursive Node Template -->
1157
1041
  <ng-template #nodeTemplate let-node let-depth="depth">
1158
- <div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
1042
+ <div
1043
+ class="p-3 border-b border-surface"
1044
+ [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'"
1045
+ >
1159
1046
  <div class="flex items-center gap-3 mb-2">
1160
- <span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
1047
+ <span class="node-type" [ngClass]="node.type">{{
1048
+ node.type.toUpperCase()
1049
+ }}</span>
1161
1050
 
1162
1051
  <div class="flex-1 flex items-center gap-2">
1163
1052
  @if (node.type === 'group') {
@@ -1166,10 +1055,16 @@ class LogicBuilderComponent {
1166
1055
  class="operator-badge"
1167
1056
  [ngClass]="node.operator === 'AND' ? 'and' : 'or'"
1168
1057
  (click)="toggleOperator(node.id)"
1169
- [title]="'iam.logic.click.to.toggle' | translate">
1058
+ [title]="'iam.logic.click.to.toggle' | translate"
1059
+ >
1170
1060
  {{ node.operator }}
1171
1061
  </span>
1172
- <span class="text-muted-color text-xs">({{ 'iam.logic.conditions' | translate: { count: node.children?.length || 0 } }})</span>
1062
+ <span class="text-muted-color text-xs"
1063
+ >({{
1064
+ 'iam.logic.conditions'
1065
+ | translate: { count: node.children?.length || 0 }
1066
+ }})</span
1067
+ >
1173
1068
  }
1174
1069
 
1175
1070
  @if (node.type === 'action') {
@@ -1177,8 +1072,14 @@ class LogicBuilderComponent {
1177
1072
  <select
1178
1073
  class="action-select flex-1 p-2 border border-surface rounded text-sm"
1179
1074
  [ngModel]="node.actionId"
1180
- (ngModelChange)="updateActionId(node.id, $event)">
1181
- <option [value]="null">{{ 'iam.logic.select.action' | translate }} ({{ 'iam.logic.actions.available' | translate: { count: actions().length } }})</option>
1075
+ (ngModelChange)="updateActionId(node.id, $event)"
1076
+ >
1077
+ <option [value]="null">
1078
+ {{ 'iam.logic.select.action' | translate }} ({{
1079
+ 'iam.logic.actions.available'
1080
+ | translate: { count: actions().length }
1081
+ }})
1082
+ </option>
1182
1083
  @for (action of actions(); track action.id) {
1183
1084
  <option [ngValue]="action.id">{{ action.name }}</option>
1184
1085
  }
@@ -1193,37 +1094,51 @@ class LogicBuilderComponent {
1193
1094
  severity="danger"
1194
1095
  size="small"
1195
1096
  (onClick)="removeNode(node.id)"
1196
- [pTooltip]="'iam.logic.remove' | translate" />
1097
+ [pTooltip]="'shared.remove' | translate"
1098
+ />
1197
1099
  </div>
1198
1100
  </div>
1199
1101
 
1200
1102
  <!-- Children for group nodes -->
1201
- @if (node.type === 'group' && node.children && node.children.length > 0) {
1103
+ @if (
1104
+ node.type === 'group' && node.children && node.children.length > 0
1105
+ ) {
1202
1106
  <div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
1203
1107
  @for (child of node.children; track child.id) {
1204
- <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
1108
+ <ng-container
1109
+ *ngTemplateOutlet="
1110
+ nodeTemplate;
1111
+ context: { $implicit: child, depth: depth + 1 }
1112
+ "
1113
+ ></ng-container>
1205
1114
  }
1206
1115
  </div>
1207
1116
  }
1208
1117
 
1209
1118
  <!-- Add child buttons for group nodes -->
1210
1119
  @if (node.type === 'group') {
1211
- <div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
1212
- <div class="text-xs font-semibold text-muted-color mb-2">{{ 'iam.logic.add.condition' | translate }}</div>
1120
+ <div
1121
+ class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface"
1122
+ >
1123
+ <div class="text-xs font-semibold text-muted-color mb-2">
1124
+ {{ 'iam.logic.add.condition' | translate }}
1125
+ </div>
1213
1126
  <div class="flex gap-2 flex-wrap">
1214
1127
  <p-button
1215
1128
  [label]="'iam.logic.group' | translate"
1216
1129
  icon="pi pi-sitemap"
1217
1130
  size="small"
1218
1131
  [outlined]="true"
1219
- (onClick)="addChildNode(node.id, 'group')" />
1132
+ (onClick)="addChildNode(node.id, 'group')"
1133
+ />
1220
1134
  <p-button
1221
1135
  [label]="'iam.logic.action' | translate"
1222
1136
  icon="pi pi-bolt"
1223
1137
  size="small"
1224
1138
  severity="success"
1225
1139
  [outlined]="true"
1226
- (onClick)="addChildNode(node.id, 'action')" />
1140
+ (onClick)="addChildNode(node.id, 'action')"
1141
+ />
1227
1142
  </div>
1228
1143
  </div>
1229
1144
  }
@@ -1236,13 +1151,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1236
1151
  args: [{ selector: 'lib-logic-builder', imports: [AngularModule, PrimeModule, TranslatePipe], template: `
1237
1152
  <div class="logic-builder">
1238
1153
  <div class="flex justify-between items-center mb-3">
1239
- <h4 class="text-sm font-semibold m-0">{{ 'iam.logic.title' | translate }}</h4>
1154
+ <h4 class="text-sm font-semibold m-0">
1155
+ {{ 'iam.logic.title' | translate }}
1156
+ </h4>
1240
1157
  @if (!builderLogic()) {
1241
1158
  <p-button
1242
1159
  [label]="'iam.logic.add.logic' | translate"
1243
1160
  icon="pi pi-plus"
1244
1161
  size="small"
1245
- (onClick)="initializeLogic()" />
1162
+ (onClick)="initializeLogic()"
1163
+ />
1246
1164
  } @else {
1247
1165
  <p-button
1248
1166
  [label]="'iam.logic.clear.logic' | translate"
@@ -1250,7 +1168,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1250
1168
  size="small"
1251
1169
  severity="danger"
1252
1170
  [outlined]="true"
1253
- (onClick)="clearLogic()" />
1171
+ (onClick)="clearLogic()"
1172
+ />
1254
1173
  }
1255
1174
  </div>
1256
1175
 
@@ -1262,7 +1181,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1262
1181
 
1263
1182
  <!-- Root Node -->
1264
1183
  <div class="text-sm bg-surface-0 rounded border border-surface">
1265
- <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: builderLogic()!, depth: 0 }"></ng-container>
1184
+ <ng-container
1185
+ *ngTemplateOutlet="
1186
+ nodeTemplate;
1187
+ context: { $implicit: builderLogic()!, depth: 0 }
1188
+ "
1189
+ ></ng-container>
1266
1190
  </div>
1267
1191
  </div>
1268
1192
  }
@@ -1270,9 +1194,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1270
1194
 
1271
1195
  <!-- Recursive Node Template -->
1272
1196
  <ng-template #nodeTemplate let-node let-depth="depth">
1273
- <div class="p-3 border-b border-surface" [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'">
1197
+ <div
1198
+ class="p-3 border-b border-surface"
1199
+ [ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'"
1200
+ >
1274
1201
  <div class="flex items-center gap-3 mb-2">
1275
- <span class="node-type" [ngClass]="node.type">{{ node.type.toUpperCase() }}</span>
1202
+ <span class="node-type" [ngClass]="node.type">{{
1203
+ node.type.toUpperCase()
1204
+ }}</span>
1276
1205
 
1277
1206
  <div class="flex-1 flex items-center gap-2">
1278
1207
  @if (node.type === 'group') {
@@ -1281,10 +1210,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1281
1210
  class="operator-badge"
1282
1211
  [ngClass]="node.operator === 'AND' ? 'and' : 'or'"
1283
1212
  (click)="toggleOperator(node.id)"
1284
- [title]="'iam.logic.click.to.toggle' | translate">
1213
+ [title]="'iam.logic.click.to.toggle' | translate"
1214
+ >
1285
1215
  {{ node.operator }}
1286
1216
  </span>
1287
- <span class="text-muted-color text-xs">({{ 'iam.logic.conditions' | translate: { count: node.children?.length || 0 } }})</span>
1217
+ <span class="text-muted-color text-xs"
1218
+ >({{
1219
+ 'iam.logic.conditions'
1220
+ | translate: { count: node.children?.length || 0 }
1221
+ }})</span
1222
+ >
1288
1223
  }
1289
1224
 
1290
1225
  @if (node.type === 'action') {
@@ -1292,8 +1227,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1292
1227
  <select
1293
1228
  class="action-select flex-1 p-2 border border-surface rounded text-sm"
1294
1229
  [ngModel]="node.actionId"
1295
- (ngModelChange)="updateActionId(node.id, $event)">
1296
- <option [value]="null">{{ 'iam.logic.select.action' | translate }} ({{ 'iam.logic.actions.available' | translate: { count: actions().length } }})</option>
1230
+ (ngModelChange)="updateActionId(node.id, $event)"
1231
+ >
1232
+ <option [value]="null">
1233
+ {{ 'iam.logic.select.action' | translate }} ({{
1234
+ 'iam.logic.actions.available'
1235
+ | translate: { count: actions().length }
1236
+ }})
1237
+ </option>
1297
1238
  @for (action of actions(); track action.id) {
1298
1239
  <option [ngValue]="action.id">{{ action.name }}</option>
1299
1240
  }
@@ -1308,37 +1249,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1308
1249
  severity="danger"
1309
1250
  size="small"
1310
1251
  (onClick)="removeNode(node.id)"
1311
- [pTooltip]="'iam.logic.remove' | translate" />
1252
+ [pTooltip]="'shared.remove' | translate"
1253
+ />
1312
1254
  </div>
1313
1255
  </div>
1314
1256
 
1315
1257
  <!-- Children for group nodes -->
1316
- @if (node.type === 'group' && node.children && node.children.length > 0) {
1258
+ @if (
1259
+ node.type === 'group' && node.children && node.children.length > 0
1260
+ ) {
1317
1261
  <div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
1318
1262
  @for (child of node.children; track child.id) {
1319
- <ng-container *ngTemplateOutlet="nodeTemplate; context: { $implicit: child, depth: depth + 1 }"></ng-container>
1263
+ <ng-container
1264
+ *ngTemplateOutlet="
1265
+ nodeTemplate;
1266
+ context: { $implicit: child, depth: depth + 1 }
1267
+ "
1268
+ ></ng-container>
1320
1269
  }
1321
1270
  </div>
1322
1271
  }
1323
1272
 
1324
1273
  <!-- Add child buttons for group nodes -->
1325
1274
  @if (node.type === 'group') {
1326
- <div class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface">
1327
- <div class="text-xs font-semibold text-muted-color mb-2">{{ 'iam.logic.add.condition' | translate }}</div>
1275
+ <div
1276
+ class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface"
1277
+ >
1278
+ <div class="text-xs font-semibold text-muted-color mb-2">
1279
+ {{ 'iam.logic.add.condition' | translate }}
1280
+ </div>
1328
1281
  <div class="flex gap-2 flex-wrap">
1329
1282
  <p-button
1330
1283
  [label]="'iam.logic.group' | translate"
1331
1284
  icon="pi pi-sitemap"
1332
1285
  size="small"
1333
1286
  [outlined]="true"
1334
- (onClick)="addChildNode(node.id, 'group')" />
1287
+ (onClick)="addChildNode(node.id, 'group')"
1288
+ />
1335
1289
  <p-button
1336
1290
  [label]="'iam.logic.action' | translate"
1337
1291
  icon="pi pi-bolt"
1338
1292
  size="small"
1339
1293
  severity="success"
1340
1294
  [outlined]="true"
1341
- (onClick)="addChildNode(node.id, 'action')" />
1295
+ (onClick)="addChildNode(node.id, 'action')"
1296
+ />
1342
1297
  </div>
1343
1298
  </div>
1344
1299
  }
@@ -1406,92 +1361,35 @@ function convertActionToTreeNode(actions) {
1406
1361
  return actions.map(convert);
1407
1362
  }
1408
1363
 
1409
- /**
1410
- * Role-Action Selector Component
1411
- *
1412
- * Manages role action permissions with hierarchical tree view and intelligent dependency management.
1413
- *
1414
- * **Architecture:**
1415
- * - Presentation: TreeTable for hierarchical display
1416
- * - Business Logic: Flat action list for selection/validation
1417
- * - State: Signals with computed dependencies
1418
- *
1419
- * **Core Features:**
1420
- * - Role dropdown with async loading
1421
- * - Hierarchical TreeTable with expand/collapse
1422
- * - Action permission assignment (add/remove)
1423
- * - Real-time change tracking
1424
- *
1425
- * **Dependency Management:**
1426
- * - Auto-selection with AND/OR logic
1427
- * - Dependency detection on uncheck
1428
- * - Cascade remove for AND dependencies
1429
- * - Visual warnings for unmet prerequisites
1430
- * - Pre-save validation with auto-fix
1431
- *
1432
- * **Performance:**
1433
- * - Computed signals for reactive updates
1434
- * - Memoized prerequisite validation
1435
- * - AbortController for request cancellation
1436
- *
1437
- * @example
1438
- * ```html
1439
- * <flusys-role-action-selector />
1440
- * ```
1441
- */
1442
- class RoleActionSelectorComponent {
1443
- // Permission constants for template
1444
- ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
1445
- // Dependencies
1446
- destroyRef = inject(DestroyRef);
1447
- roleApi = inject(RoleApiService);
1448
- actionApi = inject(ActionApiService);
1449
- permissionApi = inject(PermissionApiService);
1450
- messageService = inject(MessageService);
1364
+ class BaseActionSelectorComponent {
1451
1365
  permissionLogic = inject(ActionPermissionLogicService);
1452
1366
  translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
1453
- translate(key, vars) {
1454
- return this.translateAdapter?.translate(key, vars) ?? key;
1455
- }
1456
- // State - Role Selection
1457
- selectedRoleId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedRoleId" }] : []));
1458
- roles = signal([], ...(ngDevMode ? [{ debugName: "roles" }] : []));
1459
- // State - Loading/Saving
1460
1367
  loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
1461
1368
  saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
1462
- // State - Actions (Business Logic)
1463
1369
  _actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
1464
1370
  actions = this._actions.asReadonly();
1465
- // State - Actions (Display)
1466
1371
  _actionsTree = signal([], ...(ngDevMode ? [{ debugName: "_actionsTree" }] : []));
1467
1372
  actionsTree = this._actionsTree.asReadonly();
1468
- // State - Selection
1469
1373
  _selectionMap = signal({}, ...(ngDevMode ? [{ debugName: "_selectionMap" }] : []));
1470
1374
  selectionMap = this._selectionMap.asReadonly();
1471
1375
  _initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
1472
1376
  initialSelection = this._initialSelection.asReadonly();
1473
- // Computed - Tree Nodes (for PrimeNG TreeTable)
1474
1377
  treeNodes = computed(() => convertActionToTreeNode(this.actionsTree()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
1475
- // Computed - Selection State
1476
1378
  allSelected = computed(() => {
1477
1379
  const selMap = this.selectionMap();
1478
1380
  const total = this.actions().length;
1479
1381
  if (total === 0)
1480
1382
  return false;
1481
- const selected = Object.values(selMap).filter((v) => v).length;
1482
- return selected === total;
1383
+ return Object.values(selMap).filter((v) => v).length === total;
1483
1384
  }, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
1484
- // Computed - Change Detection
1485
1385
  hasChanges = computed(() => {
1486
1386
  const current = this.selectionMap();
1487
1387
  const initial = this.initialSelection();
1488
1388
  const currentKeys = Object.keys(current);
1489
- const initialKeys = Object.keys(initial);
1490
- if (currentKeys.length !== initialKeys.length)
1389
+ if (currentKeys.length !== Object.keys(initial).length)
1491
1390
  return true;
1492
1391
  return currentKeys.some((key) => current[key] !== initial[key]);
1493
1392
  }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
1494
- // Computed - Validation
1495
1393
  actionsWithUnmetPrerequisites = computed(() => {
1496
1394
  const selMap = this.selectionMap();
1497
1395
  const allActions = this.actions();
@@ -1504,41 +1402,105 @@ class RoleActionSelectorComponent {
1504
1402
  return unmetSet;
1505
1403
  }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
1506
1404
  invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
1507
- // Computed - Pending Changes
1508
1405
  pendingAdd = computed(() => {
1509
1406
  const current = this.selectionMap();
1510
1407
  const initial = this.initialSelection();
1511
- const allActions = this.actions();
1512
- return allActions.filter((action) => current[action.id] && !initial[action.id]);
1408
+ return this.actions().filter((a) => current[a.id] && !initial[a.id]);
1513
1409
  }, ...(ngDevMode ? [{ debugName: "pendingAdd" }] : []));
1514
1410
  pendingRemove = computed(() => {
1515
1411
  const current = this.selectionMap();
1516
1412
  const initial = this.initialSelection();
1517
- const allActions = this.actions();
1518
- return allActions.filter((action) => !current[action.id] && initial[action.id]);
1413
+ return this.actions().filter((a) => !current[a.id] && initial[a.id]);
1519
1414
  }, ...(ngDevMode ? [{ debugName: "pendingRemove" }] : []));
1520
- // Computed - Save Button State
1521
- canSave = computed(() => {
1522
- return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
1523
- }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
1524
- // AbortController for data loading
1525
- loadDataAbortController = null;
1526
- constructor() {
1527
- this.loadRoles();
1528
- // Cleanup on destroy
1529
- this.destroyRef.onDestroy(() => {
1530
- this.loadDataAbortController?.abort();
1531
- });
1532
- // Effect: Load data when role selection changes
1415
+ canSave = computed(() => this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
1416
+ translate(key, vars) {
1417
+ return this.translateAdapter?.translate(key, vars) ?? key;
1418
+ }
1419
+ hasUnmetPrerequisites(action) {
1420
+ return action.id ? this.actionsWithUnmetPrerequisites().has(action.id) : false;
1421
+ }
1422
+ getTooltip(action) {
1423
+ const selMap = this.selectionMap();
1424
+ const isCurrentlySelected = selMap[action.id];
1425
+ if (action.permissionLogic) {
1426
+ const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
1427
+ if (prerequisiteInfo) {
1428
+ const actionHint = isCurrentlySelected
1429
+ ? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
1430
+ : `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
1431
+ return `${prerequisiteInfo}${actionHint}`;
1432
+ }
1433
+ }
1434
+ return this.getDefaultTooltip(isCurrentlySelected);
1435
+ }
1436
+ onActionToggle(action, newValue) {
1437
+ if (!newValue) {
1438
+ this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
1439
+ }
1440
+ else {
1441
+ this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
1442
+ }
1443
+ }
1444
+ toggleAll() {
1445
+ this.setAllSelection(!this.allSelected());
1446
+ }
1447
+ selectAll() {
1448
+ this.setAllSelection(true);
1449
+ }
1450
+ deselectAll() {
1451
+ this.setAllSelection(false);
1452
+ }
1453
+ setAllSelection(value) {
1454
+ this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
1455
+ }
1456
+ buildSelectionMap(actions, predicate) {
1457
+ const selMap = {};
1458
+ for (const action of actions) {
1459
+ selMap[action.id] = predicate(action);
1460
+ }
1461
+ return selMap;
1462
+ }
1463
+ buildPayloadItems() {
1464
+ const items = [];
1465
+ for (const action of this.pendingAdd()) {
1466
+ items.push({ id: action.id, action: 'add' });
1467
+ }
1468
+ for (const action of this.pendingRemove()) {
1469
+ items.push({ id: action.id, action: 'remove' });
1470
+ }
1471
+ return items;
1472
+ }
1473
+ resetState() {
1474
+ this._actionsTree.set([]);
1475
+ this._actions.set([]);
1476
+ this._selectionMap.set({});
1477
+ this._initialSelection.set({});
1478
+ }
1479
+ }
1480
+
1481
+ class RoleActionSelectorComponent extends BaseActionSelectorComponent {
1482
+ ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
1483
+ destroyRef = inject(DestroyRef);
1484
+ roleApi = inject(RoleApiService);
1485
+ actionApi = inject(ActionApiService);
1486
+ permissionApi = inject(PermissionApiService);
1487
+ messageService = inject(MessageService);
1488
+ selectedRoleId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedRoleId" }] : []));
1489
+ roles = signal([], ...(ngDevMode ? [{ debugName: "roles" }] : []));
1490
+ loadDataAbortController = null;
1491
+ constructor() {
1492
+ super();
1493
+ this.destroyRef.onDestroy(() => {
1494
+ this.loadDataAbortController?.abort();
1495
+ });
1496
+ this.loadRoles();
1533
1497
  effect(() => {
1534
1498
  const roleId = this.selectedRoleId();
1535
- // Abort previous request
1536
1499
  this.loadDataAbortController?.abort();
1537
1500
  if (roleId) {
1538
1501
  this.loadDataAbortController = new AbortController();
1539
1502
  this.onRoleChange(this.loadDataAbortController.signal).catch((err) => {
1540
1503
  if (err.name !== 'AbortError') {
1541
- // Error toast handled by global interceptor
1542
1504
  this.loading.set(false);
1543
1505
  }
1544
1506
  });
@@ -1548,23 +1510,22 @@ class RoleActionSelectorComponent {
1548
1510
  }
1549
1511
  });
1550
1512
  }
1551
- /**
1552
- * Load roles from API
1553
- */
1513
+ getDefaultTooltip(isSelected) {
1514
+ if (isSelected) {
1515
+ return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.role')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.role')}`;
1516
+ }
1517
+ return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.role')}`;
1518
+ }
1554
1519
  async loadRoles() {
1555
1520
  try {
1556
- const response = await firstValueFrom(this.roleApi.getAll('', {
1521
+ const response = await this.roleApi.getAll({
1557
1522
  pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
1558
- }));
1523
+ });
1559
1524
  this.roles.set(response?.data ?? []);
1560
1525
  }
1561
1526
  catch {
1562
- // Error toast handled by global interceptor
1563
1527
  }
1564
1528
  }
1565
- /**
1566
- * Load actions and assignments for selected role
1567
- */
1568
1529
  async onRoleChange(signal) {
1569
1530
  const roleId = this.selectedRoleId();
1570
1531
  if (!roleId)
@@ -1574,26 +1535,17 @@ class RoleActionSelectorComponent {
1574
1535
  const actionsResponse = await firstValueFrom(this.actionApi.getActionsForPermission());
1575
1536
  if (signal.aborted)
1576
1537
  return;
1577
- // Get flat list (filtered by company if enabled)
1578
1538
  const flatActions = (actionsResponse?.data ?? []).filter((a) => !!a.id);
1579
- // Build tree structure from flat list with parentId
1580
1539
  const actionsTree = buildTreeFromFlat(flatActions);
1581
- // Store both representations
1582
1540
  this._actionsTree.set(actionsTree);
1583
1541
  this._actions.set(flatActions);
1584
- // Load existing assignments
1585
1542
  const assignedResponse = await firstValueFrom(this.permissionApi.getRoleActions(roleId));
1586
1543
  if (signal.aborted)
1587
1544
  return;
1588
1545
  const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
1589
- // Build selection map
1590
- const selMap = {};
1591
- for (const action of flatActions) {
1592
- if (action.id) {
1593
- selMap[action.id] = assignedIds.has(action.id);
1594
- }
1595
- }
1596
- this.applySelection(selMap);
1546
+ const selMap = this.buildSelectionMap(flatActions, (action) => assignedIds.has(action.id));
1547
+ this._selectionMap.set(selMap);
1548
+ this._initialSelection.set({ ...selMap });
1597
1549
  }
1598
1550
  finally {
1599
1551
  if (!signal.aborted) {
@@ -1601,146 +1553,63 @@ class RoleActionSelectorComponent {
1601
1553
  }
1602
1554
  }
1603
1555
  }
1604
- /**
1605
- * Get tooltip for action checkbox
1606
- */
1607
- getTooltip(action) {
1608
- const selMap = this.selectionMap();
1609
- const isCurrentlySelected = selMap[action.id];
1610
- if (action.permissionLogic) {
1611
- const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
1612
- if (prerequisiteInfo) {
1613
- const actionHint = isCurrentlySelected
1614
- ? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
1615
- : `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
1616
- return `${prerequisiteInfo}${actionHint}`;
1617
- }
1618
- }
1619
- if (isCurrentlySelected) {
1620
- return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.role')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.role')}`;
1621
- }
1622
- return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.role')}`;
1623
- }
1624
- /**
1625
- * Check if action has unmet prerequisites
1626
- */
1627
- hasUnmetPrerequisites(action) {
1628
- return action.id
1629
- ? this.actionsWithUnmetPrerequisites().has(action.id)
1630
- : false;
1631
- }
1632
- /**
1633
- * Handle action toggle with dependency management
1634
- */
1635
- onActionToggle(action, newValue) {
1636
- if (!newValue) {
1637
- // Unchecking - validate dependencies
1638
- this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
1639
- }
1640
- else {
1641
- // Checking - validate prerequisites
1642
- this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
1643
- }
1644
- }
1645
- toggleAll() {
1646
- this.setAllSelection(!this.allSelected());
1647
- }
1648
- selectAll() {
1649
- this.setAllSelection(true);
1650
- }
1651
- deselectAll() {
1652
- this.setAllSelection(false);
1653
- }
1654
- setAllSelection(value) {
1655
- const selMap = {};
1656
- for (const action of this.actions()) {
1657
- selMap[action.id] = value;
1658
- }
1659
- this._selectionMap.set(selMap);
1660
- }
1661
- /**
1662
- * Save changes to backend
1663
- */
1664
1556
  async saveChanges() {
1665
1557
  const roleId = this.selectedRoleId();
1666
1558
  if (!roleId)
1667
1559
  return;
1668
- // Pre-save validation
1669
1560
  const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
1670
1561
  if (invalidActions.length > 0) {
1671
1562
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
1672
1563
  return;
1673
1564
  }
1674
- // Build payload
1675
1565
  const items = this.buildPayloadItems();
1676
1566
  if (items.length === 0)
1677
1567
  return;
1678
1568
  this.saving.set(true);
1679
1569
  try {
1680
- const response = await firstValueFrom(this.permissionApi.assignRoleActions({
1681
- roleId,
1682
- items,
1683
- }));
1570
+ const response = await firstValueFrom(this.permissionApi.assignRoleActions({ roleId, items }));
1684
1571
  this.messageService.add({
1685
1572
  severity: 'success',
1686
1573
  summary: this.translate('shared.success'),
1687
- detail: response?.data?.message || this.translate('iam.permission.role.permissions.updated'),
1574
+ detail: response?.messageKey
1575
+ ? this.translate(response.messageKey, response.messageVariables)
1576
+ : response?.message,
1688
1577
  });
1689
- // Update baseline
1690
1578
  this._initialSelection.set({ ...this.selectionMap() });
1691
1579
  }
1692
1580
  catch {
1693
- // Error toast handled by global interceptor
1694
1581
  }
1695
1582
  finally {
1696
1583
  this.saving.set(false);
1697
1584
  }
1698
1585
  }
1699
- applySelection(selMap) {
1700
- this._selectionMap.set(selMap);
1701
- this._initialSelection.set({ ...selMap });
1702
- }
1703
- buildPayloadItems() {
1704
- const items = [];
1705
- for (const action of this.pendingAdd()) {
1706
- items.push({ id: action.id, action: 'add' });
1707
- }
1708
- for (const action of this.pendingRemove()) {
1709
- items.push({ id: action.id, action: 'remove' });
1710
- }
1711
- return items;
1712
- }
1713
- resetState() {
1714
- this._actionsTree.set([]);
1715
- this._actions.set([]);
1716
- this._selectionMap.set({});
1717
- this._initialSelection.set({});
1718
- }
1719
1586
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1720
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", ngImport: i0, template: `
1587
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", usesInheritance: true, ngImport: i0, template: `
1721
1588
  <div class="role-action-selector">
1722
- <!-- Role Selector -->
1723
1589
  <div class="mb-4">
1724
1590
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1725
1591
  <div>
1726
- <label class="block font-semibold mb-2">{{ 'iam.permission.select.role' | translate }}</label>
1592
+ <label class="block font-semibold mb-2">{{
1593
+ 'iam.permission.select.role' | translate
1594
+ }}</label>
1727
1595
  <p-select
1728
1596
  [ngModel]="selectedRoleId()"
1729
1597
  (ngModelChange)="selectedRoleId.set($event)"
1730
1598
  [options]="roles()"
1731
1599
  optionLabel="name"
1732
1600
  optionValue="id"
1733
- [placeholder]="'iam.permission.select.role.placeholder' | translate"
1601
+ [placeholder]="
1602
+ 'iam.permission.select.role.placeholder' | translate
1603
+ "
1734
1604
  [showClear]="true"
1735
1605
  [filter]="true"
1736
- styleClass="w-full"
1606
+ class="w-full"
1737
1607
  />
1738
1608
  </div>
1739
1609
  </div>
1740
1610
  </div>
1741
1611
 
1742
1612
  @if (selectedRoleId()) {
1743
- <!-- Loading State -->
1744
1613
  @if (loading()) {
1745
1614
  <div
1746
1615
  class="surface-card p-5 rounded-border shadow-sm flex justify-center"
@@ -1749,16 +1618,20 @@ class RoleActionSelectorComponent {
1749
1618
  </div>
1750
1619
  }
1751
1620
 
1752
- <!-- Action List -->
1753
1621
  @if (!loading() && actions().length > 0) {
1754
1622
  <div class="surface-card p-4 rounded-border shadow-sm">
1755
1623
  <div
1756
1624
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
1757
1625
  >
1758
1626
  <div>
1759
- <h5 class="m-0 mb-1">{{ 'iam.permission.action.permissions' | translate }}</h5>
1627
+ <h5 class="m-0 mb-1">
1628
+ {{ 'iam.permission.action.permissions' | translate }}
1629
+ </h5>
1760
1630
  <p class="text-sm text-muted-color m-0">
1761
- {{ 'iam.permission.actions.available' | translate: { count: actions().length } }}
1631
+ {{
1632
+ 'iam.permission.actions.available'
1633
+ | translate: { count: actions().length }
1634
+ }}
1762
1635
  </p>
1763
1636
  </div>
1764
1637
  <div class="flex flex-wrap gap-2">
@@ -1791,27 +1664,32 @@ class RoleActionSelectorComponent {
1791
1664
  </div>
1792
1665
  </div>
1793
1666
 
1794
- <!-- Validation Warning -->
1795
1667
  @if (invalidActionsCount() > 0) {
1796
1668
  <div class="validation-warning rounded-border p-3 mb-4">
1797
1669
  <div class="flex items-start gap-2">
1798
1670
  <i class="pi pi-exclamation-triangle text-xl"></i>
1799
1671
  <div class="flex-1">
1800
- <span class="font-semibold">{{ 'iam.validation.warning.title' | translate }}:</span>
1672
+ <span class="font-semibold"
1673
+ >{{ 'iam.validation.warning.title' | translate }}:</span
1674
+ >
1801
1675
  <p class="text-sm mt-1 mb-0">
1802
- {{ (invalidActionsCount() > 1 ? 'iam.validation.unmet.prerequisites.plural' : 'iam.validation.unmet.prerequisites.singular') | translate: { count: invalidActionsCount() } }}
1676
+ {{
1677
+ (invalidActionsCount() > 1
1678
+ ? 'iam.validation.unmet.prerequisites.plural'
1679
+ : 'iam.validation.unmet.prerequisites.singular'
1680
+ ) | translate: { count: invalidActionsCount() }
1681
+ }}
1803
1682
  </p>
1804
1683
  </div>
1805
1684
  </div>
1806
1685
  </div>
1807
1686
  }
1808
1687
 
1809
- <!-- Action Tree Table -->
1810
1688
  <div class="overflow-x-auto -mx-4 sm:mx-0">
1811
1689
  <p-treeTable
1812
1690
  [value]="treeNodes()"
1813
1691
  dataKey="id"
1814
- styleClass="p-treetable-sm"
1692
+ class="p-treetable-sm"
1815
1693
  [tableStyle]="{ 'min-width': '50rem' }"
1816
1694
  >
1817
1695
  <ng-template #header>
@@ -1826,79 +1704,100 @@ class RoleActionSelectorComponent {
1826
1704
  />
1827
1705
  </th>
1828
1706
  <th>{{ 'shared.name' | translate }}</th>
1829
- <th class="hidden md:table-cell">{{ 'shared.code' | translate }}</th>
1707
+ <th class="hidden md:table-cell">
1708
+ {{ 'shared.code' | translate }}
1709
+ </th>
1830
1710
  <th>{{ 'iam.action.type' | translate }}</th>
1831
- <th class="hidden lg:table-cell">{{ 'iam.permission.requirements' | translate }}</th>
1711
+ <th class="hidden lg:table-cell">
1712
+ {{ 'iam.permission.requirements' | translate }}
1713
+ </th>
1714
+ </tr>
1715
+ </ng-template>
1716
+ <ng-template #body let-rowNode let-rowData="rowData">
1717
+ @let hasUnmet = hasUnmetPrerequisites(rowData);
1718
+ @let tooltip = getTooltip(rowData);
1719
+ <tr [class.highlight-warning]="hasUnmet">
1720
+ <td class="w-12">
1721
+ <p-checkbox
1722
+ [ngModel]="selectionMap()[rowData.id]"
1723
+ [binary]="true"
1724
+ (ngModelChange)="onActionToggle(rowData, $event)"
1725
+ [pTooltip]="tooltip"
1726
+ tooltipPosition="top"
1727
+ />
1728
+ </td>
1729
+ <td>
1730
+ <p-treeTableToggler [rowNode]="rowNode" />
1731
+ <span class="inline-flex items-center gap-2">
1732
+ {{ rowData.name }}
1733
+ @if (hasUnmet) {
1734
+ <i
1735
+ class="pi pi-exclamation-triangle text-orange-500"
1736
+ [pTooltip]="
1737
+ 'iam.validation.unmet.prerequisites.tooltip'
1738
+ | translate
1739
+ "
1740
+ tooltipPosition="top"
1741
+ ></i>
1742
+ }
1743
+ </span>
1744
+ </td>
1745
+ <td class="hidden md:table-cell">
1746
+ {{ rowData.code || '-' }}
1747
+ </td>
1748
+ <td>
1749
+ <p-tag
1750
+ [value]="rowData.actionType"
1751
+ [severity]="
1752
+ rowData.actionType === 'backend' ? 'info' : 'success'
1753
+ "
1754
+ />
1755
+ </td>
1756
+ <td class="hidden lg:table-cell">
1757
+ @if (rowData.permissionLogic) {
1758
+ <span class="text-sm text-muted-color">{{
1759
+ 'iam.permission.has.prerequisites' | translate
1760
+ }}</span>
1761
+ } @else {
1762
+ <span class="text-muted-color">-</span>
1763
+ }
1764
+ </td>
1832
1765
  </tr>
1833
1766
  </ng-template>
1834
- <ng-template #body let-rowNode let-rowData="rowData">
1835
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
1836
- <td class="w-12">
1837
- <p-checkbox
1838
- [ngModel]="selectionMap()[rowData.id]"
1839
- [binary]="true"
1840
- (ngModelChange)="onActionToggle(rowData, $event)"
1841
- [pTooltip]="getTooltip(rowData)"
1842
- tooltipPosition="top"
1843
- />
1844
- </td>
1845
- <td>
1846
- <p-treeTableToggler [rowNode]="rowNode" />
1847
- <span class="inline-flex items-center gap-2">
1848
- {{ rowData.name }}
1849
- @if (hasUnmetPrerequisites(rowData)) {
1850
- <i
1851
- class="pi pi-exclamation-triangle text-orange-500"
1852
- [pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
1853
- tooltipPosition="top"
1854
- ></i>
1767
+ <ng-template #emptymessage>
1768
+ <tr>
1769
+ <td colspan="5" class="text-center p-4 text-muted-color">
1770
+ @if (loading()) {
1771
+ <i class="pi pi-spin pi-spinner"></i>
1772
+ {{ 'shared.loading.actions' | translate }}
1773
+ } @else {
1774
+ {{ 'iam.permission.no.actions.available' | translate }}
1855
1775
  }
1856
- </span>
1857
- </td>
1858
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
1859
- <td>
1860
- <p-tag
1861
- [value]="rowData.actionType"
1862
- [severity]="
1863
- rowData.actionType === 'backend' ? 'info' : 'success'
1864
- "
1865
- />
1866
- </td>
1867
- <td class="hidden lg:table-cell">
1868
- @if (rowData.permissionLogic) {
1869
- <span class="text-sm text-muted-color">{{ 'iam.permission.has.prerequisites' | translate }}</span>
1870
- } @else {
1871
- <span class="text-muted-color">-</span>
1872
- }
1873
- </td>
1874
- </tr>
1875
- </ng-template>
1876
- <ng-template #emptymessage>
1877
- <tr>
1878
- <td colspan="5" class="text-center p-4 text-muted-color">
1879
- @if (loading()) {
1880
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
1881
- } @else { {{ 'iam.permission.no.actions.available' | translate }} }
1882
- </td>
1883
- </tr>
1884
- </ng-template>
1776
+ </td>
1777
+ </tr>
1778
+ </ng-template>
1885
1779
  </p-treeTable>
1886
1780
  </div>
1887
1781
  </div>
1888
1782
 
1889
- <!-- Change Summary -->
1890
1783
  @if (hasChanges()) {
1891
1784
  <div class="border border-surface rounded-border p-3 mt-4">
1892
1785
  <div class="flex items-center gap-2 mb-3">
1893
1786
  <i class="pi pi-info-circle text-primary"></i>
1894
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
1787
+ <span class="font-bold">{{
1788
+ 'shared.pending.changes' | translate
1789
+ }}</span>
1895
1790
  </div>
1896
1791
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1897
1792
  @if (pendingAdd().length > 0) {
1898
1793
  <div>
1899
1794
  <div class="flex items-center gap-2 mb-2">
1900
1795
  <i class="pi pi-plus-circle text-green-500"></i>
1901
- <strong class="text-sm">{{ 'shared.to.add' | translate }} ({{ pendingAdd().length }})</strong>
1796
+ <strong class="text-sm"
1797
+ >{{ 'shared.to.add' | translate }} ({{
1798
+ pendingAdd().length
1799
+ }})</strong
1800
+ >
1902
1801
  </div>
1903
1802
  <ul class="list-none p-0 m-0 pl-4">
1904
1803
  @for (action of pendingAdd(); track action.id) {
@@ -1911,7 +1810,11 @@ class RoleActionSelectorComponent {
1911
1810
  <div>
1912
1811
  <div class="flex items-center gap-2 mb-2">
1913
1812
  <i class="pi pi-minus-circle text-red-500"></i>
1914
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
1813
+ <strong class="text-sm"
1814
+ >{{ 'shared.to.remove' | translate }} ({{
1815
+ pendingRemove().length
1816
+ }})</strong
1817
+ >
1915
1818
  </div>
1916
1819
  <ul class="list-none p-0 m-0 pl-4">
1917
1820
  @for (action of pendingRemove(); track action.id) {
@@ -1927,7 +1830,9 @@ class RoleActionSelectorComponent {
1927
1830
 
1928
1831
  @if (!loading() && actions().length === 0) {
1929
1832
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
1930
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
1833
+ <i
1834
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
1835
+ ></i>
1931
1836
  <p class="text-muted-color m-0">
1932
1837
  {{ 'iam.permission.no.actions.for.role' | translate }}
1933
1838
  </p>
@@ -1935,34 +1840,36 @@ class RoleActionSelectorComponent {
1935
1840
  }
1936
1841
  }
1937
1842
  </div>
1938
- `, 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: "pipe", type: TranslatePipe, name: "translate" }] });
1843
+ `, 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: 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: "pipe", type: TranslatePipe, name: "translate" }] });
1939
1844
  }
1940
1845
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
1941
1846
  type: Component,
1942
- args: [{ selector: 'flusys-role-action-selector', imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, TranslatePipe], template: `
1847
+ args: [{ selector: 'flusys-role-action-selector', imports: [FormsModule, PrimeModule, HasPermissionDirective, TranslatePipe], template: `
1943
1848
  <div class="role-action-selector">
1944
- <!-- Role Selector -->
1945
1849
  <div class="mb-4">
1946
1850
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1947
1851
  <div>
1948
- <label class="block font-semibold mb-2">{{ 'iam.permission.select.role' | translate }}</label>
1852
+ <label class="block font-semibold mb-2">{{
1853
+ 'iam.permission.select.role' | translate
1854
+ }}</label>
1949
1855
  <p-select
1950
1856
  [ngModel]="selectedRoleId()"
1951
1857
  (ngModelChange)="selectedRoleId.set($event)"
1952
1858
  [options]="roles()"
1953
1859
  optionLabel="name"
1954
1860
  optionValue="id"
1955
- [placeholder]="'iam.permission.select.role.placeholder' | translate"
1861
+ [placeholder]="
1862
+ 'iam.permission.select.role.placeholder' | translate
1863
+ "
1956
1864
  [showClear]="true"
1957
1865
  [filter]="true"
1958
- styleClass="w-full"
1866
+ class="w-full"
1959
1867
  />
1960
1868
  </div>
1961
1869
  </div>
1962
1870
  </div>
1963
1871
 
1964
1872
  @if (selectedRoleId()) {
1965
- <!-- Loading State -->
1966
1873
  @if (loading()) {
1967
1874
  <div
1968
1875
  class="surface-card p-5 rounded-border shadow-sm flex justify-center"
@@ -1971,16 +1878,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
1971
1878
  </div>
1972
1879
  }
1973
1880
 
1974
- <!-- Action List -->
1975
1881
  @if (!loading() && actions().length > 0) {
1976
1882
  <div class="surface-card p-4 rounded-border shadow-sm">
1977
1883
  <div
1978
1884
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
1979
1885
  >
1980
1886
  <div>
1981
- <h5 class="m-0 mb-1">{{ 'iam.permission.action.permissions' | translate }}</h5>
1887
+ <h5 class="m-0 mb-1">
1888
+ {{ 'iam.permission.action.permissions' | translate }}
1889
+ </h5>
1982
1890
  <p class="text-sm text-muted-color m-0">
1983
- {{ 'iam.permission.actions.available' | translate: { count: actions().length } }}
1891
+ {{
1892
+ 'iam.permission.actions.available'
1893
+ | translate: { count: actions().length }
1894
+ }}
1984
1895
  </p>
1985
1896
  </div>
1986
1897
  <div class="flex flex-wrap gap-2">
@@ -2013,27 +1924,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2013
1924
  </div>
2014
1925
  </div>
2015
1926
 
2016
- <!-- Validation Warning -->
2017
1927
  @if (invalidActionsCount() > 0) {
2018
1928
  <div class="validation-warning rounded-border p-3 mb-4">
2019
1929
  <div class="flex items-start gap-2">
2020
1930
  <i class="pi pi-exclamation-triangle text-xl"></i>
2021
1931
  <div class="flex-1">
2022
- <span class="font-semibold">{{ 'iam.validation.warning.title' | translate }}:</span>
1932
+ <span class="font-semibold"
1933
+ >{{ 'iam.validation.warning.title' | translate }}:</span
1934
+ >
2023
1935
  <p class="text-sm mt-1 mb-0">
2024
- {{ (invalidActionsCount() > 1 ? 'iam.validation.unmet.prerequisites.plural' : 'iam.validation.unmet.prerequisites.singular') | translate: { count: invalidActionsCount() } }}
1936
+ {{
1937
+ (invalidActionsCount() > 1
1938
+ ? 'iam.validation.unmet.prerequisites.plural'
1939
+ : 'iam.validation.unmet.prerequisites.singular'
1940
+ ) | translate: { count: invalidActionsCount() }
1941
+ }}
2025
1942
  </p>
2026
1943
  </div>
2027
1944
  </div>
2028
1945
  </div>
2029
1946
  }
2030
1947
 
2031
- <!-- Action Tree Table -->
2032
1948
  <div class="overflow-x-auto -mx-4 sm:mx-0">
2033
1949
  <p-treeTable
2034
1950
  [value]="treeNodes()"
2035
1951
  dataKey="id"
2036
- styleClass="p-treetable-sm"
1952
+ class="p-treetable-sm"
2037
1953
  [tableStyle]="{ 'min-width': '50rem' }"
2038
1954
  >
2039
1955
  <ng-template #header>
@@ -2048,79 +1964,100 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2048
1964
  />
2049
1965
  </th>
2050
1966
  <th>{{ 'shared.name' | translate }}</th>
2051
- <th class="hidden md:table-cell">{{ 'shared.code' | translate }}</th>
1967
+ <th class="hidden md:table-cell">
1968
+ {{ 'shared.code' | translate }}
1969
+ </th>
2052
1970
  <th>{{ 'iam.action.type' | translate }}</th>
2053
- <th class="hidden lg:table-cell">{{ 'iam.permission.requirements' | translate }}</th>
1971
+ <th class="hidden lg:table-cell">
1972
+ {{ 'iam.permission.requirements' | translate }}
1973
+ </th>
2054
1974
  </tr>
2055
1975
  </ng-template>
2056
- <ng-template #body let-rowNode let-rowData="rowData">
2057
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
2058
- <td class="w-12">
2059
- <p-checkbox
2060
- [ngModel]="selectionMap()[rowData.id]"
2061
- [binary]="true"
2062
- (ngModelChange)="onActionToggle(rowData, $event)"
2063
- [pTooltip]="getTooltip(rowData)"
2064
- tooltipPosition="top"
2065
- />
2066
- </td>
2067
- <td>
2068
- <p-treeTableToggler [rowNode]="rowNode" />
2069
- <span class="inline-flex items-center gap-2">
2070
- {{ rowData.name }}
2071
- @if (hasUnmetPrerequisites(rowData)) {
2072
- <i
2073
- class="pi pi-exclamation-triangle text-orange-500"
2074
- [pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
2075
- tooltipPosition="top"
2076
- ></i>
1976
+ <ng-template #body let-rowNode let-rowData="rowData">
1977
+ @let hasUnmet = hasUnmetPrerequisites(rowData);
1978
+ @let tooltip = getTooltip(rowData);
1979
+ <tr [class.highlight-warning]="hasUnmet">
1980
+ <td class="w-12">
1981
+ <p-checkbox
1982
+ [ngModel]="selectionMap()[rowData.id]"
1983
+ [binary]="true"
1984
+ (ngModelChange)="onActionToggle(rowData, $event)"
1985
+ [pTooltip]="tooltip"
1986
+ tooltipPosition="top"
1987
+ />
1988
+ </td>
1989
+ <td>
1990
+ <p-treeTableToggler [rowNode]="rowNode" />
1991
+ <span class="inline-flex items-center gap-2">
1992
+ {{ rowData.name }}
1993
+ @if (hasUnmet) {
1994
+ <i
1995
+ class="pi pi-exclamation-triangle text-orange-500"
1996
+ [pTooltip]="
1997
+ 'iam.validation.unmet.prerequisites.tooltip'
1998
+ | translate
1999
+ "
2000
+ tooltipPosition="top"
2001
+ ></i>
2002
+ }
2003
+ </span>
2004
+ </td>
2005
+ <td class="hidden md:table-cell">
2006
+ {{ rowData.code || '-' }}
2007
+ </td>
2008
+ <td>
2009
+ <p-tag
2010
+ [value]="rowData.actionType"
2011
+ [severity]="
2012
+ rowData.actionType === 'backend' ? 'info' : 'success'
2013
+ "
2014
+ />
2015
+ </td>
2016
+ <td class="hidden lg:table-cell">
2017
+ @if (rowData.permissionLogic) {
2018
+ <span class="text-sm text-muted-color">{{
2019
+ 'iam.permission.has.prerequisites' | translate
2020
+ }}</span>
2021
+ } @else {
2022
+ <span class="text-muted-color">-</span>
2077
2023
  }
2078
- </span>
2079
- </td>
2080
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
2081
- <td>
2082
- <p-tag
2083
- [value]="rowData.actionType"
2084
- [severity]="
2085
- rowData.actionType === 'backend' ? 'info' : 'success'
2086
- "
2087
- />
2088
- </td>
2089
- <td class="hidden lg:table-cell">
2090
- @if (rowData.permissionLogic) {
2091
- <span class="text-sm text-muted-color">{{ 'iam.permission.has.prerequisites' | translate }}</span>
2092
- } @else {
2093
- <span class="text-muted-color">-</span>
2094
- }
2095
- </td>
2096
- </tr>
2097
- </ng-template>
2098
- <ng-template #emptymessage>
2099
- <tr>
2100
- <td colspan="5" class="text-center p-4 text-muted-color">
2101
- @if (loading()) {
2102
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
2103
- } @else { {{ 'iam.permission.no.actions.available' | translate }} }
2104
- </td>
2105
- </tr>
2106
- </ng-template>
2024
+ </td>
2025
+ </tr>
2026
+ </ng-template>
2027
+ <ng-template #emptymessage>
2028
+ <tr>
2029
+ <td colspan="5" class="text-center p-4 text-muted-color">
2030
+ @if (loading()) {
2031
+ <i class="pi pi-spin pi-spinner"></i>
2032
+ {{ 'shared.loading.actions' | translate }}
2033
+ } @else {
2034
+ {{ 'iam.permission.no.actions.available' | translate }}
2035
+ }
2036
+ </td>
2037
+ </tr>
2038
+ </ng-template>
2107
2039
  </p-treeTable>
2108
2040
  </div>
2109
2041
  </div>
2110
2042
 
2111
- <!-- Change Summary -->
2112
2043
  @if (hasChanges()) {
2113
2044
  <div class="border border-surface rounded-border p-3 mt-4">
2114
2045
  <div class="flex items-center gap-2 mb-3">
2115
2046
  <i class="pi pi-info-circle text-primary"></i>
2116
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
2047
+ <span class="font-bold">{{
2048
+ 'shared.pending.changes' | translate
2049
+ }}</span>
2117
2050
  </div>
2118
2051
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2119
2052
  @if (pendingAdd().length > 0) {
2120
2053
  <div>
2121
2054
  <div class="flex items-center gap-2 mb-2">
2122
2055
  <i class="pi pi-plus-circle text-green-500"></i>
2123
- <strong class="text-sm">{{ 'shared.to.add' | translate }} ({{ pendingAdd().length }})</strong>
2056
+ <strong class="text-sm"
2057
+ >{{ 'shared.to.add' | translate }} ({{
2058
+ pendingAdd().length
2059
+ }})</strong
2060
+ >
2124
2061
  </div>
2125
2062
  <ul class="list-none p-0 m-0 pl-4">
2126
2063
  @for (action of pendingAdd(); track action.id) {
@@ -2133,7 +2070,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2133
2070
  <div>
2134
2071
  <div class="flex items-center gap-2 mb-2">
2135
2072
  <i class="pi pi-minus-circle text-red-500"></i>
2136
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
2073
+ <strong class="text-sm"
2074
+ >{{ 'shared.to.remove' | translate }} ({{
2075
+ pendingRemove().length
2076
+ }})</strong
2077
+ >
2137
2078
  </div>
2138
2079
  <ul class="list-none p-0 m-0 pl-4">
2139
2080
  @for (action of pendingRemove(); track action.id) {
@@ -2149,7 +2090,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2149
2090
 
2150
2091
  @if (!loading() && actions().length === 0) {
2151
2092
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
2152
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
2093
+ <i
2094
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
2095
+ ></i>
2153
2096
  <p class="text-muted-color m-0">
2154
2097
  {{ 'iam.permission.no.actions.for.role' | translate }}
2155
2098
  </p>
@@ -2160,140 +2103,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2160
2103
  `, 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"] }]
2161
2104
  }], ctorParameters: () => [] });
2162
2105
 
2163
- /**
2164
- * Company-Action Selector Component
2165
- *
2166
- * Manages company action whitelisting with hierarchical tree view and intelligent dependency management.
2167
- *
2168
- * **Architecture:**
2169
- * - Presentation: TreeTable for hierarchical display
2170
- * - Business Logic: Flat action list for selection/validation
2171
- * - State: Signals with computed dependencies
2172
- *
2173
- * **Core Features:**
2174
- * - Company dropdown with async loading
2175
- * - Hierarchical TreeTable with expand/collapse
2176
- * - Action whitelist management (add/remove)
2177
- * - Real-time change tracking
2178
- *
2179
- * **Dependency Management:**
2180
- * - Auto-selection with AND/OR logic
2181
- * - Dependency detection on uncheck
2182
- * - Cascade remove for AND dependencies
2183
- * - Visual warnings for unmet prerequisites
2184
- * - Pre-save validation with auto-fix
2185
- *
2186
- * **Performance:**
2187
- * - Computed signals for reactive updates
2188
- * - Memoized prerequisite validation
2189
- * - AbortController for request cancellation
2190
- *
2191
- * @example
2192
- * ```html
2193
- * <flusys-company-action-selector />
2194
- * ```
2195
- */
2196
- class CompanyActionSelectorComponent {
2197
- // Permission constants for template
2106
+ class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
2198
2107
  COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
2199
- // Dependencies
2200
2108
  companyApiProvider = inject(COMPANY_API_PROVIDER);
2201
2109
  actionApi = inject(ActionApiService);
2202
2110
  permissionApi = inject(PermissionApiService);
2203
2111
  messageService = inject(MessageService);
2204
2112
  confirmationService = inject(ConfirmationService);
2205
- permissionLogic = inject(ActionPermissionLogicService);
2206
2113
  destroyRef = inject(DestroyRef);
2207
- translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
2208
- translate(key, vars) {
2209
- return this.translateAdapter?.translate(key, vars) ?? key;
2210
- }
2211
- // State - Company Selection
2212
2114
  selectedCompanyId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
2213
2115
  companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
2214
- // State - Loading/Saving
2215
- loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
2216
- saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
2217
- // State - Actions (Business Logic)
2218
- _actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
2219
- actions = this._actions.asReadonly();
2220
- // State - Actions (Display)
2221
- _actionsTree = signal([], ...(ngDevMode ? [{ debugName: "_actionsTree" }] : []));
2222
- actionsTree = this._actionsTree.asReadonly();
2223
- // State - Selection
2224
- _selectionMap = signal({}, ...(ngDevMode ? [{ debugName: "_selectionMap" }] : []));
2225
- selectionMap = this._selectionMap.asReadonly();
2226
- _initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
2227
- initialSelection = this._initialSelection.asReadonly();
2228
- // Computed - Tree Nodes (for PrimeNG TreeTable)
2229
- treeNodes = computed(() => convertActionToTreeNode(this.actionsTree()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
2230
- // Computed - Selection State
2231
- allSelected = computed(() => {
2232
- const selMap = this.selectionMap();
2233
- const total = this.actions().length;
2234
- if (total === 0)
2235
- return false;
2236
- const selected = Object.values(selMap).filter((v) => v).length;
2237
- return selected === total;
2238
- }, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
2239
- // Computed - Change Detection
2240
- hasChanges = computed(() => {
2241
- const current = this.selectionMap();
2242
- const initial = this.initialSelection();
2243
- const currentKeys = Object.keys(current);
2244
- const initialKeys = Object.keys(initial);
2245
- if (currentKeys.length !== initialKeys.length)
2246
- return true;
2247
- return currentKeys.some((key) => current[key] !== initial[key]);
2248
- }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
2249
- // Computed - Validation
2250
- actionsWithUnmetPrerequisites = computed(() => {
2251
- const selMap = this.selectionMap();
2252
- const allActions = this.actions();
2253
- const unmetSet = new Set();
2254
- for (const action of allActions) {
2255
- if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
2256
- unmetSet.add(action.id);
2257
- }
2258
- }
2259
- return unmetSet;
2260
- }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
2261
- invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
2262
- // Computed - Pending Changes
2263
- pendingAdd = computed(() => {
2264
- const current = this.selectionMap();
2265
- const initial = this.initialSelection();
2266
- const allActions = this.actions();
2267
- return allActions.filter((action) => current[action.id] && !initial[action.id]);
2268
- }, ...(ngDevMode ? [{ debugName: "pendingAdd" }] : []));
2269
- pendingRemove = computed(() => {
2270
- const current = this.selectionMap();
2271
- const initial = this.initialSelection();
2272
- const allActions = this.actions();
2273
- return allActions.filter((action) => !current[action.id] && initial[action.id]);
2274
- }, ...(ngDevMode ? [{ debugName: "pendingRemove" }] : []));
2275
- // Computed - Save Button State
2276
- canSave = computed(() => {
2277
- return this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0;
2278
- }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
2279
- // AbortController for data loading
2280
2116
  loadDataAbortController = null;
2281
2117
  constructor() {
2282
- // Cleanup on destroy
2118
+ super();
2283
2119
  this.destroyRef.onDestroy(() => {
2284
2120
  this.loadDataAbortController?.abort();
2285
2121
  });
2286
2122
  this.loadCompanies();
2287
- // Effect: Load data when company selection changes
2288
2123
  effect(() => {
2289
2124
  const companyId = this.selectedCompanyId();
2290
- // Abort previous request
2291
2125
  this.loadDataAbortController?.abort();
2292
2126
  if (companyId) {
2293
2127
  this.loadDataAbortController = new AbortController();
2294
2128
  this.loadData(this.loadDataAbortController.signal).catch((err) => {
2295
2129
  if (err.name !== 'AbortError') {
2296
- // Error toast handled by global interceptor
2297
2130
  this.loading.set(false);
2298
2131
  }
2299
2132
  });
@@ -2303,9 +2136,12 @@ class CompanyActionSelectorComponent {
2303
2136
  }
2304
2137
  });
2305
2138
  }
2306
- /**
2307
- * Load companies from API
2308
- */
2139
+ getDefaultTooltip(isSelected) {
2140
+ if (isSelected) {
2141
+ return `[${this.translate('iam.tooltip.selected')}] ${this.translate('iam.tooltip.selected')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove')}`;
2142
+ }
2143
+ return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.add')}`;
2144
+ }
2309
2145
  async loadCompanies() {
2310
2146
  try {
2311
2147
  const response = await firstValueFrom(this.companyApiProvider.getCompanies({
@@ -2315,29 +2151,21 @@ class CompanyActionSelectorComponent {
2315
2151
  this.companies.set(response?.data ?? []);
2316
2152
  }
2317
2153
  catch {
2318
- // Error toast handled by global interceptor
2319
2154
  }
2320
2155
  }
2321
- /**
2322
- * Load actions and assignments for selected company
2323
- */
2324
2156
  async loadData(signal) {
2325
2157
  const companyId = this.selectedCompanyId();
2326
2158
  if (!companyId)
2327
2159
  return;
2328
2160
  this.loading.set(true);
2329
2161
  try {
2330
- // Fetch hierarchical action tree
2331
2162
  const actionsResponse = await firstValueFrom(this.actionApi.getTree(undefined, undefined, false));
2332
2163
  if (signal.aborted)
2333
2164
  return;
2334
2165
  const actionsTree = actionsResponse?.data ?? [];
2335
- // Flatten tree for business logic
2336
2166
  const actionsList = flattenTree(actionsTree).filter((a) => !!a.id);
2337
- // Store both representations
2338
2167
  this._actionsTree.set(actionsTree);
2339
2168
  this._actions.set(actionsList);
2340
- // Load existing assignments
2341
2169
  const assignedResponse = await firstValueFrom(this.permissionApi.getCompanyActions(companyId));
2342
2170
  if (signal.aborted)
2343
2171
  return;
@@ -2352,67 +2180,10 @@ class CompanyActionSelectorComponent {
2352
2180
  }
2353
2181
  }
2354
2182
  }
2355
- /**
2356
- * Check if action has unmet prerequisites
2357
- */
2358
- hasUnmetPrerequisites(action) {
2359
- return action.id
2360
- ? this.actionsWithUnmetPrerequisites().has(action.id)
2361
- : false;
2362
- }
2363
- /**
2364
- * Get tooltip for action checkbox
2365
- */
2366
- getTooltip(action) {
2367
- const selMap = this.selectionMap();
2368
- const isCurrentlySelected = selMap[action.id];
2369
- if (action.permissionLogic) {
2370
- const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
2371
- if (prerequisiteInfo) {
2372
- const actionHint = isCurrentlySelected
2373
- ? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
2374
- : `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
2375
- return `${prerequisiteInfo}${actionHint}`;
2376
- }
2377
- }
2378
- if (isCurrentlySelected) {
2379
- return `[${this.translate('iam.tooltip.selected')}] ${this.translate('iam.tooltip.selected')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove')}`;
2380
- }
2381
- return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.add')}`;
2382
- }
2383
- /**
2384
- * Handle action checkbox toggle
2385
- */
2386
- onActionToggle(action, newValue) {
2387
- if (!newValue) {
2388
- // Unchecking - validate dependencies
2389
- this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
2390
- }
2391
- else {
2392
- // Checking - validate prerequisites
2393
- this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
2394
- }
2395
- }
2396
- toggleAll() {
2397
- this.setAllSelection(!this.allSelected());
2398
- }
2399
- selectAll() {
2400
- this.setAllSelection(true);
2401
- }
2402
- deselectAll() {
2403
- this.setAllSelection(false);
2404
- }
2405
- setAllSelection(value) {
2406
- this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
2407
- }
2408
- /**
2409
- * Save changes to backend
2410
- */
2411
2183
  async saveChanges() {
2412
2184
  const companyId = this.selectedCompanyId();
2413
2185
  if (!companyId)
2414
2186
  return;
2415
- // Pre-save validation
2416
2187
  const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
2417
2188
  if (invalidActions.length > 0) {
2418
2189
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
@@ -2423,11 +2194,7 @@ class CompanyActionSelectorComponent {
2423
2194
  return;
2424
2195
  this.saving.set(true);
2425
2196
  try {
2426
- const response = await firstValueFrom(this.permissionApi.assignCompanyActions({
2427
- companyId,
2428
- items,
2429
- }));
2430
- // Handle backend prerequisite errors
2197
+ const response = await firstValueFrom(this.permissionApi.assignCompanyActions({ companyId, items }));
2431
2198
  if (response?.data?.prerequisiteErrors &&
2432
2199
  response.data.prerequisiteErrors.length > 0) {
2433
2200
  this.handleBackendPrerequisiteErrors(response.data.prerequisiteErrors);
@@ -2436,22 +2203,18 @@ class CompanyActionSelectorComponent {
2436
2203
  this.messageService.add({
2437
2204
  severity: 'success',
2438
2205
  summary: this.translate('shared.success'),
2439
- detail: response?.data?.message || this.translate('iam.permission.company.actions.updated'),
2206
+ detail: response?.messageKey
2207
+ ? this.translate(response.messageKey, response.messageVariables)
2208
+ : (response?.message ?? ''),
2440
2209
  });
2441
- // Update baseline
2442
2210
  this._initialSelection.set({ ...this.selectionMap() });
2443
2211
  }
2444
2212
  catch {
2445
- // Error toast handled by global interceptor
2446
2213
  }
2447
2214
  finally {
2448
2215
  this.saving.set(false);
2449
2216
  }
2450
2217
  }
2451
- /**
2452
- * Handle backend prerequisite validation errors
2453
- * Shows confirmation dialog with auto-fix option
2454
- */
2455
2218
  handleBackendPrerequisiteErrors(prerequisiteErrors) {
2456
2219
  const errorList = prerequisiteErrors
2457
2220
  .map((error) => {
@@ -2470,7 +2233,6 @@ class CompanyActionSelectorComponent {
2470
2233
  acceptIcon: 'pi pi-check',
2471
2234
  rejectIcon: 'pi pi-times',
2472
2235
  accept: () => {
2473
- // Auto-select missing prerequisites
2474
2236
  const selMap = { ...this.selectionMap() };
2475
2237
  prerequisiteErrors.forEach((error) => {
2476
2238
  error.requiredActions.forEach((a) => {
@@ -2485,7 +2247,6 @@ class CompanyActionSelectorComponent {
2485
2247
  });
2486
2248
  },
2487
2249
  reject: () => {
2488
- // Revert to initial state
2489
2250
  this._selectionMap.set({ ...this.initialSelection() });
2490
2251
  this.messageService.add({
2491
2252
  severity: 'info',
@@ -2495,54 +2256,33 @@ class CompanyActionSelectorComponent {
2495
2256
  },
2496
2257
  });
2497
2258
  }
2498
- resetState() {
2499
- this._actionsTree.set([]);
2500
- this._actions.set([]);
2501
- this._selectionMap.set({});
2502
- this._initialSelection.set({});
2503
- }
2504
- buildSelectionMap(actions, predicate) {
2505
- const selMap = {};
2506
- for (const action of actions) {
2507
- selMap[action.id] = predicate(action);
2508
- }
2509
- return selMap;
2510
- }
2511
- buildPayloadItems() {
2512
- const items = [];
2513
- for (const action of this.pendingAdd()) {
2514
- items.push({ id: action.id, action: 'add' });
2515
- }
2516
- for (const action of this.pendingRemove()) {
2517
- items.push({ id: action.id, action: 'remove' });
2518
- }
2519
- return items;
2520
- }
2521
2259
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2522
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", ngImport: i0, template: `
2260
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", usesInheritance: true, ngImport: i0, template: `
2523
2261
  <div class="company-action-selector">
2524
- <!-- Company Selector -->
2525
2262
  <div class="mb-4">
2526
2263
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2527
2264
  <div>
2528
- <label class="block font-semibold mb-2">{{ 'iam.permission.select.company' | translate }}</label>
2265
+ <label class="block font-semibold mb-2">{{
2266
+ 'iam.permission.select.company' | translate
2267
+ }}</label>
2529
2268
  <p-select
2530
2269
  [ngModel]="selectedCompanyId()"
2531
2270
  (ngModelChange)="selectedCompanyId.set($event)"
2532
2271
  [options]="companies()"
2533
2272
  optionLabel="name"
2534
2273
  optionValue="id"
2535
- [placeholder]="'iam.permission.select.company.placeholder' | translate"
2274
+ [placeholder]="
2275
+ 'iam.permission.select.company.placeholder' | translate
2276
+ "
2536
2277
  [showClear]="true"
2537
2278
  [filter]="true"
2538
- styleClass="w-full"
2279
+ class="w-full"
2539
2280
  />
2540
2281
  </div>
2541
2282
  </div>
2542
2283
  </div>
2543
2284
 
2544
2285
  @if (selectedCompanyId()) {
2545
- <!-- Loading State -->
2546
2286
  @if (loading()) {
2547
2287
  <div
2548
2288
  class="surface-card p-5 rounded-border shadow-sm flex justify-center"
@@ -2551,16 +2291,20 @@ class CompanyActionSelectorComponent {
2551
2291
  </div>
2552
2292
  }
2553
2293
 
2554
- <!-- Action List -->
2555
2294
  @if (!loading() && actions().length > 0) {
2556
2295
  <div class="surface-card p-4 rounded-border shadow-sm">
2557
2296
  <div
2558
2297
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
2559
2298
  >
2560
2299
  <div>
2561
- <h5 class="m-0 mb-1">{{ 'iam.permission.action.whitelist' | translate }}</h5>
2300
+ <h5 class="m-0 mb-1">
2301
+ {{ 'iam.permission.action.whitelist' | translate }}
2302
+ </h5>
2562
2303
  <p class="text-sm text-muted-color m-0">
2563
- {{ 'iam.permission.actions.available' | translate: { count: actions().length } }}
2304
+ {{
2305
+ 'iam.permission.actions.available'
2306
+ | translate: { count: actions().length }
2307
+ }}
2564
2308
  </p>
2565
2309
  </div>
2566
2310
  <div class="flex flex-wrap gap-2">
@@ -2593,27 +2337,32 @@ class CompanyActionSelectorComponent {
2593
2337
  </div>
2594
2338
  </div>
2595
2339
 
2596
- <!-- Validation Warning -->
2597
2340
  @if (invalidActionsCount() > 0) {
2598
2341
  <div class="validation-warning rounded-border p-3 mb-4">
2599
2342
  <div class="flex items-start gap-2">
2600
2343
  <i class="pi pi-exclamation-triangle text-xl"></i>
2601
2344
  <div class="flex-1">
2602
- <span class="font-semibold">{{ 'iam.validation.warning.title' | translate }}:</span>
2345
+ <span class="font-semibold"
2346
+ >{{ 'iam.validation.warning.title' | translate }}:</span
2347
+ >
2603
2348
  <p class="text-sm mt-1 mb-0">
2604
- {{ (invalidActionsCount() > 1 ? 'iam.validation.unmet.prerequisites.plural' : 'iam.validation.unmet.prerequisites.singular') | translate: { count: invalidActionsCount() } }}
2349
+ {{
2350
+ (invalidActionsCount() > 1
2351
+ ? 'iam.validation.unmet.prerequisites.plural'
2352
+ : 'iam.validation.unmet.prerequisites.singular'
2353
+ ) | translate: { count: invalidActionsCount() }
2354
+ }}
2605
2355
  </p>
2606
2356
  </div>
2607
2357
  </div>
2608
2358
  </div>
2609
2359
  }
2610
2360
 
2611
- <!-- Action Tree Table -->
2612
2361
  <div class="overflow-x-auto -mx-4 sm:mx-0">
2613
2362
  <p-treeTable
2614
2363
  [value]="treeNodes()"
2615
2364
  dataKey="id"
2616
- styleClass="p-treetable-sm"
2365
+ class="p-treetable-sm"
2617
2366
  [tableStyle]="{ 'min-width': '50rem' }"
2618
2367
  >
2619
2368
  <ng-template #header>
@@ -2628,73 +2377,93 @@ class CompanyActionSelectorComponent {
2628
2377
  />
2629
2378
  </th>
2630
2379
  <th>{{ 'shared.name' | translate }}</th>
2631
- <th class="hidden md:table-cell">{{ 'shared.code' | translate }}</th>
2380
+ <th class="hidden md:table-cell">
2381
+ {{ 'shared.code' | translate }}
2382
+ </th>
2632
2383
  <th>{{ 'iam.action.type' | translate }}</th>
2633
- <th class="hidden lg:table-cell">{{ 'shared.description' | translate }}</th>
2384
+ <th class="hidden lg:table-cell">
2385
+ {{ 'shared.description' | translate }}
2386
+ </th>
2634
2387
  </tr>
2635
2388
  </ng-template>
2636
- <ng-template #body let-rowNode let-rowData="rowData">
2637
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
2638
- <td class="w-12">
2639
- <p-checkbox
2640
- [ngModel]="selectionMap()[rowData.id]"
2641
- [binary]="true"
2642
- (ngModelChange)="onActionToggle(rowData, $event)"
2643
- [pTooltip]="getTooltip(rowData)"
2644
- tooltipPosition="top"
2645
- />
2646
- </td>
2647
- <td>
2648
- <p-treeTableToggler [rowNode]="rowNode" />
2649
- <span class="inline-flex items-center gap-2">
2650
- {{ rowData.name }}
2651
- @if (hasUnmetPrerequisites(rowData)) {
2652
- <i
2653
- class="pi pi-exclamation-triangle text-orange-500"
2654
- [pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
2655
- tooltipPosition="top"
2656
- ></i>
2389
+ <ng-template #body let-rowNode let-rowData="rowData">
2390
+ @let hasUnmet = hasUnmetPrerequisites(rowData);
2391
+ <tr [class.highlight-warning]="hasUnmet">
2392
+ <td class="w-12">
2393
+ <p-checkbox
2394
+ [ngModel]="selectionMap()[rowData.id]"
2395
+ [binary]="true"
2396
+ (ngModelChange)="onActionToggle(rowData, $event)"
2397
+ [pTooltip]="getTooltip(rowData)"
2398
+ tooltipPosition="top"
2399
+ />
2400
+ </td>
2401
+ <td>
2402
+ <p-treeTableToggler [rowNode]="rowNode" />
2403
+ <span class="inline-flex items-center gap-2">
2404
+ {{ rowData.name }}
2405
+ @if (hasUnmet) {
2406
+ <i
2407
+ class="pi pi-exclamation-triangle text-orange-500"
2408
+ [pTooltip]="
2409
+ 'iam.validation.unmet.prerequisites.tooltip'
2410
+ | translate
2411
+ "
2412
+ tooltipPosition="top"
2413
+ ></i>
2414
+ }
2415
+ </span>
2416
+ </td>
2417
+ <td class="hidden md:table-cell">
2418
+ {{ rowData.code || '-' }}
2419
+ </td>
2420
+ <td>
2421
+ <p-tag
2422
+ [value]="rowData.actionType"
2423
+ [severity]="
2424
+ rowData.actionType === 'backend' ? 'info' : 'success'
2425
+ "
2426
+ />
2427
+ </td>
2428
+ <td class="hidden lg:table-cell">
2429
+ {{ rowData.description || '-' }}
2430
+ </td>
2431
+ </tr>
2432
+ </ng-template>
2433
+ <ng-template #emptymessage>
2434
+ <tr>
2435
+ <td colspan="5" class="text-center p-4 text-muted-color">
2436
+ @if (loading()) {
2437
+ <i class="pi pi-spin pi-spinner"></i>
2438
+ {{ 'shared.loading.actions' | translate }}
2439
+ } @else {
2440
+ {{ 'iam.permission.no.actions.available' | translate }}
2657
2441
  }
2658
- </span>
2659
- </td>
2660
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
2661
- <td>
2662
- <p-tag
2663
- [value]="rowData.actionType"
2664
- [severity]="
2665
- rowData.actionType === 'backend' ? 'info' : 'success'
2666
- "
2667
- />
2668
- </td>
2669
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
2670
- </tr>
2671
- </ng-template>
2672
- <ng-template #emptymessage>
2673
- <tr>
2674
- <td colspan="5" class="text-center p-4 text-muted-color">
2675
- @if (loading()) {
2676
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
2677
- } @else { {{ 'iam.permission.no.actions.available' | translate }} }
2678
- </td>
2679
- </tr>
2680
- </ng-template>
2442
+ </td>
2443
+ </tr>
2444
+ </ng-template>
2681
2445
  </p-treeTable>
2682
2446
  </div>
2683
2447
  </div>
2684
2448
 
2685
- <!-- Change Summary -->
2686
2449
  @if (hasChanges()) {
2687
2450
  <div class="border border-surface rounded-border p-3 mt-4">
2688
2451
  <div class="flex items-center gap-2 mb-3">
2689
2452
  <i class="pi pi-info-circle text-primary"></i>
2690
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
2453
+ <span class="font-bold">{{
2454
+ 'shared.pending.changes' | translate
2455
+ }}</span>
2691
2456
  </div>
2692
2457
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2693
2458
  @if (pendingAdd().length > 0) {
2694
2459
  <div>
2695
2460
  <div class="flex items-center gap-2 mb-2">
2696
2461
  <i class="pi pi-plus-circle text-green-500"></i>
2697
- <strong class="text-sm">{{ 'shared.to.whitelist' | translate }} ({{ pendingAdd().length }})</strong>
2462
+ <strong class="text-sm"
2463
+ >{{ 'shared.to.whitelist' | translate }} ({{
2464
+ pendingAdd().length
2465
+ }})</strong
2466
+ >
2698
2467
  </div>
2699
2468
  <ul class="list-none p-0 m-0 pl-4">
2700
2469
  @for (action of pendingAdd(); track action.id) {
@@ -2707,7 +2476,11 @@ class CompanyActionSelectorComponent {
2707
2476
  <div>
2708
2477
  <div class="flex items-center gap-2 mb-2">
2709
2478
  <i class="pi pi-minus-circle text-red-500"></i>
2710
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
2479
+ <strong class="text-sm"
2480
+ >{{ 'shared.to.remove' | translate }} ({{
2481
+ pendingRemove().length
2482
+ }})</strong
2483
+ >
2711
2484
  </div>
2712
2485
  <ul class="list-none p-0 m-0 pl-4">
2713
2486
  @for (action of pendingRemove(); track action.id) {
@@ -2723,7 +2496,9 @@ class CompanyActionSelectorComponent {
2723
2496
 
2724
2497
  @if (!loading() && actions().length === 0) {
2725
2498
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
2726
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
2499
+ <i
2500
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
2501
+ ></i>
2727
2502
  <p class="text-muted-color m-0">
2728
2503
  {{ 'iam.permission.no.actions.for.company' | translate }}
2729
2504
  </p>
@@ -2731,34 +2506,41 @@ class CompanyActionSelectorComponent {
2731
2506
  }
2732
2507
  }
2733
2508
  </div>
2734
- `, 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: "pipe", type: TranslatePipe, name: "translate" }] });
2509
+ `, 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: 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: "pipe", type: TranslatePipe, name: "translate" }] });
2735
2510
  }
2736
2511
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
2737
2512
  type: Component,
2738
- args: [{ selector: 'flusys-company-action-selector', imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, TranslatePipe], template: `
2513
+ args: [{ selector: 'flusys-company-action-selector', imports: [
2514
+ FormsModule,
2515
+ PrimeModule,
2516
+ HasPermissionDirective,
2517
+ TranslatePipe,
2518
+ ], template: `
2739
2519
  <div class="company-action-selector">
2740
- <!-- Company Selector -->
2741
2520
  <div class="mb-4">
2742
2521
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
2743
2522
  <div>
2744
- <label class="block font-semibold mb-2">{{ 'iam.permission.select.company' | translate }}</label>
2523
+ <label class="block font-semibold mb-2">{{
2524
+ 'iam.permission.select.company' | translate
2525
+ }}</label>
2745
2526
  <p-select
2746
2527
  [ngModel]="selectedCompanyId()"
2747
2528
  (ngModelChange)="selectedCompanyId.set($event)"
2748
2529
  [options]="companies()"
2749
2530
  optionLabel="name"
2750
2531
  optionValue="id"
2751
- [placeholder]="'iam.permission.select.company.placeholder' | translate"
2532
+ [placeholder]="
2533
+ 'iam.permission.select.company.placeholder' | translate
2534
+ "
2752
2535
  [showClear]="true"
2753
2536
  [filter]="true"
2754
- styleClass="w-full"
2537
+ class="w-full"
2755
2538
  />
2756
2539
  </div>
2757
2540
  </div>
2758
2541
  </div>
2759
2542
 
2760
2543
  @if (selectedCompanyId()) {
2761
- <!-- Loading State -->
2762
2544
  @if (loading()) {
2763
2545
  <div
2764
2546
  class="surface-card p-5 rounded-border shadow-sm flex justify-center"
@@ -2767,16 +2549,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2767
2549
  </div>
2768
2550
  }
2769
2551
 
2770
- <!-- Action List -->
2771
2552
  @if (!loading() && actions().length > 0) {
2772
2553
  <div class="surface-card p-4 rounded-border shadow-sm">
2773
2554
  <div
2774
2555
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
2775
2556
  >
2776
2557
  <div>
2777
- <h5 class="m-0 mb-1">{{ 'iam.permission.action.whitelist' | translate }}</h5>
2558
+ <h5 class="m-0 mb-1">
2559
+ {{ 'iam.permission.action.whitelist' | translate }}
2560
+ </h5>
2778
2561
  <p class="text-sm text-muted-color m-0">
2779
- {{ 'iam.permission.actions.available' | translate: { count: actions().length } }}
2562
+ {{
2563
+ 'iam.permission.actions.available'
2564
+ | translate: { count: actions().length }
2565
+ }}
2780
2566
  </p>
2781
2567
  </div>
2782
2568
  <div class="flex flex-wrap gap-2">
@@ -2809,27 +2595,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2809
2595
  </div>
2810
2596
  </div>
2811
2597
 
2812
- <!-- Validation Warning -->
2813
2598
  @if (invalidActionsCount() > 0) {
2814
2599
  <div class="validation-warning rounded-border p-3 mb-4">
2815
2600
  <div class="flex items-start gap-2">
2816
2601
  <i class="pi pi-exclamation-triangle text-xl"></i>
2817
2602
  <div class="flex-1">
2818
- <span class="font-semibold">{{ 'iam.validation.warning.title' | translate }}:</span>
2603
+ <span class="font-semibold"
2604
+ >{{ 'iam.validation.warning.title' | translate }}:</span
2605
+ >
2819
2606
  <p class="text-sm mt-1 mb-0">
2820
- {{ (invalidActionsCount() > 1 ? 'iam.validation.unmet.prerequisites.plural' : 'iam.validation.unmet.prerequisites.singular') | translate: { count: invalidActionsCount() } }}
2607
+ {{
2608
+ (invalidActionsCount() > 1
2609
+ ? 'iam.validation.unmet.prerequisites.plural'
2610
+ : 'iam.validation.unmet.prerequisites.singular'
2611
+ ) | translate: { count: invalidActionsCount() }
2612
+ }}
2821
2613
  </p>
2822
2614
  </div>
2823
2615
  </div>
2824
2616
  </div>
2825
2617
  }
2826
2618
 
2827
- <!-- Action Tree Table -->
2828
2619
  <div class="overflow-x-auto -mx-4 sm:mx-0">
2829
2620
  <p-treeTable
2830
2621
  [value]="treeNodes()"
2831
2622
  dataKey="id"
2832
- styleClass="p-treetable-sm"
2623
+ class="p-treetable-sm"
2833
2624
  [tableStyle]="{ 'min-width': '50rem' }"
2834
2625
  >
2835
2626
  <ng-template #header>
@@ -2844,73 +2635,93 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2844
2635
  />
2845
2636
  </th>
2846
2637
  <th>{{ 'shared.name' | translate }}</th>
2847
- <th class="hidden md:table-cell">{{ 'shared.code' | translate }}</th>
2638
+ <th class="hidden md:table-cell">
2639
+ {{ 'shared.code' | translate }}
2640
+ </th>
2848
2641
  <th>{{ 'iam.action.type' | translate }}</th>
2849
- <th class="hidden lg:table-cell">{{ 'shared.description' | translate }}</th>
2642
+ <th class="hidden lg:table-cell">
2643
+ {{ 'shared.description' | translate }}
2644
+ </th>
2645
+ </tr>
2646
+ </ng-template>
2647
+ <ng-template #body let-rowNode let-rowData="rowData">
2648
+ @let hasUnmet = hasUnmetPrerequisites(rowData);
2649
+ <tr [class.highlight-warning]="hasUnmet">
2650
+ <td class="w-12">
2651
+ <p-checkbox
2652
+ [ngModel]="selectionMap()[rowData.id]"
2653
+ [binary]="true"
2654
+ (ngModelChange)="onActionToggle(rowData, $event)"
2655
+ [pTooltip]="getTooltip(rowData)"
2656
+ tooltipPosition="top"
2657
+ />
2658
+ </td>
2659
+ <td>
2660
+ <p-treeTableToggler [rowNode]="rowNode" />
2661
+ <span class="inline-flex items-center gap-2">
2662
+ {{ rowData.name }}
2663
+ @if (hasUnmet) {
2664
+ <i
2665
+ class="pi pi-exclamation-triangle text-orange-500"
2666
+ [pTooltip]="
2667
+ 'iam.validation.unmet.prerequisites.tooltip'
2668
+ | translate
2669
+ "
2670
+ tooltipPosition="top"
2671
+ ></i>
2672
+ }
2673
+ </span>
2674
+ </td>
2675
+ <td class="hidden md:table-cell">
2676
+ {{ rowData.code || '-' }}
2677
+ </td>
2678
+ <td>
2679
+ <p-tag
2680
+ [value]="rowData.actionType"
2681
+ [severity]="
2682
+ rowData.actionType === 'backend' ? 'info' : 'success'
2683
+ "
2684
+ />
2685
+ </td>
2686
+ <td class="hidden lg:table-cell">
2687
+ {{ rowData.description || '-' }}
2688
+ </td>
2850
2689
  </tr>
2851
2690
  </ng-template>
2852
- <ng-template #body let-rowNode let-rowData="rowData">
2853
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
2854
- <td class="w-12">
2855
- <p-checkbox
2856
- [ngModel]="selectionMap()[rowData.id]"
2857
- [binary]="true"
2858
- (ngModelChange)="onActionToggle(rowData, $event)"
2859
- [pTooltip]="getTooltip(rowData)"
2860
- tooltipPosition="top"
2861
- />
2862
- </td>
2863
- <td>
2864
- <p-treeTableToggler [rowNode]="rowNode" />
2865
- <span class="inline-flex items-center gap-2">
2866
- {{ rowData.name }}
2867
- @if (hasUnmetPrerequisites(rowData)) {
2868
- <i
2869
- class="pi pi-exclamation-triangle text-orange-500"
2870
- [pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
2871
- tooltipPosition="top"
2872
- ></i>
2691
+ <ng-template #emptymessage>
2692
+ <tr>
2693
+ <td colspan="5" class="text-center p-4 text-muted-color">
2694
+ @if (loading()) {
2695
+ <i class="pi pi-spin pi-spinner"></i>
2696
+ {{ 'shared.loading.actions' | translate }}
2697
+ } @else {
2698
+ {{ 'iam.permission.no.actions.available' | translate }}
2873
2699
  }
2874
- </span>
2875
- </td>
2876
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
2877
- <td>
2878
- <p-tag
2879
- [value]="rowData.actionType"
2880
- [severity]="
2881
- rowData.actionType === 'backend' ? 'info' : 'success'
2882
- "
2883
- />
2884
- </td>
2885
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
2886
- </tr>
2887
- </ng-template>
2888
- <ng-template #emptymessage>
2889
- <tr>
2890
- <td colspan="5" class="text-center p-4 text-muted-color">
2891
- @if (loading()) {
2892
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
2893
- } @else { {{ 'iam.permission.no.actions.available' | translate }} }
2894
- </td>
2895
- </tr>
2896
- </ng-template>
2700
+ </td>
2701
+ </tr>
2702
+ </ng-template>
2897
2703
  </p-treeTable>
2898
2704
  </div>
2899
2705
  </div>
2900
2706
 
2901
- <!-- Change Summary -->
2902
2707
  @if (hasChanges()) {
2903
2708
  <div class="border border-surface rounded-border p-3 mt-4">
2904
2709
  <div class="flex items-center gap-2 mb-3">
2905
2710
  <i class="pi pi-info-circle text-primary"></i>
2906
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
2711
+ <span class="font-bold">{{
2712
+ 'shared.pending.changes' | translate
2713
+ }}</span>
2907
2714
  </div>
2908
2715
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2909
2716
  @if (pendingAdd().length > 0) {
2910
2717
  <div>
2911
2718
  <div class="flex items-center gap-2 mb-2">
2912
2719
  <i class="pi pi-plus-circle text-green-500"></i>
2913
- <strong class="text-sm">{{ 'shared.to.whitelist' | translate }} ({{ pendingAdd().length }})</strong>
2720
+ <strong class="text-sm"
2721
+ >{{ 'shared.to.whitelist' | translate }} ({{
2722
+ pendingAdd().length
2723
+ }})</strong
2724
+ >
2914
2725
  </div>
2915
2726
  <ul class="list-none p-0 m-0 pl-4">
2916
2727
  @for (action of pendingAdd(); track action.id) {
@@ -2923,7 +2734,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2923
2734
  <div>
2924
2735
  <div class="flex items-center gap-2 mb-2">
2925
2736
  <i class="pi pi-minus-circle text-red-500"></i>
2926
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
2737
+ <strong class="text-sm"
2738
+ >{{ 'shared.to.remove' | translate }} ({{
2739
+ pendingRemove().length
2740
+ }})</strong
2741
+ >
2927
2742
  </div>
2928
2743
  <ul class="list-none p-0 m-0 pl-4">
2929
2744
  @for (action of pendingRemove(); track action.id) {
@@ -2939,7 +2754,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
2939
2754
 
2940
2755
  @if (!loading() && actions().length === 0) {
2941
2756
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
2942
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
2757
+ <i
2758
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
2759
+ ></i>
2943
2760
  <p class="text-muted-color m-0">
2944
2761
  {{ 'iam.permission.no.actions.for.company' | translate }}
2945
2762
  </p>
@@ -2989,7 +2806,9 @@ class UserRoleSelectorComponent {
2989
2806
  roleApi = inject(RoleApiService);
2990
2807
  permissionApi = inject(PermissionApiService);
2991
2808
  messageService = inject(MessageService);
2992
- translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
2809
+ translateAdapter = inject(TRANSLATE_ADAPTER, {
2810
+ optional: true,
2811
+ });
2993
2812
  translate(key, vars) {
2994
2813
  return this.translateAdapter?.translate(key, vars) ?? key;
2995
2814
  }
@@ -3112,9 +2931,9 @@ class UserRoleSelectorComponent {
3112
2931
  this.loading.set(true);
3113
2932
  try {
3114
2933
  // Load all roles
3115
- const rolesResponse = await firstValueFrom(this.roleApi.getAll('', {
2934
+ const rolesResponse = await this.roleApi.getAll({
3116
2935
  pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
3117
- }));
2936
+ });
3118
2937
  const rolesList = rolesResponse?.data ?? [];
3119
2938
  this._roles.set(rolesList);
3120
2939
  // Load existing user-role assignments
@@ -3228,7 +3047,9 @@ class UserRoleSelectorComponent {
3228
3047
  this.messageService.add({
3229
3048
  severity: 'success',
3230
3049
  summary: this.translate('shared.success'),
3231
- detail: response?.data?.message || this.translate('iam.permission.user.roles.updated'),
3050
+ detail: response?.messageKey
3051
+ ? this.translate(response.messageKey, response.messageVariables)
3052
+ : response?.message,
3232
3053
  });
3233
3054
  // Update baseline
3234
3055
  this._initialSelection.set({ ...this.selectionMap() });
@@ -3263,7 +3084,9 @@ class UserRoleSelectorComponent {
3263
3084
  [value]="selectedUserId()"
3264
3085
  (valueChange)="selectedUserId.set($event)"
3265
3086
  [isEditMode]="true"
3266
- [placeHolder]="'iam.permission.select.user.placeholder' | translate"
3087
+ [placeHolder]="
3088
+ 'iam.permission.select.user.placeholder' | translate
3089
+ "
3267
3090
  />
3268
3091
  </div>
3269
3092
 
@@ -3279,7 +3102,9 @@ class UserRoleSelectorComponent {
3279
3102
  [options]="filteredBranches()"
3280
3103
  optionLabel="name"
3281
3104
  optionValue="id"
3282
- [placeholder]="'iam.permission.select.branch.placeholder' | translate"
3105
+ [placeholder]="
3106
+ 'iam.permission.select.branch.placeholder' | translate
3107
+ "
3283
3108
  [showClear]="true"
3284
3109
  [filter]="true"
3285
3110
  styleClass="w-full"
@@ -3300,7 +3125,12 @@ class UserRoleSelectorComponent {
3300
3125
  </ng-template>
3301
3126
  </p-select>
3302
3127
  <small class="text-muted-color block mt-1">
3303
- {{ (filteredBranches().length !== 1 ? 'iam.branch.permitted.count.plural' : 'iam.branch.permitted.count') | translate: { count: filteredBranches().length } }}
3128
+ {{
3129
+ (filteredBranches().length !== 1
3130
+ ? 'iam.branch.permitted.count.plural'
3131
+ : 'iam.branch.permitted.count'
3132
+ ) | translate: { count: filteredBranches().length }
3133
+ }}
3304
3134
  </small>
3305
3135
  </div>
3306
3136
  }
@@ -3324,9 +3154,14 @@ class UserRoleSelectorComponent {
3324
3154
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
3325
3155
  >
3326
3156
  <div>
3327
- <h5 class="m-0 mb-1">{{ 'iam.permission.role.assignments' | translate }}</h5>
3157
+ <h5 class="m-0 mb-1">
3158
+ {{ 'iam.permission.role.assignments' | translate }}
3159
+ </h5>
3328
3160
  <p class="text-sm text-muted-color m-0">
3329
- {{ 'iam.permission.roles.available' | translate: { count: roles().length } }}
3161
+ {{
3162
+ 'iam.permission.roles.available'
3163
+ | translate: { count: roles().length }
3164
+ }}
3330
3165
  </p>
3331
3166
  </div>
3332
3167
  <div class="flex flex-wrap gap-2">
@@ -3368,7 +3203,9 @@ class UserRoleSelectorComponent {
3368
3203
  [rowsPerPageOptions]="[10, 20, 50]"
3369
3204
  [globalFilterFields]="['name', 'code', 'description']"
3370
3205
  [showCurrentPageReport]="true"
3371
- [currentPageReportTemplate]="'iam.pagination.roles.template' | translate"
3206
+ [currentPageReportTemplate]="
3207
+ 'iam.pagination.roles.template' | translate
3208
+ "
3372
3209
  styleClass="p-datatable-sm"
3373
3210
  [tableStyle]="{ 'min-width': '35rem' }"
3374
3211
  >
@@ -3384,8 +3221,12 @@ class UserRoleSelectorComponent {
3384
3221
  />
3385
3222
  </th>
3386
3223
  <th>{{ 'shared.name' | translate }}</th>
3387
- <th class="hidden sm:table-cell">{{ 'shared.code' | translate }}</th>
3388
- <th class="hidden md:table-cell">{{ 'shared.description' | translate }}</th>
3224
+ <th class="hidden sm:table-cell">
3225
+ {{ 'shared.code' | translate }}
3226
+ </th>
3227
+ <th class="hidden md:table-cell">
3228
+ {{ 'shared.description' | translate }}
3229
+ </th>
3389
3230
  </tr>
3390
3231
  </ng-template>
3391
3232
  <ng-template #body let-role>
@@ -3401,14 +3242,17 @@ class UserRoleSelectorComponent {
3401
3242
  </td>
3402
3243
  <td>{{ role.name }}</td>
3403
3244
  <td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
3404
- <td class="hidden md:table-cell">{{ role.description || '-' }}</td>
3245
+ <td class="hidden md:table-cell">
3246
+ {{ role.description || '-' }}
3247
+ </td>
3405
3248
  </tr>
3406
3249
  </ng-template>
3407
3250
  <ng-template #emptymessage>
3408
3251
  <tr>
3409
3252
  <td colspan="4" class="text-center p-4 text-muted-color">
3410
3253
  @if (loading()) {
3411
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.roles' | translate }}
3254
+ <i class="pi pi-spin pi-spinner"></i>
3255
+ {{ 'shared.loading.roles' | translate }}
3412
3256
  } @else {
3413
3257
  {{ 'iam.permission.no.roles.available' | translate }}
3414
3258
  }
@@ -3424,14 +3268,20 @@ class UserRoleSelectorComponent {
3424
3268
  <div class="border border-surface rounded-border p-3 mt-4">
3425
3269
  <div class="flex items-center gap-2 mb-3">
3426
3270
  <i class="pi pi-info-circle text-primary"></i>
3427
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
3271
+ <span class="font-bold">{{
3272
+ 'shared.pending.changes' | translate
3273
+ }}</span>
3428
3274
  </div>
3429
3275
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
3430
3276
  @if (pendingAdd().length > 0) {
3431
3277
  <div>
3432
3278
  <div class="flex items-center gap-2 mb-2">
3433
3279
  <i class="pi pi-plus-circle text-green-500"></i>
3434
- <strong class="text-sm">{{ 'shared.to.assign' | translate }} ({{ pendingAdd().length }})</strong>
3280
+ <strong class="text-sm"
3281
+ >{{ 'shared.to.assign' | translate }} ({{
3282
+ pendingAdd().length
3283
+ }})</strong
3284
+ >
3435
3285
  </div>
3436
3286
  <ul class="list-none p-0 m-0 pl-4">
3437
3287
  @for (role of pendingAdd(); track role.id) {
@@ -3444,7 +3294,11 @@ class UserRoleSelectorComponent {
3444
3294
  <div>
3445
3295
  <div class="flex items-center gap-2 mb-2">
3446
3296
  <i class="pi pi-minus-circle text-red-500"></i>
3447
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
3297
+ <strong class="text-sm"
3298
+ >{{ 'shared.to.remove' | translate }} ({{
3299
+ pendingRemove().length
3300
+ }})</strong
3301
+ >
3448
3302
  </div>
3449
3303
  <ul class="list-none p-0 m-0 pl-4">
3450
3304
  @for (role of pendingRemove(); track role.id) {
@@ -3460,7 +3314,9 @@ class UserRoleSelectorComponent {
3460
3314
 
3461
3315
  @if (!loading() && roles().length === 0) {
3462
3316
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
3463
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
3317
+ <i
3318
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
3319
+ ></i>
3464
3320
  <p class="text-muted-color m-0">
3465
3321
  {{ 'iam.permission.no.roles.for.user' | translate }}
3466
3322
  </p>
@@ -3472,7 +3328,14 @@ class UserRoleSelectorComponent {
3472
3328
  }
3473
3329
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
3474
3330
  type: Component,
3475
- args: [{ selector: 'flusys-user-role-selector', imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent, TranslatePipe], template: `
3331
+ args: [{ selector: 'flusys-user-role-selector', imports: [
3332
+ CommonModule,
3333
+ FormsModule,
3334
+ PrimeModule,
3335
+ HasPermissionDirective,
3336
+ UserSelectComponent,
3337
+ TranslatePipe,
3338
+ ], template: `
3476
3339
  <div class="user-role-selector">
3477
3340
  <!-- User and Branch Selectors -->
3478
3341
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
@@ -3486,7 +3349,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3486
3349
  [value]="selectedUserId()"
3487
3350
  (valueChange)="selectedUserId.set($event)"
3488
3351
  [isEditMode]="true"
3489
- [placeHolder]="'iam.permission.select.user.placeholder' | translate"
3352
+ [placeHolder]="
3353
+ 'iam.permission.select.user.placeholder' | translate
3354
+ "
3490
3355
  />
3491
3356
  </div>
3492
3357
 
@@ -3502,7 +3367,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3502
3367
  [options]="filteredBranches()"
3503
3368
  optionLabel="name"
3504
3369
  optionValue="id"
3505
- [placeholder]="'iam.permission.select.branch.placeholder' | translate"
3370
+ [placeholder]="
3371
+ 'iam.permission.select.branch.placeholder' | translate
3372
+ "
3506
3373
  [showClear]="true"
3507
3374
  [filter]="true"
3508
3375
  styleClass="w-full"
@@ -3523,7 +3390,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3523
3390
  </ng-template>
3524
3391
  </p-select>
3525
3392
  <small class="text-muted-color block mt-1">
3526
- {{ (filteredBranches().length !== 1 ? 'iam.branch.permitted.count.plural' : 'iam.branch.permitted.count') | translate: { count: filteredBranches().length } }}
3393
+ {{
3394
+ (filteredBranches().length !== 1
3395
+ ? 'iam.branch.permitted.count.plural'
3396
+ : 'iam.branch.permitted.count'
3397
+ ) | translate: { count: filteredBranches().length }
3398
+ }}
3527
3399
  </small>
3528
3400
  </div>
3529
3401
  }
@@ -3547,9 +3419,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3547
3419
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
3548
3420
  >
3549
3421
  <div>
3550
- <h5 class="m-0 mb-1">{{ 'iam.permission.role.assignments' | translate }}</h5>
3422
+ <h5 class="m-0 mb-1">
3423
+ {{ 'iam.permission.role.assignments' | translate }}
3424
+ </h5>
3551
3425
  <p class="text-sm text-muted-color m-0">
3552
- {{ 'iam.permission.roles.available' | translate: { count: roles().length } }}
3426
+ {{
3427
+ 'iam.permission.roles.available'
3428
+ | translate: { count: roles().length }
3429
+ }}
3553
3430
  </p>
3554
3431
  </div>
3555
3432
  <div class="flex flex-wrap gap-2">
@@ -3591,7 +3468,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3591
3468
  [rowsPerPageOptions]="[10, 20, 50]"
3592
3469
  [globalFilterFields]="['name', 'code', 'description']"
3593
3470
  [showCurrentPageReport]="true"
3594
- [currentPageReportTemplate]="'iam.pagination.roles.template' | translate"
3471
+ [currentPageReportTemplate]="
3472
+ 'iam.pagination.roles.template' | translate
3473
+ "
3595
3474
  styleClass="p-datatable-sm"
3596
3475
  [tableStyle]="{ 'min-width': '35rem' }"
3597
3476
  >
@@ -3607,8 +3486,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3607
3486
  />
3608
3487
  </th>
3609
3488
  <th>{{ 'shared.name' | translate }}</th>
3610
- <th class="hidden sm:table-cell">{{ 'shared.code' | translate }}</th>
3611
- <th class="hidden md:table-cell">{{ 'shared.description' | translate }}</th>
3489
+ <th class="hidden sm:table-cell">
3490
+ {{ 'shared.code' | translate }}
3491
+ </th>
3492
+ <th class="hidden md:table-cell">
3493
+ {{ 'shared.description' | translate }}
3494
+ </th>
3612
3495
  </tr>
3613
3496
  </ng-template>
3614
3497
  <ng-template #body let-role>
@@ -3624,14 +3507,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3624
3507
  </td>
3625
3508
  <td>{{ role.name }}</td>
3626
3509
  <td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
3627
- <td class="hidden md:table-cell">{{ role.description || '-' }}</td>
3510
+ <td class="hidden md:table-cell">
3511
+ {{ role.description || '-' }}
3512
+ </td>
3628
3513
  </tr>
3629
3514
  </ng-template>
3630
3515
  <ng-template #emptymessage>
3631
3516
  <tr>
3632
3517
  <td colspan="4" class="text-center p-4 text-muted-color">
3633
3518
  @if (loading()) {
3634
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.roles' | translate }}
3519
+ <i class="pi pi-spin pi-spinner"></i>
3520
+ {{ 'shared.loading.roles' | translate }}
3635
3521
  } @else {
3636
3522
  {{ 'iam.permission.no.roles.available' | translate }}
3637
3523
  }
@@ -3647,14 +3533,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3647
3533
  <div class="border border-surface rounded-border p-3 mt-4">
3648
3534
  <div class="flex items-center gap-2 mb-3">
3649
3535
  <i class="pi pi-info-circle text-primary"></i>
3650
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
3536
+ <span class="font-bold">{{
3537
+ 'shared.pending.changes' | translate
3538
+ }}</span>
3651
3539
  </div>
3652
3540
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
3653
3541
  @if (pendingAdd().length > 0) {
3654
3542
  <div>
3655
3543
  <div class="flex items-center gap-2 mb-2">
3656
3544
  <i class="pi pi-plus-circle text-green-500"></i>
3657
- <strong class="text-sm">{{ 'shared.to.assign' | translate }} ({{ pendingAdd().length }})</strong>
3545
+ <strong class="text-sm"
3546
+ >{{ 'shared.to.assign' | translate }} ({{
3547
+ pendingAdd().length
3548
+ }})</strong
3549
+ >
3658
3550
  </div>
3659
3551
  <ul class="list-none p-0 m-0 pl-4">
3660
3552
  @for (role of pendingAdd(); track role.id) {
@@ -3667,7 +3559,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3667
3559
  <div>
3668
3560
  <div class="flex items-center gap-2 mb-2">
3669
3561
  <i class="pi pi-minus-circle text-red-500"></i>
3670
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
3562
+ <strong class="text-sm"
3563
+ >{{ 'shared.to.remove' | translate }} ({{
3564
+ pendingRemove().length
3565
+ }})</strong
3566
+ >
3671
3567
  </div>
3672
3568
  <ul class="list-none p-0 m-0 pl-4">
3673
3569
  @for (role of pendingRemove(); track role.id) {
@@ -3683,7 +3579,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3683
3579
 
3684
3580
  @if (!loading() && roles().length === 0) {
3685
3581
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
3686
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
3582
+ <i
3583
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
3584
+ ></i>
3687
3585
  <p class="text-muted-color m-0">
3688
3586
  {{ 'iam.permission.no.roles.for.user' | translate }}
3689
3587
  </p>
@@ -3694,72 +3592,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
3694
3592
  `, styles: [":host{display:block}\n"] }]
3695
3593
  }], ctorParameters: () => [] });
3696
3594
 
3697
- /**
3698
- * User-Action Selector Component
3699
- *
3700
- * Manages direct user action permissions with hierarchical tree view.
3701
- *
3702
- * **Architecture:**
3703
- * - Presentation: TreeTable for hierarchical display
3704
- * - Business Logic: Flat action list for selection/validation
3705
- * - State: Signals with computed dependencies
3706
- *
3707
- * **Core Features:**
3708
- * - User dropdown with async loading
3709
- * - Branch selector (if company feature enabled)
3710
- * - Hierarchical TreeTable with expand/collapse
3711
- * - Direct user-action permission assignment (bypassing roles)
3712
- * - Real-time change tracking
3713
- *
3714
- * **Granularity:**
3715
- * - Company-wide: branchId = undefined
3716
- * - Branch-specific: branchId = selected value
3717
- *
3718
- * **Performance:**
3719
- * - Computed signals for reactive updates
3720
- * - Branch filtering on company context change
3721
- *
3722
- * @example
3723
- * ```html
3724
- * <flusys-user-action-selector />
3725
- * ```
3726
- */
3727
- class UserActionSelectorComponent {
3728
- // Permission constants for template
3595
+ class UserActionSelectorComponent extends BaseActionSelectorComponent {
3729
3596
  USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
3730
- // Dependencies
3731
3597
  appConfig = inject(APP_CONFIG);
3732
3598
  companyContext = inject(LAYOUT_AUTH_STATE);
3733
3599
  userPermissionProvider = inject(USER_PERMISSION_PROVIDER);
3734
3600
  actionApi = inject(ActionApiService);
3735
3601
  permissionApi = inject(PermissionApiService);
3736
- permissionLogic = inject(ActionPermissionLogicService);
3737
3602
  messageService = inject(MessageService);
3738
- translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
3739
- translate(key, vars) {
3740
- return this.translateAdapter?.translate(key, vars) ?? key;
3741
- }
3742
- // State - User/Branch Selection
3743
3603
  selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
3744
3604
  selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
3745
3605
  branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : []));
3746
- // State - Loading/Saving
3747
- loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
3748
- saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
3749
- // State - Actions (Business Logic)
3750
- _actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
3751
- actions = this._actions.asReadonly();
3752
- // State - Actions (Display)
3753
- _actionsTree = signal([], ...(ngDevMode ? [{ debugName: "_actionsTree" }] : []));
3754
- actionsTree = this._actionsTree.asReadonly();
3755
- // State - Selection
3756
- _selectionMap = signal({}, ...(ngDevMode ? [{ debugName: "_selectionMap" }] : []));
3757
- selectionMap = this._selectionMap.asReadonly();
3758
- _initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
3759
- initialSelection = this._initialSelection.asReadonly();
3760
- // Computed - Company Feature Flag (cached)
3761
3606
  isCompanyFeatureActive = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "isCompanyFeatureActive" }] : []));
3762
- // Computed - UI Flags
3763
3607
  showBranchSelector = this.isCompanyFeatureActive;
3764
3608
  filteredBranches = computed(() => {
3765
3609
  const currentCompanyId = this.companyContext.currentCompanyInfo()?.id || undefined;
@@ -3767,64 +3611,11 @@ class UserActionSelectorComponent {
3767
3611
  return this.branches();
3768
3612
  return this.branches().filter((b) => b.companyId === currentCompanyId);
3769
3613
  }, ...(ngDevMode ? [{ debugName: "filteredBranches" }] : []));
3770
- // Computed - Tree Nodes (for PrimeNG TreeTable)
3771
- treeNodes = computed(() => convertActionToTreeNode(this.actionsTree()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
3772
- // Computed - Selection State
3773
- allSelected = computed(() => {
3774
- const selMap = this.selectionMap();
3775
- const total = this.actions().length;
3776
- if (total === 0)
3777
- return false;
3778
- const selected = Object.values(selMap).filter((v) => v).length;
3779
- return selected === total;
3780
- }, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
3781
- // Computed - Change Detection
3782
- hasChanges = computed(() => {
3783
- const current = this.selectionMap();
3784
- const initial = this.initialSelection();
3785
- const currentKeys = Object.keys(current);
3786
- const initialKeys = Object.keys(initial);
3787
- if (currentKeys.length !== initialKeys.length)
3788
- return true;
3789
- return currentKeys.some((key) => current[key] !== initial[key]);
3790
- }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
3791
- // Computed - Validation
3792
- actionsWithUnmetPrerequisites = computed(() => {
3793
- const selMap = this.selectionMap();
3794
- const allActions = this.actions();
3795
- const unmetSet = new Set();
3796
- allActions.forEach((action) => {
3797
- if (action.id &&
3798
- this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
3799
- unmetSet.add(action.id);
3800
- }
3801
- });
3802
- return unmetSet;
3803
- }, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
3804
- invalidActionsCount = computed(() => {
3805
- return this.actionsWithUnmetPrerequisites().size;
3806
- }, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
3807
- // Computed - Pending Changes
3808
- pendingAdd = computed(() => {
3809
- const current = this.selectionMap();
3810
- const initial = this.initialSelection();
3811
- const allActions = this.actions();
3812
- return allActions.filter((action) => current[action.id] && !initial[action.id]);
3813
- }, ...(ngDevMode ? [{ debugName: "pendingAdd" }] : []));
3814
- pendingRemove = computed(() => {
3815
- const current = this.selectionMap();
3816
- const initial = this.initialSelection();
3817
- const allActions = this.actions();
3818
- return allActions.filter((action) => !current[action.id] && initial[action.id]);
3819
- }, ...(ngDevMode ? [{ debugName: "pendingRemove" }] : []));
3820
- // Computed - Save Button State
3821
- canSave = computed(() => {
3822
- return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
3823
- }, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
3824
3614
  constructor() {
3825
- // Effect: Load user branches and data when user changes
3615
+ super();
3826
3616
  effect(() => {
3827
3617
  const userId = this.selectedUserId();
3618
+ this.selectedBranchId.set(undefined);
3828
3619
  if (userId) {
3829
3620
  if (this.isCompanyFeatureActive()) {
3830
3621
  this.loadUserBranches(userId);
@@ -3833,22 +3624,23 @@ class UserActionSelectorComponent {
3833
3624
  }
3834
3625
  else {
3835
3626
  this.branches.set([]);
3836
- this.selectedBranchId.set(undefined);
3837
3627
  this.resetState();
3838
3628
  }
3839
3629
  });
3840
- // Effect: Reload data when branch changes
3841
3630
  effect(() => {
3842
- const userId = this.selectedUserId();
3843
3631
  const branchId = this.selectedBranchId();
3632
+ const userId = this.selectedUserId();
3844
3633
  if (this.isCompanyFeatureActive() && userId && branchId !== undefined) {
3845
3634
  this.loadData();
3846
3635
  }
3847
3636
  });
3848
3637
  }
3849
- /**
3850
- * Load user's permitted branches
3851
- */
3638
+ getDefaultTooltip(isSelected) {
3639
+ if (isSelected) {
3640
+ return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.user')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.user')}`;
3641
+ }
3642
+ return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.user')}`;
3643
+ }
3852
3644
  async loadUserBranches(userId) {
3853
3645
  const companyId = this.companyContext.currentCompanyInfo()?.id;
3854
3646
  if (!companyId) {
@@ -3865,12 +3657,8 @@ class UserActionSelectorComponent {
3865
3657
  this.branches.set(userBranches);
3866
3658
  }
3867
3659
  catch {
3868
- // Error toast handled by global interceptor
3869
3660
  }
3870
3661
  }
3871
- /**
3872
- * Load actions and assignments for selected user
3873
- */
3874
3662
  async loadData() {
3875
3663
  const userId = this.selectedUserId();
3876
3664
  if (!userId)
@@ -3891,65 +3679,11 @@ class UserActionSelectorComponent {
3891
3679
  this._initialSelection.set({ ...selMap });
3892
3680
  }
3893
3681
  catch {
3894
- // Error toast handled by global interceptor
3895
3682
  }
3896
3683
  finally {
3897
3684
  this.loading.set(false);
3898
3685
  }
3899
3686
  }
3900
- /**
3901
- * Get tooltip for action checkbox
3902
- */
3903
- getTooltip(action) {
3904
- const selMap = this.selectionMap();
3905
- const isCurrentlySelected = selMap[action.id];
3906
- if (action.permissionLogic) {
3907
- const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
3908
- if (prerequisiteInfo) {
3909
- const actionHint = isCurrentlySelected
3910
- ? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
3911
- : `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
3912
- return `${prerequisiteInfo}${actionHint}`;
3913
- }
3914
- }
3915
- if (isCurrentlySelected) {
3916
- return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.user')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.user')}`;
3917
- }
3918
- return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.user')}`;
3919
- }
3920
- /**
3921
- * Check if action has unmet prerequisites
3922
- */
3923
- hasUnmetPrerequisites(action) {
3924
- return action.id
3925
- ? this.actionsWithUnmetPrerequisites().has(action.id)
3926
- : false;
3927
- }
3928
- /**
3929
- * Handle action toggle with dependency management
3930
- */
3931
- onActionToggle(action, newValue) {
3932
- if (!newValue) {
3933
- // Unchecking - validate dependencies
3934
- this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
3935
- }
3936
- else {
3937
- // Checking - validate prerequisites
3938
- this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
3939
- }
3940
- }
3941
- toggleAll() {
3942
- this.setAllActions(!this.allSelected());
3943
- }
3944
- selectAll() {
3945
- this.setAllActions(true);
3946
- }
3947
- deselectAll() {
3948
- this.setAllActions(false);
3949
- }
3950
- setAllActions(value) {
3951
- this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
3952
- }
3953
3687
  async saveChanges() {
3954
3688
  const userId = this.selectedUserId();
3955
3689
  if (!userId)
@@ -3969,7 +3703,7 @@ class UserActionSelectorComponent {
3969
3703
  this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
3970
3704
  return;
3971
3705
  }
3972
- const items = this.buildPermissionItems();
3706
+ const items = this.buildPayloadItems();
3973
3707
  if (items.length === 0)
3974
3708
  return;
3975
3709
  this.saving.set(true);
@@ -3985,44 +3719,21 @@ class UserActionSelectorComponent {
3985
3719
  this.messageService.add({
3986
3720
  severity: 'success',
3987
3721
  summary: this.translate('shared.success'),
3988
- detail: response?.data?.message || this.translate('iam.permission.user.actions.updated'),
3722
+ detail: response?.messageKey
3723
+ ? this.translate(response.messageKey, response.messageVariables)
3724
+ : response?.message,
3989
3725
  });
3990
3726
  this._initialSelection.set({ ...this.selectionMap() });
3991
3727
  }
3992
3728
  catch {
3993
- // Error toast handled by global interceptor
3994
3729
  }
3995
3730
  finally {
3996
3731
  this.saving.set(false);
3997
3732
  }
3998
3733
  }
3999
- resetState() {
4000
- this._actionsTree.set([]);
4001
- this._actions.set([]);
4002
- this._selectionMap.set({});
4003
- this._initialSelection.set({});
4004
- }
4005
- buildSelectionMap(actions, predicate) {
4006
- const selMap = {};
4007
- actions.forEach((action) => {
4008
- selMap[action.id] = predicate(action);
4009
- });
4010
- return selMap;
4011
- }
4012
- buildPermissionItems() {
4013
- const items = [];
4014
- this.pendingAdd().forEach((action) => {
4015
- items.push({ id: action.id, action: 'add' });
4016
- });
4017
- this.pendingRemove().forEach((action) => {
4018
- items.push({ id: action.id, action: 'remove' });
4019
- });
4020
- return items;
4021
- }
4022
3734
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4023
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
3735
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", usesInheritance: true, ngImport: i0, template: `
4024
3736
  <div class="user-action-selector">
4025
- <!-- User and Branch Selectors -->
4026
3737
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
4027
3738
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
4028
3739
  <div>
@@ -4034,7 +3745,9 @@ class UserActionSelectorComponent {
4034
3745
  [value]="selectedUserId()"
4035
3746
  (valueChange)="selectedUserId.set($event)"
4036
3747
  [isEditMode]="true"
4037
- [placeHolder]="'iam.permission.select.user.placeholder' | translate"
3748
+ [placeHolder]="
3749
+ 'iam.permission.select.user.placeholder' | translate
3750
+ "
4038
3751
  />
4039
3752
  </div>
4040
3753
 
@@ -4050,10 +3763,12 @@ class UserActionSelectorComponent {
4050
3763
  [options]="filteredBranches()"
4051
3764
  optionLabel="name"
4052
3765
  optionValue="id"
4053
- [placeholder]="'iam.permission.select.branch.placeholder' | translate"
3766
+ [placeholder]="
3767
+ 'iam.permission.select.branch.placeholder' | translate
3768
+ "
4054
3769
  [showClear]="true"
4055
3770
  [filter]="true"
4056
- styleClass="w-full"
3771
+ class="w-full"
4057
3772
  >
4058
3773
  <ng-template #selectedItem let-branch>
4059
3774
  @if (branch) {
@@ -4071,7 +3786,12 @@ class UserActionSelectorComponent {
4071
3786
  </ng-template>
4072
3787
  </p-select>
4073
3788
  <small class="text-muted-color block mt-1">
4074
- {{ (filteredBranches().length !== 1 ? 'iam.branch.permitted.count.plural' : 'iam.branch.permitted.count') | translate: { count: filteredBranches().length } }}
3789
+ {{
3790
+ (filteredBranches().length !== 1
3791
+ ? 'iam.branch.permitted.count.plural'
3792
+ : 'iam.branch.permitted.count'
3793
+ ) | translate: { count: filteredBranches().length }
3794
+ }}
4075
3795
  </small>
4076
3796
  </div>
4077
3797
  }
@@ -4079,23 +3799,26 @@ class UserActionSelectorComponent {
4079
3799
  </div>
4080
3800
 
4081
3801
  @if (selectedUserId()) {
4082
- <!-- Loading State -->
4083
3802
  @if (loading()) {
4084
3803
  <div class="flex justify-center p-5">
4085
3804
  <i class="pi pi-spin pi-spinner text-4xl"></i>
4086
3805
  </div>
4087
3806
  }
4088
3807
 
4089
- <!-- Action List -->
4090
3808
  @if (!loading() && actions().length > 0) {
4091
3809
  <div class="surface-card p-4 rounded-border shadow-sm">
4092
3810
  <div
4093
3811
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
4094
3812
  >
4095
3813
  <div>
4096
- <h5 class="m-0 mb-1">{{ 'iam.permission.direct.action.permissions' | translate }}</h5>
3814
+ <h5 class="m-0 mb-1">
3815
+ {{ 'iam.permission.direct.action.permissions' | translate }}
3816
+ </h5>
4097
3817
  <p class="text-sm text-muted-color m-0">
4098
- {{ 'iam.permission.actions.available' | translate: { count: actions().length } }}
3818
+ {{
3819
+ 'iam.permission.actions.available'
3820
+ | translate: { count: actions().length }
3821
+ }}
4099
3822
  </p>
4100
3823
  </div>
4101
3824
  <div class="flex flex-wrap gap-2">
@@ -4128,27 +3851,32 @@ class UserActionSelectorComponent {
4128
3851
  </div>
4129
3852
  </div>
4130
3853
 
4131
- <!-- Validation Warning -->
4132
3854
  @if (invalidActionsCount() > 0) {
4133
3855
  <div class="validation-warning rounded-border p-3 mb-4">
4134
3856
  <div class="flex items-start gap-2">
4135
3857
  <i class="pi pi-exclamation-triangle text-xl"></i>
4136
3858
  <div class="flex-1">
4137
- <span class="font-semibold">{{ 'iam.validation.warning.title' | translate }}:</span>
3859
+ <span class="font-semibold"
3860
+ >{{ 'iam.validation.warning.title' | translate }}:</span
3861
+ >
4138
3862
  <p class="text-sm mt-1 mb-0">
4139
- {{ (invalidActionsCount() > 1 ? 'iam.validation.unmet.prerequisites.plural' : 'iam.validation.unmet.prerequisites.singular') | translate: { count: invalidActionsCount() } }}
3863
+ {{
3864
+ (invalidActionsCount() > 1
3865
+ ? 'iam.validation.unmet.prerequisites.plural'
3866
+ : 'iam.validation.unmet.prerequisites.singular'
3867
+ ) | translate: { count: invalidActionsCount() }
3868
+ }}
4140
3869
  </p>
4141
3870
  </div>
4142
3871
  </div>
4143
3872
  </div>
4144
3873
  }
4145
3874
 
4146
- <!-- Action Tree Table -->
4147
3875
  <div class="overflow-x-auto -mx-4 sm:mx-0">
4148
3876
  <p-treeTable
4149
3877
  [value]="treeNodes()"
4150
3878
  dataKey="id"
4151
- styleClass="p-treetable-sm"
3879
+ class="p-treetable-sm"
4152
3880
  [tableStyle]="{ 'min-width': '50rem' }"
4153
3881
  >
4154
3882
  <ng-template #header>
@@ -4163,75 +3891,94 @@ class UserActionSelectorComponent {
4163
3891
  />
4164
3892
  </th>
4165
3893
  <th>{{ 'shared.name' | translate }}</th>
4166
- <th class="hidden md:table-cell">{{ 'shared.code' | translate }}</th>
3894
+ <th class="hidden md:table-cell">
3895
+ {{ 'shared.code' | translate }}
3896
+ </th>
4167
3897
  <th>{{ 'iam.action.type' | translate }}</th>
4168
- <th class="hidden lg:table-cell">{{ 'shared.description' | translate }}</th>
3898
+ <th class="hidden lg:table-cell">
3899
+ {{ 'shared.description' | translate }}
3900
+ </th>
3901
+ </tr>
3902
+ </ng-template>
3903
+ <ng-template #body let-rowNode let-rowData="rowData">
3904
+ @let hasUnmet = hasUnmetPrerequisites(rowData);
3905
+ @let tooltip = getTooltip(rowData);
3906
+ <tr [class.highlight-warning]="hasUnmet">
3907
+ <td class="w-12">
3908
+ <p-checkbox
3909
+ [ngModel]="selectionMap()[rowData.id]"
3910
+ [binary]="true"
3911
+ (ngModelChange)="onActionToggle(rowData, $event)"
3912
+ [pTooltip]="tooltip"
3913
+ tooltipPosition="top"
3914
+ />
3915
+ </td>
3916
+ <td>
3917
+ <p-treeTableToggler [rowNode]="rowNode" />
3918
+ <span class="inline-flex items-center gap-2">
3919
+ {{ rowData.name }}
3920
+ @if (hasUnmet) {
3921
+ <i
3922
+ class="pi pi-exclamation-triangle text-orange-500"
3923
+ [pTooltip]="
3924
+ 'iam.validation.unmet.prerequisites.tooltip'
3925
+ | translate
3926
+ "
3927
+ tooltipPosition="top"
3928
+ ></i>
3929
+ }
3930
+ </span>
3931
+ </td>
3932
+ <td class="hidden md:table-cell">
3933
+ {{ rowData.code || '-' }}
3934
+ </td>
3935
+ <td>
3936
+ <p-tag
3937
+ [value]="rowData.actionType"
3938
+ [severity]="
3939
+ rowData.actionType === 'backend' ? 'info' : 'success'
3940
+ "
3941
+ />
3942
+ </td>
3943
+ <td class="hidden lg:table-cell">
3944
+ {{ rowData.description || '-' }}
3945
+ </td>
4169
3946
  </tr>
4170
3947
  </ng-template>
4171
- <ng-template #body let-rowNode let-rowData="rowData">
4172
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
4173
- <td class="w-12">
4174
- <p-checkbox
4175
- [ngModel]="selectionMap()[rowData.id]"
4176
- [binary]="true"
4177
- (ngModelChange)="onActionToggle(rowData, $event)"
4178
- [pTooltip]="getTooltip(rowData)"
4179
- tooltipPosition="top"
4180
- />
4181
- </td>
4182
- <td>
4183
- <p-treeTableToggler [rowNode]="rowNode" />
4184
- <span class="inline-flex items-center gap-2">
4185
- {{ rowData.name }}
4186
- @if (hasUnmetPrerequisites(rowData)) {
4187
- <i
4188
- class="pi pi-exclamation-triangle text-orange-500"
4189
- [pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
4190
- tooltipPosition="top"
4191
- ></i>
3948
+ <ng-template #emptymessage>
3949
+ <tr>
3950
+ <td colspan="5" class="text-center p-4 text-muted-color">
3951
+ @if (loading()) {
3952
+ <i class="pi pi-spin pi-spinner"></i>
3953
+ {{ 'shared.loading.actions' | translate }}
3954
+ } @else {
3955
+ {{ 'iam.permission.no.actions.available' | translate }}
4192
3956
  }
4193
- </span>
4194
- </td>
4195
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
4196
- <td>
4197
- <p-tag
4198
- [value]="rowData.actionType"
4199
- [severity]="
4200
- rowData.actionType === 'backend' ? 'info' : 'success'
4201
- "
4202
- />
4203
- </td>
4204
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
4205
- </tr>
4206
- </ng-template>
4207
- <ng-template #emptymessage>
4208
- <tr>
4209
- <td colspan="5" class="text-center p-4 text-muted-color">
4210
- @if (loading()) {
4211
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
4212
- } @else {
4213
- {{ 'iam.permission.no.actions.available' | translate }}
4214
- }
4215
- </td>
4216
- </tr>
4217
- </ng-template>
3957
+ </td>
3958
+ </tr>
3959
+ </ng-template>
4218
3960
  </p-treeTable>
4219
3961
  </div>
4220
3962
  </div>
4221
3963
 
4222
- <!-- Change Summary -->
4223
3964
  @if (hasChanges()) {
4224
3965
  <div class="border border-surface rounded-border p-3 mt-4">
4225
3966
  <div class="flex items-center gap-2 mb-3">
4226
3967
  <i class="pi pi-info-circle text-primary"></i>
4227
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
3968
+ <span class="font-bold">{{
3969
+ 'shared.pending.changes' | translate
3970
+ }}</span>
4228
3971
  </div>
4229
3972
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
4230
3973
  @if (pendingAdd().length > 0) {
4231
3974
  <div>
4232
3975
  <div class="flex items-center gap-2 mb-2">
4233
3976
  <i class="pi pi-plus-circle text-green-500"></i>
4234
- <strong class="text-sm">{{ 'shared.to.assign' | translate }} ({{ pendingAdd().length }})</strong>
3977
+ <strong class="text-sm"
3978
+ >{{ 'shared.to.assign' | translate }} ({{
3979
+ pendingAdd().length
3980
+ }})</strong
3981
+ >
4235
3982
  </div>
4236
3983
  <ul class="list-none p-0 m-0 pl-4">
4237
3984
  @for (action of pendingAdd(); track action.id) {
@@ -4244,7 +3991,11 @@ class UserActionSelectorComponent {
4244
3991
  <div>
4245
3992
  <div class="flex items-center gap-2 mb-2">
4246
3993
  <i class="pi pi-minus-circle text-red-500"></i>
4247
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
3994
+ <strong class="text-sm"
3995
+ >{{ 'shared.to.remove' | translate }} ({{
3996
+ pendingRemove().length
3997
+ }})</strong
3998
+ >
4248
3999
  </div>
4249
4000
  <ul class="list-none p-0 m-0 pl-4">
4250
4001
  @for (action of pendingRemove(); track action.id) {
@@ -4260,7 +4011,9 @@ class UserActionSelectorComponent {
4260
4011
 
4261
4012
  @if (!loading() && actions().length === 0) {
4262
4013
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
4263
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
4014
+ <i
4015
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
4016
+ ></i>
4264
4017
  <p class="text-muted-color m-0">
4265
4018
  {{ 'iam.permission.no.actions.for.user' | translate }}
4266
4019
  </p>
@@ -4268,13 +4021,18 @@ class UserActionSelectorComponent {
4268
4021
  }
4269
4022
  }
4270
4023
  </div>
4271
- `, 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: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
4024
+ `, 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: 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: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
4272
4025
  }
4273
4026
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
4274
4027
  type: Component,
4275
- args: [{ selector: 'flusys-user-action-selector', imports: [CommonModule, FormsModule, PrimeModule, HasPermissionDirective, UserSelectComponent, TranslatePipe], template: `
4028
+ args: [{ selector: 'flusys-user-action-selector', imports: [
4029
+ FormsModule,
4030
+ PrimeModule,
4031
+ HasPermissionDirective,
4032
+ UserSelectComponent,
4033
+ TranslatePipe,
4034
+ ], template: `
4276
4035
  <div class="user-action-selector">
4277
- <!-- User and Branch Selectors -->
4278
4036
  <div class="surface-card p-4 rounded-border mb-4 shadow-sm">
4279
4037
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
4280
4038
  <div>
@@ -4286,7 +4044,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4286
4044
  [value]="selectedUserId()"
4287
4045
  (valueChange)="selectedUserId.set($event)"
4288
4046
  [isEditMode]="true"
4289
- [placeHolder]="'iam.permission.select.user.placeholder' | translate"
4047
+ [placeHolder]="
4048
+ 'iam.permission.select.user.placeholder' | translate
4049
+ "
4290
4050
  />
4291
4051
  </div>
4292
4052
 
@@ -4302,10 +4062,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4302
4062
  [options]="filteredBranches()"
4303
4063
  optionLabel="name"
4304
4064
  optionValue="id"
4305
- [placeholder]="'iam.permission.select.branch.placeholder' | translate"
4065
+ [placeholder]="
4066
+ 'iam.permission.select.branch.placeholder' | translate
4067
+ "
4306
4068
  [showClear]="true"
4307
4069
  [filter]="true"
4308
- styleClass="w-full"
4070
+ class="w-full"
4309
4071
  >
4310
4072
  <ng-template #selectedItem let-branch>
4311
4073
  @if (branch) {
@@ -4323,7 +4085,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4323
4085
  </ng-template>
4324
4086
  </p-select>
4325
4087
  <small class="text-muted-color block mt-1">
4326
- {{ (filteredBranches().length !== 1 ? 'iam.branch.permitted.count.plural' : 'iam.branch.permitted.count') | translate: { count: filteredBranches().length } }}
4088
+ {{
4089
+ (filteredBranches().length !== 1
4090
+ ? 'iam.branch.permitted.count.plural'
4091
+ : 'iam.branch.permitted.count'
4092
+ ) | translate: { count: filteredBranches().length }
4093
+ }}
4327
4094
  </small>
4328
4095
  </div>
4329
4096
  }
@@ -4331,23 +4098,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4331
4098
  </div>
4332
4099
 
4333
4100
  @if (selectedUserId()) {
4334
- <!-- Loading State -->
4335
4101
  @if (loading()) {
4336
4102
  <div class="flex justify-center p-5">
4337
4103
  <i class="pi pi-spin pi-spinner text-4xl"></i>
4338
4104
  </div>
4339
4105
  }
4340
4106
 
4341
- <!-- Action List -->
4342
4107
  @if (!loading() && actions().length > 0) {
4343
4108
  <div class="surface-card p-4 rounded-border shadow-sm">
4344
4109
  <div
4345
4110
  class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
4346
4111
  >
4347
4112
  <div>
4348
- <h5 class="m-0 mb-1">{{ 'iam.permission.direct.action.permissions' | translate }}</h5>
4113
+ <h5 class="m-0 mb-1">
4114
+ {{ 'iam.permission.direct.action.permissions' | translate }}
4115
+ </h5>
4349
4116
  <p class="text-sm text-muted-color m-0">
4350
- {{ 'iam.permission.actions.available' | translate: { count: actions().length } }}
4117
+ {{
4118
+ 'iam.permission.actions.available'
4119
+ | translate: { count: actions().length }
4120
+ }}
4351
4121
  </p>
4352
4122
  </div>
4353
4123
  <div class="flex flex-wrap gap-2">
@@ -4380,27 +4150,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4380
4150
  </div>
4381
4151
  </div>
4382
4152
 
4383
- <!-- Validation Warning -->
4384
4153
  @if (invalidActionsCount() > 0) {
4385
4154
  <div class="validation-warning rounded-border p-3 mb-4">
4386
4155
  <div class="flex items-start gap-2">
4387
4156
  <i class="pi pi-exclamation-triangle text-xl"></i>
4388
4157
  <div class="flex-1">
4389
- <span class="font-semibold">{{ 'iam.validation.warning.title' | translate }}:</span>
4158
+ <span class="font-semibold"
4159
+ >{{ 'iam.validation.warning.title' | translate }}:</span
4160
+ >
4390
4161
  <p class="text-sm mt-1 mb-0">
4391
- {{ (invalidActionsCount() > 1 ? 'iam.validation.unmet.prerequisites.plural' : 'iam.validation.unmet.prerequisites.singular') | translate: { count: invalidActionsCount() } }}
4162
+ {{
4163
+ (invalidActionsCount() > 1
4164
+ ? 'iam.validation.unmet.prerequisites.plural'
4165
+ : 'iam.validation.unmet.prerequisites.singular'
4166
+ ) | translate: { count: invalidActionsCount() }
4167
+ }}
4392
4168
  </p>
4393
4169
  </div>
4394
4170
  </div>
4395
4171
  </div>
4396
4172
  }
4397
4173
 
4398
- <!-- Action Tree Table -->
4399
4174
  <div class="overflow-x-auto -mx-4 sm:mx-0">
4400
4175
  <p-treeTable
4401
4176
  [value]="treeNodes()"
4402
4177
  dataKey="id"
4403
- styleClass="p-treetable-sm"
4178
+ class="p-treetable-sm"
4404
4179
  [tableStyle]="{ 'min-width': '50rem' }"
4405
4180
  >
4406
4181
  <ng-template #header>
@@ -4415,75 +4190,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4415
4190
  />
4416
4191
  </th>
4417
4192
  <th>{{ 'shared.name' | translate }}</th>
4418
- <th class="hidden md:table-cell">{{ 'shared.code' | translate }}</th>
4193
+ <th class="hidden md:table-cell">
4194
+ {{ 'shared.code' | translate }}
4195
+ </th>
4419
4196
  <th>{{ 'iam.action.type' | translate }}</th>
4420
- <th class="hidden lg:table-cell">{{ 'shared.description' | translate }}</th>
4197
+ <th class="hidden lg:table-cell">
4198
+ {{ 'shared.description' | translate }}
4199
+ </th>
4200
+ </tr>
4201
+ </ng-template>
4202
+ <ng-template #body let-rowNode let-rowData="rowData">
4203
+ @let hasUnmet = hasUnmetPrerequisites(rowData);
4204
+ @let tooltip = getTooltip(rowData);
4205
+ <tr [class.highlight-warning]="hasUnmet">
4206
+ <td class="w-12">
4207
+ <p-checkbox
4208
+ [ngModel]="selectionMap()[rowData.id]"
4209
+ [binary]="true"
4210
+ (ngModelChange)="onActionToggle(rowData, $event)"
4211
+ [pTooltip]="tooltip"
4212
+ tooltipPosition="top"
4213
+ />
4214
+ </td>
4215
+ <td>
4216
+ <p-treeTableToggler [rowNode]="rowNode" />
4217
+ <span class="inline-flex items-center gap-2">
4218
+ {{ rowData.name }}
4219
+ @if (hasUnmet) {
4220
+ <i
4221
+ class="pi pi-exclamation-triangle text-orange-500"
4222
+ [pTooltip]="
4223
+ 'iam.validation.unmet.prerequisites.tooltip'
4224
+ | translate
4225
+ "
4226
+ tooltipPosition="top"
4227
+ ></i>
4228
+ }
4229
+ </span>
4230
+ </td>
4231
+ <td class="hidden md:table-cell">
4232
+ {{ rowData.code || '-' }}
4233
+ </td>
4234
+ <td>
4235
+ <p-tag
4236
+ [value]="rowData.actionType"
4237
+ [severity]="
4238
+ rowData.actionType === 'backend' ? 'info' : 'success'
4239
+ "
4240
+ />
4241
+ </td>
4242
+ <td class="hidden lg:table-cell">
4243
+ {{ rowData.description || '-' }}
4244
+ </td>
4421
4245
  </tr>
4422
4246
  </ng-template>
4423
- <ng-template #body let-rowNode let-rowData="rowData">
4424
- <tr [class.highlight-warning]="hasUnmetPrerequisites(rowData)">
4425
- <td class="w-12">
4426
- <p-checkbox
4427
- [ngModel]="selectionMap()[rowData.id]"
4428
- [binary]="true"
4429
- (ngModelChange)="onActionToggle(rowData, $event)"
4430
- [pTooltip]="getTooltip(rowData)"
4431
- tooltipPosition="top"
4432
- />
4433
- </td>
4434
- <td>
4435
- <p-treeTableToggler [rowNode]="rowNode" />
4436
- <span class="inline-flex items-center gap-2">
4437
- {{ rowData.name }}
4438
- @if (hasUnmetPrerequisites(rowData)) {
4439
- <i
4440
- class="pi pi-exclamation-triangle text-orange-500"
4441
- [pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
4442
- tooltipPosition="top"
4443
- ></i>
4247
+ <ng-template #emptymessage>
4248
+ <tr>
4249
+ <td colspan="5" class="text-center p-4 text-muted-color">
4250
+ @if (loading()) {
4251
+ <i class="pi pi-spin pi-spinner"></i>
4252
+ {{ 'shared.loading.actions' | translate }}
4253
+ } @else {
4254
+ {{ 'iam.permission.no.actions.available' | translate }}
4444
4255
  }
4445
- </span>
4446
- </td>
4447
- <td class="hidden md:table-cell">{{ rowData.code || '-' }}</td>
4448
- <td>
4449
- <p-tag
4450
- [value]="rowData.actionType"
4451
- [severity]="
4452
- rowData.actionType === 'backend' ? 'info' : 'success'
4453
- "
4454
- />
4455
- </td>
4456
- <td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
4457
- </tr>
4458
- </ng-template>
4459
- <ng-template #emptymessage>
4460
- <tr>
4461
- <td colspan="5" class="text-center p-4 text-muted-color">
4462
- @if (loading()) {
4463
- <i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
4464
- } @else {
4465
- {{ 'iam.permission.no.actions.available' | translate }}
4466
- }
4467
- </td>
4468
- </tr>
4469
- </ng-template>
4256
+ </td>
4257
+ </tr>
4258
+ </ng-template>
4470
4259
  </p-treeTable>
4471
4260
  </div>
4472
4261
  </div>
4473
4262
 
4474
- <!-- Change Summary -->
4475
4263
  @if (hasChanges()) {
4476
4264
  <div class="border border-surface rounded-border p-3 mt-4">
4477
4265
  <div class="flex items-center gap-2 mb-3">
4478
4266
  <i class="pi pi-info-circle text-primary"></i>
4479
- <span class="font-bold">{{ 'shared.pending.changes' | translate }}</span>
4267
+ <span class="font-bold">{{
4268
+ 'shared.pending.changes' | translate
4269
+ }}</span>
4480
4270
  </div>
4481
4271
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
4482
4272
  @if (pendingAdd().length > 0) {
4483
4273
  <div>
4484
4274
  <div class="flex items-center gap-2 mb-2">
4485
4275
  <i class="pi pi-plus-circle text-green-500"></i>
4486
- <strong class="text-sm">{{ 'shared.to.assign' | translate }} ({{ pendingAdd().length }})</strong>
4276
+ <strong class="text-sm"
4277
+ >{{ 'shared.to.assign' | translate }} ({{
4278
+ pendingAdd().length
4279
+ }})</strong
4280
+ >
4487
4281
  </div>
4488
4282
  <ul class="list-none p-0 m-0 pl-4">
4489
4283
  @for (action of pendingAdd(); track action.id) {
@@ -4496,7 +4290,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4496
4290
  <div>
4497
4291
  <div class="flex items-center gap-2 mb-2">
4498
4292
  <i class="pi pi-minus-circle text-red-500"></i>
4499
- <strong class="text-sm">{{ 'shared.to.remove' | translate }} ({{ pendingRemove().length }})</strong>
4293
+ <strong class="text-sm"
4294
+ >{{ 'shared.to.remove' | translate }} ({{
4295
+ pendingRemove().length
4296
+ }})</strong
4297
+ >
4500
4298
  </div>
4501
4299
  <ul class="list-none p-0 m-0 pl-4">
4502
4300
  @for (action of pendingRemove(); track action.id) {
@@ -4512,7 +4310,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
4512
4310
 
4513
4311
  @if (!loading() && actions().length === 0) {
4514
4312
  <div class="surface-card p-5 rounded-border shadow-sm text-center">
4515
- <i class="pi pi-info-circle text-muted-color mb-3 block text-5xl"></i>
4313
+ <i
4314
+ class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
4315
+ ></i>
4516
4316
  <p class="text-muted-color m-0">
4517
4317
  {{ 'iam.permission.no.actions.for.user' | translate }}
4518
4318
  </p>
@@ -4605,15 +4405,15 @@ const IAM_ROUTES = [
4605
4405
  children: [
4606
4406
  {
4607
4407
  path: '',
4608
- loadComponent: () => import('./flusys-ng-iam-action-list-page.component-DYnYC7Nn.mjs').then((m) => m.ActionListPageComponent),
4408
+ loadComponent: () => import('./flusys-ng-iam-action-list-page.component-DRK79zUR.mjs').then((m) => m.ActionListPageComponent),
4609
4409
  },
4610
4410
  {
4611
4411
  path: 'new',
4612
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-DJbMu2aV.mjs').then((m) => m.ActionFormPageComponent),
4412
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs').then((m) => m.ActionFormPageComponent),
4613
4413
  },
4614
4414
  {
4615
4415
  path: ':id',
4616
- loadComponent: () => import('./flusys-ng-iam-action-form-page.component-DJbMu2aV.mjs').then((m) => m.ActionFormPageComponent),
4416
+ loadComponent: () => import('./flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs').then((m) => m.ActionFormPageComponent),
4617
4417
  },
4618
4418
  ],
4619
4419
  },
@@ -4624,15 +4424,15 @@ const IAM_ROUTES = [
4624
4424
  children: [
4625
4425
  {
4626
4426
  path: '',
4627
- loadComponent: () => import('./flusys-ng-iam-role-list-page.component-C7GVYYIn.mjs').then((m) => m.RoleListPageComponent),
4427
+ loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CT7CvvHj.mjs').then((m) => m.RoleListPageComponent),
4628
4428
  },
4629
4429
  {
4630
4430
  path: 'new',
4631
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-BuZmko2b.mjs').then((m) => m.RoleFormPageComponent),
4431
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-49dKMKOj.mjs').then((m) => m.RoleFormPageComponent),
4632
4432
  },
4633
4433
  {
4634
4434
  path: ':id',
4635
- loadComponent: () => import('./flusys-ng-iam-role-form-page.component-BuZmko2b.mjs').then((m) => m.RoleFormPageComponent),
4435
+ loadComponent: () => import('./flusys-ng-iam-role-form-page.component-49dKMKOj.mjs').then((m) => m.RoleFormPageComponent),
4636
4436
  },
4637
4437
  ],
4638
4438
  },
@@ -4647,7 +4447,7 @@ const IAM_ROUTES = [
4647
4447
  COMPANY_ACTION_PERMISSIONS.READ,
4648
4448
  ]),
4649
4449
  ],
4650
- loadComponent: () => import('./flusys-ng-iam-permission-page.component-_cLVilgz.mjs').then((m) => m.PermissionPageComponent),
4450
+ loadComponent: () => import('./flusys-ng-iam-permission-page.component-CZebeUhC.mjs').then((m) => m.PermissionPageComponent),
4651
4451
  },
4652
4452
  // Default redirect to actions
4653
4453
  {
@@ -4666,4 +4466,4 @@ const IAM_ROUTES = [
4666
4466
  */
4667
4467
 
4668
4468
  export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_MESSAGES 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, IAM_ROUTES as f, MyPermissionsApiService as g, PermissionStateService as h, ProfilePermissionProviderAdapter as i, provideIamProviders as p };
4669
- //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-CnVOy_4s.mjs.map
4469
+ //# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-C6I4k78L.mjs.map