@flusys/ng-shared 1.1.1-beta → 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,1293 +1,165 @@
1
- # @flusys/ng-shared Package Guide
1
+ # @flusys/ng-shared
2
2
 
3
- ## Overview
3
+ Shared utilities, provider tokens, base page classes, and reusable components for the FLUSYS Angular platform.
4
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`
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/ng-shared.svg)](https://www.npmjs.com/package/@flusys/ng-shared)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
15
7
 
16
8
  ---
17
9
 
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
10
+ ## Installation
49
11
 
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
- }
12
+ ```bash
13
+ npm install @flusys/ng-shared @flusys/ng-core
87
14
  ```
88
15
 
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.
16
+ ---
105
17
 
106
- ```typescript
107
- interface IDropDown {
108
- label: string;
109
- value: string;
110
- }
111
- ```
18
+ ## 1. ApiResourceService — Signal-Driven CRUD
112
19
 
113
- #### User Select Interfaces
20
+ Extend this class to get a reactive list resource backed by `resource()`, with pagination, search, and all standard POST-only RPC endpoints.
114
21
 
115
22
  ```typescript
116
- interface IUserSelectFilter {
117
- page: number;
118
- pageSize: number;
119
- search: string;
120
- [key: string]: unknown;
121
- }
23
+ import { Injectable, inject } from "@angular/core";
24
+ import { HttpClient } from "@angular/common/http";
25
+ import { ApiResourceService } from "@flusys/ng-shared";
122
26
 
123
- type LoadUsersFn = (filter: IUserSelectFilter) => Observable<IListResponse<IUserBasicInfo>>;
124
- ```
125
-
126
- #### File Select Interfaces
127
-
128
- ```typescript
129
- interface IFileBasicInfo {
27
+ export interface IProduct {
130
28
  id: string;
131
29
  name: string;
132
- contentType: string;
133
- size: string;
134
- url: string | null;
135
- }
136
-
137
- interface IFileUploadOptions {
138
- storageConfigId?: string;
139
- folderPath?: string;
140
- maxWidth?: number;
141
- maxHeight?: number;
142
- quality?: number;
143
- compress?: boolean;
144
- }
145
-
146
- interface IUploadedFile {
147
- name: string;
148
- key: string;
149
- size: number;
150
- contentType: string;
151
- }
152
-
153
- interface IFileSelectFilter {
154
- page: number;
155
- pageSize: number;
156
- search: string;
157
- contentTypes?: string[];
158
- folderId?: string;
159
- [key: string]: unknown;
160
- }
161
-
162
- type LoadFilesFn = (filter: IFileSelectFilter) => Observable<IListResponse<IFileBasicInfo>>;
163
- type UploadFileFn = (file: File, options?: IFileUploadOptions) => Observable<ISingleResponse<IUploadedFile>>;
164
- type GetFileUrlsFn = (fileIds: string[]) => Observable<ISingleResponse<IFileBasicInfo[]>>;
165
- ```
166
-
167
- **File Type Filters (Constants):**
168
-
169
- ```typescript
170
- import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from '@flusys/ng-shared';
171
-
172
- FILE_TYPE_FILTERS.IMAGES // ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
173
- FILE_TYPE_FILTERS.DOCUMENTS // ['application/pdf', 'application/msword', ...]
174
- FILE_TYPE_FILTERS.VIDEOS // ['video/mp4', 'video/webm', ...]
175
- FILE_TYPE_FILTERS.AUDIO // ['audio/mpeg', 'audio/wav', ...]
176
- FILE_TYPE_FILTERS.ALL // [] (allows all)
177
-
178
- getAcceptString(['image/*']) // 'image/*'
179
- isFileTypeAllowed(file, ['image/*']) // true/false
180
- getFileIconClass('image/png') // 'pi pi-image'
181
- formatFileSize('1024') // '1 KB'
182
- ```
183
-
184
- ### Response Interfaces
185
-
186
- Type-safe interfaces matching FLUSYS_NEST backend response DTOs.
187
-
188
- ```typescript
189
- // Single item response (POST /resource/insert, /update, /get/:id)
190
- interface ISingleResponse<T> {
191
- success: boolean;
192
- message: string;
193
- data?: T;
194
- _meta?: IRequestMeta;
195
- }
196
-
197
- // List response with pagination (POST /resource/get-all)
198
- interface IListResponse<T> {
199
- success: boolean;
200
- message: string;
201
- data?: T[];
202
- meta: IPaginationMeta;
203
- _meta?: IRequestMeta;
204
- }
205
-
206
- // Bulk operation response (POST /resource/insert-many, /update-many)
207
- interface IBulkResponse<T> {
208
- success: boolean;
209
- message: string;
210
- data?: T[];
211
- meta: IBulkMeta;
212
- _meta?: IRequestMeta;
213
- }
214
-
215
- // Message-only response (POST /resource/delete, /logout)
216
- interface IMessageResponse {
217
- success: boolean;
218
- message: string;
219
- _meta?: IRequestMeta;
220
- }
221
-
222
- // Error response (validation errors, exceptions)
223
- interface IErrorResponse {
224
- success: false;
225
- message: string;
226
- code?: string;
227
- errors?: IValidationError[];
228
- _meta?: IRequestMeta;
229
- }
230
-
231
- // Union type
232
- type ApiResponse<T> = ISingleResponse<T> | IListResponse<T> | IBulkResponse<T> | IMessageResponse | IErrorResponse;
233
- ```
234
-
235
- **Metadata types:**
236
-
237
- | Interface | Fields |
238
- | ------------------ | -------------------------------------------- |
239
- | `IRequestMeta` | `requestId?, timestamp?, responseTime?` |
240
- | `IPaginationMeta` | `total, page, pageSize, count, hasMore?, totalPages?` |
241
- | `IBulkMeta` | `count, failed?, total?` |
242
- | `IValidationError` | `field, message, constraint?` |
243
-
244
- ### Auth & Storage Response Types
245
-
246
- ```typescript
247
- interface ILoginResponse {
248
- success: boolean;
249
- message: string;
250
- data: { accessToken: string; refreshToken: string; user: ILoginUserData };
30
+ price: number;
251
31
  }
252
-
253
- interface IRefreshTokenResponse {
254
- success: boolean;
255
- message: string;
256
- data: { accessToken: string; refreshToken?: string };
257
- }
258
-
259
- interface IFileData {
260
- id: string;
32
+ export interface IProductDto {
261
33
  name: string;
262
- originalName: string;
263
- contentType: string;
264
- size: number;
265
- key: string;
266
- url?: string;
267
- thumbnailUrl?: string;
268
- createdAt: Date;
269
- }
270
- ```
271
-
272
- ### Permission Interfaces
273
-
274
- Discriminated union for building complex permission logic trees.
275
-
276
- ```typescript
277
- // Single permission check
278
- interface IActionNode {
279
- type: 'action';
280
- actionId: string;
281
- }
282
-
283
- // Group with AND/OR logic
284
- interface IGroupNode {
285
- type: 'group';
286
- operator: 'AND' | 'OR';
287
- children: ILogicNode[];
288
- }
289
-
290
- // Union type
291
- type ILogicNode = IActionNode | IGroupNode;
292
- ```
293
-
294
- ---
295
-
296
- ## 2. Enums
297
-
298
- ```typescript
299
- enum ContactTypeEnum {
300
- PHONE = 1,
301
- EMAIL = 2,
34
+ price: number;
302
35
  }
303
36
 
304
- enum IconTypeEnum {
305
- PRIMENG_ICON = 1,
306
- IMAGE_FILE_LINK = 2,
307
- DIRECT_TAG_SVG = 3,
308
- }
309
- ```
310
-
311
- ---
312
-
313
- ## 3. Services
314
-
315
- ### ApiResourceService
316
-
317
- Signal-based CRUD service using Angular 21 `resource()` API. All endpoints use POST (RPC-style).
318
-
319
- **Define a service:**
320
-
321
- ```typescript
322
- import { Injectable } from '@angular/core';
323
- import { HttpClient } from '@angular/common/http';
324
- import { ApiResourceService } from '@flusys/ng-shared';
325
-
326
- @Injectable({ providedIn: 'root' })
327
- export class UserService extends ApiResourceService<UserDto, IUser> {
328
- constructor(http: HttpClient) {
329
- super('users', http); // Base URL: APP_CONFIG.apiBaseUrl + '/users'
37
+ @Injectable({ providedIn: "root" })
38
+ export class ProductService extends ApiResourceService<IProductDto, IProduct> {
39
+ constructor() {
40
+ super("products", "administration"); // moduleApiName, http, serviceName (key in APP_CONFIG.services)
330
41
  }
331
42
  }
332
43
  ```
333
44
 
334
- **Endpoint mapping:**
335
-
336
- | Method | HTTP | Endpoint | Response |
337
- | -------------- | ---- | ------------------------- | ------------------------ |
338
- | `insert(dto)` | POST | `/{resource}/insert` | `ISingleResponse<T>` |
339
- | `insertMany(dtos)` | POST | `/{resource}/insert-many` | `IBulkResponse<T>` |
340
- | `findById(id, select?)` | POST | `/{resource}/get/:id` | `ISingleResponse<T>` |
341
- | `getAll(search, filter)` | POST | `/{resource}/get-all?q=` | `IListResponse<T>` |
342
- | `update(dto)` | POST | `/{resource}/update` | `ISingleResponse<T>` |
343
- | `updateMany(dtos)` | POST | `/{resource}/update-many` | `IBulkResponse<T>` |
344
- | `delete(deleteDto)` | POST | `/{resource}/delete` | `IMessageResponse` |
345
-
346
- All methods above return `Observable`. Async (Promise) variants are also available: `insertAsync`, `insertManyAsync`, `findByIdAsync`, `updateAsync`, `updateManyAsync`, `deleteAsync`.
347
-
348
- **Reactive signals:**
349
-
350
- | Signal | Type | Description |
351
- | ------------ | ------------------------- | ------------------------ |
352
- | `isLoading` | `Signal<boolean>` | Whether data is loading |
353
- | `data` | `Signal<T[]>` | Current list data |
354
- | `total` | `Signal<number>` | Total item count |
355
- | `pageInfo` | `Signal<IPaginationMeta>` | Pagination metadata |
356
- | `hasMore` | `Signal<boolean>` | More pages available |
357
- | `searchTerm` | `WritableSignal<string>` | Current search term |
358
- | `filterData` | `WritableSignal<IFilterData>` | Filter/pagination state |
359
-
360
- **List management:**
45
+ Using the reactive list in a component:
361
46
 
362
47
  ```typescript
363
- // Trigger data fetch (updates searchTerm and filterData signals, resource auto-reloads)
364
- userService.fetchList('search term', { pagination: { currentPage: 0, pageSize: 10 } });
48
+ @Component({ ... })
49
+ export class ProductListComponent {
50
+ private readonly svc = inject(ProductService);
365
51
 
366
- // Pagination helpers
367
- userService.setPagination({ currentPage: 1, pageSize: 20 });
368
- userService.nextPage();
369
- userService.resetPagination();
370
- userService.reload();
371
- ```
52
+ readonly items = this.svc.data; // Signal<IProduct[]>
53
+ readonly total = this.svc.total; // Signal<number>
54
+ readonly loading = this.svc.isLoading; // Signal<boolean>
372
55
 
373
- **Component usage:**
56
+ ngOnInit() { this.svc.fetchList(); }
374
57
 
375
- ```typescript
376
- @Component({...})
377
- export class UserListComponent {
378
- private readonly userService = inject(UserService);
58
+ onSearch(q: string) { this.svc.fetchList(q); }
379
59
 
380
- readonly users = this.userService.data; // Signal<IUser[]>
381
- readonly isLoading = this.userService.isLoading; // Signal<boolean>
382
- readonly total = this.userService.total; // Signal<number>
60
+ onPageChange(page: number, size: number) {
61
+ this.svc.setPagination({ currentPage: page, pageSize: size });
62
+ }
383
63
 
384
- constructor() {
385
- this.userService.fetchList('', { pagination: { currentPage: 0, pageSize: 10 } });
64
+ async onCreate(dto: IProductDto) {
65
+ await this.svc.insert(dto);
66
+ this.svc.reload();
386
67
  }
387
68
 
388
- async createUser(dto: UserDto) {
389
- await this.userService.insertAsync(dto);
390
- this.userService.reload(); // Refresh list
69
+ async onDelete(id: string) {
70
+ await this.svc.delete({ id });
71
+ this.svc.reload();
391
72
  }
392
73
  }
393
74
  ```
394
75
 
395
- > **Note:** `ApiService` is exported as an alias for `ApiResourceService` for backward compatibility.
76
+ Available CRUD methods: `insert`, `insertMany`, `findById`, `findByIds`, `getAll`, `getByFilter`, `update`, `updateMany`, `bulkUpsert`, `delete`.
396
77
 
397
- ### FileUrlService
78
+ ---
398
79
 
399
- Fetches file URLs from the backend. Supports presigned URLs for cloud storage (S3, Azure).
80
+ ## 2. FileUrlService — Presigned URL Fetching
400
81
 
401
- **CRITICAL:** Never construct file URLs manually. Always use this service.
82
+ Never construct file URLs manually. `FileUrlService` handles S3/Azure presigned URLs, caching, and deduplication.
402
83
 
403
84
  ```typescript
404
85
  import { FileUrlService } from '@flusys/ng-shared';
405
86
 
406
- @Component({...})
407
- export class ProductComponent {
87
+ @Component({ ... })
88
+ export class AvatarComponent {
408
89
  private readonly fileUrlService = inject(FileUrlService);
90
+ readonly avatarUrl = signal<string | null>(null);
409
91
 
410
- loadImage(fileId: string) {
411
- // Fetch from backend: POST /file-manager/get-files
412
- this.fileUrlService.fetchSingleFileUrl(fileId).subscribe(file => {
413
- this.imageUrl.set(file?.url ?? null);
92
+ ngOnInit() {
93
+ this.fileUrlService.fetchSingleFileUrl('file-id-123').subscribe(file => {
94
+ this.avatarUrl.set(file?.url ?? null);
414
95
  });
415
96
  }
416
97
 
417
- loadMultiple(fileIds: string[]) {
418
- this.fileUrlService.fetchFileUrls(fileIds).subscribe(files => {
419
- // files: FilesResponseDto[] with { id, name, contentType, url }
420
- });
98
+ // Batch fetch — only requests IDs not already cached
99
+ loadMultiple(ids: string[]) {
100
+ this.fileUrlService.fetchFileUrls(ids).subscribe(files => { /* ... */ });
421
101
  }
422
- }
423
- ```
424
-
425
- **Methods:**
426
-
427
- | Method | Returns | Description |
428
- | ----------------------------- | ----------------------------------- | ---------------------------------- |
429
- | `getFileUrl(fileId)` | `string \| null` | Get cached URL (synchronous) |
430
- | `fileUrlSignal(fileId)` | `Signal<string \| null>` | Computed signal from cache |
431
- | `fetchFileUrls(fileIds[])` | `Observable<FilesResponseDto[]>` | Fetch from backend, updates cache |
432
- | `fetchSingleFileUrl(fileId)` | `Observable<FilesResponseDto \| null>` | Fetch single file |
433
- | `clearCache()` | `void` | Clear all cached URLs |
434
- | `removeFromCache(fileId)` | `void` | Remove specific entry from cache |
435
-
436
- ### PermissionValidatorService
437
-
438
- Signal-based permission state management. Used by `HasPermissionDirective`, permission guards, and IAM.
439
-
440
- ```typescript
441
- import { PermissionValidatorService } from '@flusys/ng-shared';
442
-
443
- @Component({...})
444
- export class MyComponent {
445
- private readonly permissionValidator = inject(PermissionValidatorService);
446
102
 
447
- ngOnInit() {
448
- // Set permissions (typically done by IAM PermissionStateService)
449
- this.permissionValidator.setPermissions(['user.view', 'user.create']);
450
-
451
- // Check single permission
452
- if (this.permissionValidator.hasPermission('user.view')) {
453
- // User has permission
454
- }
455
-
456
- // Check loaded state
457
- if (this.permissionValidator.isPermissionsLoaded()) {
458
- // Permissions have been loaded
459
- }
103
+ // Synchronous read from cache after a prior fetch
104
+ getCachedUrl(id: string): string | null {
105
+ return this.fileUrlService.getFileUrl(id);
460
106
  }
461
107
  }
462
108
  ```
463
109
 
464
- **Methods:**
465
-
466
- | Method | Returns | Description |
467
- | ---------------------------- | --------- | ---------------------------------- |
468
- | `setPermissions(codes[])` | `void` | Replace all permissions |
469
- | `clearPermissions()` | `void` | Clear all permissions |
470
- | `hasPermission(code)` | `boolean` | Check single permission |
471
- | `isPermissionsLoaded()` | `boolean` | Whether permissions have been set |
472
-
473
- **Signals:**
474
-
475
- | Signal | Type | Description |
476
- | ------------- | ------------------ | ------------------------------ |
477
- | `permissions` | `Signal<string[]>` | Readonly current permissions |
478
-
479
- ### CookieService
480
-
481
- SSR-aware cookie reading service.
482
-
483
- ```typescript
484
- import { CookieService } from '@flusys/ng-shared';
485
-
486
- const cookies = inject(CookieService).get(); // Returns document.cookie (browser) or request header cookie (server)
487
- ```
488
-
489
- ### PlatformService
490
-
491
- SSR environment detection service.
492
-
493
- ```typescript
494
- import { PlatformService } from '@flusys/ng-shared';
495
-
496
- const platform = inject(PlatformService);
497
- if (!platform.isServer) {
498
- // Browser-only code (localStorage, window, etc.)
499
- }
500
- ```
501
-
502
110
  ---
503
111
 
504
- ## 4. Components
505
-
506
- ### IconComponent
507
-
508
- Flexible icon renderer supporting PrimeNG icons, image files, and SVG.
509
-
510
- - **Selector:** `lib-icon`
511
- - **Inputs:** `icon` (required string), `iconType` (optional `IconTypeEnum`, default: `PRIMENG_ICON`)
512
-
513
- ```html
514
- <!-- PrimeNG icon (default) -->
515
- <lib-icon icon="pi pi-user" />
516
-
517
- <!-- Image file -->
518
- <lib-icon icon="/assets/logo.png" [iconType]="IconTypeEnum.IMAGE_FILE_LINK" />
519
-
520
- <!-- SVG tag -->
521
- <lib-icon icon="<svg>...</svg>" [iconType]="IconTypeEnum.DIRECT_TAG_SVG" />
522
- ```
523
-
524
- ### LazySelectComponent
525
-
526
- Single-select dropdown with lazy loading, search, and scroll pagination.
527
-
528
- - **Selector:** `lib-lazy-select`
529
- - **Extends:** `BaseFormControl<string | null>`
530
- - **Supports:** Template-driven, reactive forms, signal forms
112
+ ## 3. PermissionValidatorService + HasPermissionDirective
531
113
 
532
114
  ```typescript
533
- @Component({
534
- imports: [LazySelectComponent],
535
- template: `
536
- <lib-lazy-select
537
- [(value)]="selectedId"
538
- [selectDataList]="items()"
539
- [optionLabel]="'label'"
540
- [optionValue]="'value'"
541
- [isEditMode]="true"
542
- [isLoading]="loading()"
543
- [total]="total()"
544
- [pagination]="pagination()"
545
- [placeHolder]="'Select item'"
546
- (onSearch)="handleSearch($event)"
547
- (onPagination)="handlePagination($event)"
548
- />
549
- `,
550
- })
551
- export class MyComponent {
552
- readonly selectedId = signal<string | null>(null);
553
- readonly items = signal<IDropDown[]>([]);
554
- readonly loading = signal(false);
555
- readonly total = signal<number | undefined>(undefined);
556
- readonly pagination = signal<IPagination>({ currentPage: 0, pageSize: 20 });
557
- }
558
- ```
559
-
560
- **Inputs:**
561
-
562
- | Input | Type | Description |
563
- | ---------------- | ----------------- | -------------------------------- |
564
- | `selectDataList` | `Array<IDropDown>` | Dropdown options (required) |
565
- | `optionLabel` | `string` | Label field name (required) |
566
- | `optionValue` | `string` | Value field name (required) |
567
- | `isEditMode` | `boolean` | Enable/disable editing (required)|
568
- | `isLoading` | `boolean` | Loading state (required) |
569
- | `total` | `number \| undefined` | Total items for pagination |
570
- | `pagination` | `IPagination` | Current pagination state |
571
- | `placeHolder` | `string` | Placeholder text (default: `'Select Option'`) |
572
-
573
- **Model:** `value` - Two-way bound selected value (`string | null`)
574
-
575
- **Outputs:** `onSearch` (debounced 500ms), `onPagination` (scroll-triggered)
576
-
577
- ### LazyMultiSelectComponent
578
-
579
- Multi-select dropdown with lazy loading, search, select-all, and scroll pagination.
580
-
581
- - **Selector:** `lib-lazy-multi-select`
582
- - **Extends:** `BaseFormControl<string[] | null>`
583
- - **Supports:** Template-driven, reactive forms, signal forms
115
+ const pv = inject(PermissionValidatorService);
584
116
 
585
- ```typescript
586
- @Component({
587
- imports: [LazyMultiSelectComponent],
588
- template: `
589
- <lib-lazy-multi-select
590
- [(value)]="selectedIds"
591
- [selectDataList]="items()"
592
- [isEditMode]="true"
593
- [isLoading]="loading()"
594
- [total]="total()"
595
- [pagination]="pagination()"
596
- [placeHolder]="'Select items'"
597
- (onSearch)="handleSearch($event)"
598
- (onPagination)="handlePagination($event)"
599
- />
600
- `,
601
- })
602
- export class MyComponent {
603
- readonly selectedIds = signal<string[] | null>(null);
604
- }
605
- ```
606
-
607
- **Inputs:** `selectDataList`, `isEditMode`, `isLoading`, `total`, `pagination`, `placeHolder` (same as `LazySelectComponent`). Does not have `optionLabel`/`optionValue` (uses `IDropDown.label`/`IDropDown.value` directly).
608
-
609
- **Model:** `value` - Two-way bound selected values (`string[] | null`)
610
-
611
- **Computed signals:** `selectedValueDisplay` (display text), `isSelectAll` (all items selected)
612
-
613
- ### UserSelectComponent
614
-
615
- Single user selection with lazy loading. Uses `USER_PROVIDER` internally or accepts custom `loadUsers` function.
616
-
617
- - **Selector:** `lib-user-select`
618
- - **Supports:** Template-driven, reactive forms, signal forms
619
-
620
- ```typescript
621
- @Component({
622
- imports: [UserSelectComponent],
623
- template: `
624
- <!-- Simple usage - uses USER_PROVIDER internally -->
625
- <lib-user-select
626
- [(value)]="selectedUserId"
627
- [isEditMode]="true"
628
- />
629
-
630
- <!-- With custom loadUsers function -->
631
- <lib-user-select
632
- [(value)]="selectedUserId"
633
- [isEditMode]="true"
634
- [loadUsers]="customLoadUsers"
635
- />
636
- `,
637
- })
638
- export class MyComponent {
639
- readonly selectedUserId = signal<string | null>(null);
640
- }
117
+ pv.hasPermission("user.create"); // true
118
+ pv.hasPermission("report.export"); // true — wildcard match
119
+ pv.isPermissionsLoaded(); // boolean
641
120
  ```
642
121
 
643
- **Inputs:**
644
-
645
- | Input | Type | Default | Description |
646
- | ----- | ---- | ------- | ----------- |
647
- | `isEditMode` | `boolean` | required | Enable/disable editing |
648
- | `placeHolder` | `string` | `'Select User'` | Placeholder text |
649
- | `filterActive` | `boolean` | `true` | Filter active users only |
650
- | `additionalFilters` | `Record<string, unknown>` | `{}` | Extra filter params |
651
- | `pageSize` | `number` | `20` | Page size for pagination |
652
- | `loadUsers` | `LoadUsersFn` | - | Custom user loading function |
653
-
654
- **Model:** `value` - Two-way bound selected user ID (`string | null`)
655
-
656
- **Outputs:** `userSelected` (IUserBasicInfo | null), `onError` (Error)
657
-
658
- ### UserMultiSelectComponent
659
-
660
- Multiple user selection with lazy loading. Uses `USER_PROVIDER` internally or accepts custom `loadUsers` function.
661
-
662
- - **Selector:** `lib-user-multi-select`
663
- - **Supports:** Template-driven, reactive forms, signal forms
664
-
665
- ```typescript
666
- @Component({
667
- imports: [UserMultiSelectComponent],
668
- template: `
669
- <lib-user-multi-select
670
- [(value)]="selectedUserIds"
671
- [isEditMode]="true"
672
- />
673
- `,
674
- })
675
- export class MyComponent {
676
- readonly selectedUserIds = signal<string[] | null>(null);
677
- }
678
- ```
679
-
680
- **Inputs:** Same as `UserSelectComponent`.
681
-
682
- **Model:** `value` - Two-way bound selected user IDs (`string[] | null`)
683
-
684
- **Outputs:** `usersSelected` (IUserBasicInfo[]), `onError` (Error)
685
-
686
- ### FileUploaderComponent
687
-
688
- Drag & drop file upload with type filtering. Pass your own `uploadFile` function.
689
-
690
- - **Selector:** `lib-file-uploader`
691
-
692
- ```typescript
693
- @Component({
694
- imports: [FileUploaderComponent],
695
- template: `
696
- <!-- Single image upload -->
697
- <lib-file-uploader
698
- [uploadFile]="uploadFile"
699
- [acceptTypes]="['image/*']"
700
- [multiple]="false"
701
- (fileUploaded)="onFileUploaded($event)"
702
- />
703
-
704
- <!-- Multiple document upload -->
705
- <lib-file-uploader
706
- [uploadFile]="uploadFile"
707
- [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
708
- [multiple]="true"
709
- [maxFiles]="5"
710
- (filesUploaded)="onFilesUploaded($event)"
711
- />
712
- `,
713
- })
714
- export class MyComponent {
715
- readonly uploadService = inject(UploadService);
716
-
717
- readonly uploadFile: UploadFileFn = (file, options) =>
718
- this.uploadService.uploadSingleFile(file, options);
719
- }
720
- ```
721
-
722
- **Inputs:**
723
-
724
- | Input | Type | Default | Description |
725
- | ----- | ---- | ------- | ----------- |
726
- | `uploadFile` | `UploadFileFn` | required | Upload function |
727
- | `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
728
- | `multiple` | `boolean` | `false` | Allow multiple files |
729
- | `maxFiles` | `number` | `10` | Max files for multiple |
730
- | `maxSizeMb` | `number` | `10` | Max file size in MB |
731
- | `uploadOptions` | `IFileUploadOptions` | `{}` | Upload options |
732
- | `disabled` | `boolean` | `false` | Disable uploader |
733
- | `showPreview` | `boolean` | `true` | Show selected files preview |
734
- | `autoUpload` | `boolean` | `true` | Upload immediately on selection |
735
-
736
- **Outputs:** `fileUploaded` (IUploadedFile), `filesUploaded` (IUploadedFile[]), `onError` (Error), `fileSelected` (File[])
737
-
738
- ### FileSelectorDialogComponent
739
-
740
- Dialog to browse and select existing files with filtering.
741
-
742
- - **Selector:** `lib-file-selector-dialog`
743
-
744
- ```typescript
745
- @Component({
746
- imports: [FileSelectorDialogComponent],
747
- template: `
748
- <lib-file-selector-dialog
749
- [(visible)]="showFileSelector"
750
- [loadFiles]="loadFiles"
751
- [acceptTypes]="['image/*']"
752
- [multiple]="false"
753
- (fileSelected)="onFileSelected($event)"
754
- />
755
- `,
756
- })
757
- export class MyComponent {
758
- readonly showFileSelector = signal(false);
759
- readonly fileService = inject(FileManagerApiService);
760
-
761
- readonly loadFiles: LoadFilesFn = (filter) =>
762
- this.fileService.getAll(filter.search, {
763
- pagination: { currentPage: filter.page, pageSize: filter.pageSize },
764
- });
765
- }
766
- ```
767
-
768
- **Inputs:**
769
-
770
- | Input | Type | Default | Description |
771
- | ----- | ---- | ------- | ----------- |
772
- | `loadFiles` | `LoadFilesFn` | required | File loading function |
773
- | `header` | `string` | `'Select File'` | Dialog header |
774
- | `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
775
- | `multiple` | `boolean` | `false` | Allow multiple selection |
776
- | `maxSelection` | `number` | `10` | Max files for multiple |
777
- | `pageSize` | `number` | `20` | Page size for pagination |
778
-
779
- **Model:** `visible` - Two-way bound dialog visibility (`boolean`)
780
-
781
- **Outputs:** `fileSelected` (IFileBasicInfo), `filesSelected` (IFileBasicInfo[]), `closed` (void), `onError` (Error)
782
-
783
- ---
784
-
785
- ## 5. Directives
786
-
787
- ### HasPermissionDirective
788
-
789
- Structural directive for permission-based rendering. Fail-closed: hides content when permissions not loaded.
790
-
791
- - **Selector:** `[hasPermission]`
792
- - **Input:** `hasPermission` - `string | ILogicNode | null`
793
-
794
- ```typescript
795
- import { HasPermissionDirective, ILogicNode } from '@flusys/ng-shared';
796
-
797
- @Component({
798
- imports: [HasPermissionDirective],
799
- template: `
800
- <!-- Simple permission check -->
801
- <button *hasPermission="'user.create'">Create User</button>
802
-
803
- <!-- Complex AND/OR logic -->
804
- <div *hasPermission="editLogic">Edit Panel</div>
805
- `,
806
- })
807
- export class MyComponent {
808
- readonly editLogic: ILogicNode = {
809
- type: 'group',
810
- operator: 'AND',
811
- children: [
812
- { type: 'action', actionId: 'user.view' },
813
- { type: 'action', actionId: 'user.update' },
814
- ],
815
- };
816
- }
817
- ```
818
-
819
- ### EditModeElementChangerDirective
820
-
821
- Toggles readonly/disabled state for form controls based on edit mode. Supports `<input>`, `<p-select>`, and `<p-calendar>`.
822
-
823
- - **Selector:** `[appEditModeElementChanger]`
824
- - **Input:** `isEditMode` (required boolean)
825
-
826
- ```html
827
- <input [appEditModeElementChanger] [isEditMode]="isEditing()" />
828
- <p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
829
- ```
830
-
831
- ### IsEmptyImageDirective
832
-
833
- Automatically replaces broken or empty image `src` with a default fallback image.
834
-
835
- - **Selector:** `img` (applies to all `<img>` elements)
836
- - **Input:** `src` (standard img src)
837
- - **Default image:** `lib/assets/images/default/default-image.jpg`
838
-
839
- ```html
840
- <img [src]="imageUrl()" alt="Product" />
841
- <!-- Falls back to default image on error or empty src -->
842
- ```
843
-
844
- ### PreventDefaultDirective
845
-
846
- Prevents default browser behavior on specified events and emits the event.
847
-
848
- - **Selector:** `[appPreventDefault]`
849
- - **Inputs:** `eventType` (`'click' | 'keydown' | 'keyup'`, default: `'click'`), `preventKey` (optional key filter)
850
- - **Output:** `action` - Emits the prevented event
851
-
852
122
  ```html
853
- <a href="#" appPreventDefault (action)="handleClick($event)">Click me</a>
854
- <input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />
123
+ <!-- Removes element from DOM when permission fails -->
124
+ <button *hasPermission="'user.create'">Create User</button>
855
125
  ```
856
126
 
857
127
  ---
858
128
 
859
- ## 6. Guards
860
-
861
- Route-level guards for permission-based access control. All guards deny access when permissions are not loaded (fail-closed).
862
-
863
- ### permissionGuard
864
-
865
- Single permission or complex logic check.
866
-
867
- ```typescript
868
- import { permissionGuard } from '@flusys/ng-shared';
869
-
870
- const routes: Routes = [
871
- // Simple permission
872
- { path: 'users', canActivate: [permissionGuard('user.view')] },
873
-
874
- // Complex logic (ILogicNode)
875
- { path: 'admin', canActivate: [permissionGuard({
876
- type: 'group',
877
- operator: 'AND',
878
- children: [
879
- { type: 'action', actionId: 'admin.view' },
880
- { type: 'action', actionId: 'admin.manage' },
881
- ],
882
- })] },
883
-
884
- // Custom redirect on deny
885
- { path: 'settings', canActivate: [permissionGuard('settings.view', '/access-denied')] },
129
+ ## 4. Permission Guards
130
+
131
+ ```typescript
132
+ import { permissionGuard, anyPermissionGuard, allPermissionsGuard } from "@flusys/ng-shared";
133
+
134
+ export const routes: Routes = [
135
+ {
136
+ path: "users",
137
+ canActivate: [permissionGuard("user.view")], // single
138
+ loadComponent: () => import("./user-list.component"),
139
+ },
140
+ {
141
+ path: "reports",
142
+ canActivate: [anyPermissionGuard(["report.view", "report.export"])], // OR
143
+ loadComponent: () => import("./reports.component"),
144
+ },
145
+ {
146
+ path: "admin",
147
+ canActivate: [allPermissionsGuard(["admin.view", "admin.manage"])], // AND
148
+ loadComponent: () => import("./admin.component"),
149
+ },
886
150
  ];
887
151
  ```
888
152
 
889
- ### anyPermissionGuard
890
-
891
- OR logic - allows access if user has ANY of the specified permissions.
892
-
893
- ```typescript
894
- { path: 'reports', canActivate: [anyPermissionGuard(['report.view', 'report.export'])] }
895
- ```
896
-
897
- ### allPermissionsGuard
898
-
899
- AND logic - allows access only if user has ALL specified permissions.
900
-
901
- ```typescript
902
- { path: 'admin', canActivate: [allPermissionsGuard(['admin.view', 'admin.manage'])] }
903
- ```
153
+ All three guards redirect to `/` by default. Pass a second argument to change it: `permissionGuard('user.view', '/access-denied')`.
904
154
 
905
155
  ---
906
156
 
907
- ## 7. Utilities
908
-
909
- ### Permission Evaluator
910
-
911
- Pure functions for permission logic evaluation. Used internally by `HasPermissionDirective` and guards.
912
-
913
- ```typescript
914
- import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions } from '@flusys/ng-shared';
915
-
916
- const userPermissions = ['user.view', 'user.create'];
917
-
918
- // Evaluate string or ILogicNode
919
- evaluatePermission('user.view', userPermissions); // true
920
- evaluatePermission(null, userPermissions); // false
921
-
922
- // Evaluate ILogicNode tree recursively
923
- evaluateLogicNode(logicNode, userPermissions);
924
-
925
- // Simple OR/AND checks
926
- hasAnyPermission(['user.view', 'user.delete'], userPermissions); // true (has user.view)
927
- hasAllPermissions(['user.view', 'user.delete'], userPermissions); // false (missing user.delete)
928
- ```
929
-
930
- ---
931
-
932
- ## 8. Classes
933
-
934
- ### BaseFormControl
935
-
936
- Abstract base class for custom form controls. Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
937
-
938
- ```typescript
939
- import { BaseFormControl, provideValueAccessor } from '@flusys/ng-shared';
940
-
941
- @Component({
942
- selector: 'my-select',
943
- providers: [provideValueAccessor(MySelectComponent)],
944
- })
945
- export class MySelectComponent extends BaseFormControl<string | null> {
946
- override readonly value = model<string | null>(null);
947
-
948
- constructor() {
949
- super();
950
- this.initializeFormControl(); // Required for ControlValueAccessor sync
951
- }
952
- }
953
-
954
- // Usage in all form patterns:
955
- // Template-driven: <my-select [(value)]="selectedId" />
956
- // Reactive forms: <my-select [formControl]="myControl" />
957
- // Signal forms: <my-select [formField]="formTree.myField" />
958
- ```
959
-
960
- **Abstract property:** `value: ModelSignal<T>` - Must override with `model<T>()`
961
-
962
- **Models:** `disabled` (boolean), `touched` (boolean)
963
-
964
- **Methods:** `initializeFormControl()` (call in constructor), `markAsTouched()` (call on blur)
965
-
966
- **Helper:** `provideValueAccessor(ComponentClass)` - Factory for `NG_VALUE_ACCESSOR` provider
967
-
968
- ---
969
-
970
- ## 9. Modules
971
-
972
- ### AngularModule
973
-
974
- Re-exports common Angular modules for convenience.
975
-
976
- ```typescript
977
- import { AngularModule } from '@flusys/ng-shared';
978
- // Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet, + directives
979
- // Providers: DatePipe
980
- ```
981
-
982
- ### PrimeModule
983
-
984
- Re-exports PrimeNG component modules for convenience.
985
-
986
- ```typescript
987
- import { PrimeModule } from '@flusys/ng-shared';
988
- // Includes: Button, Table, Card, Dialog, InputText, Select, MultiSelect,
989
- // DatePicker, Checkbox, FileUpload, Image, Tag, Tabs, TreeTable, and more (27 modules)
990
- ```
991
-
992
- ---
993
-
994
- ## 10. Provider Interfaces (Package Independence)
995
-
996
- ng-shared defines **provider interfaces** to enable feature packages (ng-iam, ng-storage) to access auth functionality without direct dependencies.
997
-
998
- ### Architecture
999
-
1000
- ```
1001
- ng-shared (defines interfaces + tokens)
1002
- |
1003
- ng-auth (implements interfaces with adapters)
1004
- |
1005
- ng-iam/ng-storage (consume interfaces via DI)
1006
- ```
1007
-
1008
- ### Available Providers
1009
-
1010
- #### IUserProvider / `USER_PROVIDER`
1011
-
1012
- User list access for IAM user selection.
1013
-
1014
- ```typescript
1015
- interface IUserBasicInfo { id: string; name: string; email: string }
1016
-
1017
- interface IUserProvider {
1018
- getUsers(filter?: {
1019
- page?: number; pageSize?: number; search?: string;
1020
- companyId?: string; branchId?: string;
1021
- }): Observable<IListResponse<IUserBasicInfo>>;
1022
- }
1023
- ```
1024
-
1025
- #### ICompanyApiProvider / `COMPANY_API_PROVIDER`
1026
-
1027
- Company list access for IAM company selection.
1028
-
1029
- ```typescript
1030
- interface ICompanyBasicInfo { id: string; name: string; slug?: string }
1031
-
1032
- interface ICompanyApiProvider {
1033
- getCompanies(filter?: {
1034
- page?: number; pageSize?: number; search?: string;
1035
- }): Observable<IListResponse<ICompanyBasicInfo>>;
1036
- }
1037
- ```
1038
-
1039
- #### IUserPermissionProvider / `USER_PERMISSION_PROVIDER`
1040
-
1041
- User permission queries for IAM.
1042
-
1043
- ```typescript
1044
- interface IUserPermissionProvider {
1045
- getUserBranchPermissions(userId: string): Observable<ISingleResponse<any>>;
1046
- }
1047
- ```
1048
-
1049
- #### IProfileUploadProvider / `PROFILE_UPLOAD_PROVIDER`
1050
-
1051
- Profile picture upload for ng-auth profile page. Implemented by ng-storage.
1052
-
1053
- ```typescript
1054
- interface IProfileUploadResult {
1055
- id: string; // File manager ID (UUID)
1056
- name: string; // Original file name
1057
- key: string; // Storage key/path
1058
- size: number; // File size in bytes
1059
- contentType: string; // MIME type
1060
- }
1061
-
1062
- interface IProfileUploadOptions {
1063
- folderPath?: string;
1064
- compress?: boolean;
1065
- maxWidth?: number;
1066
- maxHeight?: number;
1067
- }
1068
-
1069
- interface IProfileUploadProvider {
1070
- uploadProfilePicture(
1071
- file: File,
1072
- options?: IProfileUploadOptions
1073
- ): Observable<ISingleResponse<IProfileUploadResult>>;
1074
- }
1075
- ```
1076
-
1077
- #### IProfilePermissionProvider / `PROFILE_PERMISSION_PROVIDER`
1078
-
1079
- User permission queries for ng-auth profile page. Implemented by ng-iam.
1080
-
1081
- ```typescript
1082
- interface IProfileRoleInfo {
1083
- id: string;
1084
- name: string;
1085
- description?: string | null;
1086
- }
1087
-
1088
- interface IProfileActionInfo {
1089
- id: string;
1090
- code: string;
1091
- name: string;
1092
- description?: string | null;
1093
- }
1094
-
1095
- interface IProfilePermissionProvider {
1096
- getUserRoles(userId: string, branchId?: string): Observable<ISingleResponse<IProfileRoleInfo[]>>;
1097
- getUserActions(userId: string, branchId?: string): Observable<ISingleResponse<IProfileActionInfo[]>>;
1098
- }
1099
- ```
1100
-
1101
- ### Usage in Consuming Packages
1102
-
1103
- ```typescript
1104
- import { USER_PROVIDER } from '@flusys/ng-shared';
1105
-
1106
- export class UserSelectorComponent {
1107
- private readonly userProvider = inject(USER_PROVIDER);
1108
-
1109
- loadUsers() {
1110
- this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe(response => {
1111
- this.users.set(response.data ?? []);
1112
- });
1113
- }
1114
- }
1115
- ```
1116
-
1117
- ### Wiring in App
1118
-
1119
- ```typescript
1120
- // app.config.ts
1121
- import { provideAuthProviders } from '@flusys/ng-auth';
1122
-
1123
- export const appConfig: ApplicationConfig = {
1124
- providers: [
1125
- ...provideAuthProviders(), // Registers all auth adapters for provider tokens
1126
- ],
1127
- };
1128
- ```
1129
-
1130
- ### See Also
1131
-
1132
- - [AUTH-GUIDE.md](AUTH-GUIDE.md) - Adapter implementations
1133
- - [IAM-GUIDE.md](IAM-GUIDE.md) - IAM usage examples
1134
- - [../CLAUDE.md](../CLAUDE.md) - Complete pattern documentation
1135
-
1136
- ---
1137
-
1138
- ## Best Practices
1139
-
1140
- ### API Services
1141
-
1142
- - **Extend `ApiResourceService`** for new services (signal-based)
1143
- - Use reactive signals (`data`, `isLoading`, `total`) in templates
1144
- - Use `fetchList()` to trigger queries, `reload()` to refresh
1145
- - Use async methods (`insertAsync`, `updateAsync`) for one-off operations
1146
-
1147
- ### File URLs
1148
-
1149
- - **Always use `FileUrlService`** - never construct URLs manually
1150
- - Use `fetchSingleFileUrl()` for one-off fetches, `fetchFileUrls()` for batches
1151
- - Use `fileUrlSignal()` for reactive template bindings
1152
-
1153
- ### Components
1154
-
1155
- - Use `AngularModule` and `PrimeModule` for common imports
1156
- - Use signal inputs via `input()` and `input.required()`
1157
- - Components use `lib-` prefix for selectors
1158
-
1159
- ### Directives
1160
-
1161
- - Directives use `app` prefix for selectors (`appPreventDefault`, `appEditModeElementChanger`)
1162
- - Exception: `HasPermissionDirective` uses `[hasPermission]`
1163
- - Exception: `IsEmptyImageDirective` uses `img` selector (auto-applies to all images)
1164
-
1165
- ### Permissions
1166
-
1167
- - Use `HasPermissionDirective` for template-level permission checks
1168
- - Use permission guards for route-level access control
1169
- - Use `PermissionValidatorService` for programmatic checks in services
1170
- - Permissions follow fail-closed model: no access by default
1171
-
1172
- ---
1173
-
1174
- ## Common Issues
1175
-
1176
- ### ApiResourceService Not Updating UI
1177
-
1178
- **Problem:** UI doesn't update after operations.
1179
-
1180
- **Solution:** Use signal syntax in templates:
157
+ ## 5. TranslatePipe
1181
158
 
1182
159
  ```html
1183
- <!-- Correct -->
1184
- <div>{{ users() }}</div>
1185
-
1186
- <!-- Wrong -->
1187
- <div>{{ users }}</div>
160
+ {{ 'shared.save' | translate }} {{ 'pagination.showing' | translate: { from: 1, to: 10 } }}
1188
161
  ```
1189
162
 
1190
- ### FileUrlService Returns Error
1191
-
1192
- **Problem:** File URL fetching fails.
1193
-
1194
- **Solution:**
1195
-
1196
- 1. Ensure storage service is enabled in environment config
1197
- 2. Verify file ID exists in database
1198
- 3. Check storage provider configuration (local/S3/Azure)
1199
-
1200
- ### Circular Dependency with ng-layout
1201
-
1202
- **Problem:** Build fails with circular dependency.
1203
-
1204
- **Solution:** ng-shared must NEVER import from ng-layout. Move shared components to ng-shared, layout-specific components to ng-layout.
1205
-
1206
- ---
1207
-
1208
- ## API Reference
1209
-
1210
- ### Services
1211
-
1212
- | Service | Description |
1213
- | ------------------------------ | ------------------------------------- |
1214
- | `ApiResourceService<DTO, T>` | Signal-based CRUD with resource() API |
1215
- | `FileUrlService` | Cloud storage URL fetching |
1216
- | `PermissionValidatorService` | Permission state management |
1217
- | `CookieService` | SSR-aware cookie reading |
1218
- | `PlatformService` | SSR environment detection |
1219
-
1220
- ### Components
1221
-
1222
- | Component | Selector | Description |
1223
- | -------------------------- | ---------------------- | ------------------------- |
1224
- | `IconComponent` | `lib-icon` | Flexible icon renderer |
1225
- | `LazySelectComponent` | `lib-lazy-select` | Lazy-loading dropdown |
1226
- | `LazyMultiSelectComponent` | `lib-lazy-multi-select`| Lazy-loading multi-select |
1227
- | `UserSelectComponent` | `lib-user-select` | Single user selector |
1228
- | `UserMultiSelectComponent` | `lib-user-multi-select`| Multiple user selector |
1229
- | `FileUploaderComponent` | `lib-file-uploader` | Drag & drop file upload |
1230
- | `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog |
1231
-
1232
- ### Directives
1233
-
1234
- | Directive | Selector | Description |
1235
- | --------------------------------- | --------------------------- | ----------------------------------- |
1236
- | `HasPermissionDirective` | `[hasPermission]` | Permission-based rendering |
1237
- | `EditModeElementChangerDirective` | `[appEditModeElementChanger]` | Toggle edit mode on form controls |
1238
- | `IsEmptyImageDirective` | `img` | Image fallback on error/empty |
1239
- | `PreventDefaultDirective` | `[appPreventDefault]` | Prevent default event behavior |
1240
-
1241
- ### Guards
1242
-
1243
- | Guard | Description |
1244
- | ---------------------- | -------------------------------------- |
1245
- | `permissionGuard` | Single permission or ILogicNode check |
1246
- | `anyPermissionGuard` | OR logic (any of listed permissions) |
1247
- | `allPermissionsGuard` | AND logic (all of listed permissions) |
1248
-
1249
- ### Interfaces
1250
-
1251
- | Interface | Description |
1252
- | -------------------- | ------------------------------------ |
1253
- | `IBaseEntity` | Base entity with ID and timestamps |
1254
- | `ILoggedUserInfo` | Current user info with company ctx |
1255
- | `IFilterData` | Filter, pagination, sort payload |
1256
- | `IDeleteData` | Delete request payload |
1257
- | `IDropDown` | Simple label/value pair |
1258
- | `ISingleResponse<T>` | Single item response |
1259
- | `IListResponse<T>` | List with pagination |
1260
- | `IBulkResponse<T>` | Bulk operation response |
1261
- | `IMessageResponse` | Message-only response |
1262
- | `IErrorResponse` | Error with validation details |
1263
- | `ILogicNode` | Permission logic tree (AND/OR nodes) |
1264
- | `IUserSelectFilter` | User select filter params |
1265
- | `LoadUsersFn` | User loading function type |
1266
- | `IFileBasicInfo` | Basic file info for selectors |
1267
- | `IFileUploadOptions`| Upload options (compression, etc.) |
1268
- | `IUploadedFile` | Uploaded file response |
1269
- | `IFileSelectFilter` | File select filter params |
1270
- | `LoadFilesFn` | File loading function type |
1271
- | `UploadFileFn` | File upload function type |
1272
-
1273
- ### Injection Tokens
1274
-
1275
- | Token | Interface | Description |
1276
- | -------------------------- | ------------------------- | ---------------------------- |
1277
- | `USER_PROVIDER` | `IUserProvider` | User list for IAM |
1278
- | `COMPANY_API_PROVIDER` | `ICompanyApiProvider` | Company list for IAM |
1279
- | `USER_PERMISSION_PROVIDER` | `IUserPermissionProvider` | User permission queries |
1280
- | `PROFILE_UPLOAD_PROVIDER` | `IProfileUploadProvider` | Profile picture upload (ng-storage) |
1281
- | `PROFILE_PERMISSION_PROVIDER` | `IProfilePermissionProvider` | User permissions for profile (ng-iam) |
1282
-
1283
- ## See Also
1284
-
1285
- - **[CORE-GUIDE.md](./CORE-GUIDE.md)** - Configuration, interceptors
1286
- - **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Layout system
1287
- - **[AUTH-GUIDE.md](./AUTH-GUIDE.md)** - Auth adapters for provider interfaces
1288
- - **[IAM-GUIDE.md](./IAM-GUIDE.md)** - IAM permission usage
1289
-
1290
- ---
163
+ ## License
1291
164
 
1292
- **Last Updated:** 2026-02-16
1293
- **Angular Version:** 21
165
+ MIT © FLUSYS