@flusys/ng-iam 3.0.0-rc → 3.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 (19) hide show
  1. package/README.md +232 -853
  2. package/fesm2022/{flusys-ng-iam-action-form-page.component-CVN8sV-c.mjs → flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs} +5 -5
  3. package/fesm2022/{flusys-ng-iam-action-form-page.component-CVN8sV-c.mjs.map → flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs.map} +1 -1
  4. package/fesm2022/{flusys-ng-iam-action-list-page.component-CQ6RazN0.mjs → flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs} +5 -5
  5. package/fesm2022/{flusys-ng-iam-action-list-page.component-CQ6RazN0.mjs.map → flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs.map} +1 -1
  6. package/fesm2022/{flusys-ng-iam-flusys-ng-iam-DrGHlTiz.mjs → flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs} +54 -56
  7. package/fesm2022/flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs.map +1 -0
  8. package/fesm2022/{flusys-ng-iam-iam-container.component-BToYxEej.mjs → flusys-ng-iam-iam-container.component-UYJjqYV9.mjs} +4 -4
  9. package/fesm2022/{flusys-ng-iam-iam-container.component-BToYxEej.mjs.map → flusys-ng-iam-iam-container.component-UYJjqYV9.mjs.map} +1 -1
  10. package/fesm2022/{flusys-ng-iam-permission-page.component-BS7xXmsn.mjs → flusys-ng-iam-permission-page.component-DcgT7L3_.mjs} +5 -5
  11. package/fesm2022/{flusys-ng-iam-permission-page.component-BS7xXmsn.mjs.map → flusys-ng-iam-permission-page.component-DcgT7L3_.mjs.map} +1 -1
  12. package/fesm2022/{flusys-ng-iam-role-form-page.component-BjPwXkip.mjs → flusys-ng-iam-role-form-page.component-D_AAEay2.mjs} +5 -5
  13. package/fesm2022/{flusys-ng-iam-role-form-page.component-BjPwXkip.mjs.map → flusys-ng-iam-role-form-page.component-D_AAEay2.mjs.map} +1 -1
  14. package/fesm2022/{flusys-ng-iam-role-list-page.component-Cz-jk-R_.mjs → flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs} +5 -5
  15. package/fesm2022/{flusys-ng-iam-role-list-page.component-Cz-jk-R_.mjs.map → flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs.map} +1 -1
  16. package/fesm2022/flusys-ng-iam.mjs +1 -1
  17. package/package.json +11 -11
  18. package/types/flusys-ng-iam.d.ts +0 -1
  19. package/fesm2022/flusys-ng-iam-flusys-ng-iam-DrGHlTiz.mjs.map +0 -1
package/README.md CHANGED
@@ -4,37 +4,15 @@
4
4
 
5
5
  `@flusys/ng-iam` provides Identity and Access Management (IAM) for FLUSYS applications. It includes permission state management, role-based access control (RBAC), action management with AND/OR prerequisite logic, and dynamic menu filtering based on user permissions.
6
6
 
7
- **Key Principle:** ng-iam is **100% independent** from ng-auth through the **Provider Interface Pattern**. No compile-time or runtime dependencies on ng-auth.
8
-
9
- ## Package Information
7
+ **Key Principle:** ng-iam is **100% independent** from ng-auth through the **Provider Interface Pattern**.
10
8
 
11
9
  | Item | Value |
12
10
  |------|-------|
13
11
  | Package | `@flusys/ng-iam` |
14
- | Version | 1.0.0 |
12
+ | Version | 3.0.0 |
15
13
  | Dependencies | ng-core, ng-shared, ng-layout (IMenuItem only) |
16
- | NO dependency on | ng-auth (fully independent via Provider Pattern) |
17
14
  | CSS Framework | Tailwind CSS (not PrimeFlex) |
18
15
  | Build | `npm run build:ng-iam` |
19
- | Entry point | `public-api.ts` |
20
-
21
- ---
22
-
23
- ## Table of Contents
24
-
25
- 1. [Architecture](#architecture)
26
- 2. [Provider Interfaces](#provider-interfaces)
27
- 3. [Integration Options](#integration-options)
28
- 4. [Permission Modes](#permission-modes)
29
- 5. [Routes](#routes)
30
- 6. [Services](#services)
31
- 7. [Components](#components)
32
- 8. [Page Components](#page-components)
33
- 9. [Interfaces](#interfaces)
34
- 10. [Utils & Constants](#utils--constants)
35
- 11. [Backend Integration](#backend-integration)
36
- 12. [Best Practices](#best-practices)
37
- 13. [Common Issues](#common-issues)
38
16
 
39
17
  ---
40
18
 
@@ -42,131 +20,74 @@
42
20
 
43
21
  ```
44
22
  ng-shared (defines interfaces + tokens)
45
- |
23
+
46
24
  Your Auth System (implements interfaces with adapters)
47
- |
25
+
48
26
  ng-iam (consumes interfaces via DI)
49
27
  ```
50
28
 
51
- ng-iam resolves all auth-related data at runtime via DI tokens defined in ng-shared. If a required token is not provided, Angular throws a clear error at injection time.
29
+ ng-iam resolves all auth-related data at runtime via DI tokens defined in ng-shared.
52
30
 
53
31
  ---
54
32
 
55
33
  ## Provider Interfaces
56
34
 
57
- ng-iam requires these provider interfaces:
58
-
59
35
  | Token | Source | Purpose | Required? |
60
36
  |-------|--------|---------|-----------|
61
- | `USER_PROVIDER` | `@flusys/ng-shared` | User list for role/action assignment | Yes |
62
- | `LAYOUT_AUTH_STATE` | `@flusys/ng-layout` | Current company/branch context | Yes (if company feature enabled) |
63
- | `COMPANY_API_PROVIDER` | `@flusys/ng-shared` | Company list for whitelisting | Optional (for CompanyActionSelector) |
64
- | `USER_PERMISSION_PROVIDER` | `@flusys/ng-shared` | User branch permissions | Optional (for branch-scoped permissions) |
65
-
66
- ### How Components Use Providers
67
-
68
- User selection uses `lib-user-select` component from ng-shared (encapsulates USER_PROVIDER):
69
-
70
- ```html
71
- <!-- UserActionSelectorComponent / UserRoleSelectorComponent -->
72
- <lib-user-select
73
- [(value)]="selectedUserId"
74
- [isEditMode]="true"
75
- placeHolder="Select a user"
76
- />
77
- ```
78
-
79
- Company context is accessed via `LAYOUT_AUTH_STATE`:
80
-
81
- ```typescript
82
- private readonly companyContext = inject(LAYOUT_AUTH_STATE);
83
-
84
- // Get current company
85
- const companyId = this.companyContext.currentCompanyInfo()?.id;
86
- ```
87
-
88
- Branch permissions loaded from `USER_PERMISSION_PROVIDER`:
37
+ | `USER_PROVIDER` | ng-shared | User list for role/action assignment | Yes |
38
+ | `LAYOUT_AUTH_STATE` | ng-layout | Current company/branch context | Yes (if company enabled) |
39
+ | `COMPANY_API_PROVIDER` | ng-shared | Company list for whitelisting | Optional |
40
+ | `USER_PERMISSION_PROVIDER` | ng-shared | User branch permissions | Optional |
89
41
 
90
42
  ```typescript
91
- private readonly userPermissionProvider = inject(USER_PERMISSION_PROVIDER);
92
-
93
- const response = await this.userPermissionProvider
94
- .getUserBranchPermissions(userId)
95
- .toPromise();
43
+ // Provider usage
44
+ const companyId = inject(LAYOUT_AUTH_STATE).currentCompanyInfo()?.id;
45
+ const response = await inject(USER_PERMISSION_PROVIDER).getUserBranchPermissions(userId).toPromise();
96
46
  ```
97
47
 
98
48
  ---
99
49
 
100
50
  ## Integration Options
101
51
 
102
- ### Option 1: With FLUSYS ng-auth (Recommended)
103
-
104
- ng-auth provides pre-built adapters for all required interfaces.
52
+ ### With FLUSYS ng-auth (Recommended)
105
53
 
106
54
  ```typescript
107
- // app.config.ts
108
55
  import { provideAuthProviders } from '@flusys/ng-auth';
109
-
110
56
  export const appConfig: ApplicationConfig = {
111
- providers: [
112
- provideAuthProviders(), // Registers all adapters
113
- ],
57
+ providers: [provideAuthProviders()], // Registers all adapters
114
58
  };
115
59
  ```
116
60
 
117
- **Reference adapters in ng-auth:**
118
-
119
- | Adapter | Interface | Path |
120
- |---------|-----------|------|
121
- | `AuthUserProviderAdapter` | `IUserProvider` | `projects/ng-auth/adapters/` |
122
- | `AuthLayoutStateAdapter` | `ILayoutAuthState` | `projects/ng-auth/adapters/` |
123
- | `AuthCompanyApiProviderAdapter` | `ICompanyApiProvider` | `projects/ng-auth/adapters/` |
124
- | `AuthUserPermissionProviderAdapter` | `IUserPermissionProvider` | `projects/ng-auth/adapters/` |
125
-
126
- ### Option 2: With Custom Auth System
127
-
128
- Implement the required interfaces with your own auth system. No ng-auth dependency needed.
61
+ ### With Custom Auth System
129
62
 
130
63
  ```typescript
131
- // Minimum configuration (basic IAM)
132
64
  providers: [
133
65
  { provide: USER_PROVIDER, useClass: YourUserProvider },
134
66
  { provide: LAYOUT_AUTH_STATE, useClass: YourCompanyContextProvider },
135
- ]
136
-
137
- // Full configuration (company + branch permissions)
138
- providers: [
139
- { provide: USER_PROVIDER, useClass: YourUserProvider },
140
- { provide: LAYOUT_AUTH_STATE, useClass: YourCompanyContextProvider },
141
- { provide: COMPANY_API_PROVIDER, useClass: YourCompanyApiProvider },
142
- { provide: USER_PERMISSION_PROVIDER, useClass: YourUserPermissionProvider },
67
+ { provide: COMPANY_API_PROVIDER, useClass: YourCompanyApiProvider }, // Optional
68
+ { provide: USER_PERMISSION_PROVIDER, useClass: YourUserPermissionProvider }, // Optional
143
69
  ]
144
70
  ```
145
71
 
146
- **Feature matrix:**
147
-
148
72
  | Feature | Providers Needed |
149
73
  |---------|-----------------|
150
74
  | Basic role assignments | USER_PROVIDER only |
151
75
  | Company-scoped permissions | + LAYOUT_AUTH_STATE, COMPANY_API_PROVIDER |
152
76
  | Branch-scoped permissions | + LAYOUT_AUTH_STATE, USER_PERMISSION_PROVIDER |
153
- | Direct user permissions | + USER_PERMISSION_PROVIDER |
154
-
155
- All adapters must return `IListResponse<T>` or `ISingleResponse<T>` format from `@flusys/ng-shared`.
156
77
 
157
78
  ---
158
79
 
159
80
  ## Permission Modes
160
81
 
161
- FLUSYS IAM supports three permission modes configured via `APP_CONFIG.iam.permissionMode`:
82
+ Configured via `APP_CONFIG.iam.permissionMode`:
162
83
 
163
84
  | Mode | Structure | Tabs Available | Use Case |
164
85
  |------|-----------|---------------|----------|
165
- | **DIRECT** | User -> Actions | User-Actions | Small apps, per-user permissions |
166
- | **RBAC** | User -> Role -> Actions | Role-Actions, User-Roles | Standard apps, role templates |
167
- | **FULL** | Both combined | Role-Actions, User-Roles, User-Actions | Maximum flexibility |
86
+ | **DIRECT** | User -> Actions | User-Actions | Small apps |
87
+ | **RBAC** | User -> Role -> Actions | Role-Actions, User-Roles | Standard apps |
88
+ | **FULL** | Both combined | All tabs | Maximum flexibility |
168
89
 
169
- When company feature is enabled, a **Company-Actions** tab is added for whitelisting actions per company.
90
+ When company feature is enabled, a **Company-Actions** tab is added for whitelisting.
170
91
 
171
92
  ---
172
93
 
@@ -175,26 +96,21 @@ When company feature is enabled, a **Company-Actions** tab is added for whitelis
175
96
  **Export:** `IAM_ROUTES` from `@flusys/ng-iam`
176
97
 
177
98
  ```
178
- /iam (IamContainerComponent - tabbed navigation)
179
- /actions (ActionListPageComponent - hierarchical tree)
180
- /actions/new (ActionFormPageComponent - create)
181
- /actions/:id (ActionFormPageComponent - edit)
182
- /roles (RoleListPageComponent - paginated table)
183
- /roles/new (RoleFormPageComponent - create)
184
- /roles/:id (RoleFormPageComponent - edit)
185
- /permissions (PermissionPageComponent - tabbed assignment UI)
186
- (default redirect -> /actions)
99
+ /iamIamContainerComponent (tabbed navigation)
100
+ /actionsActionListPageComponent (hierarchical tree)
101
+ /actions/newActionFormPageComponent (create)
102
+ /actions/:idActionFormPageComponent (edit)
103
+ /rolesRoleListPageComponent (paginated table)
104
+ /roles/newRoleFormPageComponent (create)
105
+ /roles/:idRoleFormPageComponent (edit)
106
+ /permissionsPermissionPageComponent (tabbed assignment UI)
187
107
  ```
188
108
 
189
- **Usage in app routes:**
190
-
191
- ```typescript
192
- import { IAM_ROUTES } from '@flusys/ng-iam';
193
-
194
- export const routes: Routes = [
195
- { path: 'iam', children: IAM_ROUTES },
196
- ];
197
- ```
109
+ | Route | Guard | Permission |
110
+ |-------|-------|------------|
111
+ | `/actions` | permissionGuard | `ACTION_PERMISSIONS.READ` |
112
+ | `/roles` | permissionGuard | `ROLE_PERMISSIONS.READ` |
113
+ | `/permissions` | anyPermissionGuard | Multiple READ permissions |
198
114
 
199
115
  ---
200
116
 
@@ -202,139 +118,84 @@ export const routes: Routes = [
202
118
 
203
119
  ### PermissionStateService
204
120
 
205
- Manages current user's permissions with signal-based state. Provided at root level.
121
+ **File:** `services/permission-state.service.ts` | Provided at root level
206
122
 
207
- **Dependencies:** `MyPermissionsApiService`, `PermissionValidatorService` (from ng-shared)
123
+ Manages current user's permissions with signal-based state.
208
124
 
209
- **Signals:**
210
-
211
- | Signal | Type | Description |
212
- |--------|------|-------------|
125
+ | API | Type | Description |
126
+ |-----|------|-------------|
213
127
  | `permissions` | `Signal<IMyPermissionsResponseDto \| null>` | Current user permissions (readonly) |
214
128
  | `isLoading` | `Signal<boolean>` | Loading state (readonly) |
129
+ | `loadPermissions(dto?)` | `Observable<void>` | Load from `/iam/permissions/my-permissions` |
130
+ | `isLoaded()` | `boolean` | Check if permissions loaded |
215
131
 
216
- **Methods:**
217
-
218
- | Method | Returns | Description |
219
- |--------|---------|-------------|
220
- | `loadPermissions(dto?)` | `Observable<void>` | Load permissions from `/iam/permissions/my-permissions` |
221
- | `isLoaded()` | `boolean` | Check if permissions have been loaded |
222
-
223
- Permission checking is delegated to `PermissionValidatorService` from ng-shared (hasAction, hasAllActions, hasAnyAction, getActionCodes).
132
+ Permission checking delegated to `PermissionValidatorService` from ng-shared.
224
133
 
225
134
  ```typescript
226
- const permissionState = inject(PermissionStateService);
227
-
228
- // Load permissions (typically in guard)
229
- await firstValueFrom(permissionState.loadPermissions());
230
-
231
- // Check permissions (via PermissionValidatorService)
232
- const validator = inject(PermissionValidatorService);
233
- validator.hasAction('user.create');
234
- validator.hasAllActions(['user.create', 'user.update']);
235
- validator.hasAnyAction(['user.view', 'user.list']);
135
+ await firstValueFrom(permissionState.loadPermissions()); // Load in guard
136
+ validator.hasAction('user.create'); // Check permission
137
+ validator.hasAllActions(['user.create', 'user.update']); // Check multiple
236
138
  ```
237
139
 
238
140
  ---
239
141
 
240
142
  ### ActionApiService
241
143
 
242
- Extends `ApiResourceService<IUpdateActionDto, IAction>` for action CRUD.
144
+ **File:** `services/action-api.service.ts` | Extends `ApiResourceService<IUpdateActionDto, IAction>`
243
145
 
244
146
  **Resource:** `iam/actions`
245
147
 
246
- **Additional Methods:**
247
-
248
148
  | Method | Endpoint | Description |
249
149
  |--------|----------|-------------|
250
- | `getTree(search?, isActive?, withDeleted?)` | `POST /iam/actions/tree` | Get hierarchical action tree |
251
- | `getActionsForPermission()` | `POST /iam/actions/tree-for-permission` | Get actions filtered by company whitelist |
150
+ | `getTree(search?, isActive?, withDeleted?)` | `POST /iam/actions/tree` | Hierarchical action tree |
151
+ | `getActionsForPermission()` | `POST /iam/actions/tree-for-permission` | Actions filtered by company whitelist |
252
152
 
253
- Inherits standard CRUD from `ApiResourceService`: `getAll`, `getById`, `insert`, `update`, `delete`.
153
+ Inherits standard CRUD: `getAll`, `getById`, `insert`, `update`, `delete`.
254
154
 
255
155
  ---
256
156
 
257
157
  ### RoleApiService
258
158
 
259
- Extends `ApiResourceService<IUpdateRoleDto, IRole>` for role CRUD.
260
-
261
- **Resource:** `iam/roles`
159
+ **File:** `services/role-api.service.ts` | Extends `ApiResourceService<IUpdateRoleDto, IRole>`
262
160
 
263
- Inherits standard CRUD from `ApiResourceService`. Only available in RBAC/FULL mode.
161
+ **Resource:** `iam/roles` | Only available in RBAC/FULL mode.
264
162
 
265
163
  ---
266
164
 
267
165
  ### PermissionApiService
268
166
 
269
- Extends `BaseApiService` for all permission assignment operations.
167
+ **File:** `services/permission-api.service.ts` | Extends `BaseApiService`
270
168
 
271
- **Base:** `super('iam')` - all paths resolved via `this.getUrl(...)` relative to `/iam/`
272
-
273
- **User -> Action (Direct Permissions):**
169
+ All endpoints use POST (POST-only RPC convention).
274
170
 
275
171
  | Method | Endpoint | Description |
276
172
  |--------|----------|-------------|
277
173
  | `assignUserActions(data)` | `POST /iam/permissions/user-actions/assign` | Assign/remove actions for user |
278
- | `getUserActions(userId, query?)` | `GET /iam/permissions/user-actions/{userId}` | Get user's direct actions |
279
-
280
- **User -> Role:**
281
-
282
- | Method | Endpoint | Description |
283
- |--------|----------|-------------|
174
+ | `getUserActions(userId, query?)` | `POST /iam/permissions/get-user-actions` | Get user's direct actions |
284
175
  | `assignUserRoles(data)` | `POST /iam/permissions/user-roles/assign` | Assign/remove roles for user |
285
- | `getUserRoles(userId, query?)` | `GET /iam/permissions/user-roles/{userId}` | Get user's roles |
286
-
287
- **Role -> Action:**
288
-
289
- | Method | Endpoint | Description |
290
- |--------|----------|-------------|
176
+ | `getUserRoles(userId, query?)` | `POST /iam/permissions/get-user-roles` | Get user's roles |
291
177
  | `assignRoleActions(data)` | `POST /iam/permissions/role-actions/assign` | Assign/remove actions for role |
292
- | `getRoleActions(roleId, query?)` | `GET /iam/permissions/role-actions/{roleId}` | Get role's actions |
293
-
294
- **Company -> Action (Whitelisting):**
295
-
296
- | Method | Endpoint | Description |
297
- |--------|----------|-------------|
178
+ | `getRoleActions(roleId, query?)` | `POST /iam/permissions/get-role-actions` | Get role's actions |
298
179
  | `assignCompanyActions(data)` | `POST /iam/permissions/company-actions/assign` | Assign/remove company whitelist |
299
- | `getCompanyActions(companyId)` | `GET /iam/permissions/company-actions/{companyId}` | Get company's whitelisted actions |
300
-
301
- > **Note:** Read operations use GET (exception to the POST-only RPC convention). Write operations use POST.
302
-
303
- ---
304
-
305
- ### MyPermissionsApiService
306
-
307
- Fetches current user's permissions.
308
-
309
- **Method:**
310
-
311
- | Method | Endpoint | Description |
312
- |--------|----------|-------------|
313
- | `getMyPermissions(dto?)` | `POST /iam/permissions/my-permissions` | Get current user's frontend actions and cached endpoint count |
180
+ | `getCompanyActions(companyId)` | `POST /iam/permissions/get-company-actions` | Get company's whitelisted actions |
314
181
 
315
182
  ---
316
183
 
317
184
  ### ActionPermissionLogicService
318
185
 
319
- Manages smart prerequisite validation using AND/OR logic trees. Uses PrimeNG `ConfirmationService` and `MessageService` for dialogs.
186
+ **File:** `services/action-permission-logic.service.ts`
320
187
 
321
- **Core Methods:**
188
+ Manages smart prerequisite validation using AND/OR logic trees.
322
189
 
323
190
  | Method | Description |
324
191
  |--------|-------------|
325
- | `handleCheck(action, selection, allActions, onUpdate, onCancel)` | Handle action selection with recursive prerequisite validation |
192
+ | `handleCheck(action, selection, allActions, onUpdate, onCancel)` | Handle selection with prerequisite validation |
326
193
  | `handleUncheck(action, selection, allActions, onUpdate)` | Handle deselection with dependency detection |
327
194
  | `hasUnmetPrerequisites(action, selection, allActions)` | Check if action has unmet prerequisites |
328
- | `getActionsWithUnmetPrerequisites(selection, allActions)` | Get all selected actions with unmet prerequisites |
329
- | `showValidationErrorDialog(invalid, selection, allActions, onUpdate)` | Pre-save validation error dialog with auto-fix |
195
+ | `showValidationErrorDialog(invalid, selection, allActions, onUpdate)` | Pre-save validation with auto-fix |
330
196
  | `getPrerequisiteTooltip(action, selection, allActions)` | Get tooltip text with prerequisite info |
331
197
 
332
- **Features:**
333
- - Recursive deep-scan for cascading prerequisites
334
- - Smart AND/OR optimization (selects minimum required actions)
335
- - Visual formatting with status indicators
336
- - Alternative suggestions for OR logic branches
337
- - Pre-save validation with auto-fix capability
198
+ Features: Recursive deep-scan, smart AND/OR optimization, alternative suggestions, pre-save validation.
338
199
 
339
200
  ---
340
201
 
@@ -342,257 +203,99 @@ Manages smart prerequisite validation using AND/OR logic trees. Uses PrimeNG `Co
342
203
 
343
204
  ### LogicBuilderComponent
344
205
 
345
- Visual AND/OR permission logic tree editor.
206
+ **Selector:** `lib-logic-builder` | Visual AND/OR permission logic tree editor.
346
207
 
347
- | Property | Value |
348
- |----------|-------|
349
- | Selector | `lib-logic-builder` |
350
- | Standalone | Yes |
351
- | Change Detection | OnPush |
208
+ | Input/Output | Type | Description |
209
+ |--------------|------|-------------|
210
+ | `logic` (input) | `ILogicNode \| null` | Current permission logic tree |
211
+ | `actions` (input) | `Array<{id, name}>` | Available actions for dropdowns |
212
+ | `logicChange` (output) | `ILogicNode \| null` | Emitted when logic changes |
352
213
 
353
- **Inputs:**
214
+ ---
354
215
 
355
- | Input | Type | Description |
356
- |-------|------|-------------|
357
- | `logic` | `ILogicNode \| null` | Current permission logic tree |
358
- | `actions` | `Array<{id, name}>` | Available actions for dropdowns |
216
+ ### Selector Components (Common Pattern)
359
217
 
360
- **Outputs:**
218
+ All selector components share a common pattern with these signals:
361
219
 
362
- | Output | Type | Description |
363
- |--------|------|-------------|
364
- | `logicChange` | `ILogicNode \| null` | Emitted when logic changes |
220
+ | Signal | Description |
221
+ |--------|-------------|
222
+ | `selectedXxxId` | Selected entity ID (user/role/company) |
223
+ | `treeNodes` / `items` | Available items (TreeNode[] or flat array) |
224
+ | `flatActions` | Flattened action list (for action selectors) |
225
+ | `selectedIds` | Currently selected IDs (Set) |
226
+ | `originalSelectedIds` | Original state for change tracking |
227
+ | `isLoading` / `isSaving` | Loading/saving states |
228
+ | `pendingAdd` / `pendingRemove` | Computed: items to add/remove |
229
+ | `hasChanges` | Computed: whether there are pending changes |
365
230
 
366
- **Features:** Recursive tree builder, AND/OR operator toggling, action selector dropdowns, node add/remove.
231
+ Common features: AbortController for request cancellation, responsive tables with `overflow-x-auto`, change tracking, select/deselect all.
367
232
 
368
233
  ---
369
234
 
370
235
  ### UserActionSelectorComponent
371
236
 
372
- Assigns actions directly to users (DIRECT/FULL mode).
373
-
374
- | Property | Value |
375
- |----------|-------|
376
- | Selector | `flusys-user-action-selector` |
377
- | Standalone | Yes |
378
- | Change Detection | OnPush |
379
-
380
- **Dependencies:** `APP_CONFIG`, `LAYOUT_AUTH_STATE`, `USER_PERMISSION_PROVIDER`, `ActionApiService`, `ActionPermissionLogicService`, `PermissionApiService`, `MessageService`
237
+ **Selector:** `flusys-user-action-selector` | Assigns actions directly to users (DIRECT/FULL mode).
381
238
 
382
- **Features:**
383
- - User dropdown (`lib-user-select` component from ng-shared)
384
- - Branch selector (conditional, loaded from USER_PERMISSION_PROVIDER)
239
+ - User dropdown (`lib-user-select` from ng-shared)
240
+ - Branch selector (conditional, from USER_PERMISSION_PROVIDER)
385
241
  - Hierarchical TreeTable with checkboxes
386
- - Responsive table with `overflow-x-auto` container
387
- - Responsive columns (Code hidden on mobile, Description hidden until lg)
388
- - Action type tags (backend/frontend/both)
389
242
  - Prerequisite validation with tooltips
390
- - Change tracking with pending add/remove summary
391
- - Select/deselect all
392
- - Pre-save validation
393
243
 
394
244
  ---
395
245
 
396
246
  ### UserRoleSelectorComponent
397
247
 
398
- Assigns roles to users (RBAC/FULL mode).
248
+ **Selector:** `flusys-user-role-selector` | Assigns roles to users (RBAC/FULL mode).
399
249
 
400
- | Property | Value |
401
- |----------|-------|
402
- | Selector | `flusys-user-role-selector` |
403
- | Standalone | Yes |
404
- | Change Detection | OnPush |
405
-
406
- **Dependencies:** `APP_CONFIG`, `LAYOUT_AUTH_STATE`, `USER_PERMISSION_PROVIDER`, `RoleApiService`, `PermissionApiService`, `MessageService`
407
-
408
- **Features:**
409
- - User dropdown (`lib-user-select` component from ng-shared)
410
- - Branch selector (conditional)
411
- - DataTable with pagination and `overflow-x-auto` container
412
- - Responsive columns (Code hidden on mobile, Description hidden until md)
413
- - Change tracking with pending add/remove summary
414
- - Select/deselect all
250
+ - User dropdown with branch selector
251
+ - DataTable with pagination
415
252
  - No prerequisite validation (roles are simple assignments)
416
253
 
417
254
  ---
418
255
 
419
256
  ### RoleActionSelectorComponent
420
257
 
421
- Assigns actions to roles (RBAC/FULL mode).
422
-
423
- | Property | Value |
424
- |----------|-------|
425
- | Selector | `flusys-role-action-selector` |
426
- | Standalone | Yes |
427
- | Change Detection | OnPush |
258
+ **Selector:** `flusys-role-action-selector` | Assigns actions to roles (RBAC/FULL mode).
428
259
 
429
- **Dependencies:** `RoleApiService`, `ActionApiService`, `PermissionApiService`, `MessageService`, `ActionPermissionLogicService`
430
-
431
- **Features:**
432
260
  - Role dropdown with filter
433
- - Hierarchical TreeTable with checkboxes and `overflow-x-auto` container
434
- - Responsive columns (Code hidden until md, Requirements hidden until lg)
435
- - Prerequisite validation with smart dependency handling
436
- - Request cancellation with AbortController
437
- - Pre-save validation with auto-fix dialog
438
- - Change tracking with pending add/remove summary
261
+ - Hierarchical TreeTable with checkboxes
262
+ - Prerequisite validation with auto-fix dialog
439
263
 
440
264
  ---
441
265
 
442
266
  ### CompanyActionSelectorComponent
443
267
 
444
- Manages company action whitelisting (when company feature enabled).
268
+ **Selector:** `flusys-company-action-selector` | Manages company action whitelisting.
445
269
 
446
- | Property | Value |
447
- |----------|-------|
448
- | Selector | `flusys-company-action-selector` |
449
- | Standalone | Yes |
450
- | Change Detection | OnPush |
451
-
452
- **Dependencies:** `COMPANY_API_PROVIDER`, `ActionApiService`, `PermissionApiService`, `MessageService`, `ConfirmationService`, `ActionPermissionLogicService`
453
-
454
- **Features:**
455
- - Company dropdown with filter (loaded from COMPANY_API_PROVIDER)
456
- - Full action tree (not filtered by company whitelist)
457
- - Hierarchical TreeTable with `overflow-x-auto` container
458
- - Responsive columns (Code hidden until md, Description hidden until lg)
459
- - Prerequisite validation with tooltips
270
+ - Company dropdown (from COMPANY_API_PROVIDER)
271
+ - Full action tree (not filtered)
460
272
  - Backend prerequisite error handling with auto-fix
461
- - Request cancellation with AbortController
462
- - Change tracking with pending whitelist/remove summary
463
273
 
464
274
  ---
465
275
 
466
276
  ## Page Components
467
277
 
468
- > Page components are **not exported** from the public API. They are internal and accessed via `IAM_ROUTES` (lazy-loaded by the router).
469
-
470
- ### IamContainerComponent
471
-
472
- Tabbed navigation container for IAM pages.
473
-
474
- | Property | Value |
475
- |----------|-------|
476
- | Selector | `lib-iam-container` |
477
- | Tabs | Actions (always), Roles (RBAC/FULL), Permissions (always) |
478
- | Change Detection | OnPush |
479
-
480
- **Features:**
481
- - Responsive page title (`text-xl sm:text-2xl`)
482
- - Horizontally scrollable tabs on mobile (hidden scrollbar)
483
- - Uses `RouterLink` and `RouterLinkActive` for navigation
484
- - Tab visibility based on permission mode
485
-
486
- **Responsive Patterns:**
487
-
488
- ```html
489
- <!-- Page title -->
490
- <h1 class="text-xl sm:text-2xl font-bold m-0">Identity & Access Management</h1>
491
-
492
- <!-- Horizontally scrollable tabs (hidden scrollbar) -->
493
- <div class="scrollbar-hide flex gap-1 overflow-x-auto flex-nowrap">
494
- <!-- tabs -->
495
- </div>
496
- ```
497
-
498
- **Scrollbar hide CSS:**
499
- ```css
500
- .scrollbar-hide {
501
- -ms-overflow-style: none;
502
- scrollbar-width: none;
503
- &::-webkit-scrollbar { display: none; }
504
- }
505
- ```
506
-
507
- ---
508
-
509
- ### ActionListPageComponent
510
-
511
- Hierarchical action list with CRUD operations.
512
-
513
- | Property | Value |
514
- |----------|-------|
515
- | Selector | `lib-action-list-page` |
516
- | Features | TreeTable display, create/edit/delete, read-only indicators, company context awareness |
517
-
518
- **Responsive features:**
519
- - TreeTable with `overflow-x-auto -mx-4 sm:mx-0` container
520
- - Responsive columns: Code (hidden until md), Active (hidden until sm), Read Only (hidden until lg)
521
- - Responsive header with stacked layout on mobile
522
-
523
- Reloads actions when company context changes via effect.
524
-
525
- ---
526
-
527
- ### ActionFormPageComponent
528
-
529
- Action create/edit form with LogicBuilder integration.
530
-
531
- | Property | Value |
532
- |----------|-------|
533
- | Selector | `lib-action-form-page` |
534
- | Features | Create/edit mode, parent action selector, ActionType enum, permissionLogic editor, display order, active flag |
278
+ Page components are **internal** (not exported). Accessed via `IAM_ROUTES`.
535
279
 
536
- **Form fields:** name (required), description, code, actionType, permissionLogic, parentId, serial, isActive, metadata.
537
-
538
- ---
539
-
540
- ### RoleListPageComponent
541
-
542
- Paginated role list with CRUD operations.
543
-
544
- | Property | Value |
545
- |----------|-------|
546
- | Selector | `lib-role-list-page` |
547
- | Features | Paginated table (10/25/50), create/edit/delete, company context filtering, read-only indicators |
548
-
549
- **Responsive features:**
550
- - Table with `overflow-x-auto -mx-4 sm:mx-0` container
551
- - Responsive columns: Description (hidden until md), Read Only (hidden until sm)
552
- - Conditional paginator (only shown when data exists)
553
-
554
- ---
555
-
556
- ### RoleFormPageComponent
557
-
558
- Role create/edit form.
559
-
560
- | Property | Value |
561
- |----------|-------|
562
- | Selector | `lib-role-form-page` |
563
- | Features | Create/edit mode, company auto-assignment on create, active flag, display order |
564
-
565
- **Form fields:** name (required), description, serial, isActive. Company assigned automatically from context.
566
-
567
- ---
568
-
569
- ### PermissionPageComponent
570
-
571
- Main permission management interface with tabbed layout.
572
-
573
- | Property | Value |
574
- |----------|-------|
575
- | Selector | `lib-permission-page` |
576
- | Features | Dynamic tabs based on permission mode and company feature |
577
-
578
- **Tabs:**
579
- - Role-Actions (RBAC/FULL) - `RoleActionSelectorComponent`
580
- - User-Roles (RBAC/FULL) - `UserRoleSelectorComponent`
581
- - User-Actions (DIRECT/FULL) - `UserActionSelectorComponent`
582
- - Company-Actions (if company feature enabled) - `CompanyActionSelectorComponent`
280
+ | Component | Selector | Features |
281
+ |-----------|----------|----------|
282
+ | `IamContainerComponent` | `lib-iam-container` | Tabbed navigation, responsive tabs |
283
+ | `ActionListPageComponent` | `lib-action-list-page` | TreeTable, CRUD, read-only indicators |
284
+ | `ActionFormPageComponent` | `lib-action-form-page` | Signal forms, LogicBuilder integration |
285
+ | `RoleListPageComponent` | `lib-role-list-page` | Paginated table (10/25/50), CRUD |
286
+ | `RoleFormPageComponent` | `lib-role-form-page` | Signal forms, company auto-assignment |
287
+ | `PermissionPageComponent` | `lib-permission-page` | Dynamic tabs based on permission mode |
583
288
 
584
289
  ---
585
290
 
586
291
  ## Interfaces
587
292
 
588
- ### ActionType (Enum)
293
+ ### Core Types
589
294
 
590
295
  ```typescript
591
- enum ActionType {
592
- BACKEND = 'backend', // API endpoint permissions (cached by PermissionGuard)
593
- FRONTEND = 'frontend', // UI feature permissions (returned in my-permissions)
594
- BOTH = 'both', // Both backend and frontend
595
- }
296
+ enum ActionType { BACKEND = 'backend', FRONTEND = 'frontend', BOTH = 'both' }
297
+ type PermissionAction = 'add' | 'remove';
298
+ type PermissionMode = 'rbac' | 'direct' | 'full';
596
299
  ```
597
300
 
598
301
  ### IAction (extends IBaseEntity)
@@ -605,39 +308,12 @@ interface IAction extends IBaseEntity {
605
308
  code: string | null;
606
309
  actionType: ActionType;
607
310
  permissionLogic: ILogicNode | null; // AND/OR prerequisite rules
608
- parentId: string | null; // For hierarchy
609
- serial: number | null; // Display order
311
+ parentId: string | null;
312
+ serial: number | null;
610
313
  isActive: boolean;
611
- metadata: Record<string, any> | null;
612
- }
613
- ```
614
-
615
- ### IActionTreeDto (extends IAction)
616
-
617
- ```typescript
618
- interface IActionTreeDto extends IAction {
619
- children: IActionTreeDto[];
620
- }
621
- ```
622
-
623
- ### ICreateActionDto / IUpdateActionDto
624
-
625
- ```typescript
626
- interface ICreateActionDto {
627
- name: string;
628
- description?: string;
629
- code?: string;
630
- actionType?: ActionType;
631
- permissionLogic?: ILogicNode;
632
- parentId?: string;
633
- serial?: number;
634
- isActive?: boolean;
635
- metadata?: Record<string, any>;
636
- }
637
-
638
- interface IUpdateActionDto extends Partial<ICreateActionDto> {
639
- id: string;
314
+ metadata: Record<string, unknown> | null;
640
315
  }
316
+ interface IActionTreeDto extends IAction { children: IActionTreeDto[]; }
641
317
  ```
642
318
 
643
319
  ### IRole (extends IBaseEntity)
@@ -650,159 +326,37 @@ interface IRole extends IBaseEntity {
650
326
  companyId: string | null;
651
327
  isActive: boolean;
652
328
  serial: number | null;
653
- metadata: Record<string, any> | null;
329
+ metadata: Record<string, unknown> | null;
654
330
  }
655
331
  ```
656
332
 
657
- ### ICreateRoleDto / IUpdateRoleDto / IRoleQueryDto
333
+ ### Assignment DTOs
658
334
 
659
335
  ```typescript
660
- interface ICreateRoleDto {
661
- name: string;
662
- description?: string;
663
- companyId?: string;
664
- isActive?: boolean;
665
- serial?: number;
666
- metadata?: Record<string, any>;
667
- }
668
-
669
- interface IUpdateRoleDto extends Partial<ICreateRoleDto> {
670
- id: string;
671
- }
672
-
673
- interface IRoleQueryDto {
674
- companyId?: string;
675
- isActive?: boolean;
676
- }
677
- ```
678
-
679
- ### Permission Interfaces
680
-
681
- ```typescript
682
- type PermissionAction = 'add' | 'remove';
683
- type PermissionMode = 'rbac' | 'direct' | 'full';
684
-
685
- interface IPermissionItemDto {
686
- id: string;
687
- action: PermissionAction;
688
- }
336
+ interface IPermissionItemDto { id: string; action: PermissionAction; }
689
337
 
690
- // Assignment DTOs
691
338
  interface IAssignUserActionsDto {
692
- userId: string;
693
- companyId?: string;
694
- branchId?: string; // null = company-wide
695
- items: IPermissionItemDto[];
339
+ userId: string; companyId?: string; branchId?: string; items: IPermissionItemDto[];
696
340
  }
697
-
698
- interface IAssignCompanyActionsDto {
699
- companyId: string;
700
- items: IPermissionItemDto[];
701
- }
702
-
703
- interface IAssignRoleActionsDto {
704
- roleId: string;
705
- items: IPermissionItemDto[];
706
- }
707
-
341
+ interface IAssignRoleActionsDto { roleId: string; items: IPermissionItemDto[]; }
342
+ interface IAssignCompanyActionsDto { companyId: string; items: IPermissionItemDto[]; }
708
343
  interface IAssignUserRolesDto {
709
- userId: string;
710
- companyId?: string;
711
- branchId?: string;
712
- items: IPermissionItemDto[];
344
+ userId: string; companyId?: string; branchId?: string; items: IPermissionItemDto[];
713
345
  }
714
346
  ```
715
347
 
716
348
  ### Response DTOs
717
349
 
718
- ```typescript
719
- interface IUserActionResponseDto {
720
- id: string;
721
- userId: string;
722
- actionId: string;
723
- actionCode: string;
724
- actionName: string;
725
- branchId: string | null;
726
- createdAt: Date;
727
- }
728
-
729
- interface IRoleActionResponseDto {
730
- id: string;
731
- roleId: string;
732
- actionId: string;
733
- actionCode: string;
734
- actionName: string;
735
- createdAt: Date;
736
- }
737
-
738
- interface ICompanyActionResponseDto {
739
- id: string;
740
- companyId: string;
741
- actionId: string;
742
- actionCode: string;
743
- actionName: string;
744
- createdAt: Date;
745
- }
746
-
747
- interface IUserRoleResponseDto {
748
- id: string;
749
- userId: string;
750
- roleId: string;
751
- roleCode: string;
752
- roleName: string;
753
- branchId: string | null; // Always null - roles are NOT branch-scoped
754
- createdAt: Date;
755
- }
756
- ```
757
-
758
- ### My Permissions Response
350
+ All response DTOs include: `id`, entity reference IDs, `actionCode`/`roleCode`, `actionName`/`roleName`, `createdAt`.
759
351
 
760
352
  ```typescript
761
353
  interface IMyPermissionsResponseDto {
762
354
  frontendActions: Array<{ id: string; code: string; name: string; description: string }>;
763
355
  cachedEndpoints: number; // Endpoint count for backend PermissionGuard
764
356
  }
765
- ```
766
-
767
- ### Menu & Logic Interfaces
768
-
769
- ```typescript
770
- interface IMenuAction {
771
- id: string;
772
- code: string;
773
- name: string;
774
- route: string | null;
775
- icon: string | null;
776
- iconType: number | null;
777
- serial: number | null;
778
- parentId: string | null;
779
- children?: IMenuAction[];
780
- }
781
-
782
- // ILogicNode is imported from @flusys/ng-shared
783
- ```
784
-
785
- ### Prerequisite Interfaces
786
-
787
- ```typescript
788
- interface IPrerequisiteActionDto {
789
- actionId: string;
790
- actionCode: string;
791
- actionName: string;
792
- }
793
-
794
- interface IPrerequisiteValidationError {
795
- actionId: string;
796
- actionCode: string;
797
- actionName: string;
798
- requiredActions: IPrerequisiteActionDto[];
799
- }
800
357
 
801
358
  interface IPermissionOperationResultDto {
802
- success: boolean;
803
- added: number;
804
- removed: number;
805
- message: string;
359
+ success: boolean; added: number; removed: number; message: string;
806
360
  prerequisiteErrors?: IPrerequisiteValidationError[];
807
361
  }
808
362
  ```
@@ -810,351 +364,195 @@ interface IPermissionOperationResultDto {
810
364
  ### Simple Models
811
365
 
812
366
  ```typescript
813
- interface IUser {
814
- id: string;
815
- name: string;
816
- email: string;
817
- }
818
-
819
- interface IBranch {
820
- id: string;
821
- name: string;
822
- companyId: string;
823
- }
824
-
825
- interface ICompany {
826
- id: string;
827
- name: string;
828
- slug?: string;
829
- }
830
- ```
831
-
832
- ### Query DTOs
833
-
834
- ```typescript
835
- interface IGetUserActionsDto {
836
- branchId?: string;
837
- }
838
-
839
- interface IGetRoleActionsDto {}
840
-
841
- interface IGetUserRolesDto {
842
- branchId?: string; // Ignored by backend
843
- }
844
-
845
- interface IGetMyPermissionsDto {
846
- parentCodes?: string[];
847
- }
367
+ interface IUser { id: string; name: string; email: string; }
368
+ interface IBranch { id: string; name: string; companyId: string; }
369
+ interface ICompany { id: string; name: string; slug?: string; }
848
370
  ```
849
371
 
850
372
  ---
851
373
 
852
374
  ## Utils & Constants
853
375
 
854
- > Utils are **internal** (not exported from the public API). They are used by components and services within the package.
376
+ Utils are **internal** (not exported from public API).
855
377
 
856
378
  ### permission-logic.util.ts
857
379
 
858
380
  | Function | Description |
859
381
  |----------|-------------|
860
- | `validateActionPrerequisites(action, selectedIds, allActions)` | Validate prerequisites respecting AND/OR. Returns `{valid, missingActions}` |
861
- | `extractRequiredActionIds(logic, allActions)` | Extract all action IDs from a logic tree |
862
- | `describePrerequisites(logic, allActions)` | Human-readable prerequisite description |
382
+ | `validateActionPrerequisites(action, selectedIds, allActions)` | Validate prerequisites respecting AND/OR |
383
+ | `extractRequiredActionIds(logic, allActions)` | Extract all action IDs from logic tree |
863
384
 
864
385
  ### tree-utils.ts
865
386
 
866
387
  | Function | Description |
867
388
  |----------|-------------|
868
389
  | `flattenTree<T>(tree)` | Flatten hierarchical tree into flat array |
869
- | `buildItemMap<T>(items)` | Build ID-to-item `Map` for O(1) lookup |
870
390
  | `buildTreeFromFlat<T>(flatList)` | Build tree from flat list using `parentId` |
871
- | `convertActionToTreeNode(actions)` | Convert `IActionTreeDto[]` to PrimeNG `TreeNode<IAction>[]` |
391
+ | `convertActionToTreeNode(actions)` | Convert to PrimeNG `TreeNode<IAction>[]` |
872
392
 
873
393
  ### Constants
874
394
 
875
395
  ```typescript
876
- export const MAX_DROPDOWN_ITEMS = 100; // Max items for dropdown lists (users, companies, roles, branches)
396
+ export const MAX_DROPDOWN_ITEMS = 100; // Max items for dropdown lists
877
397
  ```
878
398
 
879
- Exported directly as `MAX_DROPDOWN_ITEMS` from the package.
880
-
881
399
  ---
882
400
 
883
- ## Backend Integration
884
-
885
- ### Required Endpoints
886
-
887
- **Action CRUD:**
401
+ ## Responsive Table Patterns
888
402
 
889
- ```
890
- POST /iam/actions/tree # Get hierarchical action tree
891
- POST /iam/actions/tree-for-permission # Get actions filtered by company whitelist
892
- POST /iam/actions/get-all # List all actions (flat)
893
- POST /iam/actions/get/:id # Get action by ID
894
- POST /iam/actions/insert # Create action
895
- POST /iam/actions/update # Update action
896
- POST /iam/actions/delete # Delete action
897
- ```
403
+ All IAM tables use consistent Tailwind CSS patterns:
898
404
 
899
- **Role CRUD:**
405
+ ```html
406
+ <!-- Container: horizontal scroll on mobile, full-width on desktop -->
407
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
408
+ <p-treeTable [tableStyle]="{ 'min-width': '50rem' }">...</p-treeTable>
409
+ </div>
900
410
 
411
+ <!-- Responsive columns -->
412
+ <th>Name</th> <!-- Always visible -->
413
+ <th class="hidden sm:table-cell">Code</th> <!-- 640px+ -->
414
+ <th class="hidden md:table-cell">Status</th> <!-- 768px+ -->
415
+ <th class="hidden lg:table-cell">Description</th> <!-- 1024px+ -->
901
416
  ```
902
- POST /iam/roles/get-all # List roles (with pagination + company filter)
903
- POST /iam/roles/get/:id # Get role by ID
904
- POST /iam/roles/insert # Create role
905
- POST /iam/roles/update # Update role
906
- POST /iam/roles/delete # Delete role
907
- ```
908
-
909
- **Permission Assignments:**
910
417
 
911
- ```
912
- POST /iam/permissions/my-permissions # Current user's permissions
913
- POST /iam/permissions/user-actions/assign # Assign/remove user actions
914
- GET /iam/permissions/user-actions/:id # Get user's direct actions
915
- POST /iam/permissions/user-roles/assign # Assign/remove user roles
916
- GET /iam/permissions/user-roles/:id # Get user's roles
917
- POST /iam/permissions/role-actions/assign # Assign/remove role actions
918
- GET /iam/permissions/role-actions/:id # Get role's actions
919
- POST /iam/permissions/company-actions/assign # Assign/remove company whitelist
920
- GET /iam/permissions/company-actions/:id # Get company's whitelisted actions
921
- ```
922
-
923
- ### My Permissions Response Format
924
-
925
- ```json
926
- {
927
- "success": true,
928
- "data": {
929
- "frontendActions": [
930
- {
931
- "id": "uuid",
932
- "code": "user.create",
933
- "name": "Create User",
934
- "description": "Allows creating new users"
935
- }
936
- ],
937
- "cachedEndpoints": 42
938
- }
939
- }
940
- ```
418
+ | Breakpoint | Class | Visibility |
419
+ |------------|-------|------------|
420
+ | Always | (none) | Always visible |
421
+ | sm | `hidden sm:table-cell` | 640px+ |
422
+ | md | `hidden md:table-cell` | 768px+ |
423
+ | lg | `hidden lg:table-cell` | 1024px+ |
941
424
 
942
425
  ---
943
426
 
944
- ## Responsive Table Patterns
427
+ ## Angular 21 Signal Forms Pattern
945
428
 
946
- All IAM tables and tree tables use consistent responsive patterns with Tailwind CSS.
429
+ ```typescript
430
+ import { form, required } from '@angular/forms';
431
+
432
+ readonly formModel = {
433
+ name: '',
434
+ description: '',
435
+ code: '',
436
+ actionType: ActionType.FRONTEND,
437
+ parentId: null as string | null,
438
+ serial: null as number | null,
439
+ isActive: true,
440
+ permissionLogic: null as ILogicNode | null,
441
+ };
947
442
 
948
- ### Table Container Pattern
443
+ readonly actionForm = form(this.formModel, (f) => {
444
+ required(f.name, { message: 'Name is required' });
445
+ });
949
446
 
950
- ```html
951
- <!-- Wrapper enables horizontal scroll on mobile, full-width on desktop -->
952
- <div class="overflow-x-auto -mx-4 sm:mx-0">
953
- <p-treeTable
954
- [value]="treeNodes()"
955
- dataKey="id"
956
- styleClass="p-treetable-sm"
957
- [tableStyle]="{ 'min-width': '50rem' }"
958
- >
959
- <!-- ... -->
960
- </p-treeTable>
961
- </div>
447
+ readonly canSubmit = computed(() => this.actionForm.valid() && !this.isSaving());
962
448
  ```
963
449
 
964
- **Key properties:**
965
- - `overflow-x-auto` - Enables horizontal scroll when content exceeds container
966
- - `-mx-4 sm:mx-0` - Edge-to-edge on mobile, normal margins on desktop
967
- - `[tableStyle]="{ 'min-width': '50rem' }"` - Minimum width ensures consistent layout
968
-
969
- ### Responsive Column Visibility
970
-
971
- ```html
972
- <ng-template #header>
973
- <tr>
974
- <th class="w-12"><!-- checkbox --></th>
975
- <th>Name</th> <!-- Always visible -->
976
- <th class="hidden sm:table-cell">Code</th> <!-- Hidden on xs -->
977
- <th>Type</th> <!-- Always visible -->
978
- <th class="hidden md:table-cell">Status</th> <!-- Hidden until md -->
979
- <th class="hidden lg:table-cell">Description</th> <!-- Hidden until lg -->
980
- </tr>
981
- </ng-template>
982
- ```
450
+ ---
983
451
 
984
- **Breakpoint classes:**
985
- | Class | Visibility |
986
- |-------|------------|
987
- | (none) | Always visible |
988
- | `hidden sm:table-cell` | Visible at 640px+ |
989
- | `hidden md:table-cell` | Visible at 768px+ |
990
- | `hidden lg:table-cell` | Visible at 1024px+ |
452
+ ## Backend Integration
991
453
 
992
- ### Header Layout Pattern
454
+ ### Required Endpoints
993
455
 
994
- ```html
995
- <div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4">
996
- <div>
997
- <h5 class="m-0 mb-1">Section Title</h5>
998
- <p class="text-sm text-muted-color m-0">{{ count }} items available</p>
999
- </div>
1000
- <div class="flex flex-wrap gap-2">
1001
- <!-- Action buttons -->
1002
- </div>
1003
- </div>
1004
456
  ```
457
+ # Action CRUD
458
+ POST /iam/actions/tree
459
+ POST /iam/actions/tree-for-permission
460
+ POST /iam/actions/get-all
461
+ POST /iam/actions/get/:id
462
+ POST /iam/actions/insert
463
+ POST /iam/actions/update
464
+ POST /iam/actions/delete
1005
465
 
1006
- ### Dark Mode Styling
466
+ # Role CRUD
467
+ POST /iam/roles/get-all
468
+ POST /iam/roles/get/:id
469
+ POST /iam/roles/insert
470
+ POST /iam/roles/update
471
+ POST /iam/roles/delete
1007
472
 
1008
- ```css
1009
- /* Light mode */
1010
- .validation-warning {
1011
- background-color: var(--p-yellow-50, #fefce8);
1012
- border-left: 4px solid var(--p-yellow-500, #eab308);
1013
- color: var(--p-yellow-700, #a16207);
1014
- }
1015
-
1016
- /* Dark mode */
1017
- :host-context(.p-dark) .validation-warning {
1018
- background-color: rgba(234, 179, 8, 0.1);
1019
- color: var(--p-yellow-400, #facc15);
1020
- }
473
+ # Permission Assignments
474
+ POST /iam/permissions/my-permissions
475
+ POST /iam/permissions/user-actions/assign
476
+ POST /iam/permissions/get-user-actions
477
+ POST /iam/permissions/user-roles/assign
478
+ POST /iam/permissions/get-user-roles
479
+ POST /iam/permissions/role-actions/assign
480
+ POST /iam/permissions/get-role-actions
481
+ POST /iam/permissions/company-actions/assign
482
+ POST /iam/permissions/get-company-actions
1021
483
  ```
1022
484
 
1023
- **Color classes (Tailwind):**
1024
- | Purpose | Class |
1025
- |---------|-------|
1026
- | Muted text | `text-muted-color` |
1027
- | Success | `text-green-500` |
1028
- | Danger | `text-red-500` |
1029
- | Warning | `text-orange-500` |
1030
- | Primary | `text-primary` |
1031
-
1032
485
  ---
1033
486
 
1034
487
  ## Best Practices
1035
488
 
1036
489
  ### Permission Loading
1037
490
 
1038
- Load permissions in a guard to block navigation until ready:
1039
-
1040
491
  ```typescript
1041
- // appInitGuard
492
+ // In guard - block navigation until ready
1042
493
  if (!permissionState.isLoaded()) {
1043
- try {
1044
- await firstValueFrom(permissionState.loadPermissions());
1045
- } catch (error) {
1046
- console.error('Failed to load permissions:', error);
1047
- // Continue with graceful degradation - don't block the app
1048
- }
494
+ await firstValueFrom(permissionState.loadPermissions());
1049
495
  }
1050
- return true;
1051
496
  ```
1052
497
 
1053
- - Cache permissions in memory (PermissionStateService handles this)
1054
- - Clear permissions on logout
1055
- - Never store permissions in localStorage (security risk)
1056
- - Never load permissions in component `ngOnInit` (causes race conditions)
498
+ - Cache in memory (PermissionStateService handles this)
499
+ - Clear on logout
500
+ - Never store in localStorage (security risk)
501
+ - Never load in component `ngOnInit` (race conditions)
1057
502
 
1058
503
  ### Permission Checks
1059
504
 
1060
505
  ```typescript
1061
- // Single action
1062
- validator.hasAction('user.create');
1063
-
1064
- // ALL required
1065
- validator.hasAllActions(['user.create', 'user.update']);
1066
-
1067
- // ANY required
1068
- validator.hasAnyAction(['user.view', 'user.list']);
506
+ validator.hasAction('user.create'); // Single
507
+ validator.hasAllActions(['user.create', 'user.update']); // ALL required
508
+ validator.hasAnyAction(['user.view', 'user.list']); // ANY required
1069
509
  ```
1070
510
 
1071
- Cache check results in computed signals rather than calling in templates or tight loops.
511
+ Cache check results in computed signals, not in templates.
1072
512
 
1073
513
  ### Menu Filtering
1074
514
 
1075
515
  ```typescript
1076
- // Set menu with requiredAction or logicNode
1077
516
  this.layoutService.setMenu(menuItems);
1078
-
1079
- // Set permission checker (layout handles filtering automatically)
1080
- this.layoutService.setPermissionChecker((actionCode) =>
1081
- validator.hasAction(actionCode)
1082
- );
517
+ this.layoutService.setPermissionChecker((code) => validator.hasAction(code));
1083
518
  ```
1084
519
 
1085
- Do not filter menus manually - let layout handle it.
1086
-
1087
520
  ### Action Naming
1088
521
 
1089
522
  Format: `module.operation` or `module.entity.operation`
1090
523
 
1091
524
  ```typescript
1092
- // Good
1093
- 'user.create'
1094
- 'user.update'
1095
- 'report.export'
1096
- 'admin.system.config'
1097
-
1098
- // Bad
1099
- 'create' // Too generic
1100
- 'userCreate' // Not dot notation
1101
- 'users' // Entity name, not action
525
+ // Good: 'user.create', 'report.export', 'admin.system.config'
526
+ // Bad: 'create' (generic), 'userCreate' (not dot notation)
1102
527
  ```
1103
528
 
1104
529
  ### LogicNode Pattern
1105
530
 
1106
- Complex permission rules using AND/OR operators:
1107
-
1108
531
  ```typescript
1109
532
  // User needs 'user.create' AND ('user.update' OR 'user.delete')
1110
533
  const logic: ILogicNode = {
1111
534
  type: 'AND',
1112
535
  children: [
1113
536
  { type: 'ACTION', value: 'user.create' },
1114
- {
1115
- type: 'OR',
1116
- children: [
1117
- { type: 'ACTION', value: 'user.update' },
1118
- { type: 'ACTION', value: 'user.delete' },
1119
- ],
1120
- },
537
+ { type: 'OR', children: [
538
+ { type: 'ACTION', value: 'user.update' },
539
+ { type: 'ACTION', value: 'user.delete' },
540
+ ]},
1121
541
  ],
1122
542
  };
1123
543
  ```
1124
544
 
1125
545
  ---
1126
546
 
1127
- ## Common Issues
1128
-
1129
- | Problem | Cause | Solution |
1130
- |---------|-------|----------|
1131
- | `permissionState.isLoaded()` returns false | IAM not enabled or endpoint failing | Check `services.iam.enabled: true` in environment, verify `POST /iam/permissions/my-permissions` works |
1132
- | Empty menu after login | Permissions not loaded or no matching actions | Check `permissionState.isLoaded()`, verify user has frontend actions, check `requiredAction` matches action codes |
1133
- | Menu items not filtered | Permission checker not set | Call `layoutService.setPermissionChecker()` after loading permissions |
1134
- | 401 on IAM endpoints with external auth | Auth token not forwarded | Ensure your auth interceptor adds `Authorization` header to IAM requests |
1135
- | Provider not configured error | Missing DI registration | Add required providers to `app.config.ts` (USER_PROVIDER, LAYOUT_AUTH_STATE) |
1136
- | Empty user/company dropdowns | API response format mismatch | Ensure adapters return `IListResponse<T>` format with `data` array |
1137
- | TreeTable has excessive whitespace | Using `scrollHeight="flex"` without fixed-height parent | Remove `scrollHeight="flex"` and `[scrollable]="true"`, use `overflow-x-auto` container instead |
1138
- | Table not horizontally scrollable on mobile | Missing responsive wrapper | Add `<div class="overflow-x-auto -mx-4 sm:mx-0">` around table |
1139
- | Columns visible on mobile causing overflow | Missing responsive column classes | Add `hidden sm:table-cell` or `hidden md:table-cell` to non-essential columns |
1140
-
1141
- ---
1142
-
1143
547
  ## Configuration
1144
548
 
1145
- ### Frontend Environment
1146
-
1147
549
  ```typescript
1148
- // environment.ts
1149
- services: {
1150
- iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
1151
- }
550
+ // Frontend: environment.ts
551
+ services: { iam: { baseUrl: 'http://localhost:2002/iam', enabled: true } }
1152
552
  ```
1153
553
 
1154
- ### Backend Environment
1155
-
1156
554
  ```bash
1157
- # .env
555
+ # Backend: .env
1158
556
  IAM_PERMISSION_MODE=FULL # DIRECT, RBAC, or FULL
1159
557
  ```
1160
558
 
@@ -1170,60 +568,41 @@ IAM_PERMISSION_MODE=FULL # DIRECT, RBAC, or FULL
1170
568
 
1171
569
  ### provideIamProviders()
1172
570
 
1173
- Registers provider adapters that enable other packages (ng-auth) to access IAM functionality without direct dependencies.
571
+ Registers adapters for cross-package communication (e.g., ng-auth profile page).
1174
572
 
1175
573
  ```typescript
1176
574
  import { provideIamProviders } from '@flusys/ng-iam';
1177
-
1178
- // app.config.ts
1179
- export const appConfig: ApplicationConfig = {
1180
- providers: [
1181
- ...provideIamProviders(), // Register IAM providers
1182
- ],
1183
- };
575
+ providers: [...provideIamProviders()]
1184
576
  ```
1185
577
 
1186
- **Registers:**
1187
-
1188
- | Token | Adapter | Purpose |
1189
- |-------|---------|---------|
1190
- | `PROFILE_PERMISSION_PROVIDER` | `ProfilePermissionProviderAdapter` | User roles/actions for profile page |
1191
-
1192
- ### ProfilePermissionProviderAdapter
1193
-
1194
- Implements `IProfilePermissionProvider` from ng-shared. Enables ng-auth's profile page to display user permissions.
578
+ Registers `PROFILE_PERMISSION_PROVIDER` with `ProfilePermissionProviderAdapter`.
1195
579
 
1196
- ```typescript
1197
- interface IProfilePermissionProvider {
1198
- getUserRoles(userId: string, branchId?: string): Observable<ISingleResponse<IProfileRoleInfo[]>>;
1199
- getUserActions(userId: string, branchId?: string): Observable<ISingleResponse<IProfileActionInfo[]>>;
1200
- }
1201
- ```
580
+ ---
1202
581
 
1203
- **Usage in ng-auth profile page:**
582
+ ## Common Issues
1204
583
 
1205
- ```typescript
1206
- // Optional injection - graceful degradation when IAM disabled
1207
- private readonly permissionProvider = inject(PROFILE_PERMISSION_PROVIDER, { optional: true });
1208
-
1209
- // Load user permissions
1210
- if (this.permissionProvider) {
1211
- this.permissionProvider.getUserRoles(userId, branchId).subscribe(response => {
1212
- this.roles.set(response.data ?? []);
1213
- });
1214
- }
1215
- ```
584
+ | Problem | Solution |
585
+ |---------|----------|
586
+ | `permissionState.isLoaded()` returns false | Check `services.iam.enabled: true`, verify `/iam/permissions/my-permissions` works |
587
+ | Empty menu after login | Verify user has frontend actions, check `requiredAction` matches action codes |
588
+ | Menu items not filtered | Call `layoutService.setPermissionChecker()` after loading permissions |
589
+ | 401 on IAM endpoints | Ensure auth interceptor adds `Authorization` header |
590
+ | Provider not configured error | Add required providers to `app.config.ts` |
591
+ | Empty dropdowns | Ensure adapters return `IListResponse<T>` format |
592
+ | Table not scrollable on mobile | Add `overflow-x-auto -mx-4 sm:mx-0` wrapper |
593
+ | Columns overflow on mobile | Add `hidden sm:table-cell` to non-essential columns |
1216
594
 
1217
595
  ---
1218
596
 
1219
597
  ## See Also
1220
598
 
1221
- - **[CORE-GUIDE.md](./CORE-GUIDE.md)** - Configuration system (APP_CONFIG)
1222
- - **[AUTH-GUIDE.md](./AUTH-GUIDE.md)** - Authentication and provider adapters
1223
- - **[SHARED-GUIDE.md](./SHARED-GUIDE.md)** - Provider interface definitions
1224
- - **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Menu system and filtering
599
+ - [CORE-GUIDE.md](./CORE-GUIDE.md) - Configuration system (APP_CONFIG)
600
+ - [AUTH-GUIDE.md](./AUTH-GUIDE.md) - Authentication and provider adapters
601
+ - [SHARED-GUIDE.md](./SHARED-GUIDE.md) - Provider interface definitions
602
+ - [LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md) - Menu system and filtering
1225
603
 
1226
604
  ---
1227
605
 
1228
- **Last Updated:** 2026-02-21
606
+ **Last Updated:** 2026-02-25
607
+ **Version:** 3.0.0
1229
608
  **Angular Version:** 21