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