@flusys/ng-shared 0.1.0-beta.1 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +987 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
# @flusys/ng-shared Package Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@flusys/ng-shared` provides reusable components, directives, API services, and utilities used across all FLUSYS applications. This package extends ng-core with UI components, API integration patterns, and provider interfaces for package independence.
|
|
6
|
+
|
|
7
|
+
**Key Principle:** ng-shared depends only on ng-core, never on ng-layout or feature packages.
|
|
8
|
+
|
|
9
|
+
## Package Information
|
|
10
|
+
|
|
11
|
+
- **Package:** `@flusys/ng-shared`
|
|
12
|
+
- **Dependencies:** ng-core
|
|
13
|
+
- **Dependents:** ng-layout, ng-auth, ng-iam, ng-storage, flusysng
|
|
14
|
+
- **Build Command:** `npm run build:ng-shared`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Interfaces
|
|
19
|
+
|
|
20
|
+
### Data Interfaces
|
|
21
|
+
|
|
22
|
+
#### IBaseEntity
|
|
23
|
+
|
|
24
|
+
Base entity interface matching backend Identity entity.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
interface IBaseEntity {
|
|
28
|
+
id: string;
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
updatedAt: Date;
|
|
31
|
+
deletedAt?: Date | null;
|
|
32
|
+
createdById?: string | null;
|
|
33
|
+
updatedById?: string | null;
|
|
34
|
+
deletedById?: string | null;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Entity Mixins:**
|
|
39
|
+
|
|
40
|
+
| Interface | Fields | Purpose |
|
|
41
|
+
| --------------- | ---------------------------------- | ---------------------- |
|
|
42
|
+
| `ISoftDeletable`| `deletedAt?: Date \| null` | Soft delete support |
|
|
43
|
+
| `ITimestampable`| `createdAt, updatedAt` | Timestamp tracking |
|
|
44
|
+
| `IActivatable` | `isActive: boolean` | Active status toggle |
|
|
45
|
+
| `IOrderable` | `serial?: number \| null` | Ordering/sorting |
|
|
46
|
+
| `IMetadata` | `metadata?: Record<string, unknown>` | JSON metadata field |
|
|
47
|
+
|
|
48
|
+
#### ILoggedUserInfo
|
|
49
|
+
|
|
50
|
+
Current logged-in user info with optional company context.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
interface ILoggedUserInfo {
|
|
54
|
+
id: string;
|
|
55
|
+
email: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
phone?: string;
|
|
58
|
+
profilePictureId?: string;
|
|
59
|
+
companyId?: string;
|
|
60
|
+
branchId?: string;
|
|
61
|
+
companyLogoId?: string;
|
|
62
|
+
branchLogoId?: string;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### IFilterData
|
|
67
|
+
|
|
68
|
+
Filter, pagination, and sort payload for list queries.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface IPagination {
|
|
72
|
+
pageSize: number;
|
|
73
|
+
currentPage: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ISort { [key: string]: 'ASC' | 'DESC' }
|
|
77
|
+
interface IFilter { [key: string]: any }
|
|
78
|
+
|
|
79
|
+
interface IFilterData {
|
|
80
|
+
filter?: IFilter;
|
|
81
|
+
pagination?: IPagination;
|
|
82
|
+
select?: string[];
|
|
83
|
+
sort?: ISort;
|
|
84
|
+
withDeleted?: boolean;
|
|
85
|
+
extraKey?: string[];
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### IDeleteData
|
|
90
|
+
|
|
91
|
+
Delete request payload matching backend DeleteDto.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
type DeleteType = 'delete' | 'restore' | 'permanent';
|
|
95
|
+
|
|
96
|
+
interface IDeleteData {
|
|
97
|
+
id: string | string[]; // Single or batch delete
|
|
98
|
+
type: DeleteType;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### IDropDown
|
|
103
|
+
|
|
104
|
+
Simple dropdown item for select components.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
interface IDropDown {
|
|
108
|
+
label: string;
|
|
109
|
+
value: string;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Response Interfaces
|
|
114
|
+
|
|
115
|
+
Type-safe interfaces matching FLUSYS_NEST backend response DTOs.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Single item response (POST /resource/insert, /update, /get/:id)
|
|
119
|
+
interface ISingleResponse<T> {
|
|
120
|
+
success: boolean;
|
|
121
|
+
message: string;
|
|
122
|
+
data?: T;
|
|
123
|
+
_meta?: IRequestMeta;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// List response with pagination (POST /resource/get-all)
|
|
127
|
+
interface IListResponse<T> {
|
|
128
|
+
success: boolean;
|
|
129
|
+
message: string;
|
|
130
|
+
data?: T[];
|
|
131
|
+
meta: IPaginationMeta;
|
|
132
|
+
_meta?: IRequestMeta;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Bulk operation response (POST /resource/insert-many, /update-many)
|
|
136
|
+
interface IBulkResponse<T> {
|
|
137
|
+
success: boolean;
|
|
138
|
+
message: string;
|
|
139
|
+
data?: T[];
|
|
140
|
+
meta: IBulkMeta;
|
|
141
|
+
_meta?: IRequestMeta;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Message-only response (POST /resource/delete, /logout)
|
|
145
|
+
interface IMessageResponse {
|
|
146
|
+
success: boolean;
|
|
147
|
+
message: string;
|
|
148
|
+
_meta?: IRequestMeta;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Error response (validation errors, exceptions)
|
|
152
|
+
interface IErrorResponse {
|
|
153
|
+
success: false;
|
|
154
|
+
message: string;
|
|
155
|
+
code?: string;
|
|
156
|
+
errors?: IValidationError[];
|
|
157
|
+
_meta?: IRequestMeta;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Union type
|
|
161
|
+
type ApiResponse<T> = ISingleResponse<T> | IListResponse<T> | IBulkResponse<T> | IMessageResponse | IErrorResponse;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Metadata types:**
|
|
165
|
+
|
|
166
|
+
| Interface | Fields |
|
|
167
|
+
| ------------------ | -------------------------------------------- |
|
|
168
|
+
| `IRequestMeta` | `requestId?, timestamp?, responseTime?` |
|
|
169
|
+
| `IPaginationMeta` | `total, page, pageSize, count, hasMore?, totalPages?` |
|
|
170
|
+
| `IBulkMeta` | `count, failed?, total?` |
|
|
171
|
+
| `IValidationError` | `field, message, constraint?` |
|
|
172
|
+
|
|
173
|
+
### Auth & Storage Response Types
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
interface ILoginResponse {
|
|
177
|
+
success: boolean;
|
|
178
|
+
message: string;
|
|
179
|
+
data: { accessToken: string; refreshToken: string; user: ILoginUserData };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface IRefreshTokenResponse {
|
|
183
|
+
success: boolean;
|
|
184
|
+
message: string;
|
|
185
|
+
data: { accessToken: string; refreshToken?: string };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface IFileData {
|
|
189
|
+
id: string;
|
|
190
|
+
name: string;
|
|
191
|
+
originalName: string;
|
|
192
|
+
contentType: string;
|
|
193
|
+
size: number;
|
|
194
|
+
key: string;
|
|
195
|
+
url?: string;
|
|
196
|
+
thumbnailUrl?: string;
|
|
197
|
+
createdAt: Date;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Permission Interfaces
|
|
202
|
+
|
|
203
|
+
Discriminated union for building complex permission logic trees.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Single permission check
|
|
207
|
+
interface IActionNode {
|
|
208
|
+
type: 'action';
|
|
209
|
+
actionId: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Group with AND/OR logic
|
|
213
|
+
interface IGroupNode {
|
|
214
|
+
type: 'group';
|
|
215
|
+
operator: 'AND' | 'OR';
|
|
216
|
+
children: ILogicNode[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Union type
|
|
220
|
+
type ILogicNode = IActionNode | IGroupNode;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 2. Enums
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
enum ContactTypeEnum {
|
|
229
|
+
PHONE = 1,
|
|
230
|
+
EMAIL = 2,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
enum IconTypeEnum {
|
|
234
|
+
PRIMENG_ICON = 1,
|
|
235
|
+
IMAGE_FILE_LINK = 2,
|
|
236
|
+
DIRECT_TAG_SVG = 3,
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 3. Services
|
|
243
|
+
|
|
244
|
+
### ApiResourceService
|
|
245
|
+
|
|
246
|
+
Signal-based CRUD service using Angular 21 `resource()` API. All endpoints use POST (RPC-style).
|
|
247
|
+
|
|
248
|
+
**Define a service:**
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { Injectable } from '@angular/core';
|
|
252
|
+
import { HttpClient } from '@angular/common/http';
|
|
253
|
+
import { ApiResourceService } from '@flusys/ng-shared';
|
|
254
|
+
|
|
255
|
+
@Injectable({ providedIn: 'root' })
|
|
256
|
+
export class UserService extends ApiResourceService<UserDto, IUser> {
|
|
257
|
+
constructor(http: HttpClient) {
|
|
258
|
+
super('users', http); // Base URL: APP_CONFIG.apiBaseUrl + '/users'
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Endpoint mapping:**
|
|
264
|
+
|
|
265
|
+
| Method | HTTP | Endpoint | Response |
|
|
266
|
+
| -------------- | ---- | ------------------------- | ------------------------ |
|
|
267
|
+
| `insert(dto)` | POST | `/{resource}/insert` | `ISingleResponse<T>` |
|
|
268
|
+
| `insertMany(dtos)` | POST | `/{resource}/insert-many` | `IBulkResponse<T>` |
|
|
269
|
+
| `findById(id, select?)` | POST | `/{resource}/get/:id` | `ISingleResponse<T>` |
|
|
270
|
+
| `getAll(search, filter)` | POST | `/{resource}/get-all?q=` | `IListResponse<T>` |
|
|
271
|
+
| `update(dto)` | POST | `/{resource}/update` | `ISingleResponse<T>` |
|
|
272
|
+
| `updateMany(dtos)` | POST | `/{resource}/update-many` | `IBulkResponse<T>` |
|
|
273
|
+
| `delete(deleteDto)` | POST | `/{resource}/delete` | `IMessageResponse` |
|
|
274
|
+
|
|
275
|
+
All methods above return `Observable`. Async (Promise) variants are also available: `insertAsync`, `insertManyAsync`, `findByIdAsync`, `updateAsync`, `updateManyAsync`, `deleteAsync`.
|
|
276
|
+
|
|
277
|
+
**Reactive signals:**
|
|
278
|
+
|
|
279
|
+
| Signal | Type | Description |
|
|
280
|
+
| ------------ | ------------------------- | ------------------------ |
|
|
281
|
+
| `isLoading` | `Signal<boolean>` | Whether data is loading |
|
|
282
|
+
| `data` | `Signal<T[]>` | Current list data |
|
|
283
|
+
| `total` | `Signal<number>` | Total item count |
|
|
284
|
+
| `pageInfo` | `Signal<IPaginationMeta>` | Pagination metadata |
|
|
285
|
+
| `hasMore` | `Signal<boolean>` | More pages available |
|
|
286
|
+
| `searchTerm` | `WritableSignal<string>` | Current search term |
|
|
287
|
+
| `filterData` | `WritableSignal<IFilterData>` | Filter/pagination state |
|
|
288
|
+
|
|
289
|
+
**List management:**
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Trigger data fetch (updates searchTerm and filterData signals, resource auto-reloads)
|
|
293
|
+
userService.fetchList('search term', { pagination: { currentPage: 0, pageSize: 10 } });
|
|
294
|
+
|
|
295
|
+
// Pagination helpers
|
|
296
|
+
userService.setPagination({ currentPage: 1, pageSize: 20 });
|
|
297
|
+
userService.nextPage();
|
|
298
|
+
userService.resetPagination();
|
|
299
|
+
userService.reload();
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Component usage:**
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
@Component({...})
|
|
306
|
+
export class UserListComponent {
|
|
307
|
+
private readonly userService = inject(UserService);
|
|
308
|
+
|
|
309
|
+
readonly users = this.userService.data; // Signal<IUser[]>
|
|
310
|
+
readonly isLoading = this.userService.isLoading; // Signal<boolean>
|
|
311
|
+
readonly total = this.userService.total; // Signal<number>
|
|
312
|
+
|
|
313
|
+
constructor() {
|
|
314
|
+
this.userService.fetchList('', { pagination: { currentPage: 0, pageSize: 10 } });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async createUser(dto: UserDto) {
|
|
318
|
+
await this.userService.insertAsync(dto);
|
|
319
|
+
this.userService.reload(); // Refresh list
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
> **Note:** `ApiService` is exported as an alias for `ApiResourceService` for backward compatibility.
|
|
325
|
+
|
|
326
|
+
### FileUrlService
|
|
327
|
+
|
|
328
|
+
Fetches file URLs from the backend. Supports presigned URLs for cloud storage (S3, Azure).
|
|
329
|
+
|
|
330
|
+
**CRITICAL:** Never construct file URLs manually. Always use this service.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { FileUrlService } from '@flusys/ng-shared';
|
|
334
|
+
|
|
335
|
+
@Component({...})
|
|
336
|
+
export class ProductComponent {
|
|
337
|
+
private readonly fileUrlService = inject(FileUrlService);
|
|
338
|
+
|
|
339
|
+
loadImage(fileId: string) {
|
|
340
|
+
// Fetch from backend: POST /file-manager/get-files
|
|
341
|
+
this.fileUrlService.fetchSingleFileUrl(fileId).subscribe(file => {
|
|
342
|
+
this.imageUrl.set(file?.url ?? null);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
loadMultiple(fileIds: string[]) {
|
|
347
|
+
this.fileUrlService.fetchFileUrls(fileIds).subscribe(files => {
|
|
348
|
+
// files: FilesResponseDto[] with { id, name, contentType, url }
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Methods:**
|
|
355
|
+
|
|
356
|
+
| Method | Returns | Description |
|
|
357
|
+
| ----------------------------- | ----------------------------------- | ---------------------------------- |
|
|
358
|
+
| `getFileUrl(fileId)` | `string \| null` | Get cached URL (synchronous) |
|
|
359
|
+
| `fileUrlSignal(fileId)` | `Signal<string \| null>` | Computed signal from cache |
|
|
360
|
+
| `fetchFileUrls(fileIds[])` | `Observable<FilesResponseDto[]>` | Fetch from backend, updates cache |
|
|
361
|
+
| `fetchSingleFileUrl(fileId)` | `Observable<FilesResponseDto \| null>` | Fetch single file |
|
|
362
|
+
| `clearCache()` | `void` | Clear all cached URLs |
|
|
363
|
+
| `removeFromCache(fileId)` | `void` | Remove specific entry from cache |
|
|
364
|
+
|
|
365
|
+
### PermissionValidatorService
|
|
366
|
+
|
|
367
|
+
Signal-based permission state management. Used by `HasPermissionDirective`, permission guards, and IAM.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { PermissionValidatorService } from '@flusys/ng-shared';
|
|
371
|
+
|
|
372
|
+
@Component({...})
|
|
373
|
+
export class MyComponent {
|
|
374
|
+
private readonly permissionValidator = inject(PermissionValidatorService);
|
|
375
|
+
|
|
376
|
+
ngOnInit() {
|
|
377
|
+
// Set permissions (typically done by IAM PermissionStateService)
|
|
378
|
+
this.permissionValidator.setPermissions(['user.view', 'user.create']);
|
|
379
|
+
|
|
380
|
+
// Check single permission
|
|
381
|
+
if (this.permissionValidator.hasPermission('user.view')) {
|
|
382
|
+
// User has permission
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Check loaded state
|
|
386
|
+
if (this.permissionValidator.isPermissionsLoaded()) {
|
|
387
|
+
// Permissions have been loaded
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Methods:**
|
|
394
|
+
|
|
395
|
+
| Method | Returns | Description |
|
|
396
|
+
| ---------------------------- | --------- | ---------------------------------- |
|
|
397
|
+
| `setPermissions(codes[])` | `void` | Replace all permissions |
|
|
398
|
+
| `clearPermissions()` | `void` | Clear all permissions |
|
|
399
|
+
| `hasPermission(code)` | `boolean` | Check single permission |
|
|
400
|
+
| `isPermissionsLoaded()` | `boolean` | Whether permissions have been set |
|
|
401
|
+
|
|
402
|
+
**Signals:**
|
|
403
|
+
|
|
404
|
+
| Signal | Type | Description |
|
|
405
|
+
| ------------- | ------------------ | ------------------------------ |
|
|
406
|
+
| `permissions` | `Signal<string[]>` | Readonly current permissions |
|
|
407
|
+
|
|
408
|
+
### CookieService
|
|
409
|
+
|
|
410
|
+
SSR-aware cookie reading service.
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { CookieService } from '@flusys/ng-shared';
|
|
414
|
+
|
|
415
|
+
const cookies = inject(CookieService).get(); // Returns document.cookie (browser) or request header cookie (server)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### PlatformService
|
|
419
|
+
|
|
420
|
+
SSR environment detection service.
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import { PlatformService } from '@flusys/ng-shared';
|
|
424
|
+
|
|
425
|
+
const platform = inject(PlatformService);
|
|
426
|
+
if (!platform.isServer) {
|
|
427
|
+
// Browser-only code (localStorage, window, etc.)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 4. Components
|
|
434
|
+
|
|
435
|
+
### IconComponent
|
|
436
|
+
|
|
437
|
+
Flexible icon renderer supporting PrimeNG icons, image files, and SVG.
|
|
438
|
+
|
|
439
|
+
- **Selector:** `lib-icon`
|
|
440
|
+
- **Inputs:** `icon` (required string), `iconType` (optional `IconTypeEnum`, default: `PRIMENG_ICON`)
|
|
441
|
+
|
|
442
|
+
```html
|
|
443
|
+
<!-- PrimeNG icon (default) -->
|
|
444
|
+
<lib-icon icon="pi pi-user" />
|
|
445
|
+
|
|
446
|
+
<!-- Image file -->
|
|
447
|
+
<lib-icon icon="/assets/logo.png" [iconType]="IconTypeEnum.IMAGE_FILE_LINK" />
|
|
448
|
+
|
|
449
|
+
<!-- SVG tag -->
|
|
450
|
+
<lib-icon icon="<svg>...</svg>" [iconType]="IconTypeEnum.DIRECT_TAG_SVG" />
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### LazySelectComponent
|
|
454
|
+
|
|
455
|
+
Single-select dropdown with lazy loading, search, and scroll pagination.
|
|
456
|
+
|
|
457
|
+
- **Selector:** `lib-lazy-select`
|
|
458
|
+
- **Extends:** `BaseFormControl<string | null>`
|
|
459
|
+
- **Supports:** Template-driven, reactive forms, signal forms
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
@Component({
|
|
463
|
+
imports: [LazySelectComponent],
|
|
464
|
+
template: `
|
|
465
|
+
<lib-lazy-select
|
|
466
|
+
[(value)]="selectedId"
|
|
467
|
+
[selectDataList]="items()"
|
|
468
|
+
[optionLabel]="'label'"
|
|
469
|
+
[optionValue]="'value'"
|
|
470
|
+
[isEditMode]="true"
|
|
471
|
+
[isLoading]="loading()"
|
|
472
|
+
[total]="total()"
|
|
473
|
+
[pagination]="pagination()"
|
|
474
|
+
[placeHolder]="'Select item'"
|
|
475
|
+
(onSearch)="handleSearch($event)"
|
|
476
|
+
(onPagination)="handlePagination($event)"
|
|
477
|
+
/>
|
|
478
|
+
`,
|
|
479
|
+
})
|
|
480
|
+
export class MyComponent {
|
|
481
|
+
readonly selectedId = signal<string | null>(null);
|
|
482
|
+
readonly items = signal<IDropDown[]>([]);
|
|
483
|
+
readonly loading = signal(false);
|
|
484
|
+
readonly total = signal<number | undefined>(undefined);
|
|
485
|
+
readonly pagination = signal<IPagination>({ currentPage: 0, pageSize: 20 });
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Inputs:**
|
|
490
|
+
|
|
491
|
+
| Input | Type | Description |
|
|
492
|
+
| ---------------- | ----------------- | -------------------------------- |
|
|
493
|
+
| `selectDataList` | `Array<IDropDown>` | Dropdown options (required) |
|
|
494
|
+
| `optionLabel` | `string` | Label field name (required) |
|
|
495
|
+
| `optionValue` | `string` | Value field name (required) |
|
|
496
|
+
| `isEditMode` | `boolean` | Enable/disable editing (required)|
|
|
497
|
+
| `isLoading` | `boolean` | Loading state (required) |
|
|
498
|
+
| `total` | `number \| undefined` | Total items for pagination |
|
|
499
|
+
| `pagination` | `IPagination` | Current pagination state |
|
|
500
|
+
| `placeHolder` | `string` | Placeholder text (default: `'Select Option'`) |
|
|
501
|
+
|
|
502
|
+
**Model:** `value` - Two-way bound selected value (`string | null`)
|
|
503
|
+
|
|
504
|
+
**Outputs:** `onSearch` (debounced 500ms), `onPagination` (scroll-triggered)
|
|
505
|
+
|
|
506
|
+
### LazyMultiSelectComponent
|
|
507
|
+
|
|
508
|
+
Multi-select dropdown with lazy loading, search, select-all, and scroll pagination.
|
|
509
|
+
|
|
510
|
+
- **Selector:** `lib-lazy-multi-select`
|
|
511
|
+
- **Extends:** `BaseFormControl<string[] | null>`
|
|
512
|
+
- **Supports:** Template-driven, reactive forms, signal forms
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
@Component({
|
|
516
|
+
imports: [LazyMultiSelectComponent],
|
|
517
|
+
template: `
|
|
518
|
+
<lib-lazy-multi-select
|
|
519
|
+
[(value)]="selectedIds"
|
|
520
|
+
[selectDataList]="items()"
|
|
521
|
+
[isEditMode]="true"
|
|
522
|
+
[isLoading]="loading()"
|
|
523
|
+
[total]="total()"
|
|
524
|
+
[pagination]="pagination()"
|
|
525
|
+
[placeHolder]="'Select items'"
|
|
526
|
+
(onSearch)="handleSearch($event)"
|
|
527
|
+
(onPagination)="handlePagination($event)"
|
|
528
|
+
/>
|
|
529
|
+
`,
|
|
530
|
+
})
|
|
531
|
+
export class MyComponent {
|
|
532
|
+
readonly selectedIds = signal<string[] | null>(null);
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Inputs:** `selectDataList`, `isEditMode`, `isLoading`, `total`, `pagination`, `placeHolder` (same as `LazySelectComponent`). Does not have `optionLabel`/`optionValue` (uses `IDropDown.label`/`IDropDown.value` directly).
|
|
537
|
+
|
|
538
|
+
**Model:** `value` - Two-way bound selected values (`string[] | null`)
|
|
539
|
+
|
|
540
|
+
**Computed signals:** `selectedValueDisplay` (display text), `isSelectAll` (all items selected)
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## 5. Directives
|
|
545
|
+
|
|
546
|
+
### HasPermissionDirective
|
|
547
|
+
|
|
548
|
+
Structural directive for permission-based rendering. Fail-closed: hides content when permissions not loaded.
|
|
549
|
+
|
|
550
|
+
- **Selector:** `[hasPermission]`
|
|
551
|
+
- **Input:** `hasPermission` - `string | ILogicNode | null`
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import { HasPermissionDirective, ILogicNode } from '@flusys/ng-shared';
|
|
555
|
+
|
|
556
|
+
@Component({
|
|
557
|
+
imports: [HasPermissionDirective],
|
|
558
|
+
template: `
|
|
559
|
+
<!-- Simple permission check -->
|
|
560
|
+
<button *hasPermission="'user.create'">Create User</button>
|
|
561
|
+
|
|
562
|
+
<!-- Complex AND/OR logic -->
|
|
563
|
+
<div *hasPermission="editLogic">Edit Panel</div>
|
|
564
|
+
`,
|
|
565
|
+
})
|
|
566
|
+
export class MyComponent {
|
|
567
|
+
readonly editLogic: ILogicNode = {
|
|
568
|
+
type: 'group',
|
|
569
|
+
operator: 'AND',
|
|
570
|
+
children: [
|
|
571
|
+
{ type: 'action', actionId: 'user.view' },
|
|
572
|
+
{ type: 'action', actionId: 'user.update' },
|
|
573
|
+
],
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### EditModeElementChangerDirective
|
|
579
|
+
|
|
580
|
+
Toggles readonly/disabled state for form controls based on edit mode. Supports `<input>`, `<p-select>`, and `<p-calendar>`.
|
|
581
|
+
|
|
582
|
+
- **Selector:** `[appEditModeElementChanger]`
|
|
583
|
+
- **Input:** `isEditMode` (required boolean)
|
|
584
|
+
|
|
585
|
+
```html
|
|
586
|
+
<input [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
587
|
+
<p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### IsEmptyImageDirective
|
|
591
|
+
|
|
592
|
+
Automatically replaces broken or empty image `src` with a default fallback image.
|
|
593
|
+
|
|
594
|
+
- **Selector:** `img` (applies to all `<img>` elements)
|
|
595
|
+
- **Input:** `src` (standard img src)
|
|
596
|
+
- **Default image:** `lib/assets/images/default/default-image.jpg`
|
|
597
|
+
|
|
598
|
+
```html
|
|
599
|
+
<img [src]="imageUrl()" alt="Product" />
|
|
600
|
+
<!-- Falls back to default image on error or empty src -->
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### PreventDefaultDirective
|
|
604
|
+
|
|
605
|
+
Prevents default browser behavior on specified events and emits the event.
|
|
606
|
+
|
|
607
|
+
- **Selector:** `[appPreventDefault]`
|
|
608
|
+
- **Inputs:** `eventType` (`'click' | 'keydown' | 'keyup'`, default: `'click'`), `preventKey` (optional key filter)
|
|
609
|
+
- **Output:** `action` - Emits the prevented event
|
|
610
|
+
|
|
611
|
+
```html
|
|
612
|
+
<a href="#" appPreventDefault (action)="handleClick($event)">Click me</a>
|
|
613
|
+
<input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## 6. Guards
|
|
619
|
+
|
|
620
|
+
Route-level guards for permission-based access control. All guards deny access when permissions are not loaded (fail-closed).
|
|
621
|
+
|
|
622
|
+
### permissionGuard
|
|
623
|
+
|
|
624
|
+
Single permission or complex logic check.
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
import { permissionGuard } from '@flusys/ng-shared';
|
|
628
|
+
|
|
629
|
+
const routes: Routes = [
|
|
630
|
+
// Simple permission
|
|
631
|
+
{ path: 'users', canActivate: [permissionGuard('user.view')] },
|
|
632
|
+
|
|
633
|
+
// Complex logic (ILogicNode)
|
|
634
|
+
{ path: 'admin', canActivate: [permissionGuard({
|
|
635
|
+
type: 'group',
|
|
636
|
+
operator: 'AND',
|
|
637
|
+
children: [
|
|
638
|
+
{ type: 'action', actionId: 'admin.view' },
|
|
639
|
+
{ type: 'action', actionId: 'admin.manage' },
|
|
640
|
+
],
|
|
641
|
+
})] },
|
|
642
|
+
|
|
643
|
+
// Custom redirect on deny
|
|
644
|
+
{ path: 'settings', canActivate: [permissionGuard('settings.view', '/access-denied')] },
|
|
645
|
+
];
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### anyPermissionGuard
|
|
649
|
+
|
|
650
|
+
OR logic - allows access if user has ANY of the specified permissions.
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
{ path: 'reports', canActivate: [anyPermissionGuard(['report.view', 'report.export'])] }
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### allPermissionsGuard
|
|
657
|
+
|
|
658
|
+
AND logic - allows access only if user has ALL specified permissions.
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
{ path: 'admin', canActivate: [allPermissionsGuard(['admin.view', 'admin.manage'])] }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## 7. Utilities
|
|
667
|
+
|
|
668
|
+
### Permission Evaluator
|
|
669
|
+
|
|
670
|
+
Pure functions for permission logic evaluation. Used internally by `HasPermissionDirective` and guards.
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions } from '@flusys/ng-shared';
|
|
674
|
+
|
|
675
|
+
const userPermissions = ['user.view', 'user.create'];
|
|
676
|
+
|
|
677
|
+
// Evaluate string or ILogicNode
|
|
678
|
+
evaluatePermission('user.view', userPermissions); // true
|
|
679
|
+
evaluatePermission(null, userPermissions); // false
|
|
680
|
+
|
|
681
|
+
// Evaluate ILogicNode tree recursively
|
|
682
|
+
evaluateLogicNode(logicNode, userPermissions);
|
|
683
|
+
|
|
684
|
+
// Simple OR/AND checks
|
|
685
|
+
hasAnyPermission(['user.view', 'user.delete'], userPermissions); // true (has user.view)
|
|
686
|
+
hasAllPermissions(['user.view', 'user.delete'], userPermissions); // false (missing user.delete)
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## 8. Classes
|
|
692
|
+
|
|
693
|
+
### BaseFormControl
|
|
694
|
+
|
|
695
|
+
Abstract base class for custom form controls. Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
import { BaseFormControl, provideValueAccessor } from '@flusys/ng-shared';
|
|
699
|
+
|
|
700
|
+
@Component({
|
|
701
|
+
selector: 'my-select',
|
|
702
|
+
providers: [provideValueAccessor(MySelectComponent)],
|
|
703
|
+
})
|
|
704
|
+
export class MySelectComponent extends BaseFormControl<string | null> {
|
|
705
|
+
override readonly value = model<string | null>(null);
|
|
706
|
+
|
|
707
|
+
constructor() {
|
|
708
|
+
super();
|
|
709
|
+
this.initializeFormControl(); // Required for ControlValueAccessor sync
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Usage in all form patterns:
|
|
714
|
+
// Template-driven: <my-select [(value)]="selectedId" />
|
|
715
|
+
// Reactive forms: <my-select [formControl]="myControl" />
|
|
716
|
+
// Signal forms: <my-select [formField]="formTree.myField" />
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**Abstract property:** `value: ModelSignal<T>` - Must override with `model<T>()`
|
|
720
|
+
|
|
721
|
+
**Models:** `disabled` (boolean), `touched` (boolean)
|
|
722
|
+
|
|
723
|
+
**Methods:** `initializeFormControl()` (call in constructor), `markAsTouched()` (call on blur)
|
|
724
|
+
|
|
725
|
+
**Helper:** `provideValueAccessor(ComponentClass)` - Factory for `NG_VALUE_ACCESSOR` provider
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
## 9. Modules
|
|
730
|
+
|
|
731
|
+
### AngularModule
|
|
732
|
+
|
|
733
|
+
Re-exports common Angular modules for convenience.
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
import { AngularModule } from '@flusys/ng-shared';
|
|
737
|
+
// Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet, + directives
|
|
738
|
+
// Providers: DatePipe
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### PrimeModule
|
|
742
|
+
|
|
743
|
+
Re-exports PrimeNG component modules for convenience.
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
import { PrimeModule } from '@flusys/ng-shared';
|
|
747
|
+
// Includes: Button, Table, Card, Dialog, InputText, Select, MultiSelect,
|
|
748
|
+
// DatePicker, Checkbox, FileUpload, Image, Tag, Tabs, TreeTable, and more (27 modules)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## 10. Provider Interfaces (Package Independence)
|
|
754
|
+
|
|
755
|
+
ng-shared defines **provider interfaces** to enable feature packages (ng-iam, ng-storage) to access auth functionality without direct dependencies.
|
|
756
|
+
|
|
757
|
+
### Architecture
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
ng-shared (defines interfaces + tokens)
|
|
761
|
+
|
|
|
762
|
+
ng-auth (implements interfaces with adapters)
|
|
763
|
+
|
|
|
764
|
+
ng-iam/ng-storage (consume interfaces via DI)
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Available Providers
|
|
768
|
+
|
|
769
|
+
#### IUserProvider / `USER_PROVIDER`
|
|
770
|
+
|
|
771
|
+
User list access for IAM user selection.
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
interface IUserBasicInfo { id: string; name: string; email: string }
|
|
775
|
+
|
|
776
|
+
interface IUserProvider {
|
|
777
|
+
getUsers(filter?: {
|
|
778
|
+
page?: number; pageSize?: number; search?: string;
|
|
779
|
+
companyId?: string; branchId?: string;
|
|
780
|
+
}): Observable<IListResponse<IUserBasicInfo>>;
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
#### ICompanyApiProvider / `COMPANY_API_PROVIDER`
|
|
785
|
+
|
|
786
|
+
Company list access for IAM company selection.
|
|
787
|
+
|
|
788
|
+
```typescript
|
|
789
|
+
interface ICompanyBasicInfo { id: string; name: string; slug?: string }
|
|
790
|
+
|
|
791
|
+
interface ICompanyApiProvider {
|
|
792
|
+
getCompanies(filter?: {
|
|
793
|
+
page?: number; pageSize?: number; search?: string;
|
|
794
|
+
}): Observable<IListResponse<ICompanyBasicInfo>>;
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
#### IUserPermissionProvider / `USER_PERMISSION_PROVIDER`
|
|
799
|
+
|
|
800
|
+
User permission queries for IAM.
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
interface IUserPermissionProvider {
|
|
804
|
+
getUserBranchPermissions(userId: string): Observable<ISingleResponse<any>>;
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### Usage in Consuming Packages
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
import { USER_PROVIDER } from '@flusys/ng-shared';
|
|
812
|
+
|
|
813
|
+
export class UserSelectorComponent {
|
|
814
|
+
private readonly userProvider = inject(USER_PROVIDER);
|
|
815
|
+
|
|
816
|
+
loadUsers() {
|
|
817
|
+
this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe(response => {
|
|
818
|
+
this.users.set(response.data ?? []);
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
### Wiring in App
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
// app.config.ts
|
|
828
|
+
import { provideAuthProviders } from '@flusys/ng-auth';
|
|
829
|
+
|
|
830
|
+
export const appConfig: ApplicationConfig = {
|
|
831
|
+
providers: [
|
|
832
|
+
...provideAuthProviders(), // Registers all auth adapters for provider tokens
|
|
833
|
+
],
|
|
834
|
+
};
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### See Also
|
|
838
|
+
|
|
839
|
+
- [AUTH-GUIDE.md](AUTH-GUIDE.md) - Adapter implementations
|
|
840
|
+
- [IAM-GUIDE.md](IAM-GUIDE.md) - IAM usage examples
|
|
841
|
+
- [../CLAUDE.md](../CLAUDE.md) - Complete pattern documentation
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Best Practices
|
|
846
|
+
|
|
847
|
+
### API Services
|
|
848
|
+
|
|
849
|
+
- **Extend `ApiResourceService`** for new services (signal-based)
|
|
850
|
+
- Use reactive signals (`data`, `isLoading`, `total`) in templates
|
|
851
|
+
- Use `fetchList()` to trigger queries, `reload()` to refresh
|
|
852
|
+
- Use async methods (`insertAsync`, `updateAsync`) for one-off operations
|
|
853
|
+
|
|
854
|
+
### File URLs
|
|
855
|
+
|
|
856
|
+
- **Always use `FileUrlService`** - never construct URLs manually
|
|
857
|
+
- Use `fetchSingleFileUrl()` for one-off fetches, `fetchFileUrls()` for batches
|
|
858
|
+
- Use `fileUrlSignal()` for reactive template bindings
|
|
859
|
+
|
|
860
|
+
### Components
|
|
861
|
+
|
|
862
|
+
- Use `AngularModule` and `PrimeModule` for common imports
|
|
863
|
+
- Use signal inputs via `input()` and `input.required()`
|
|
864
|
+
- Components use `lib-` prefix for selectors
|
|
865
|
+
|
|
866
|
+
### Directives
|
|
867
|
+
|
|
868
|
+
- Directives use `app` prefix for selectors (`appPreventDefault`, `appEditModeElementChanger`)
|
|
869
|
+
- Exception: `HasPermissionDirective` uses `[hasPermission]`
|
|
870
|
+
- Exception: `IsEmptyImageDirective` uses `img` selector (auto-applies to all images)
|
|
871
|
+
|
|
872
|
+
### Permissions
|
|
873
|
+
|
|
874
|
+
- Use `HasPermissionDirective` for template-level permission checks
|
|
875
|
+
- Use permission guards for route-level access control
|
|
876
|
+
- Use `PermissionValidatorService` for programmatic checks in services
|
|
877
|
+
- Permissions follow fail-closed model: no access by default
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
## Common Issues
|
|
882
|
+
|
|
883
|
+
### ApiResourceService Not Updating UI
|
|
884
|
+
|
|
885
|
+
**Problem:** UI doesn't update after operations.
|
|
886
|
+
|
|
887
|
+
**Solution:** Use signal syntax in templates:
|
|
888
|
+
|
|
889
|
+
```html
|
|
890
|
+
<!-- Correct -->
|
|
891
|
+
<div>{{ users() }}</div>
|
|
892
|
+
|
|
893
|
+
<!-- Wrong -->
|
|
894
|
+
<div>{{ users }}</div>
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
### FileUrlService Returns Error
|
|
898
|
+
|
|
899
|
+
**Problem:** File URL fetching fails.
|
|
900
|
+
|
|
901
|
+
**Solution:**
|
|
902
|
+
|
|
903
|
+
1. Ensure storage service is enabled in environment config
|
|
904
|
+
2. Verify file ID exists in database
|
|
905
|
+
3. Check storage provider configuration (local/S3/Azure)
|
|
906
|
+
|
|
907
|
+
### Circular Dependency with ng-layout
|
|
908
|
+
|
|
909
|
+
**Problem:** Build fails with circular dependency.
|
|
910
|
+
|
|
911
|
+
**Solution:** ng-shared must NEVER import from ng-layout. Move shared components to ng-shared, layout-specific components to ng-layout.
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
915
|
+
## API Reference
|
|
916
|
+
|
|
917
|
+
### Services
|
|
918
|
+
|
|
919
|
+
| Service | Description |
|
|
920
|
+
| ------------------------------ | ------------------------------------- |
|
|
921
|
+
| `ApiResourceService<DTO, T>` | Signal-based CRUD with resource() API |
|
|
922
|
+
| `FileUrlService` | Cloud storage URL fetching |
|
|
923
|
+
| `PermissionValidatorService` | Permission state management |
|
|
924
|
+
| `CookieService` | SSR-aware cookie reading |
|
|
925
|
+
| `PlatformService` | SSR environment detection |
|
|
926
|
+
|
|
927
|
+
### Components
|
|
928
|
+
|
|
929
|
+
| Component | Selector | Description |
|
|
930
|
+
| -------------------------- | ---------------------- | ------------------------- |
|
|
931
|
+
| `IconComponent` | `lib-icon` | Flexible icon renderer |
|
|
932
|
+
| `LazySelectComponent` | `lib-lazy-select` | Lazy-loading dropdown |
|
|
933
|
+
| `LazyMultiSelectComponent` | `lib-lazy-multi-select`| Lazy-loading multi-select |
|
|
934
|
+
|
|
935
|
+
### Directives
|
|
936
|
+
|
|
937
|
+
| Directive | Selector | Description |
|
|
938
|
+
| --------------------------------- | --------------------------- | ----------------------------------- |
|
|
939
|
+
| `HasPermissionDirective` | `[hasPermission]` | Permission-based rendering |
|
|
940
|
+
| `EditModeElementChangerDirective` | `[appEditModeElementChanger]` | Toggle edit mode on form controls |
|
|
941
|
+
| `IsEmptyImageDirective` | `img` | Image fallback on error/empty |
|
|
942
|
+
| `PreventDefaultDirective` | `[appPreventDefault]` | Prevent default event behavior |
|
|
943
|
+
|
|
944
|
+
### Guards
|
|
945
|
+
|
|
946
|
+
| Guard | Description |
|
|
947
|
+
| ---------------------- | -------------------------------------- |
|
|
948
|
+
| `permissionGuard` | Single permission or ILogicNode check |
|
|
949
|
+
| `anyPermissionGuard` | OR logic (any of listed permissions) |
|
|
950
|
+
| `allPermissionsGuard` | AND logic (all of listed permissions) |
|
|
951
|
+
|
|
952
|
+
### Interfaces
|
|
953
|
+
|
|
954
|
+
| Interface | Description |
|
|
955
|
+
| -------------------- | ------------------------------------ |
|
|
956
|
+
| `IBaseEntity` | Base entity with ID and timestamps |
|
|
957
|
+
| `ILoggedUserInfo` | Current user info with company ctx |
|
|
958
|
+
| `IFilterData` | Filter, pagination, sort payload |
|
|
959
|
+
| `IDeleteData` | Delete request payload |
|
|
960
|
+
| `IDropDown` | Simple label/value pair |
|
|
961
|
+
| `ISingleResponse<T>` | Single item response |
|
|
962
|
+
| `IListResponse<T>` | List with pagination |
|
|
963
|
+
| `IBulkResponse<T>` | Bulk operation response |
|
|
964
|
+
| `IMessageResponse` | Message-only response |
|
|
965
|
+
| `IErrorResponse` | Error with validation details |
|
|
966
|
+
| `ILogicNode` | Permission logic tree (AND/OR nodes) |
|
|
967
|
+
|
|
968
|
+
### Injection Tokens
|
|
969
|
+
|
|
970
|
+
| Token | Interface | Description |
|
|
971
|
+
| -------------------------- | ------------------------- | ---------------------------- |
|
|
972
|
+
| `USER_PROVIDER` | `IUserProvider` | User list for IAM |
|
|
973
|
+
| `COMPANY_API_PROVIDER` | `ICompanyApiProvider` | Company list for IAM |
|
|
974
|
+
| `USER_PERMISSION_PROVIDER` | `IUserPermissionProvider` | User permission queries |
|
|
975
|
+
|
|
976
|
+
## See Also
|
|
977
|
+
|
|
978
|
+
- **[CORE-GUIDE.md](./CORE-GUIDE.md)** - Configuration, interceptors
|
|
979
|
+
- **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Layout system
|
|
980
|
+
- **[AUTH-GUIDE.md](./AUTH-GUIDE.md)** - Auth adapters for provider interfaces
|
|
981
|
+
- **[IAM-GUIDE.md](./IAM-GUIDE.md)** - IAM permission usage
|
|
982
|
+
|
|
983
|
+
---
|
|
984
|
+
|
|
985
|
+
**Last Updated:** 2026-02-07
|
|
986
|
+
**Package Version:** 1.0.0
|
|
987
|
+
**Angular Version:** 21
|