@flusys/ng-shared 1.0.0-beta → 1.0.0-rc
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 +411 -2
- package/fesm2022/flusys-ng-shared.mjs +1007 -372
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +3 -3
- package/types/flusys-ng-shared.d.ts +877 -109
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output,
|
|
2
|
+
import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output, NgModule, Injector, runInInjectionContext, resource, model, untracked, forwardRef, ChangeDetectionStrategy, Component, DestroyRef, viewChild, afterNextRender, InjectionToken, isDevMode } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { isPlatformServer, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe } from '@angular/common';
|
|
5
5
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
6
6
|
import { APP_CONFIG, getServiceUrl, ApiLoaderService } from '@flusys/ng-core';
|
|
7
|
-
import { of, firstValueFrom,
|
|
8
|
-
import { tap, catchError
|
|
7
|
+
import { of, firstValueFrom, map as map$1 } from 'rxjs';
|
|
8
|
+
import { map, tap, catchError } from 'rxjs/operators';
|
|
9
9
|
import * as i1$2 from '@angular/forms';
|
|
10
10
|
import { NgControl, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
11
|
-
import { RouterOutlet, RouterLink, Router } from '@angular/router';
|
|
11
|
+
import { RouterOutlet, RouterLink, RouterLinkActive, Router, ActivatedRoute } from '@angular/router';
|
|
12
12
|
import { AutoCompleteModule } from 'primeng/autocomplete';
|
|
13
|
+
import { AvatarModule } from 'primeng/avatar';
|
|
13
14
|
import * as i1$3 from 'primeng/button';
|
|
14
15
|
import { ButtonModule } from 'primeng/button';
|
|
15
16
|
import { CardModule } from 'primeng/card';
|
|
16
|
-
import * as
|
|
17
|
+
import * as i1$1 from 'primeng/checkbox';
|
|
17
18
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
19
|
+
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
|
18
20
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
19
|
-
import * as
|
|
21
|
+
import * as i4 from 'primeng/dialog';
|
|
20
22
|
import { DialogModule } from 'primeng/dialog';
|
|
21
23
|
import { DividerModule } from 'primeng/divider';
|
|
22
24
|
import { FileUploadModule } from 'primeng/fileupload';
|
|
@@ -24,7 +26,7 @@ import { IconFieldModule } from 'primeng/iconfield';
|
|
|
24
26
|
import { ImageModule } from 'primeng/image';
|
|
25
27
|
import { InputIconModule } from 'primeng/inputicon';
|
|
26
28
|
import { InputNumberModule } from 'primeng/inputnumber';
|
|
27
|
-
import * as
|
|
29
|
+
import * as i2 from 'primeng/inputtext';
|
|
28
30
|
import { InputTextModule } from 'primeng/inputtext';
|
|
29
31
|
import { ListboxModule } from 'primeng/listbox';
|
|
30
32
|
import { MultiSelectModule } from 'primeng/multiselect';
|
|
@@ -39,21 +41,143 @@ import { RippleModule } from 'primeng/ripple';
|
|
|
39
41
|
import * as i3 from 'primeng/select';
|
|
40
42
|
import { SelectModule } from 'primeng/select';
|
|
41
43
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
|
44
|
+
import { SkeletonModule } from 'primeng/skeleton';
|
|
42
45
|
import { SplitButtonModule } from 'primeng/splitbutton';
|
|
43
46
|
import { StepsModule } from 'primeng/steps';
|
|
44
47
|
import { TableModule } from 'primeng/table';
|
|
45
48
|
import { TabsModule } from 'primeng/tabs';
|
|
46
49
|
import { TagModule } from 'primeng/tag';
|
|
47
50
|
import { TextareaModule } from 'primeng/textarea';
|
|
51
|
+
import { ToastModule } from 'primeng/toast';
|
|
48
52
|
import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
|
49
53
|
import { TooltipModule } from 'primeng/tooltip';
|
|
50
54
|
import { TreeTableModule } from 'primeng/treetable';
|
|
51
|
-
import {
|
|
52
|
-
import
|
|
53
|
-
import { MessageService } from 'primeng/api';
|
|
55
|
+
import { MessageService, ConfirmationService } from 'primeng/api';
|
|
56
|
+
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Centralized Permission Codes
|
|
60
|
+
*
|
|
61
|
+
* Single source of truth for all permission codes used across the application.
|
|
62
|
+
* Use these constants instead of hardcoded strings to prevent typos and enable easy refactoring.
|
|
63
|
+
*
|
|
64
|
+
* Naming Convention: <entity>.<action>
|
|
65
|
+
* - entity: The resource being accessed (e.g., user, role, company)
|
|
66
|
+
* - action: The operation being performed (create, read, update, delete, assign)
|
|
67
|
+
*/
|
|
68
|
+
// ==================== AUTH MODULE ====================
|
|
69
|
+
const USER_PERMISSIONS = {
|
|
70
|
+
CREATE: 'user.create',
|
|
71
|
+
READ: 'user.read',
|
|
72
|
+
UPDATE: 'user.update',
|
|
73
|
+
DELETE: 'user.delete',
|
|
74
|
+
};
|
|
75
|
+
const COMPANY_PERMISSIONS = {
|
|
76
|
+
CREATE: 'company.create',
|
|
77
|
+
READ: 'company.read',
|
|
78
|
+
UPDATE: 'company.update',
|
|
79
|
+
DELETE: 'company.delete',
|
|
80
|
+
};
|
|
81
|
+
const BRANCH_PERMISSIONS = {
|
|
82
|
+
CREATE: 'branch.create',
|
|
83
|
+
READ: 'branch.read',
|
|
84
|
+
UPDATE: 'branch.update',
|
|
85
|
+
DELETE: 'branch.delete',
|
|
86
|
+
};
|
|
87
|
+
// ==================== IAM MODULE ====================
|
|
88
|
+
const ACTION_PERMISSIONS = {
|
|
89
|
+
CREATE: 'action.create',
|
|
90
|
+
READ: 'action.read',
|
|
91
|
+
UPDATE: 'action.update',
|
|
92
|
+
DELETE: 'action.delete',
|
|
93
|
+
};
|
|
94
|
+
const ROLE_PERMISSIONS = {
|
|
95
|
+
CREATE: 'role.create',
|
|
96
|
+
READ: 'role.read',
|
|
97
|
+
UPDATE: 'role.update',
|
|
98
|
+
DELETE: 'role.delete',
|
|
99
|
+
};
|
|
100
|
+
const ROLE_ACTION_PERMISSIONS = {
|
|
101
|
+
READ: 'role-action.read',
|
|
102
|
+
ASSIGN: 'role-action.assign',
|
|
103
|
+
};
|
|
104
|
+
const USER_ROLE_PERMISSIONS = {
|
|
105
|
+
READ: 'user-role.read',
|
|
106
|
+
ASSIGN: 'user-role.assign',
|
|
107
|
+
};
|
|
108
|
+
const USER_ACTION_PERMISSIONS = {
|
|
109
|
+
READ: 'user-action.read',
|
|
110
|
+
ASSIGN: 'user-action.assign',
|
|
111
|
+
};
|
|
112
|
+
const COMPANY_ACTION_PERMISSIONS = {
|
|
113
|
+
READ: 'company-action.read',
|
|
114
|
+
ASSIGN: 'company-action.assign',
|
|
115
|
+
};
|
|
116
|
+
// ==================== STORAGE MODULE ====================
|
|
117
|
+
const FILE_PERMISSIONS = {
|
|
118
|
+
CREATE: 'file.create',
|
|
119
|
+
READ: 'file.read',
|
|
120
|
+
UPDATE: 'file.update',
|
|
121
|
+
DELETE: 'file.delete',
|
|
122
|
+
};
|
|
123
|
+
const FOLDER_PERMISSIONS = {
|
|
124
|
+
CREATE: 'folder.create',
|
|
125
|
+
READ: 'folder.read',
|
|
126
|
+
UPDATE: 'folder.update',
|
|
127
|
+
DELETE: 'folder.delete',
|
|
128
|
+
};
|
|
129
|
+
const STORAGE_CONFIG_PERMISSIONS = {
|
|
130
|
+
CREATE: 'storage-config.create',
|
|
131
|
+
READ: 'storage-config.read',
|
|
132
|
+
UPDATE: 'storage-config.update',
|
|
133
|
+
DELETE: 'storage-config.delete',
|
|
134
|
+
};
|
|
135
|
+
// ==================== EMAIL MODULE ====================
|
|
136
|
+
const EMAIL_CONFIG_PERMISSIONS = {
|
|
137
|
+
CREATE: 'email-config.create',
|
|
138
|
+
READ: 'email-config.read',
|
|
139
|
+
UPDATE: 'email-config.update',
|
|
140
|
+
DELETE: 'email-config.delete',
|
|
141
|
+
};
|
|
142
|
+
const EMAIL_TEMPLATE_PERMISSIONS = {
|
|
143
|
+
CREATE: 'email-template.create',
|
|
144
|
+
READ: 'email-template.read',
|
|
145
|
+
UPDATE: 'email-template.update',
|
|
146
|
+
DELETE: 'email-template.delete',
|
|
147
|
+
};
|
|
148
|
+
// ==================== FORM BUILDER MODULE ====================
|
|
149
|
+
const FORM_PERMISSIONS = {
|
|
150
|
+
CREATE: 'form.create',
|
|
151
|
+
READ: 'form.read',
|
|
152
|
+
UPDATE: 'form.update',
|
|
153
|
+
DELETE: 'form.delete',
|
|
154
|
+
};
|
|
155
|
+
// ==================== AGGREGATED EXPORTS ====================
|
|
156
|
+
/**
|
|
157
|
+
* All permission codes grouped by module
|
|
158
|
+
*/
|
|
159
|
+
const PERMISSIONS = {
|
|
160
|
+
// Auth
|
|
161
|
+
USER: USER_PERMISSIONS,
|
|
162
|
+
COMPANY: COMPANY_PERMISSIONS,
|
|
163
|
+
BRANCH: BRANCH_PERMISSIONS,
|
|
164
|
+
// IAM
|
|
165
|
+
ACTION: ACTION_PERMISSIONS,
|
|
166
|
+
ROLE: ROLE_PERMISSIONS,
|
|
167
|
+
ROLE_ACTION: ROLE_ACTION_PERMISSIONS,
|
|
168
|
+
USER_ROLE: USER_ROLE_PERMISSIONS,
|
|
169
|
+
USER_ACTION: USER_ACTION_PERMISSIONS,
|
|
170
|
+
COMPANY_ACTION: COMPANY_ACTION_PERMISSIONS,
|
|
171
|
+
// Storage
|
|
172
|
+
FILE: FILE_PERMISSIONS,
|
|
173
|
+
FOLDER: FOLDER_PERMISSIONS,
|
|
174
|
+
STORAGE_CONFIG: STORAGE_CONFIG_PERMISSIONS,
|
|
175
|
+
// Email
|
|
176
|
+
EMAIL_CONFIG: EMAIL_CONFIG_PERMISSIONS,
|
|
177
|
+
EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
|
|
178
|
+
// Form Builder
|
|
179
|
+
FORM: FORM_PERMISSIONS,
|
|
180
|
+
};
|
|
57
181
|
|
|
58
182
|
/**
|
|
59
183
|
* Common file type filters
|
|
@@ -147,9 +271,7 @@ class PlatformService {
|
|
|
147
271
|
}
|
|
148
272
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, decorators: [{
|
|
149
273
|
type: Injectable,
|
|
150
|
-
args: [{
|
|
151
|
-
providedIn: 'root'
|
|
152
|
-
}]
|
|
274
|
+
args: [{ providedIn: 'root' }]
|
|
153
275
|
}] });
|
|
154
276
|
|
|
155
277
|
class CookieService {
|
|
@@ -198,25 +320,54 @@ class FileUrlService {
|
|
|
198
320
|
}
|
|
199
321
|
/**
|
|
200
322
|
* Fetch file URLs from backend and update cache.
|
|
201
|
-
*
|
|
323
|
+
* Skips IDs already in cache to prevent duplicate calls.
|
|
324
|
+
* Returns Observable of fetched files (including cached ones).
|
|
202
325
|
*/
|
|
203
|
-
fetchFileUrls(fileIds) {
|
|
326
|
+
fetchFileUrls(fileIds, forceRefresh = false) {
|
|
204
327
|
if (!fileIds.length)
|
|
205
328
|
return of([]);
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
329
|
+
const cache = this.urlCache();
|
|
330
|
+
// Filter out IDs already in cache (unless force refresh)
|
|
331
|
+
const missingIds = forceRefresh
|
|
332
|
+
? fileIds
|
|
333
|
+
: fileIds.filter((id) => !cache.has(id));
|
|
334
|
+
// If all files are cached, return from cache
|
|
335
|
+
if (missingIds.length === 0) {
|
|
336
|
+
const cachedFiles = fileIds
|
|
337
|
+
.map((id) => cache.get(id))
|
|
338
|
+
.filter((f) => !!f);
|
|
339
|
+
return of(cachedFiles);
|
|
340
|
+
}
|
|
341
|
+
const requestDto = missingIds.map((id) => ({ id }));
|
|
342
|
+
return this.http
|
|
343
|
+
.post(`${getServiceUrl(this.appConfig, 'storage')}/file-manager/get-files`, requestDto)
|
|
344
|
+
.pipe(map((response) => response.data ?? []), tap((files) => {
|
|
345
|
+
// Update cache with new files
|
|
346
|
+
const newCache = new Map(this.urlCache());
|
|
347
|
+
files.forEach((file) => newCache.set(file.id, file));
|
|
348
|
+
this.urlCache.set(newCache);
|
|
349
|
+
}), map(() => {
|
|
350
|
+
// Return all requested files (cached + newly fetched)
|
|
351
|
+
const allCache = this.urlCache();
|
|
352
|
+
return fileIds
|
|
353
|
+
.map((id) => allCache.get(id))
|
|
354
|
+
.filter((f) => !!f);
|
|
212
355
|
}), catchError(() => of([])));
|
|
213
356
|
}
|
|
214
357
|
/**
|
|
215
358
|
* Fetch a single file URL.
|
|
359
|
+
* Uses cache if available to prevent duplicate calls.
|
|
216
360
|
* Returns Observable of file info or null if not found.
|
|
217
361
|
*/
|
|
218
|
-
fetchSingleFileUrl(fileId) {
|
|
219
|
-
|
|
362
|
+
fetchSingleFileUrl(fileId, forceRefresh = false) {
|
|
363
|
+
// Return from cache immediately if available
|
|
364
|
+
if (!forceRefresh) {
|
|
365
|
+
const cached = this.urlCache().get(fileId);
|
|
366
|
+
if (cached) {
|
|
367
|
+
return of(cached);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return this.fetchFileUrls([fileId], forceRefresh).pipe(map((files) => files[0] ?? null));
|
|
220
371
|
}
|
|
221
372
|
/**
|
|
222
373
|
* Clear the URL cache.
|
|
@@ -261,6 +412,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
261
412
|
* // Set permissions (typically from auth/IAM)
|
|
262
413
|
* this.permissionValidator.setPermissions(['user.view', 'user.create']);
|
|
263
414
|
*
|
|
415
|
+
* // Or set wildcard for no-IAM mode
|
|
416
|
+
* this.permissionValidator.setPermissions(['*']);
|
|
417
|
+
*
|
|
264
418
|
* // Check individual permission
|
|
265
419
|
* if (this.permissionValidator.hasPermission('user.view')) {
|
|
266
420
|
* // Show UI element
|
|
@@ -392,19 +546,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
392
546
|
}]
|
|
393
547
|
}], ctorParameters: () => [], propDecorators: { isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }] } });
|
|
394
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Check if user has a specific permission using wildcard matching.
|
|
551
|
+
* Supports:
|
|
552
|
+
* - Exact match: 'user.create' matches 'user.create'
|
|
553
|
+
* - Full wildcard: '*' matches everything
|
|
554
|
+
* - Module wildcard: 'user.*' matches 'user.create', 'user.read', etc.
|
|
555
|
+
*/
|
|
556
|
+
function hasPermission(requiredPermission, userPermissions) {
|
|
557
|
+
// Exact match
|
|
558
|
+
if (userPermissions.includes(requiredPermission))
|
|
559
|
+
return true;
|
|
560
|
+
// Wildcard matching
|
|
561
|
+
for (const permission of userPermissions) {
|
|
562
|
+
// Full wildcard - grants all permissions
|
|
563
|
+
if (permission === '*')
|
|
564
|
+
return true;
|
|
565
|
+
// Module wildcard (e.g., 'user.*' matches 'user.create')
|
|
566
|
+
if (permission.endsWith('.*') &&
|
|
567
|
+
requiredPermission.startsWith(permission.slice(0, -1))) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
395
573
|
/** Evaluate permission logic (string or ILogicNode) against user permissions */
|
|
396
574
|
function evaluatePermission(logic, permissions) {
|
|
397
575
|
if (!logic)
|
|
398
576
|
return false;
|
|
399
577
|
if (typeof logic === 'string')
|
|
400
|
-
return
|
|
578
|
+
return hasPermission(logic, permissions);
|
|
401
579
|
return evaluateLogicNode(logic, permissions);
|
|
402
580
|
}
|
|
403
581
|
/** Recursively evaluate an ILogicNode tree */
|
|
404
582
|
function evaluateLogicNode(node, permissions) {
|
|
405
583
|
switch (node.type) {
|
|
406
584
|
case 'action':
|
|
407
|
-
return node.actionId ?
|
|
585
|
+
return node.actionId ? hasPermission(node.actionId, permissions) : false;
|
|
408
586
|
case 'group':
|
|
409
587
|
if (!node.children || node.children.length === 0)
|
|
410
588
|
return false;
|
|
@@ -419,13 +597,13 @@ function evaluateLogicNode(node, permissions) {
|
|
|
419
597
|
function hasAnyPermission(permissionCodes, permissions) {
|
|
420
598
|
if (!permissionCodes?.length)
|
|
421
599
|
return false;
|
|
422
|
-
return permissionCodes.some((code) =>
|
|
600
|
+
return permissionCodes.some((code) => hasPermission(code, permissions));
|
|
423
601
|
}
|
|
424
602
|
/** Check if user has ALL of the specified permissions (AND logic) */
|
|
425
603
|
function hasAllPermissions(permissionCodes, permissions) {
|
|
426
604
|
if (!permissionCodes?.length)
|
|
427
605
|
return false;
|
|
428
|
-
return permissionCodes.every((code) =>
|
|
606
|
+
return permissionCodes.every((code) => hasPermission(code, permissions));
|
|
429
607
|
}
|
|
430
608
|
|
|
431
609
|
/**
|
|
@@ -616,17 +794,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
616
794
|
args: [{
|
|
617
795
|
selector: '[appPreventDefault]',
|
|
618
796
|
standalone: true,
|
|
797
|
+
host: {
|
|
798
|
+
'(click)': 'onClick($event)',
|
|
799
|
+
'(keydown)': 'onKeydown($event)',
|
|
800
|
+
'(keyup)': 'onKeyup($event)',
|
|
801
|
+
},
|
|
619
802
|
}]
|
|
620
|
-
}], propDecorators: { eventType: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventType", required: false }] }], preventKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventKey", required: false }] }], action: [{ type: i0.Output, args: ["action"] }]
|
|
621
|
-
type: HostListener,
|
|
622
|
-
args: ['click', ['$event']]
|
|
623
|
-
}], onKeydown: [{
|
|
624
|
-
type: HostListener,
|
|
625
|
-
args: ['keydown', ['$event']]
|
|
626
|
-
}], onKeyup: [{
|
|
627
|
-
type: HostListener,
|
|
628
|
-
args: ['keyup', ['$event']]
|
|
629
|
-
}] } });
|
|
803
|
+
}], propDecorators: { eventType: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventType", required: false }] }], preventKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventKey", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
|
|
630
804
|
|
|
631
805
|
class AngularModule {
|
|
632
806
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
@@ -635,6 +809,7 @@ class AngularModule {
|
|
|
635
809
|
ReactiveFormsModule,
|
|
636
810
|
RouterOutlet,
|
|
637
811
|
RouterLink,
|
|
812
|
+
RouterLinkActive,
|
|
638
813
|
IsEmptyImageDirective,
|
|
639
814
|
NgOptimizedImage,
|
|
640
815
|
NgComponentOutlet,
|
|
@@ -643,6 +818,7 @@ class AngularModule {
|
|
|
643
818
|
ReactiveFormsModule,
|
|
644
819
|
RouterOutlet,
|
|
645
820
|
RouterLink,
|
|
821
|
+
RouterLinkActive,
|
|
646
822
|
IsEmptyImageDirective,
|
|
647
823
|
NgOptimizedImage,
|
|
648
824
|
NgComponentOutlet,
|
|
@@ -662,6 +838,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
662
838
|
ReactiveFormsModule,
|
|
663
839
|
RouterOutlet,
|
|
664
840
|
RouterLink,
|
|
841
|
+
RouterLinkActive,
|
|
665
842
|
IsEmptyImageDirective,
|
|
666
843
|
NgOptimizedImage,
|
|
667
844
|
NgComponentOutlet,
|
|
@@ -674,6 +851,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
674
851
|
ReactiveFormsModule,
|
|
675
852
|
RouterOutlet,
|
|
676
853
|
RouterLink,
|
|
854
|
+
RouterLinkActive,
|
|
677
855
|
IsEmptyImageDirective,
|
|
678
856
|
NgOptimizedImage,
|
|
679
857
|
NgComponentOutlet,
|
|
@@ -684,109 +862,121 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
684
862
|
|
|
685
863
|
class PrimeModule {
|
|
686
864
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
687
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, exports: [
|
|
688
|
-
|
|
689
|
-
SelectButtonModule,
|
|
690
|
-
PasswordModule,
|
|
865
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, exports: [AutoCompleteModule,
|
|
866
|
+
AvatarModule,
|
|
691
867
|
ButtonModule,
|
|
692
|
-
|
|
868
|
+
CardModule,
|
|
693
869
|
CheckboxModule,
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
TableModule,
|
|
699
|
-
InputNumberModule,
|
|
700
|
-
TextareaModule,
|
|
701
|
-
ProgressBarModule,
|
|
870
|
+
ConfirmDialogModule,
|
|
871
|
+
DatePickerModule,
|
|
872
|
+
DialogModule,
|
|
873
|
+
DividerModule,
|
|
702
874
|
FileUploadModule,
|
|
703
|
-
CardModule,
|
|
704
|
-
SelectModule,
|
|
705
|
-
InputIconModule,
|
|
706
875
|
IconFieldModule,
|
|
707
|
-
|
|
876
|
+
ImageModule,
|
|
877
|
+
InputIconModule,
|
|
878
|
+
InputNumberModule,
|
|
879
|
+
InputTextModule,
|
|
708
880
|
ListboxModule,
|
|
881
|
+
MultiSelectModule,
|
|
882
|
+
PaginatorModule,
|
|
883
|
+
PanelModule,
|
|
884
|
+
PasswordModule,
|
|
885
|
+
PopoverModule,
|
|
886
|
+
ProgressBarModule,
|
|
709
887
|
RadioButtonModule,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
888
|
+
RippleModule,
|
|
889
|
+
SelectButtonModule,
|
|
890
|
+
SelectModule,
|
|
891
|
+
SkeletonModule,
|
|
713
892
|
SplitButtonModule,
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
AutoCompleteModule,
|
|
893
|
+
StepsModule,
|
|
894
|
+
TableModule,
|
|
717
895
|
TabsModule,
|
|
718
|
-
DialogModule,
|
|
719
|
-
TreeTableModule] });
|
|
720
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, imports: [InputTextModule,
|
|
721
896
|
TagModule,
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
897
|
+
TextareaModule,
|
|
898
|
+
ToastModule,
|
|
899
|
+
ToggleSwitchModule,
|
|
725
900
|
TooltipModule,
|
|
901
|
+
TreeTableModule] });
|
|
902
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, imports: [AutoCompleteModule,
|
|
903
|
+
AvatarModule,
|
|
904
|
+
ButtonModule,
|
|
905
|
+
CardModule,
|
|
726
906
|
CheckboxModule,
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
TableModule,
|
|
732
|
-
InputNumberModule,
|
|
733
|
-
TextareaModule,
|
|
734
|
-
ProgressBarModule,
|
|
907
|
+
ConfirmDialogModule,
|
|
908
|
+
DatePickerModule,
|
|
909
|
+
DialogModule,
|
|
910
|
+
DividerModule,
|
|
735
911
|
FileUploadModule,
|
|
736
|
-
CardModule,
|
|
737
|
-
SelectModule,
|
|
738
|
-
InputIconModule,
|
|
739
912
|
IconFieldModule,
|
|
740
|
-
|
|
913
|
+
ImageModule,
|
|
914
|
+
InputIconModule,
|
|
915
|
+
InputNumberModule,
|
|
916
|
+
InputTextModule,
|
|
741
917
|
ListboxModule,
|
|
918
|
+
MultiSelectModule,
|
|
919
|
+
PaginatorModule,
|
|
920
|
+
PanelModule,
|
|
921
|
+
PasswordModule,
|
|
922
|
+
PopoverModule,
|
|
923
|
+
ProgressBarModule,
|
|
742
924
|
RadioButtonModule,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
925
|
+
RippleModule,
|
|
926
|
+
SelectButtonModule,
|
|
927
|
+
SelectModule,
|
|
928
|
+
SkeletonModule,
|
|
746
929
|
SplitButtonModule,
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
AutoCompleteModule,
|
|
930
|
+
StepsModule,
|
|
931
|
+
TableModule,
|
|
750
932
|
TabsModule,
|
|
751
|
-
|
|
933
|
+
TagModule,
|
|
934
|
+
TextareaModule,
|
|
935
|
+
ToastModule,
|
|
936
|
+
ToggleSwitchModule,
|
|
937
|
+
TooltipModule,
|
|
752
938
|
TreeTableModule] });
|
|
753
939
|
}
|
|
754
940
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, decorators: [{
|
|
755
941
|
type: NgModule,
|
|
756
942
|
args: [{
|
|
757
943
|
exports: [
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
SelectButtonModule,
|
|
761
|
-
PasswordModule,
|
|
944
|
+
AutoCompleteModule,
|
|
945
|
+
AvatarModule,
|
|
762
946
|
ButtonModule,
|
|
763
|
-
|
|
947
|
+
CardModule,
|
|
764
948
|
CheckboxModule,
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
TableModule,
|
|
770
|
-
InputNumberModule,
|
|
771
|
-
TextareaModule,
|
|
772
|
-
ProgressBarModule,
|
|
949
|
+
ConfirmDialogModule,
|
|
950
|
+
DatePickerModule,
|
|
951
|
+
DialogModule,
|
|
952
|
+
DividerModule,
|
|
773
953
|
FileUploadModule,
|
|
774
|
-
CardModule,
|
|
775
|
-
SelectModule,
|
|
776
|
-
InputIconModule,
|
|
777
954
|
IconFieldModule,
|
|
778
|
-
|
|
955
|
+
ImageModule,
|
|
956
|
+
InputIconModule,
|
|
957
|
+
InputNumberModule,
|
|
958
|
+
InputTextModule,
|
|
779
959
|
ListboxModule,
|
|
960
|
+
MultiSelectModule,
|
|
961
|
+
PaginatorModule,
|
|
962
|
+
PanelModule,
|
|
963
|
+
PasswordModule,
|
|
964
|
+
PopoverModule,
|
|
965
|
+
ProgressBarModule,
|
|
780
966
|
RadioButtonModule,
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
967
|
+
RippleModule,
|
|
968
|
+
SelectButtonModule,
|
|
969
|
+
SelectModule,
|
|
970
|
+
SkeletonModule,
|
|
784
971
|
SplitButtonModule,
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
AutoCompleteModule,
|
|
972
|
+
StepsModule,
|
|
973
|
+
TableModule,
|
|
788
974
|
TabsModule,
|
|
789
|
-
|
|
975
|
+
TagModule,
|
|
976
|
+
TextareaModule,
|
|
977
|
+
ToastModule,
|
|
978
|
+
ToggleSwitchModule,
|
|
979
|
+
TooltipModule,
|
|
790
980
|
TreeTableModule,
|
|
791
981
|
],
|
|
792
982
|
}]
|
|
@@ -861,6 +1051,12 @@ class ApiResourceService {
|
|
|
861
1051
|
_listResource = null;
|
|
862
1052
|
/** Whether the list resource has been initialized */
|
|
863
1053
|
_resourceInitialized = false;
|
|
1054
|
+
/**
|
|
1055
|
+
* Signal to track resource initialization for computed signals.
|
|
1056
|
+
* This allows computed signals to re-evaluate when the resource is created.
|
|
1057
|
+
* Without this, computed signals would not detect when _listResource changes from null.
|
|
1058
|
+
*/
|
|
1059
|
+
_resourceInitSignal = signal(false, ...(ngDevMode ? [{ debugName: "_resourceInitSignal" }] : []));
|
|
864
1060
|
/** Get or create the list resource (lazy initialization) */
|
|
865
1061
|
get listResource() {
|
|
866
1062
|
if (!this._listResource) {
|
|
@@ -887,20 +1083,50 @@ class ApiResourceService {
|
|
|
887
1083
|
return this.fetchAllAsync(search, filter);
|
|
888
1084
|
} });
|
|
889
1085
|
});
|
|
1086
|
+
// Signal that resource is now initialized - triggers computed re-evaluation
|
|
1087
|
+
this._resourceInitSignal.set(true);
|
|
890
1088
|
}
|
|
891
1089
|
// ==========================================================================
|
|
892
1090
|
// Computed State Accessors
|
|
893
1091
|
// ==========================================================================
|
|
894
|
-
/**
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
/**
|
|
1092
|
+
/**
|
|
1093
|
+
* Whether data is currently loading.
|
|
1094
|
+
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1095
|
+
*/
|
|
1096
|
+
isLoading = computed(() => {
|
|
1097
|
+
this._resourceInitSignal(); // Track initialization
|
|
1098
|
+
return this._listResource?.isLoading() ?? false;
|
|
1099
|
+
}, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1100
|
+
/**
|
|
1101
|
+
* List data array.
|
|
1102
|
+
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1103
|
+
*/
|
|
1104
|
+
data = computed(() => {
|
|
1105
|
+
this._resourceInitSignal(); // Track initialization
|
|
1106
|
+
return this._listResource?.value()?.data ?? [];
|
|
1107
|
+
}, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1108
|
+
/**
|
|
1109
|
+
* Total count of items.
|
|
1110
|
+
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1111
|
+
*/
|
|
1112
|
+
total = computed(() => {
|
|
1113
|
+
this._resourceInitSignal(); // Track initialization
|
|
1114
|
+
return this._listResource?.value()?.meta?.total ?? 0;
|
|
1115
|
+
}, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1116
|
+
/**
|
|
1117
|
+
* Pagination metadata.
|
|
1118
|
+
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1119
|
+
*/
|
|
1120
|
+
pageInfo = computed(() => {
|
|
1121
|
+
this._resourceInitSignal(); // Track initialization
|
|
1122
|
+
return this._listResource?.value()?.meta;
|
|
1123
|
+
}, ...(ngDevMode ? [{ debugName: "pageInfo" }] : []));
|
|
1124
|
+
/**
|
|
1125
|
+
* Whether there are more pages.
|
|
1126
|
+
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1127
|
+
*/
|
|
903
1128
|
hasMore = computed(() => {
|
|
1129
|
+
this._resourceInitSignal(); // Track initialization
|
|
904
1130
|
const meta = this._listResource?.value()?.meta;
|
|
905
1131
|
if (!meta)
|
|
906
1132
|
return false;
|
|
@@ -1075,38 +1301,6 @@ class ApiResourceService {
|
|
|
1075
1301
|
}
|
|
1076
1302
|
}
|
|
1077
1303
|
|
|
1078
|
-
class IconComponent {
|
|
1079
|
-
icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
1080
|
-
iconType = input(IconTypeEnum.PRIMENG_ICON, ...(ngDevMode ? [{ debugName: "iconType" }] : []));
|
|
1081
|
-
IconTypeEnum = IconTypeEnum; // Needed for template reference
|
|
1082
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1083
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: IconComponent, isStandalone: true, selector: "lib-icon", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1084
|
-
@if(icon()){ @if(iconType()==IconTypeEnum.PRIMENG_ICON){
|
|
1085
|
-
<i [ngClass]="icon()"></i>
|
|
1086
|
-
}@else if(iconType()==IconTypeEnum.IMAGE_FILE_LINK){
|
|
1087
|
-
<img [alt]="icon()" [src]="icon()" />
|
|
1088
|
-
}@else if(iconType()==IconTypeEnum.DIRECT_TAG_SVG){
|
|
1089
|
-
{{ icon() }}
|
|
1090
|
-
}@else{ I } }
|
|
1091
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }] });
|
|
1092
|
-
}
|
|
1093
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, decorators: [{
|
|
1094
|
-
type: Component,
|
|
1095
|
-
args: [{
|
|
1096
|
-
selector: 'lib-icon',
|
|
1097
|
-
imports: [AngularModule],
|
|
1098
|
-
template: `
|
|
1099
|
-
@if(icon()){ @if(iconType()==IconTypeEnum.PRIMENG_ICON){
|
|
1100
|
-
<i [ngClass]="icon()"></i>
|
|
1101
|
-
}@else if(iconType()==IconTypeEnum.IMAGE_FILE_LINK){
|
|
1102
|
-
<img [alt]="icon()" [src]="icon()" />
|
|
1103
|
-
}@else if(iconType()==IconTypeEnum.DIRECT_TAG_SVG){
|
|
1104
|
-
{{ icon() }}
|
|
1105
|
-
}@else{ I } }
|
|
1106
|
-
`,
|
|
1107
|
-
}]
|
|
1108
|
-
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }] } });
|
|
1109
|
-
|
|
1110
1304
|
/**
|
|
1111
1305
|
* Base class for form controls that support ALL Angular form patterns:
|
|
1112
1306
|
*
|
|
@@ -1230,6 +1424,44 @@ function provideValueAccessor(component) {
|
|
|
1230
1424
|
};
|
|
1231
1425
|
}
|
|
1232
1426
|
|
|
1427
|
+
class IconComponent {
|
|
1428
|
+
icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
1429
|
+
iconType = input(IconTypeEnum.PRIMENG_ICON, ...(ngDevMode ? [{ debugName: "iconType" }] : []));
|
|
1430
|
+
IconTypeEnum = IconTypeEnum; // Needed for template reference
|
|
1431
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1432
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: IconComponent, isStandalone: true, selector: "lib-icon", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1433
|
+
@if (icon()) {
|
|
1434
|
+
@if (iconType() === IconTypeEnum.PRIMENG_ICON) {
|
|
1435
|
+
<i [ngClass]="icon()"></i>
|
|
1436
|
+
} @else if (iconType() === IconTypeEnum.IMAGE_FILE_LINK) {
|
|
1437
|
+
<img [alt]="icon()" [src]="icon()" />
|
|
1438
|
+
} @else {
|
|
1439
|
+
<i class="pi pi-question"></i>
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1443
|
+
}
|
|
1444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, decorators: [{
|
|
1445
|
+
type: Component,
|
|
1446
|
+
args: [{
|
|
1447
|
+
selector: 'lib-icon',
|
|
1448
|
+
standalone: true,
|
|
1449
|
+
imports: [AngularModule],
|
|
1450
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1451
|
+
template: `
|
|
1452
|
+
@if (icon()) {
|
|
1453
|
+
@if (iconType() === IconTypeEnum.PRIMENG_ICON) {
|
|
1454
|
+
<i [ngClass]="icon()"></i>
|
|
1455
|
+
} @else if (iconType() === IconTypeEnum.IMAGE_FILE_LINK) {
|
|
1456
|
+
<img [alt]="icon()" [src]="icon()" />
|
|
1457
|
+
} @else {
|
|
1458
|
+
<i class="pi pi-question"></i>
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
`,
|
|
1462
|
+
}]
|
|
1463
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }] } });
|
|
1464
|
+
|
|
1233
1465
|
/**
|
|
1234
1466
|
* Lazy-loading multi-select component with search, pagination, and select-all.
|
|
1235
1467
|
*
|
|
@@ -1239,6 +1471,10 @@ function provideValueAccessor(component) {
|
|
|
1239
1471
|
* - Signal forms: `[formField]="formTree.field"`
|
|
1240
1472
|
*/
|
|
1241
1473
|
class LazyMultiSelectComponent extends BaseFormControl {
|
|
1474
|
+
destroyRef = inject(DestroyRef);
|
|
1475
|
+
onDocumentClickBound = this.handleDocumentClick.bind(this);
|
|
1476
|
+
// View references
|
|
1477
|
+
pSelectRef = viewChild.required('pSelect');
|
|
1242
1478
|
// Inputs
|
|
1243
1479
|
placeHolder = input('Select Options', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1244
1480
|
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
@@ -1246,14 +1482,15 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1246
1482
|
total = input.required(...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1247
1483
|
pagination = input.required(...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
1248
1484
|
selectDataList = input.required(...(ngDevMode ? [{ debugName: "selectDataList" }] : []));
|
|
1249
|
-
|
|
1485
|
+
// Two-way bound value
|
|
1250
1486
|
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1251
1487
|
// Outputs
|
|
1252
1488
|
onSearch = output();
|
|
1253
1489
|
onPagination = output();
|
|
1254
|
-
// UI
|
|
1490
|
+
// UI state
|
|
1255
1491
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1256
|
-
|
|
1492
|
+
openOptions = signal(false, ...(ngDevMode ? [{ debugName: "openOptions" }] : []));
|
|
1493
|
+
// Computed values
|
|
1257
1494
|
selectedValueDisplay = computed(() => {
|
|
1258
1495
|
const selectedValues = this.value() ?? [];
|
|
1259
1496
|
if (selectedValues.length === 0)
|
|
@@ -1262,44 +1499,63 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1262
1499
|
return `${selectedValues.length} Items Selected`;
|
|
1263
1500
|
}
|
|
1264
1501
|
return this.selectDataList()
|
|
1265
|
-
.filter(item => selectedValues.includes(item.value))
|
|
1266
|
-
.map(item => item.label)
|
|
1502
|
+
.filter((item) => selectedValues.includes(item.value))
|
|
1503
|
+
.map((item) => item.label)
|
|
1267
1504
|
.join(', ');
|
|
1268
1505
|
}, ...(ngDevMode ? [{ debugName: "selectedValueDisplay" }] : []));
|
|
1269
|
-
/** Computed: Whether all items are selected (replaces isSelectAll signal) */
|
|
1270
1506
|
isSelectAll = computed(() => {
|
|
1271
1507
|
const selectedValues = this.value() ?? [];
|
|
1272
|
-
const allValues = this.selectDataList().map(item => item.value);
|
|
1508
|
+
const allValues = this.selectDataList().map((item) => item.value);
|
|
1273
1509
|
if (selectedValues.length === 0 || allValues.length === 0)
|
|
1274
1510
|
return false;
|
|
1275
|
-
return allValues.every(val => selectedValues.includes(val));
|
|
1511
|
+
return allValues.every((val) => selectedValues.includes(val));
|
|
1276
1512
|
}, ...(ngDevMode ? [{ debugName: "isSelectAll" }] : []));
|
|
1277
1513
|
constructor() {
|
|
1278
1514
|
super();
|
|
1279
1515
|
this.initializeFormControl();
|
|
1280
|
-
// Search debounce effect
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1516
|
+
// Search debounce using effect
|
|
1517
|
+
let debounceTimeout = null;
|
|
1518
|
+
let previousValue = this.searchTerm();
|
|
1519
|
+
effect((onCleanup) => {
|
|
1520
|
+
const currentValue = this.searchTerm();
|
|
1521
|
+
// Skip unchanged values
|
|
1522
|
+
if (currentValue === previousValue)
|
|
1523
|
+
return;
|
|
1524
|
+
previousValue = currentValue;
|
|
1525
|
+
// Clear existing timeout
|
|
1526
|
+
if (debounceTimeout)
|
|
1527
|
+
clearTimeout(debounceTimeout);
|
|
1528
|
+
// Debounced emit
|
|
1529
|
+
debounceTimeout = setTimeout(() => {
|
|
1530
|
+
this.onSearch.emit(currentValue);
|
|
1531
|
+
}, 500);
|
|
1532
|
+
onCleanup(() => {
|
|
1533
|
+
if (debounceTimeout)
|
|
1534
|
+
clearTimeout(debounceTimeout);
|
|
1535
|
+
});
|
|
1536
|
+
});
|
|
1537
|
+
// Document click listener for closing dropdown
|
|
1538
|
+
afterNextRender(() => {
|
|
1539
|
+
document.addEventListener('click', this.onDocumentClickBound);
|
|
1540
|
+
});
|
|
1541
|
+
this.destroyRef.onDestroy(() => {
|
|
1542
|
+
document.removeEventListener('click', this.onDocumentClickBound);
|
|
1285
1543
|
});
|
|
1286
1544
|
}
|
|
1287
|
-
onScrollBound = this.onScroll.bind(this);
|
|
1288
|
-
multiScrollContainer = viewChild.required('multiScrollContainer');
|
|
1289
1545
|
onScroll(event) {
|
|
1290
1546
|
const el = event.target;
|
|
1547
|
+
if (!(el instanceof HTMLElement))
|
|
1548
|
+
return;
|
|
1291
1549
|
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
|
|
1292
1550
|
if (nearBottom && !this.isLoading()) {
|
|
1293
|
-
const
|
|
1294
|
-
const nextPage =
|
|
1295
|
-
const hasMore = nextPage *
|
|
1551
|
+
const pag = this.pagination();
|
|
1552
|
+
const nextPage = pag.currentPage + 1;
|
|
1553
|
+
const hasMore = nextPage * pag.pageSize < (this.total() ?? 0);
|
|
1296
1554
|
if (hasMore) {
|
|
1297
|
-
this.onPagination.emit({ ...
|
|
1555
|
+
this.onPagination.emit({ ...pag, currentPage: nextPage });
|
|
1298
1556
|
}
|
|
1299
1557
|
}
|
|
1300
1558
|
}
|
|
1301
|
-
pSelectRef = viewChild.required('pSelect');
|
|
1302
|
-
openOptions = signal(false, ...(ngDevMode ? [{ debugName: "openOptions" }] : []));
|
|
1303
1559
|
onSelectClick(event) {
|
|
1304
1560
|
if (this.disabled())
|
|
1305
1561
|
return;
|
|
@@ -1318,7 +1574,7 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1318
1574
|
}
|
|
1319
1575
|
}
|
|
1320
1576
|
isSelected(data) {
|
|
1321
|
-
return this.value()?.includes(data.value);
|
|
1577
|
+
return this.value()?.includes(data.value) ?? false;
|
|
1322
1578
|
}
|
|
1323
1579
|
key(option) {
|
|
1324
1580
|
return option.value;
|
|
@@ -1331,8 +1587,7 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1331
1587
|
}
|
|
1332
1588
|
}
|
|
1333
1589
|
else {
|
|
1334
|
-
|
|
1335
|
-
this.value.set(updated);
|
|
1590
|
+
this.value.set(previousValue.filter((v) => v !== option.value));
|
|
1336
1591
|
}
|
|
1337
1592
|
}
|
|
1338
1593
|
changeSelectAll(event) {
|
|
@@ -1348,15 +1603,12 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1348
1603
|
this.value.set([]);
|
|
1349
1604
|
}
|
|
1350
1605
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazyMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1351
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: LazyMultiSelectComponent, isStandalone: true, selector: "lib-lazy-multi-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" },
|
|
1606
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: LazyMultiSelectComponent, isStandalone: true, selector: "lib-lazy-multi-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazyMultiSelectComponent)], viewQueries: [{ propertyName: "pSelectRef", first: true, predicate: ["pSelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\" [class.p-disabled]=\"disabled()\">\n @if (selectedValueDisplay()) {\n <span class=\"p-select-label\">{{ selectedValueDisplay() }}</span>\n } @else {\n <span class=\"p-select-label p-placeholder\">{{ placeHolder() }}</span>\n }\n\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\">\n <i class=\"pi pi-times\"></i>\n </span>\n\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\n class=\"p-multiselect-dropdown-icon p-icon\" aria-hidden=\"true\">\n <path d=\"M7.01744 10.398C6.91269 10.3985 6.8089 10.378 6.71215 10.3379C6.61541 10.2977 6.52766 10.2386 6.45405 10.1641L1.13907 4.84913C1.03306 4.69404 0.985221 4.5065 1.00399 4.31958C1.02276 4.13266 1.10693 3.95838 1.24166 3.82747C1.37639 3.69655 1.55301 3.61742 1.74039 3.60402C1.92777 3.59062 2.11386 3.64382 2.26584 3.75424L7.01744 8.47394L11.769 3.75424C11.9189 3.65709 12.097 3.61306 12.2748 3.62921C12.4527 3.64535 12.6199 3.72073 12.7498 3.84328C12.8797 3.96582 12.9647 4.12842 12.9912 4.30502C13.0177 4.48162 12.9841 4.662 12.8958 4.81724L7.58083 10.1322C7.50996 10.2125 7.42344 10.2775 7.32656 10.3232C7.22968 10.3689 7.12449 10.3944 7.01744 10.398Z\" fill=\"currentColor\" />\n </svg>\n </span>\n </div>\n\n @if (openOptions()) {\n <div class=\"p-select-overlay\" (click)=\"onOverlayClick($event)\">\n <div class=\"p-select-header flex flex-row gap-2 items-center\">\n <p-checkbox\n binary=\"true\"\n [ngModel]=\"isSelectAll()\"\n [disabled]=\"disabled()\"\n (onChange)=\"changeSelectAll($event)\"\n />\n <input\n type=\"text\"\n pInputText\n class=\"w-full\"\n placeholder=\"Search...\"\n [ngModel]=\"searchTerm()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track key(data)) {\n <li class=\"p-select-option flex flex-row gap-2 items-center\"\n [ngClass]=\"{ 'p-select-option-selected': isSelected(data) }\">\n <p-checkbox\n binary=\"true\"\n [ngModel]=\"isSelected(data)\"\n [disabled]=\"disabled()\"\n (onChange)=\"selectValue($event, data)\"\n />\n <span>{{ data.label }}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>", styles: [".p-select-overlay{position:absolute;top:100%;left:0;right:0;z-index:var(--p-overlay-select-zindex, 1004);margin-top:var(--p-select-overlay-offset, 2px);background:var(--p-select-overlay-background, var(--p-surface-0));border:1px solid var(--p-select-overlay-border-color, var(--p-surface-200));border-radius:var(--p-select-overlay-border-radius, var(--p-border-radius));box-shadow:var(--p-select-overlay-shadow, var(--p-overlay-shadow))}.p-select-header{padding:.75rem;border-bottom:1px solid var(--p-surface-200);background:var(--p-surface-50)}:host-context(.p-dark) .p-select-header,.dark .p-select-header{border-color:var(--p-surface-700);background:var(--p-surface-800)}.p-select-list-container{max-height:10rem;overflow-y:auto}@media(min-width:640px){.p-select-list-container{max-height:12.5rem}}.p-select-list{margin:0;padding:.25rem 0;list-style:none}.p-select-option{padding:.5rem .75rem;cursor:pointer;transition:background-color .2s ease}.p-select-option:hover{background:var(--p-select-option-focus-background, var(--p-surface-100));color:var(--p-select-option-focus-color, var(--p-text-color))}:host-context(.p-dark) .p-select-option:hover,.dark .p-select-option:hover{background:var(--p-surface-700)}.p-select-option.p-select-option-selected{background:var(--p-select-option-selected-background, var(--p-primary-50));color:var(--p-select-option-selected-color, var(--p-primary-color))}:host-context(.p-dark) .p-select-option.p-select-option-selected,.dark .p-select-option.p-select-option-selected{background:var(--p-primary-900)}.p-select-option.p-select-option-selected:hover{background:var(--p-select-option-selected-focus-background, var(--p-primary-100));color:var(--p-select-option-selected-focus-color, var(--p-primary-color))}:host-context(.p-dark) .p-select-option.p-select-option-selected:hover,.dark .p-select-option.p-select-option-selected:hover{background:var(--p-primary-800)}.p-select-clear-icon{display:flex;align-items:center;padding:0 .5rem;color:var(--p-text-color-secondary);cursor:pointer;transition:color .2s ease}.p-select-clear-icon:hover{color:var(--p-text-color)}\n"], dependencies: [{ kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1352
1607
|
}
|
|
1353
1608
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazyMultiSelectComponent, decorators: [{
|
|
1354
1609
|
type: Component,
|
|
1355
|
-
args: [{ selector: 'lib-lazy-multi-select', imports: [PrimeModule, AngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazyMultiSelectComponent)], template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\"
|
|
1356
|
-
}], ctorParameters: () => [], propDecorators: { placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }]
|
|
1357
|
-
type: HostListener,
|
|
1358
|
-
args: ['document:click', ['$event']]
|
|
1359
|
-
}] } });
|
|
1610
|
+
args: [{ selector: 'lib-lazy-multi-select', standalone: true, imports: [PrimeModule, AngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazyMultiSelectComponent)], template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\" [class.p-disabled]=\"disabled()\">\n @if (selectedValueDisplay()) {\n <span class=\"p-select-label\">{{ selectedValueDisplay() }}</span>\n } @else {\n <span class=\"p-select-label p-placeholder\">{{ placeHolder() }}</span>\n }\n\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\">\n <i class=\"pi pi-times\"></i>\n </span>\n\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\n class=\"p-multiselect-dropdown-icon p-icon\" aria-hidden=\"true\">\n <path d=\"M7.01744 10.398C6.91269 10.3985 6.8089 10.378 6.71215 10.3379C6.61541 10.2977 6.52766 10.2386 6.45405 10.1641L1.13907 4.84913C1.03306 4.69404 0.985221 4.5065 1.00399 4.31958C1.02276 4.13266 1.10693 3.95838 1.24166 3.82747C1.37639 3.69655 1.55301 3.61742 1.74039 3.60402C1.92777 3.59062 2.11386 3.64382 2.26584 3.75424L7.01744 8.47394L11.769 3.75424C11.9189 3.65709 12.097 3.61306 12.2748 3.62921C12.4527 3.64535 12.6199 3.72073 12.7498 3.84328C12.8797 3.96582 12.9647 4.12842 12.9912 4.30502C13.0177 4.48162 12.9841 4.662 12.8958 4.81724L7.58083 10.1322C7.50996 10.2125 7.42344 10.2775 7.32656 10.3232C7.22968 10.3689 7.12449 10.3944 7.01744 10.398Z\" fill=\"currentColor\" />\n </svg>\n </span>\n </div>\n\n @if (openOptions()) {\n <div class=\"p-select-overlay\" (click)=\"onOverlayClick($event)\">\n <div class=\"p-select-header flex flex-row gap-2 items-center\">\n <p-checkbox\n binary=\"true\"\n [ngModel]=\"isSelectAll()\"\n [disabled]=\"disabled()\"\n (onChange)=\"changeSelectAll($event)\"\n />\n <input\n type=\"text\"\n pInputText\n class=\"w-full\"\n placeholder=\"Search...\"\n [ngModel]=\"searchTerm()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track key(data)) {\n <li class=\"p-select-option flex flex-row gap-2 items-center\"\n [ngClass]=\"{ 'p-select-option-selected': isSelected(data) }\">\n <p-checkbox\n binary=\"true\"\n [ngModel]=\"isSelected(data)\"\n [disabled]=\"disabled()\"\n (onChange)=\"selectValue($event, data)\"\n />\n <span>{{ data.label }}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>", styles: [".p-select-overlay{position:absolute;top:100%;left:0;right:0;z-index:var(--p-overlay-select-zindex, 1004);margin-top:var(--p-select-overlay-offset, 2px);background:var(--p-select-overlay-background, var(--p-surface-0));border:1px solid var(--p-select-overlay-border-color, var(--p-surface-200));border-radius:var(--p-select-overlay-border-radius, var(--p-border-radius));box-shadow:var(--p-select-overlay-shadow, var(--p-overlay-shadow))}.p-select-header{padding:.75rem;border-bottom:1px solid var(--p-surface-200);background:var(--p-surface-50)}:host-context(.p-dark) .p-select-header,.dark .p-select-header{border-color:var(--p-surface-700);background:var(--p-surface-800)}.p-select-list-container{max-height:10rem;overflow-y:auto}@media(min-width:640px){.p-select-list-container{max-height:12.5rem}}.p-select-list{margin:0;padding:.25rem 0;list-style:none}.p-select-option{padding:.5rem .75rem;cursor:pointer;transition:background-color .2s ease}.p-select-option:hover{background:var(--p-select-option-focus-background, var(--p-surface-100));color:var(--p-select-option-focus-color, var(--p-text-color))}:host-context(.p-dark) .p-select-option:hover,.dark .p-select-option:hover{background:var(--p-surface-700)}.p-select-option.p-select-option-selected{background:var(--p-select-option-selected-background, var(--p-primary-50));color:var(--p-select-option-selected-color, var(--p-primary-color))}:host-context(.p-dark) .p-select-option.p-select-option-selected,.dark .p-select-option.p-select-option-selected{background:var(--p-primary-900)}.p-select-option.p-select-option-selected:hover{background:var(--p-select-option-selected-focus-background, var(--p-primary-100));color:var(--p-select-option-selected-focus-color, var(--p-primary-color))}:host-context(.p-dark) .p-select-option.p-select-option-selected:hover,.dark .p-select-option.p-select-option-selected:hover{background:var(--p-primary-800)}.p-select-clear-icon{display:flex;align-items:center;padding:0 .5rem;color:var(--p-text-color-secondary);cursor:pointer;transition:color .2s ease}.p-select-clear-icon:hover{color:var(--p-text-color)}\n"] }]
|
|
1611
|
+
}], ctorParameters: () => [], propDecorators: { pSelectRef: [{ type: i0.ViewChild, args: ['pSelect', { isSignal: true }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }] } });
|
|
1360
1612
|
|
|
1361
1613
|
/**
|
|
1362
1614
|
* Lazy-loading single select component with search and pagination.
|
|
@@ -1367,6 +1619,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1367
1619
|
* - Signal forms: `[formField]="formTree.field"`
|
|
1368
1620
|
*/
|
|
1369
1621
|
class LazySelectComponent extends BaseFormControl {
|
|
1622
|
+
destroyRef = inject(DestroyRef);
|
|
1623
|
+
onScrollBound = this.onScroll.bind(this);
|
|
1624
|
+
scrollTargetEl = null;
|
|
1625
|
+
// View references
|
|
1626
|
+
scrollContainer = viewChild.required('scrollContainer');
|
|
1370
1627
|
// Inputs
|
|
1371
1628
|
placeHolder = input('Select Option', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1372
1629
|
optionLabel = input.required(...(ngDevMode ? [{ debugName: "optionLabel" }] : []));
|
|
@@ -1376,77 +1633,89 @@ class LazySelectComponent extends BaseFormControl {
|
|
|
1376
1633
|
total = input.required(...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1377
1634
|
pagination = input.required(...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
1378
1635
|
selectDataList = input.required(...(ngDevMode ? [{ debugName: "selectDataList" }] : []));
|
|
1379
|
-
|
|
1636
|
+
// Two-way bound value
|
|
1380
1637
|
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1381
1638
|
// Outputs
|
|
1382
1639
|
onSearch = output();
|
|
1383
1640
|
onPagination = output();
|
|
1384
|
-
// UI
|
|
1641
|
+
// UI state
|
|
1385
1642
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1386
|
-
|
|
1643
|
+
isPanelShow = signal(false, ...(ngDevMode ? [{ debugName: "isPanelShow" }] : []));
|
|
1387
1644
|
constructor() {
|
|
1388
1645
|
super();
|
|
1389
1646
|
this.initializeFormControl();
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1647
|
+
// Search debounce using effect
|
|
1648
|
+
let debounceTimeout = null;
|
|
1649
|
+
let previousValue = this.searchTerm();
|
|
1650
|
+
effect((onCleanup) => {
|
|
1651
|
+
const currentValue = this.searchTerm();
|
|
1652
|
+
// Skip unchanged values
|
|
1653
|
+
if (currentValue === previousValue)
|
|
1654
|
+
return;
|
|
1655
|
+
previousValue = currentValue;
|
|
1656
|
+
// Clear existing timeout
|
|
1657
|
+
if (debounceTimeout)
|
|
1658
|
+
clearTimeout(debounceTimeout);
|
|
1659
|
+
// Debounced emit
|
|
1660
|
+
debounceTimeout = setTimeout(() => {
|
|
1661
|
+
this.onSearch.emit(currentValue);
|
|
1662
|
+
}, 500);
|
|
1663
|
+
onCleanup(() => {
|
|
1664
|
+
if (debounceTimeout)
|
|
1665
|
+
clearTimeout(debounceTimeout);
|
|
1666
|
+
});
|
|
1667
|
+
});
|
|
1668
|
+
// Cleanup scroll listener on destroy
|
|
1669
|
+
this.destroyRef.onDestroy(() => {
|
|
1670
|
+
if (this.scrollTargetEl) {
|
|
1671
|
+
this.scrollTargetEl.removeEventListener('scroll', this.onScrollBound);
|
|
1672
|
+
}
|
|
1394
1673
|
});
|
|
1395
1674
|
}
|
|
1396
|
-
// Signal to toggle panel
|
|
1397
|
-
isPanelShow = signal(false, ...(ngDevMode ? [{ debugName: "isPanelShow" }] : []));
|
|
1398
|
-
scrollTargetEl = null;
|
|
1399
|
-
onScrollBound = this.onScroll.bind(this);
|
|
1400
|
-
scrollContainer = viewChild.required('scrollContainer');
|
|
1401
1675
|
onScroll(event) {
|
|
1402
1676
|
const el = event.target;
|
|
1677
|
+
if (!(el instanceof HTMLElement))
|
|
1678
|
+
return;
|
|
1403
1679
|
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
|
|
1404
1680
|
if (nearBottom && !this.isLoading()) {
|
|
1405
|
-
const
|
|
1406
|
-
const nextPage =
|
|
1407
|
-
const hasMore = nextPage *
|
|
1681
|
+
const pag = this.pagination();
|
|
1682
|
+
const nextPage = pag.currentPage + 1;
|
|
1683
|
+
const hasMore = nextPage * pag.pageSize < (this.total() ?? 0);
|
|
1408
1684
|
if (hasMore) {
|
|
1409
|
-
this.onPagination.emit({ ...
|
|
1685
|
+
this.onPagination.emit({ ...pag, currentPage: nextPage });
|
|
1410
1686
|
}
|
|
1411
1687
|
}
|
|
1412
1688
|
}
|
|
1413
|
-
// Toggle panel and manage scroll event
|
|
1414
1689
|
showPanel() {
|
|
1415
1690
|
if (this.disabled())
|
|
1416
1691
|
return;
|
|
1417
|
-
this.isPanelShow.update(prev => !prev);
|
|
1692
|
+
this.isPanelShow.update((prev) => !prev);
|
|
1418
1693
|
const isNowVisible = this.isPanelShow();
|
|
1419
1694
|
if (isNowVisible) {
|
|
1420
|
-
|
|
1695
|
+
afterNextRender(() => {
|
|
1421
1696
|
const containerEl = this.scrollContainer().nativeElement;
|
|
1422
1697
|
const target = containerEl.querySelector('.p-select-list-container');
|
|
1423
1698
|
if (target) {
|
|
1424
1699
|
target.addEventListener('scroll', this.onScrollBound);
|
|
1425
1700
|
this.scrollTargetEl = target;
|
|
1426
1701
|
}
|
|
1427
|
-
},
|
|
1702
|
+
}, { injector: this.injector });
|
|
1428
1703
|
}
|
|
1429
|
-
else {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
this.scrollTargetEl = null;
|
|
1433
|
-
}
|
|
1704
|
+
else if (this.scrollTargetEl) {
|
|
1705
|
+
this.scrollTargetEl.removeEventListener('scroll', this.onScrollBound);
|
|
1706
|
+
this.scrollTargetEl = null;
|
|
1434
1707
|
}
|
|
1435
1708
|
}
|
|
1436
1709
|
onBlur() {
|
|
1437
1710
|
this.markAsTouched();
|
|
1438
1711
|
}
|
|
1439
1712
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazySelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1440
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.3", type: LazySelectComponent, isStandalone: true, selector: "lib-lazy-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: true, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: true, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazySelectComponent)], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n
|
|
1713
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.3", type: LazySelectComponent, isStandalone: true, selector: "lib-lazy-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: true, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: true, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazySelectComponent)], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n class=\"w-full\"\n [options]=\"selectDataList()\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"placeHolder()\"\n [disabled]=\"disabled()\"\n [(ngModel)]=\"value\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\"\n >\n <ng-template #filter let-filter>\n <input\n pInputText\n class=\"w-full\"\n [ngModel]=\"searchTerm()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template #item let-item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: EditModeElementChangerDirective, selector: "[appEditModeElementChanger]", inputs: ["isEditMode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1441
1714
|
}
|
|
1442
1715
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazySelectComponent, decorators: [{
|
|
1443
1716
|
type: Component,
|
|
1444
|
-
args: [{ selector: 'lib-lazy-select', imports: [
|
|
1445
|
-
|
|
1446
|
-
PrimeModule,
|
|
1447
|
-
EditModeElementChangerDirective
|
|
1448
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazySelectComponent)], template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n [options]=\"selectDataList()\"\n [(ngModel)]=\"value\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"placeHolder()\"\n [disabled]=\"disabled()\"\n class=\"w-full\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\">\n <ng-template let-filter #filter>\n <input\n pInputText\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n [ngModelOptions]=\"{standalone:true}\"\n class=\"w-full\" />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template let-item #item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n" }]
|
|
1449
|
-
}], ctorParameters: () => [], propDecorators: { placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: true }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: true }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
|
|
1717
|
+
args: [{ selector: 'lib-lazy-select', standalone: true, imports: [AngularModule, PrimeModule, EditModeElementChangerDirective], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazySelectComponent)], template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n class=\"w-full\"\n [options]=\"selectDataList()\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"placeHolder()\"\n [disabled]=\"disabled()\"\n [(ngModel)]=\"value\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\"\n >\n <ng-template #filter let-filter>\n <input\n pInputText\n class=\"w-full\"\n [ngModel]=\"searchTerm()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template #item let-item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n" }]
|
|
1718
|
+
}], ctorParameters: () => [], propDecorators: { scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: true }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: true }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }] } });
|
|
1450
1719
|
|
|
1451
1720
|
/**
|
|
1452
1721
|
* Injection Tokens for Provider Interfaces
|
|
@@ -1513,6 +1782,36 @@ const AUTH_STATE_PROVIDER = new InjectionToken('AUTH_STATE_PROVIDER', {
|
|
|
1513
1782
|
throw new Error('AUTH_STATE_PROVIDER not configured. Please provide an implementation in app.config.ts');
|
|
1514
1783
|
},
|
|
1515
1784
|
});
|
|
1785
|
+
/**
|
|
1786
|
+
* Profile Permission Provider Token
|
|
1787
|
+
*
|
|
1788
|
+
* Provides user permission data for profile display.
|
|
1789
|
+
* Optional - if not configured, profile permissions section is hidden.
|
|
1790
|
+
* Use with `inject(PROFILE_PERMISSION_PROVIDER, { optional: true })`.
|
|
1791
|
+
*/
|
|
1792
|
+
const PROFILE_PERMISSION_PROVIDER = new InjectionToken('PROFILE_PERMISSION_PROVIDER');
|
|
1793
|
+
/**
|
|
1794
|
+
* Profile Upload Provider Token
|
|
1795
|
+
*
|
|
1796
|
+
* Provides file upload functionality for profile pictures.
|
|
1797
|
+
* Optional - if not configured or storage not enabled, upload section is hidden.
|
|
1798
|
+
* Use with `inject(PROFILE_UPLOAD_PROVIDER, { optional: true })`.
|
|
1799
|
+
*/
|
|
1800
|
+
const PROFILE_UPLOAD_PROVIDER = new InjectionToken('PROFILE_UPLOAD_PROVIDER');
|
|
1801
|
+
/**
|
|
1802
|
+
* User List Provider Token
|
|
1803
|
+
*
|
|
1804
|
+
* Provides extra actions, columns, and data enrichment for user list.
|
|
1805
|
+
* Optional - if not configured, default user list behavior is used.
|
|
1806
|
+
* Use with `inject(USER_LIST_PROVIDER, { optional: true })`.
|
|
1807
|
+
*
|
|
1808
|
+
* @example
|
|
1809
|
+
* // In app.config.ts
|
|
1810
|
+
* providers: [
|
|
1811
|
+
* { provide: USER_LIST_PROVIDER, useClass: MyUserListProvider },
|
|
1812
|
+
* ]
|
|
1813
|
+
*/
|
|
1814
|
+
const USER_LIST_PROVIDER = new InjectionToken('USER_LIST_PROVIDER');
|
|
1516
1815
|
|
|
1517
1816
|
const DEFAULT_PAGE_SIZE$2 = 20;
|
|
1518
1817
|
/**
|
|
@@ -1914,41 +2213,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
1914
2213
|
* File Uploader Component - Drag & drop file upload with type filtering.
|
|
1915
2214
|
*
|
|
1916
2215
|
* Pass your own `uploadFile` function - works with any storage API.
|
|
1917
|
-
*
|
|
1918
|
-
* Features:
|
|
1919
|
-
* - Drag & drop support
|
|
1920
|
-
* - File type filtering (images, documents, etc.)
|
|
1921
|
-
* - Upload progress indication
|
|
1922
|
-
* - Multiple file support (optional)
|
|
1923
|
-
* - Image compression options
|
|
1924
|
-
*
|
|
1925
|
-
* @example
|
|
1926
|
-
* ```typescript
|
|
1927
|
-
* // In component
|
|
1928
|
-
* readonly uploadService = inject(UploadService);
|
|
1929
|
-
*
|
|
1930
|
-
* readonly uploadFile: UploadFileFn = (file, options) =>
|
|
1931
|
-
* this.uploadService.uploadSingleFile(file, options);
|
|
1932
|
-
* ```
|
|
1933
|
-
*
|
|
1934
|
-
* ```html
|
|
1935
|
-
* <!-- Single image upload -->
|
|
1936
|
-
* <lib-file-uploader
|
|
1937
|
-
* [uploadFile]="uploadFile"
|
|
1938
|
-
* [acceptTypes]="['image/*']"
|
|
1939
|
-
* [multiple]="false"
|
|
1940
|
-
* (fileUploaded)="onFileUploaded($event)"
|
|
1941
|
-
* />
|
|
1942
|
-
*
|
|
1943
|
-
* <!-- Multiple document upload -->
|
|
1944
|
-
* <lib-file-uploader
|
|
1945
|
-
* [uploadFile]="uploadFile"
|
|
1946
|
-
* [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
|
|
1947
|
-
* [multiple]="true"
|
|
1948
|
-
* [maxFiles]="5"
|
|
1949
|
-
* (filesUploaded)="onFilesUploaded($event)"
|
|
1950
|
-
* />
|
|
1951
|
-
* ```
|
|
1952
2216
|
*/
|
|
1953
2217
|
class FileUploaderComponent {
|
|
1954
2218
|
messageService = inject(MessageService);
|
|
@@ -2094,11 +2358,7 @@ class FileUploaderComponent {
|
|
|
2094
2358
|
});
|
|
2095
2359
|
}
|
|
2096
2360
|
catch (error) {
|
|
2097
|
-
|
|
2098
|
-
severity: 'error',
|
|
2099
|
-
summary: 'Upload Failed',
|
|
2100
|
-
detail: error.message || 'Failed to upload file',
|
|
2101
|
-
});
|
|
2361
|
+
// Error toast handled by global interceptor
|
|
2102
2362
|
this.onError.emit(error);
|
|
2103
2363
|
}
|
|
2104
2364
|
finally {
|
|
@@ -2120,37 +2380,41 @@ class FileUploaderComponent {
|
|
|
2120
2380
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2121
2381
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: FileUploaderComponent, isStandalone: true, selector: "lib-file-uploader", inputs: { uploadFile: { classPropertyName: "uploadFile", publicName: "uploadFile", isSignal: true, isRequired: true, transformFunction: null }, acceptTypes: { classPropertyName: "acceptTypes", publicName: "acceptTypes", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, uploadOptions: { classPropertyName: "uploadOptions", publicName: "uploadOptions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileUploaded: "fileUploaded", filesUploaded: "filesUploaded", onError: "onError", fileSelected: "fileSelected" }, ngImport: i0, template: `
|
|
2122
2382
|
<div
|
|
2123
|
-
class="
|
|
2124
|
-
[class.
|
|
2125
|
-
[class.disabled]="disabled()"
|
|
2383
|
+
class="w-full"
|
|
2384
|
+
[class.opacity-60]="disabled()"
|
|
2126
2385
|
(dragover)="onDragOver($event)"
|
|
2127
2386
|
(dragleave)="onDragLeave($event)"
|
|
2128
2387
|
(drop)="onDrop($event)"
|
|
2129
2388
|
>
|
|
2130
|
-
<!-- Upload Area -->
|
|
2131
|
-
<div
|
|
2389
|
+
<!-- Upload Area - Responsive padding -->
|
|
2390
|
+
<div
|
|
2391
|
+
class="upload-zone border-2 border-dashed rounded-lg p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-200 text-center"
|
|
2392
|
+
[class.drag-over]="isDragOver()"
|
|
2393
|
+
[class.cursor-not-allowed]="disabled()"
|
|
2394
|
+
(click)="fileInput.click()"
|
|
2395
|
+
>
|
|
2132
2396
|
@if (isUploading()) {
|
|
2133
|
-
<div class="
|
|
2134
|
-
<i class="pi pi-spin pi-spinner text-4xl text-primary"></i>
|
|
2135
|
-
<p class="mt-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2397
|
+
<div class="flex flex-col items-center">
|
|
2398
|
+
<i class="pi pi-spin pi-spinner text-3xl sm:text-4xl text-primary"></i>
|
|
2399
|
+
<p class="mt-2 text-sm sm:text-base break-all px-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2136
2400
|
@if (uploadProgress() > 0) {
|
|
2137
|
-
<p-progressBar [value]="uploadProgress()" [showValue]="true" />
|
|
2401
|
+
<p-progressBar [value]="uploadProgress()" [showValue]="true" class="w-full mt-2 max-w-xs" />
|
|
2138
2402
|
}
|
|
2139
2403
|
</div>
|
|
2140
2404
|
} @else {
|
|
2141
|
-
<div class="
|
|
2142
|
-
<i class="pi pi-cloud-upload text-4xl text-primary"></i>
|
|
2143
|
-
<p class="mt-2 mb-1 font-semibold">
|
|
2405
|
+
<div class="flex flex-col items-center">
|
|
2406
|
+
<i class="pi pi-cloud-upload text-3xl sm:text-4xl text-primary"></i>
|
|
2407
|
+
<p class="mt-2 mb-1 font-semibold text-sm sm:text-base">
|
|
2144
2408
|
{{ multiple() ? 'Drop files here or click to upload' : 'Drop file here or click to upload' }}
|
|
2145
2409
|
</p>
|
|
2146
|
-
<p class="text-sm text-color-secondary">
|
|
2410
|
+
<p class="text-xs sm:text-sm text-color-secondary px-2">
|
|
2147
2411
|
@if (acceptTypesDisplay()) {
|
|
2148
2412
|
Allowed: {{ acceptTypesDisplay() }}
|
|
2149
2413
|
} @else {
|
|
2150
2414
|
All file types allowed
|
|
2151
2415
|
}
|
|
2152
2416
|
@if (maxSizeMb()) {
|
|
2153
|
-
(Max {{ maxSizeMb() }}MB)
|
|
2417
|
+
<span class="whitespace-nowrap">(Max {{ maxSizeMb() }}MB)</span>
|
|
2154
2418
|
}
|
|
2155
2419
|
</p>
|
|
2156
2420
|
</div>
|
|
@@ -2161,71 +2425,76 @@ class FileUploaderComponent {
|
|
|
2161
2425
|
<input
|
|
2162
2426
|
#fileInput
|
|
2163
2427
|
type="file"
|
|
2428
|
+
class="hidden"
|
|
2164
2429
|
[accept]="acceptString()"
|
|
2165
2430
|
[multiple]="multiple()"
|
|
2166
2431
|
[disabled]="disabled() || isUploading()"
|
|
2167
2432
|
(change)="onFileSelected($event)"
|
|
2168
|
-
class="hidden"
|
|
2169
2433
|
/>
|
|
2170
2434
|
|
|
2171
|
-
<!-- Selected Files Preview -->
|
|
2435
|
+
<!-- Selected Files Preview - Responsive layout -->
|
|
2172
2436
|
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2173
|
-
<div class="
|
|
2437
|
+
<div class="mt-3 space-y-2">
|
|
2174
2438
|
@for (file of selectedFiles(); track file.name) {
|
|
2175
|
-
<div class="file-item flex
|
|
2176
|
-
<i [class]="getFileIcon(file)"></i>
|
|
2177
|
-
<span class="flex-1 text-
|
|
2178
|
-
<span class="text-sm text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2179
|
-
<button
|
|
2180
|
-
pButton
|
|
2181
|
-
type="button"
|
|
2439
|
+
<div class="file-preview-item flex items-center gap-2 p-2 sm:p-3 rounded-lg">
|
|
2440
|
+
<i [class]="getFileIcon(file)" class="text-lg sm:text-xl flex-shrink-0"></i>
|
|
2441
|
+
<span class="flex-1 truncate text-sm sm:text-base min-w-0">{{ file.name }}</span>
|
|
2442
|
+
<span class="text-xs sm:text-sm text-color-secondary whitespace-nowrap">{{ formatSize(file.size) }}</span>
|
|
2443
|
+
<p-button
|
|
2182
2444
|
icon="pi pi-times"
|
|
2183
|
-
|
|
2184
|
-
|
|
2445
|
+
[text]="true"
|
|
2446
|
+
[rounded]="true"
|
|
2447
|
+
size="small"
|
|
2448
|
+
severity="secondary"
|
|
2185
2449
|
[disabled]="isUploading()"
|
|
2186
|
-
|
|
2450
|
+
(onClick)="removeFile(file)"
|
|
2451
|
+
/>
|
|
2187
2452
|
</div>
|
|
2188
2453
|
}
|
|
2189
2454
|
</div>
|
|
2190
2455
|
}
|
|
2191
2456
|
</div>
|
|
2192
|
-
`, isInline: true, styles: [".
|
|
2457
|
+
`, isInline: true, styles: [".upload-zone{border-color:var(--p-surface-300);background:var(--p-surface-50)}.upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-100)}.upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .upload-zone,.dark .upload-zone{border-color:var(--p-surface-600);background:var(--p-surface-800)}:host-context(.p-dark) .upload-zone:hover:not(.cursor-not-allowed),.dark .upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-700)}:host-context(.p-dark) .upload-zone.drag-over,.dark .upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-900)}.file-preview-item{border:1px solid var(--p-surface-200);background:var(--p-surface-0)}:host-context(.p-dark) .file-preview-item,.dark .file-preview-item{border-color:var(--p-surface-600);background:var(--p-surface-800)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2193
2458
|
}
|
|
2194
2459
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUploaderComponent, decorators: [{
|
|
2195
2460
|
type: Component,
|
|
2196
2461
|
args: [{ selector: 'lib-file-uploader', standalone: true, imports: [AngularModule, PrimeModule], template: `
|
|
2197
2462
|
<div
|
|
2198
|
-
class="
|
|
2199
|
-
[class.
|
|
2200
|
-
[class.disabled]="disabled()"
|
|
2463
|
+
class="w-full"
|
|
2464
|
+
[class.opacity-60]="disabled()"
|
|
2201
2465
|
(dragover)="onDragOver($event)"
|
|
2202
2466
|
(dragleave)="onDragLeave($event)"
|
|
2203
2467
|
(drop)="onDrop($event)"
|
|
2204
2468
|
>
|
|
2205
|
-
<!-- Upload Area -->
|
|
2206
|
-
<div
|
|
2469
|
+
<!-- Upload Area - Responsive padding -->
|
|
2470
|
+
<div
|
|
2471
|
+
class="upload-zone border-2 border-dashed rounded-lg p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-200 text-center"
|
|
2472
|
+
[class.drag-over]="isDragOver()"
|
|
2473
|
+
[class.cursor-not-allowed]="disabled()"
|
|
2474
|
+
(click)="fileInput.click()"
|
|
2475
|
+
>
|
|
2207
2476
|
@if (isUploading()) {
|
|
2208
|
-
<div class="
|
|
2209
|
-
<i class="pi pi-spin pi-spinner text-4xl text-primary"></i>
|
|
2210
|
-
<p class="mt-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2477
|
+
<div class="flex flex-col items-center">
|
|
2478
|
+
<i class="pi pi-spin pi-spinner text-3xl sm:text-4xl text-primary"></i>
|
|
2479
|
+
<p class="mt-2 text-sm sm:text-base break-all px-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2211
2480
|
@if (uploadProgress() > 0) {
|
|
2212
|
-
<p-progressBar [value]="uploadProgress()" [showValue]="true" />
|
|
2481
|
+
<p-progressBar [value]="uploadProgress()" [showValue]="true" class="w-full mt-2 max-w-xs" />
|
|
2213
2482
|
}
|
|
2214
2483
|
</div>
|
|
2215
2484
|
} @else {
|
|
2216
|
-
<div class="
|
|
2217
|
-
<i class="pi pi-cloud-upload text-4xl text-primary"></i>
|
|
2218
|
-
<p class="mt-2 mb-1 font-semibold">
|
|
2485
|
+
<div class="flex flex-col items-center">
|
|
2486
|
+
<i class="pi pi-cloud-upload text-3xl sm:text-4xl text-primary"></i>
|
|
2487
|
+
<p class="mt-2 mb-1 font-semibold text-sm sm:text-base">
|
|
2219
2488
|
{{ multiple() ? 'Drop files here or click to upload' : 'Drop file here or click to upload' }}
|
|
2220
2489
|
</p>
|
|
2221
|
-
<p class="text-sm text-color-secondary">
|
|
2490
|
+
<p class="text-xs sm:text-sm text-color-secondary px-2">
|
|
2222
2491
|
@if (acceptTypesDisplay()) {
|
|
2223
2492
|
Allowed: {{ acceptTypesDisplay() }}
|
|
2224
2493
|
} @else {
|
|
2225
2494
|
All file types allowed
|
|
2226
2495
|
}
|
|
2227
2496
|
@if (maxSizeMb()) {
|
|
2228
|
-
(Max {{ maxSizeMb() }}MB)
|
|
2497
|
+
<span class="whitespace-nowrap">(Max {{ maxSizeMb() }}MB)</span>
|
|
2229
2498
|
}
|
|
2230
2499
|
</p>
|
|
2231
2500
|
</div>
|
|
@@ -2236,35 +2505,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2236
2505
|
<input
|
|
2237
2506
|
#fileInput
|
|
2238
2507
|
type="file"
|
|
2508
|
+
class="hidden"
|
|
2239
2509
|
[accept]="acceptString()"
|
|
2240
2510
|
[multiple]="multiple()"
|
|
2241
2511
|
[disabled]="disabled() || isUploading()"
|
|
2242
2512
|
(change)="onFileSelected($event)"
|
|
2243
|
-
class="hidden"
|
|
2244
2513
|
/>
|
|
2245
2514
|
|
|
2246
|
-
<!-- Selected Files Preview -->
|
|
2515
|
+
<!-- Selected Files Preview - Responsive layout -->
|
|
2247
2516
|
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2248
|
-
<div class="
|
|
2517
|
+
<div class="mt-3 space-y-2">
|
|
2249
2518
|
@for (file of selectedFiles(); track file.name) {
|
|
2250
|
-
<div class="file-item flex
|
|
2251
|
-
<i [class]="getFileIcon(file)"></i>
|
|
2252
|
-
<span class="flex-1 text-
|
|
2253
|
-
<span class="text-sm text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2254
|
-
<button
|
|
2255
|
-
pButton
|
|
2256
|
-
type="button"
|
|
2519
|
+
<div class="file-preview-item flex items-center gap-2 p-2 sm:p-3 rounded-lg">
|
|
2520
|
+
<i [class]="getFileIcon(file)" class="text-lg sm:text-xl flex-shrink-0"></i>
|
|
2521
|
+
<span class="flex-1 truncate text-sm sm:text-base min-w-0">{{ file.name }}</span>
|
|
2522
|
+
<span class="text-xs sm:text-sm text-color-secondary whitespace-nowrap">{{ formatSize(file.size) }}</span>
|
|
2523
|
+
<p-button
|
|
2257
2524
|
icon="pi pi-times"
|
|
2258
|
-
|
|
2259
|
-
|
|
2525
|
+
[text]="true"
|
|
2526
|
+
[rounded]="true"
|
|
2527
|
+
size="small"
|
|
2528
|
+
severity="secondary"
|
|
2260
2529
|
[disabled]="isUploading()"
|
|
2261
|
-
|
|
2530
|
+
(onClick)="removeFile(file)"
|
|
2531
|
+
/>
|
|
2262
2532
|
</div>
|
|
2263
2533
|
}
|
|
2264
2534
|
</div>
|
|
2265
2535
|
}
|
|
2266
2536
|
</div>
|
|
2267
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".
|
|
2537
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".upload-zone{border-color:var(--p-surface-300);background:var(--p-surface-50)}.upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-100)}.upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .upload-zone,.dark .upload-zone{border-color:var(--p-surface-600);background:var(--p-surface-800)}:host-context(.p-dark) .upload-zone:hover:not(.cursor-not-allowed),.dark .upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-700)}:host-context(.p-dark) .upload-zone.drag-over,.dark .upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-900)}.file-preview-item{border:1px solid var(--p-surface-200);background:var(--p-surface-0)}:host-context(.p-dark) .file-preview-item,.dark .file-preview-item{border-color:var(--p-surface-600);background:var(--p-surface-800)}\n"] }]
|
|
2268
2538
|
}], propDecorators: { uploadFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadFile", required: true }] }], acceptTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptTypes", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], maxSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeMb", required: false }] }], uploadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadOptions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreview", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], fileUploaded: [{ type: i0.Output, args: ["fileUploaded"] }], filesUploaded: [{ type: i0.Output, args: ["filesUploaded"] }], onError: [{ type: i0.Output, args: ["onError"] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }] } });
|
|
2269
2539
|
|
|
2270
2540
|
const DEFAULT_PAGE_SIZE = 20;
|
|
@@ -2499,11 +2769,13 @@ class FileSelectorDialogComponent {
|
|
|
2499
2769
|
[closable]="true"
|
|
2500
2770
|
[draggable]="false"
|
|
2501
2771
|
[resizable]="false"
|
|
2502
|
-
[
|
|
2772
|
+
[breakpoints]="{ '960px': '90vw', '640px': '95vw' }"
|
|
2773
|
+
[style]="{ width: '800px', maxWidth: '95vw', maxHeight: '90vh' }"
|
|
2774
|
+
styleClass="file-selector-dialog"
|
|
2503
2775
|
(onHide)="onDialogHide()"
|
|
2504
2776
|
>
|
|
2505
2777
|
<!-- Search Bar -->
|
|
2506
|
-
<div class="flex gap-2 mb-3">
|
|
2778
|
+
<div class="flex flex-col sm:flex-row gap-2 mb-3">
|
|
2507
2779
|
<span class="p-input-icon-left flex-1">
|
|
2508
2780
|
<i class="pi pi-search"></i>
|
|
2509
2781
|
<input
|
|
@@ -2516,25 +2788,25 @@ class FileSelectorDialogComponent {
|
|
|
2516
2788
|
/>
|
|
2517
2789
|
</span>
|
|
2518
2790
|
@if (multiple()) {
|
|
2519
|
-
<span class="text-sm text-color-secondary
|
|
2791
|
+
<span class="text-sm text-color-secondary self-center whitespace-nowrap">
|
|
2520
2792
|
{{ selectedFiles().length }} selected
|
|
2521
2793
|
</span>
|
|
2522
2794
|
}
|
|
2523
2795
|
</div>
|
|
2524
2796
|
|
|
2525
|
-
<!-- File Grid -->
|
|
2797
|
+
<!-- File Grid - Responsive columns -->
|
|
2526
2798
|
<div
|
|
2527
2799
|
class="file-grid"
|
|
2528
2800
|
#scrollContainer
|
|
2529
2801
|
(scroll)="onScroll($event)"
|
|
2530
2802
|
>
|
|
2531
2803
|
@if (isLoading() && files().length === 0) {
|
|
2532
|
-
<div class="flex justify-
|
|
2533
|
-
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
2804
|
+
<div class="col-span-full flex justify-center p-4">
|
|
2805
|
+
<i class="pi pi-spin pi-spinner text-4xl text-color-secondary"></i>
|
|
2534
2806
|
</div>
|
|
2535
2807
|
} @else if (files().length === 0) {
|
|
2536
|
-
<div class="text-center p-4 text-color-secondary">
|
|
2537
|
-
<i class="pi pi-inbox text-4xl mb-2"></i>
|
|
2808
|
+
<div class="col-span-full text-center p-4 text-color-secondary">
|
|
2809
|
+
<i class="pi pi-inbox text-4xl mb-2 block"></i>
|
|
2538
2810
|
<p>No files found</p>
|
|
2539
2811
|
</div>
|
|
2540
2812
|
} @else {
|
|
@@ -2548,50 +2820,55 @@ class FileSelectorDialogComponent {
|
|
|
2548
2820
|
<!-- File Preview -->
|
|
2549
2821
|
<div class="file-preview">
|
|
2550
2822
|
@if (isImage(file) && file.url) {
|
|
2551
|
-
<img [src]="file.url" [alt]="file.name" class="
|
|
2823
|
+
<img [src]="file.url" [alt]="file.name" class="w-full h-full object-cover" />
|
|
2552
2824
|
} @else {
|
|
2553
|
-
<i [class]="getFileIcon(file)" class="
|
|
2825
|
+
<i [class]="getFileIcon(file)" class="text-4xl sm:text-5xl text-color-secondary"></i>
|
|
2554
2826
|
}
|
|
2555
2827
|
@if (isSelected(file)) {
|
|
2556
2828
|
<div class="selected-overlay">
|
|
2557
|
-
<i class="pi pi-check"></i>
|
|
2829
|
+
<i class="pi pi-check text-xl sm:text-2xl"></i>
|
|
2558
2830
|
</div>
|
|
2559
2831
|
}
|
|
2560
2832
|
</div>
|
|
2561
2833
|
|
|
2562
2834
|
<!-- File Info -->
|
|
2563
|
-
<div class="
|
|
2564
|
-
<span class="
|
|
2565
|
-
|
|
2835
|
+
<div class="p-2 text-center bg-surface-0 dark:bg-surface-900">
|
|
2836
|
+
<span class="block text-xs sm:text-sm whitespace-nowrap overflow-hidden text-ellipsis" [title]="file.name">
|
|
2837
|
+
{{ file.name }}
|
|
2838
|
+
</span>
|
|
2839
|
+
<span class="block text-xs text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2566
2840
|
</div>
|
|
2567
2841
|
</div>
|
|
2568
2842
|
}
|
|
2569
2843
|
|
|
2570
2844
|
@if (isLoading()) {
|
|
2571
|
-
<div class="flex justify-
|
|
2572
|
-
<i class="pi pi-spin pi-spinner"></i>
|
|
2845
|
+
<div class="col-span-full flex justify-center p-2">
|
|
2846
|
+
<i class="pi pi-spin pi-spinner text-color-secondary"></i>
|
|
2573
2847
|
</div>
|
|
2574
2848
|
}
|
|
2575
2849
|
}
|
|
2576
2850
|
</div>
|
|
2577
2851
|
|
|
2578
2852
|
<!-- Footer -->
|
|
2579
|
-
<ng-template
|
|
2580
|
-
<
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2853
|
+
<ng-template #footer>
|
|
2854
|
+
<div class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:w-auto sm:justify-end">
|
|
2855
|
+
<button
|
|
2856
|
+
pButton
|
|
2857
|
+
label="Cancel"
|
|
2858
|
+
class="p-button-text w-full sm:w-auto"
|
|
2859
|
+
(click)="onCancel()"
|
|
2860
|
+
></button>
|
|
2861
|
+
<button
|
|
2862
|
+
pButton
|
|
2863
|
+
[label]="multiple() ? 'Select (' + selectedFiles().length + ')' : 'Select'"
|
|
2864
|
+
[disabled]="selectedFiles().length === 0"
|
|
2865
|
+
class="w-full sm:w-auto"
|
|
2866
|
+
(click)="onConfirm()"
|
|
2867
|
+
></button>
|
|
2868
|
+
</div>
|
|
2592
2869
|
</ng-template>
|
|
2593
2870
|
</p-dialog>
|
|
2594
|
-
`, isInline: true, styles: [".file-grid{display:grid;grid-template-columns:repeat(
|
|
2871
|
+
`, isInline: true, styles: [".file-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem;max-height:300px;overflow-y:auto;padding:.5rem}@media(min-width:480px){.file-grid{grid-template-columns:repeat(3,1fr);max-height:350px}}@media(min-width:640px){.file-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;max-height:400px}}.file-card{border:2px solid var(--p-surface-300);border-radius:var(--p-border-radius);cursor:pointer;transition:all .2s ease;overflow:hidden;background:var(--p-surface-0)}:host-context(.p-dark) .file-card,.dark .file-card{border-color:var(--p-surface-600);background:var(--p-surface-800)}.file-card:hover:not(.disabled){border-color:var(--p-primary-color);transform:translateY(-2px);box-shadow:var(--p-overlay-shadow)}.file-card.selected{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .file-card.selected,.dark .file-card.selected{background:var(--p-primary-900)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:80px;display:flex;align-items:center;justify-content:center;background:var(--p-surface-100)}@media(min-width:640px){.file-preview{height:100px}}:host-context(.p-dark) .file-preview,.dark .file-preview{background:var(--p-surface-700)}.selected-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--p-primary-500-rgb, 59, 130, 246),.3)}.selected-overlay i{color:var(--p-primary-color);background:var(--p-surface-0);border-radius:50%;padding:.5rem}:host-context(.p-dark) .selected-overlay i,.dark .selected-overlay i{background:var(--p-surface-900)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i1$3.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i4.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2595
2872
|
}
|
|
2596
2873
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileSelectorDialogComponent, decorators: [{
|
|
2597
2874
|
type: Component,
|
|
@@ -2603,11 +2880,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2603
2880
|
[closable]="true"
|
|
2604
2881
|
[draggable]="false"
|
|
2605
2882
|
[resizable]="false"
|
|
2606
|
-
[
|
|
2883
|
+
[breakpoints]="{ '960px': '90vw', '640px': '95vw' }"
|
|
2884
|
+
[style]="{ width: '800px', maxWidth: '95vw', maxHeight: '90vh' }"
|
|
2885
|
+
styleClass="file-selector-dialog"
|
|
2607
2886
|
(onHide)="onDialogHide()"
|
|
2608
2887
|
>
|
|
2609
2888
|
<!-- Search Bar -->
|
|
2610
|
-
<div class="flex gap-2 mb-3">
|
|
2889
|
+
<div class="flex flex-col sm:flex-row gap-2 mb-3">
|
|
2611
2890
|
<span class="p-input-icon-left flex-1">
|
|
2612
2891
|
<i class="pi pi-search"></i>
|
|
2613
2892
|
<input
|
|
@@ -2620,25 +2899,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2620
2899
|
/>
|
|
2621
2900
|
</span>
|
|
2622
2901
|
@if (multiple()) {
|
|
2623
|
-
<span class="text-sm text-color-secondary
|
|
2902
|
+
<span class="text-sm text-color-secondary self-center whitespace-nowrap">
|
|
2624
2903
|
{{ selectedFiles().length }} selected
|
|
2625
2904
|
</span>
|
|
2626
2905
|
}
|
|
2627
2906
|
</div>
|
|
2628
2907
|
|
|
2629
|
-
<!-- File Grid -->
|
|
2908
|
+
<!-- File Grid - Responsive columns -->
|
|
2630
2909
|
<div
|
|
2631
2910
|
class="file-grid"
|
|
2632
2911
|
#scrollContainer
|
|
2633
2912
|
(scroll)="onScroll($event)"
|
|
2634
2913
|
>
|
|
2635
2914
|
@if (isLoading() && files().length === 0) {
|
|
2636
|
-
<div class="flex justify-
|
|
2637
|
-
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
2915
|
+
<div class="col-span-full flex justify-center p-4">
|
|
2916
|
+
<i class="pi pi-spin pi-spinner text-4xl text-color-secondary"></i>
|
|
2638
2917
|
</div>
|
|
2639
2918
|
} @else if (files().length === 0) {
|
|
2640
|
-
<div class="text-center p-4 text-color-secondary">
|
|
2641
|
-
<i class="pi pi-inbox text-4xl mb-2"></i>
|
|
2919
|
+
<div class="col-span-full text-center p-4 text-color-secondary">
|
|
2920
|
+
<i class="pi pi-inbox text-4xl mb-2 block"></i>
|
|
2642
2921
|
<p>No files found</p>
|
|
2643
2922
|
</div>
|
|
2644
2923
|
} @else {
|
|
@@ -2652,50 +2931,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2652
2931
|
<!-- File Preview -->
|
|
2653
2932
|
<div class="file-preview">
|
|
2654
2933
|
@if (isImage(file) && file.url) {
|
|
2655
|
-
<img [src]="file.url" [alt]="file.name" class="
|
|
2934
|
+
<img [src]="file.url" [alt]="file.name" class="w-full h-full object-cover" />
|
|
2656
2935
|
} @else {
|
|
2657
|
-
<i [class]="getFileIcon(file)" class="
|
|
2936
|
+
<i [class]="getFileIcon(file)" class="text-4xl sm:text-5xl text-color-secondary"></i>
|
|
2658
2937
|
}
|
|
2659
2938
|
@if (isSelected(file)) {
|
|
2660
2939
|
<div class="selected-overlay">
|
|
2661
|
-
<i class="pi pi-check"></i>
|
|
2940
|
+
<i class="pi pi-check text-xl sm:text-2xl"></i>
|
|
2662
2941
|
</div>
|
|
2663
2942
|
}
|
|
2664
2943
|
</div>
|
|
2665
2944
|
|
|
2666
2945
|
<!-- File Info -->
|
|
2667
|
-
<div class="
|
|
2668
|
-
<span class="
|
|
2669
|
-
|
|
2946
|
+
<div class="p-2 text-center bg-surface-0 dark:bg-surface-900">
|
|
2947
|
+
<span class="block text-xs sm:text-sm whitespace-nowrap overflow-hidden text-ellipsis" [title]="file.name">
|
|
2948
|
+
{{ file.name }}
|
|
2949
|
+
</span>
|
|
2950
|
+
<span class="block text-xs text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2670
2951
|
</div>
|
|
2671
2952
|
</div>
|
|
2672
2953
|
}
|
|
2673
2954
|
|
|
2674
2955
|
@if (isLoading()) {
|
|
2675
|
-
<div class="flex justify-
|
|
2676
|
-
<i class="pi pi-spin pi-spinner"></i>
|
|
2956
|
+
<div class="col-span-full flex justify-center p-2">
|
|
2957
|
+
<i class="pi pi-spin pi-spinner text-color-secondary"></i>
|
|
2677
2958
|
</div>
|
|
2678
2959
|
}
|
|
2679
2960
|
}
|
|
2680
2961
|
</div>
|
|
2681
2962
|
|
|
2682
2963
|
<!-- Footer -->
|
|
2683
|
-
<ng-template
|
|
2684
|
-
<
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2964
|
+
<ng-template #footer>
|
|
2965
|
+
<div class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:w-auto sm:justify-end">
|
|
2966
|
+
<button
|
|
2967
|
+
pButton
|
|
2968
|
+
label="Cancel"
|
|
2969
|
+
class="p-button-text w-full sm:w-auto"
|
|
2970
|
+
(click)="onCancel()"
|
|
2971
|
+
></button>
|
|
2972
|
+
<button
|
|
2973
|
+
pButton
|
|
2974
|
+
[label]="multiple() ? 'Select (' + selectedFiles().length + ')' : 'Select'"
|
|
2975
|
+
[disabled]="selectedFiles().length === 0"
|
|
2976
|
+
class="w-full sm:w-auto"
|
|
2977
|
+
(click)="onConfirm()"
|
|
2978
|
+
></button>
|
|
2979
|
+
</div>
|
|
2696
2980
|
</ng-template>
|
|
2697
2981
|
</p-dialog>
|
|
2698
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-grid{display:grid;grid-template-columns:repeat(
|
|
2982
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem;max-height:300px;overflow-y:auto;padding:.5rem}@media(min-width:480px){.file-grid{grid-template-columns:repeat(3,1fr);max-height:350px}}@media(min-width:640px){.file-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;max-height:400px}}.file-card{border:2px solid var(--p-surface-300);border-radius:var(--p-border-radius);cursor:pointer;transition:all .2s ease;overflow:hidden;background:var(--p-surface-0)}:host-context(.p-dark) .file-card,.dark .file-card{border-color:var(--p-surface-600);background:var(--p-surface-800)}.file-card:hover:not(.disabled){border-color:var(--p-primary-color);transform:translateY(-2px);box-shadow:var(--p-overlay-shadow)}.file-card.selected{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .file-card.selected,.dark .file-card.selected{background:var(--p-primary-900)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:80px;display:flex;align-items:center;justify-content:center;background:var(--p-surface-100)}@media(min-width:640px){.file-preview{height:100px}}:host-context(.p-dark) .file-preview,.dark .file-preview{background:var(--p-surface-700)}.selected-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--p-primary-500-rgb, 59, 130, 246),.3)}.selected-overlay i{color:var(--p-primary-color);background:var(--p-surface-0);border-radius:50%;padding:.5rem}:host-context(.p-dark) .selected-overlay i,.dark .selected-overlay i{background:var(--p-surface-900)}\n"] }]
|
|
2699
2983
|
}], ctorParameters: () => [], propDecorators: { loadFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadFiles", required: true }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], acceptTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptTypes", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelection", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], filesSelected: [{ type: i0.Output, args: ["filesSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }], onError: [{ type: i0.Output, args: ["onError"] }] } });
|
|
2700
2984
|
|
|
2701
2985
|
/** Log only in dev mode */
|
|
@@ -2823,11 +3107,362 @@ function allPermissionsGuard(permissions, redirectTo = '/') {
|
|
|
2823
3107
|
};
|
|
2824
3108
|
}
|
|
2825
3109
|
|
|
2826
|
-
|
|
3110
|
+
/**
|
|
3111
|
+
* Base class for form page components that handle create/edit operations.
|
|
3112
|
+
* Provides common functionality for loading existing items, form submission,
|
|
3113
|
+
* navigation, and toast notifications.
|
|
3114
|
+
*
|
|
3115
|
+
* ## Features
|
|
3116
|
+
* - Automatic route parameter handling (loads item when ID is present)
|
|
3117
|
+
* - Edit mode detection based on existing item
|
|
3118
|
+
* - Unified submit handler for create/update operations
|
|
3119
|
+
* - Cancel navigation
|
|
3120
|
+
* - Toast messages for success/validation errors
|
|
3121
|
+
*
|
|
3122
|
+
* ## Usage
|
|
3123
|
+
*
|
|
3124
|
+
* ```typescript
|
|
3125
|
+
* interface IProductFormModel {
|
|
3126
|
+
* name: string;
|
|
3127
|
+
* price: number;
|
|
3128
|
+
* }
|
|
3129
|
+
*
|
|
3130
|
+
* @Component({
|
|
3131
|
+
* selector: 'app-product-form',
|
|
3132
|
+
* standalone: true,
|
|
3133
|
+
* changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3134
|
+
* template: `...`
|
|
3135
|
+
* })
|
|
3136
|
+
* export class ProductFormComponent extends BaseFormPage<IProduct, IProductFormModel> {
|
|
3137
|
+
* private readonly productService = inject(ProductApiService);
|
|
3138
|
+
*
|
|
3139
|
+
* // Form model signal (private writable, public readonly)
|
|
3140
|
+
* private readonly _formModel = signal<IProductFormModel>({ name: '', price: 0 });
|
|
3141
|
+
* readonly formModel = this._formModel.asReadonly();
|
|
3142
|
+
*
|
|
3143
|
+
* // Required abstract implementations
|
|
3144
|
+
* getFormModel(): Signal<IProductFormModel> {
|
|
3145
|
+
* return this.formModel;
|
|
3146
|
+
* }
|
|
3147
|
+
*
|
|
3148
|
+
* getResourceRoute(): string {
|
|
3149
|
+
* return '/products';
|
|
3150
|
+
* }
|
|
3151
|
+
*
|
|
3152
|
+
* getResourceName(): string {
|
|
3153
|
+
* return 'Product';
|
|
3154
|
+
* }
|
|
3155
|
+
*
|
|
3156
|
+
* isFormValid(): boolean {
|
|
3157
|
+
* const model = this.formModel();
|
|
3158
|
+
* return model.name.trim().length > 0 && model.price > 0;
|
|
3159
|
+
* }
|
|
3160
|
+
*
|
|
3161
|
+
* loadItem(id: string): void {
|
|
3162
|
+
* this.isLoading.set(true);
|
|
3163
|
+
* this.productService.findById(id)
|
|
3164
|
+
* .pipe(takeUntilDestroyed(this.destroyRef))
|
|
3165
|
+
* .subscribe({
|
|
3166
|
+
* next: (response) => {
|
|
3167
|
+
* if (response.success && response.data) {
|
|
3168
|
+
* this.existingItem.set(response.data);
|
|
3169
|
+
* this._formModel.set({
|
|
3170
|
+
* name: response.data.name,
|
|
3171
|
+
* price: response.data.price,
|
|
3172
|
+
* });
|
|
3173
|
+
* }
|
|
3174
|
+
* this.isLoading.set(false);
|
|
3175
|
+
* },
|
|
3176
|
+
* error: () => {
|
|
3177
|
+
* this.router.navigate([this.getResourceRoute()]);
|
|
3178
|
+
* this.isLoading.set(false);
|
|
3179
|
+
* },
|
|
3180
|
+
* });
|
|
3181
|
+
* }
|
|
3182
|
+
*
|
|
3183
|
+
* createItem(model: IProductFormModel): Observable<ISingleResponse<IProduct>> {
|
|
3184
|
+
* return this.productService.insert(model);
|
|
3185
|
+
* }
|
|
3186
|
+
*
|
|
3187
|
+
* updateItem(model: IProductFormModel): Observable<ISingleResponse<IProduct>> {
|
|
3188
|
+
* return this.productService.update({ id: this.existingItem()!.id, ...model });
|
|
3189
|
+
* }
|
|
3190
|
+
* }
|
|
3191
|
+
* ```
|
|
3192
|
+
*
|
|
3193
|
+
* @template T The entity/interface type being edited
|
|
3194
|
+
* @template TFormModel The form model interface
|
|
3195
|
+
*/
|
|
3196
|
+
class BaseFormPage {
|
|
3197
|
+
router = inject(Router);
|
|
3198
|
+
route = inject(ActivatedRoute);
|
|
3199
|
+
messageService = inject(MessageService);
|
|
3200
|
+
destroyRef = inject(DestroyRef);
|
|
3201
|
+
routeParams = toSignal(this.route.paramMap);
|
|
3202
|
+
initialized = false;
|
|
3203
|
+
/** Loading state for async operations */
|
|
3204
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
3205
|
+
/** The existing item when in edit mode, null when creating */
|
|
3206
|
+
existingItem = signal(null, ...(ngDevMode ? [{ debugName: "existingItem" }] : []));
|
|
3207
|
+
/** Whether the form is in edit mode (has existing item) */
|
|
3208
|
+
isEditMode = computed(() => !!this.existingItem(), ...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
3209
|
+
constructor() {
|
|
3210
|
+
effect(() => {
|
|
3211
|
+
const params = this.routeParams();
|
|
3212
|
+
if (!params || this.initialized)
|
|
3213
|
+
return;
|
|
3214
|
+
this.initialized = true;
|
|
3215
|
+
const id = params.get('id');
|
|
3216
|
+
if (id && id !== 'new') {
|
|
3217
|
+
this.loadItem(id);
|
|
3218
|
+
}
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Handle form submission.
|
|
3223
|
+
* Validates the form, then calls createItem or updateItem based on mode.
|
|
3224
|
+
* Shows appropriate toast messages and navigates back on success.
|
|
3225
|
+
*/
|
|
3226
|
+
onSubmit() {
|
|
3227
|
+
if (!this.isFormValid()) {
|
|
3228
|
+
this.showValidationError();
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
this.isLoading.set(true);
|
|
3232
|
+
const model = this.getFormModel()();
|
|
3233
|
+
const operation$ = this.isEditMode()
|
|
3234
|
+
? this.updateItem(model)
|
|
3235
|
+
: this.createItem(model);
|
|
3236
|
+
operation$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
3237
|
+
next: () => {
|
|
3238
|
+
const action = this.isEditMode() ? 'updated' : 'created';
|
|
3239
|
+
this.showSuccess(`${this.getResourceName()} ${action} successfully.`);
|
|
3240
|
+
this.router.navigate([this.getResourceRoute()]);
|
|
3241
|
+
},
|
|
3242
|
+
error: () => {
|
|
3243
|
+
this.isLoading.set(false);
|
|
3244
|
+
},
|
|
3245
|
+
complete: () => {
|
|
3246
|
+
this.isLoading.set(false);
|
|
3247
|
+
},
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Handle cancel action.
|
|
3252
|
+
* Navigates back to the resource list.
|
|
3253
|
+
*/
|
|
3254
|
+
onCancel() {
|
|
3255
|
+
this.router.navigate([this.getResourceRoute()]);
|
|
3256
|
+
}
|
|
3257
|
+
/**
|
|
3258
|
+
* Show validation error toast.
|
|
3259
|
+
* Override to customize the validation error message.
|
|
3260
|
+
*/
|
|
3261
|
+
showValidationError() {
|
|
3262
|
+
this.messageService.add({
|
|
3263
|
+
severity: 'error',
|
|
3264
|
+
summary: 'Validation Error',
|
|
3265
|
+
detail: 'Please fill in all required fields.',
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Show success toast.
|
|
3270
|
+
* @param detail The success message to display
|
|
3271
|
+
*/
|
|
3272
|
+
showSuccess(detail) {
|
|
3273
|
+
this.messageService.add({
|
|
3274
|
+
severity: 'success',
|
|
3275
|
+
summary: 'Success',
|
|
3276
|
+
detail,
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Show error toast.
|
|
3281
|
+
* @param detail The error message to display
|
|
3282
|
+
*/
|
|
3283
|
+
showError(detail) {
|
|
3284
|
+
this.messageService.add({
|
|
3285
|
+
severity: 'error',
|
|
3286
|
+
summary: 'Error',
|
|
3287
|
+
detail,
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseFormPage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3291
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: BaseFormPage, isStandalone: true, ngImport: i0 });
|
|
3292
|
+
}
|
|
3293
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseFormPage, decorators: [{
|
|
3294
|
+
type: Directive
|
|
3295
|
+
}], ctorParameters: () => [] });
|
|
3296
|
+
|
|
3297
|
+
/**
|
|
3298
|
+
* Base List Page
|
|
3299
|
+
* Abstract directive providing common signals, computed values, and utilities
|
|
3300
|
+
* for list page components across all feature packages.
|
|
3301
|
+
*
|
|
3302
|
+
* Features:
|
|
3303
|
+
* - Pagination state management (pageSize, currentPage, total)
|
|
3304
|
+
* - Loading state
|
|
3305
|
+
* - CRUD navigation helpers
|
|
3306
|
+
* - Message display utilities
|
|
3307
|
+
* - Delete confirmation with API integration
|
|
3308
|
+
* - Company feature flag support
|
|
3309
|
+
*
|
|
3310
|
+
* Usage:
|
|
3311
|
+
* ```typescript
|
|
3312
|
+
* @Component({ ... })
|
|
3313
|
+
* export class UserListComponent extends BaseListPage<IUser> {
|
|
3314
|
+
* getResourceRoute(): string { return '/users'; }
|
|
3315
|
+
* getDeleteConfirmMessage(user: IUser): string { return `Delete "${user.name}"?`; }
|
|
3316
|
+
* async loadData(): Promise<void> { ... }
|
|
3317
|
+
* }
|
|
3318
|
+
* ```
|
|
3319
|
+
*/
|
|
3320
|
+
class BaseListPage {
|
|
3321
|
+
router = inject(Router);
|
|
3322
|
+
messageService = inject(MessageService);
|
|
3323
|
+
appConfig = inject(APP_CONFIG);
|
|
3324
|
+
confirmationService = inject(ConfirmationService);
|
|
3325
|
+
destroyRef = inject(DestroyRef);
|
|
3326
|
+
/** Items list */
|
|
3327
|
+
items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
3328
|
+
/** Loading state */
|
|
3329
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
3330
|
+
/** Total records for pagination */
|
|
3331
|
+
total = signal(0, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
3332
|
+
/** Page size */
|
|
3333
|
+
pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
3334
|
+
/** First record index (for p-table lazy load) */
|
|
3335
|
+
first = signal(0, ...(ngDevMode ? [{ debugName: "first" }] : []));
|
|
3336
|
+
/** Current page (0-based for API, derived from first/pageSize) */
|
|
3337
|
+
currentPage = computed(() => Math.floor(this.first() / this.pageSize()), ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
3338
|
+
/** Show company info if company feature enabled */
|
|
3339
|
+
showCompanyInfo = computed(() => this.appConfig.enableCompanyFeature, ...(ngDevMode ? [{ debugName: "showCompanyInfo" }] : []));
|
|
3340
|
+
/**
|
|
3341
|
+
* Navigate to create page
|
|
3342
|
+
*/
|
|
3343
|
+
onCreate() {
|
|
3344
|
+
this.router.navigate([this.getResourceRoute(), 'new']);
|
|
3345
|
+
}
|
|
3346
|
+
/**
|
|
3347
|
+
* Navigate to edit page
|
|
3348
|
+
* @param id The ID of the item to edit
|
|
3349
|
+
*/
|
|
3350
|
+
onEdit(id) {
|
|
3351
|
+
this.router.navigate([this.getResourceRoute(), id]);
|
|
3352
|
+
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Handle page change from p-table lazy load
|
|
3355
|
+
*/
|
|
3356
|
+
onPageChange(event) {
|
|
3357
|
+
this.first.set(event.first ?? 0);
|
|
3358
|
+
this.pageSize.set(event.rows ?? 10);
|
|
3359
|
+
this.loadData();
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Get pagination params for API call
|
|
3363
|
+
* Returns 0-based page for backend API
|
|
3364
|
+
*/
|
|
3365
|
+
getPaginationParams() {
|
|
3366
|
+
return {
|
|
3367
|
+
currentPage: this.currentPage(),
|
|
3368
|
+
pageSize: this.pageSize(),
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* Show success toast message
|
|
3373
|
+
*/
|
|
3374
|
+
showSuccess(detail, summary = 'Success') {
|
|
3375
|
+
this.messageService.add({ severity: 'success', summary, detail });
|
|
3376
|
+
}
|
|
3377
|
+
/**
|
|
3378
|
+
* Show error toast message
|
|
3379
|
+
*/
|
|
3380
|
+
showError(detail, summary = 'Error') {
|
|
3381
|
+
this.messageService.add({ severity: 'error', summary, detail });
|
|
3382
|
+
}
|
|
3383
|
+
/**
|
|
3384
|
+
* Show info toast message
|
|
3385
|
+
*/
|
|
3386
|
+
showInfo(detail, summary = 'Info') {
|
|
3387
|
+
this.messageService.add({ severity: 'info', summary, detail });
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Show warning toast message
|
|
3391
|
+
*/
|
|
3392
|
+
showWarn(detail, summary = 'Warning') {
|
|
3393
|
+
this.messageService.add({ severity: 'warn', summary, detail });
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Delete an item with confirmation dialog
|
|
3397
|
+
* @param item The item to delete
|
|
3398
|
+
* @param idGetter Function to extract ID from item
|
|
3399
|
+
* @param deleteApiCall Function that returns Observable for delete API call
|
|
3400
|
+
* @param options Optional configuration
|
|
3401
|
+
*/
|
|
3402
|
+
onDelete(item, idGetter, deleteApiCall, options) {
|
|
3403
|
+
this.confirmationService.confirm({
|
|
3404
|
+
message: this.getDeleteConfirmMessage(item),
|
|
3405
|
+
header: options?.header ?? 'Confirm Delete',
|
|
3406
|
+
icon: 'pi pi-exclamation-triangle',
|
|
3407
|
+
acceptButtonStyleClass: 'p-button-danger',
|
|
3408
|
+
accept: () => {
|
|
3409
|
+
deleteApiCall(idGetter(item))
|
|
3410
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3411
|
+
.subscribe({
|
|
3412
|
+
next: () => {
|
|
3413
|
+
this.showSuccess(options?.successMessage ?? 'Item deleted successfully.');
|
|
3414
|
+
this.loadData();
|
|
3415
|
+
},
|
|
3416
|
+
error: () => {
|
|
3417
|
+
this.showError(options?.errorMessage ?? 'Failed to delete item.');
|
|
3418
|
+
},
|
|
3419
|
+
});
|
|
3420
|
+
},
|
|
3421
|
+
});
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3424
|
+
* Delete an item with confirmation dialog using async/await
|
|
3425
|
+
* @param item The item to delete
|
|
3426
|
+
* @param idGetter Function to extract ID from item
|
|
3427
|
+
* @param deleteApiCall Async function for delete API call
|
|
3428
|
+
* @param options Optional configuration
|
|
3429
|
+
*/
|
|
3430
|
+
async onDeleteAsync(item, idGetter, deleteApiCall, options) {
|
|
3431
|
+
this.confirmationService.confirm({
|
|
3432
|
+
message: this.getDeleteConfirmMessage(item),
|
|
3433
|
+
header: options?.header ?? 'Confirm Delete',
|
|
3434
|
+
icon: 'pi pi-exclamation-triangle',
|
|
3435
|
+
acceptButtonStyleClass: 'p-button-danger',
|
|
3436
|
+
accept: async () => {
|
|
3437
|
+
try {
|
|
3438
|
+
await deleteApiCall(idGetter(item));
|
|
3439
|
+
this.showSuccess(options?.successMessage ?? 'Item deleted successfully.');
|
|
3440
|
+
await this.loadData();
|
|
3441
|
+
}
|
|
3442
|
+
catch {
|
|
3443
|
+
this.showError(options?.errorMessage ?? 'Failed to delete item.');
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
/**
|
|
3449
|
+
* Navigate to a route
|
|
3450
|
+
*/
|
|
3451
|
+
navigateTo(path) {
|
|
3452
|
+
this.router.navigate(path);
|
|
3453
|
+
}
|
|
3454
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseListPage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3455
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: BaseListPage, isStandalone: true, ngImport: i0 });
|
|
3456
|
+
}
|
|
3457
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseListPage, decorators: [{
|
|
3458
|
+
type: Directive
|
|
3459
|
+
}] });
|
|
3460
|
+
|
|
3461
|
+
// Constants
|
|
2827
3462
|
|
|
2828
3463
|
/**
|
|
2829
3464
|
* Generated bundle index. Do not edit.
|
|
2830
3465
|
*/
|
|
2831
3466
|
|
|
2832
|
-
export { AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, COMPANY_API_PROVIDER, ContactTypeEnum, CookieService, EditModeElementChangerDirective, FILE_TYPE_FILTERS, FileSelectorDialogComponent, FileUploaderComponent, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LazyMultiSelectComponent, LazySelectComponent, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, USER_PERMISSION_PROVIDER, USER_PROVIDER, UserMultiSelectComponent, UserSelectComponent, allPermissionsGuard, anyPermissionGuard, evaluateLogicNode, evaluatePermission, formatFileSize, getAcceptString, getFileIconClass, hasAllPermissions, hasAnyPermission, isFileTypeAllowed, permissionGuard };
|
|
3467
|
+
export { ACTION_PERMISSIONS, AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, BRANCH_PERMISSIONS, BaseFormControl, BaseFormPage, BaseListPage, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, COMPANY_PERMISSIONS, ContactTypeEnum, CookieService, EMAIL_CONFIG_PERMISSIONS, EMAIL_TEMPLATE_PERMISSIONS, EditModeElementChangerDirective, FILE_PERMISSIONS, FILE_TYPE_FILTERS, FOLDER_PERMISSIONS, FORM_PERMISSIONS, FileSelectorDialogComponent, FileUploaderComponent, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LazyMultiSelectComponent, LazySelectComponent, PERMISSIONS, PROFILE_PERMISSION_PROVIDER, PROFILE_UPLOAD_PROVIDER, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, ROLE_ACTION_PERMISSIONS, ROLE_PERMISSIONS, STORAGE_CONFIG_PERMISSIONS, USER_ACTION_PERMISSIONS, USER_LIST_PROVIDER, USER_PERMISSIONS, USER_PERMISSION_PROVIDER, USER_PROVIDER, USER_ROLE_PERMISSIONS, UserMultiSelectComponent, UserSelectComponent, allPermissionsGuard, anyPermissionGuard, evaluateLogicNode, evaluatePermission, formatFileSize, getAcceptString, getFileIconClass, hasAllPermissions, hasAnyPermission, hasPermission, isFileTypeAllowed, permissionGuard, provideValueAccessor };
|
|
2833
3468
|
//# sourceMappingURL=flusys-ng-shared.mjs.map
|