@flusys/ng-shared 5.0.1 → 5.0.4
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 +29 -25
- package/fesm2022/flusys-ng-shared.mjs +427 -197
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +2 -2
- package/types/flusys-ng-shared.d.ts +210 -181
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output, Pipe, Injector, isDevMode, NgModule, resource, model, runInInjectionContext, untracked, forwardRef, InjectionToken, Component, DestroyRef, ApplicationRef, viewChild, afterNextRender, ViewEncapsulation } from '@angular/core';
|
|
3
|
-
import { isPlatformServer, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
4
|
-
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
5
|
-
import { APP_CONFIG, getServiceUrl,
|
|
6
|
-
import { of, firstValueFrom, map as map$1, from } from 'rxjs';
|
|
3
|
+
import { isPlatformServer, isPlatformBrowser, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
4
|
+
import { HttpClient, HttpContextToken, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
5
|
+
import { APP_CONFIG, getServiceUrl, LOCALIZATION_STATE_SERVICE, LOCALIZATION_API_SERVICE, FALLBACK_MESSAGES_REGISTRY } from '@flusys/ng-core';
|
|
6
|
+
import { of, catchError as catchError$1, throwError, firstValueFrom, map as map$1, from } from 'rxjs';
|
|
7
7
|
import { map, tap, catchError } from 'rxjs/operators';
|
|
8
8
|
import * as i1$1 from '@angular/forms';
|
|
9
9
|
import { NgControl, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
10
|
+
import * as i3 from 'primeng/api';
|
|
11
|
+
import { MessageService, ConfirmationService } from 'primeng/api';
|
|
10
12
|
import { RouterOutlet, RouterLink, RouterLinkActive, Router, ActivatedRoute } from '@angular/router';
|
|
11
13
|
import { AutoCompleteModule } from 'primeng/autocomplete';
|
|
12
14
|
import { AvatarModule } from 'primeng/avatar';
|
|
@@ -56,8 +58,6 @@ import { TooltipModule } from 'primeng/tooltip';
|
|
|
56
58
|
import { TreeTableModule } from 'primeng/treetable';
|
|
57
59
|
import { ProgressSpinnerModule } from 'primeng/progressspinner';
|
|
58
60
|
import { ColorPickerModule } from 'primeng/colorpicker';
|
|
59
|
-
import * as i3 from 'primeng/api';
|
|
60
|
-
import { MessageService, ConfirmationService } from 'primeng/api';
|
|
61
61
|
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
62
62
|
|
|
63
63
|
const SHARED_MESSAGES = {
|
|
@@ -209,6 +209,7 @@ const SHARED_MESSAGES = {
|
|
|
209
209
|
'shared.upload': 'Upload',
|
|
210
210
|
'shared.validation.required': '{{field}} is required',
|
|
211
211
|
'shared.verified': 'Verified',
|
|
212
|
+
'shared.unverified': 'Un-Verified',
|
|
212
213
|
'shared.view': 'View',
|
|
213
214
|
'shared.view.details': 'View Details',
|
|
214
215
|
'shared.yes': 'Yes',
|
|
@@ -308,6 +309,7 @@ const SHARED_MESSAGES = {
|
|
|
308
309
|
'primeng.country.placeholder': 'Choose a country',
|
|
309
310
|
};
|
|
310
311
|
|
|
312
|
+
// ==================== AUTH MODULE ====================
|
|
311
313
|
const USER_PERMISSIONS = {
|
|
312
314
|
CREATE: 'user.create',
|
|
313
315
|
READ: 'user.read',
|
|
@@ -326,6 +328,7 @@ const BRANCH_PERMISSIONS = {
|
|
|
326
328
|
UPDATE: 'branch.update',
|
|
327
329
|
DELETE: 'branch.delete',
|
|
328
330
|
};
|
|
331
|
+
// ==================== IAM MODULE ====================
|
|
329
332
|
const ACTION_PERMISSIONS = {
|
|
330
333
|
CREATE: 'action.create',
|
|
331
334
|
READ: 'action.read',
|
|
@@ -354,6 +357,7 @@ const COMPANY_ACTION_PERMISSIONS = {
|
|
|
354
357
|
READ: 'company-action.read',
|
|
355
358
|
ASSIGN: 'company-action.assign',
|
|
356
359
|
};
|
|
360
|
+
// ==================== STORAGE MODULE ====================
|
|
357
361
|
const FILE_PERMISSIONS = {
|
|
358
362
|
CREATE: 'file.create',
|
|
359
363
|
READ: 'file.read',
|
|
@@ -372,6 +376,10 @@ const STORAGE_CONFIG_PERMISSIONS = {
|
|
|
372
376
|
UPDATE: 'storage-config.update',
|
|
373
377
|
DELETE: 'storage-config.delete',
|
|
374
378
|
};
|
|
379
|
+
const UPLOAD_CONFIG_PERMISSIONS = {
|
|
380
|
+
DELETE: 'upload-config.delete',
|
|
381
|
+
};
|
|
382
|
+
// ==================== EMAIL MODULE ====================
|
|
375
383
|
const EMAIL_CONFIG_PERMISSIONS = {
|
|
376
384
|
CREATE: 'email-config.create',
|
|
377
385
|
READ: 'email-config.read',
|
|
@@ -384,30 +392,34 @@ const EMAIL_TEMPLATE_PERMISSIONS = {
|
|
|
384
392
|
UPDATE: 'email-template.update',
|
|
385
393
|
DELETE: 'email-template.delete',
|
|
386
394
|
};
|
|
395
|
+
// ==================== FORM BUILDER MODULE ====================
|
|
387
396
|
const FORM_PERMISSIONS = {
|
|
388
397
|
CREATE: 'form.create',
|
|
389
398
|
READ: 'form.read',
|
|
390
399
|
UPDATE: 'form.update',
|
|
391
400
|
DELETE: 'form.delete',
|
|
392
401
|
};
|
|
402
|
+
const FORM_RESULT_PERMISSIONS = {
|
|
403
|
+
CREATE: 'form-result.create',
|
|
404
|
+
READ: 'form-result.read',
|
|
405
|
+
UPDATE: 'form-result.update',
|
|
406
|
+
DELETE: 'form-result.delete',
|
|
407
|
+
};
|
|
408
|
+
// ==================== EVENT MANAGER MODULE ====================
|
|
393
409
|
const EVENT_PERMISSIONS = {
|
|
394
410
|
CREATE: 'event.create',
|
|
395
411
|
READ: 'event.read',
|
|
396
412
|
UPDATE: 'event.update',
|
|
397
413
|
DELETE: 'event.delete',
|
|
398
414
|
};
|
|
399
|
-
|
|
400
|
-
CREATE: 'event-participant.create',
|
|
401
|
-
READ: 'event-participant.read',
|
|
402
|
-
UPDATE: 'event-participant.update',
|
|
403
|
-
DELETE: 'event-participant.delete',
|
|
404
|
-
};
|
|
415
|
+
// ==================== NOTIFICATION MODULE ====================
|
|
405
416
|
const NOTIFICATION_PERMISSIONS = {
|
|
406
417
|
CREATE: 'notification.create',
|
|
407
418
|
READ: 'notification.read',
|
|
408
419
|
UPDATE: 'notification.update',
|
|
409
420
|
DELETE: 'notification.delete',
|
|
410
421
|
};
|
|
422
|
+
// ==================== LOCALIZATION MODULE ====================
|
|
411
423
|
const LANGUAGE_PERMISSIONS = {
|
|
412
424
|
CREATE: 'language.create',
|
|
413
425
|
READ: 'language.read',
|
|
@@ -426,10 +438,48 @@ const TRANSLATION_PERMISSIONS = {
|
|
|
426
438
|
UPDATE: 'translation.update',
|
|
427
439
|
DELETE: 'translation.delete',
|
|
428
440
|
};
|
|
441
|
+
// ==================== AGGREGATED EXPORTS ====================
|
|
442
|
+
const PERMISSIONS = {
|
|
443
|
+
// Auth
|
|
444
|
+
USER: USER_PERMISSIONS,
|
|
445
|
+
COMPANY: COMPANY_PERMISSIONS,
|
|
446
|
+
BRANCH: BRANCH_PERMISSIONS,
|
|
447
|
+
// IAM
|
|
448
|
+
ACTION: ACTION_PERMISSIONS,
|
|
449
|
+
ROLE: ROLE_PERMISSIONS,
|
|
450
|
+
ROLE_ACTION: ROLE_ACTION_PERMISSIONS,
|
|
451
|
+
USER_ROLE: USER_ROLE_PERMISSIONS,
|
|
452
|
+
USER_ACTION: USER_ACTION_PERMISSIONS,
|
|
453
|
+
COMPANY_ACTION: COMPANY_ACTION_PERMISSIONS,
|
|
454
|
+
// Storage
|
|
455
|
+
FILE: FILE_PERMISSIONS,
|
|
456
|
+
FOLDER: FOLDER_PERMISSIONS,
|
|
457
|
+
STORAGE_CONFIG: STORAGE_CONFIG_PERMISSIONS,
|
|
458
|
+
// Email
|
|
459
|
+
EMAIL_CONFIG: EMAIL_CONFIG_PERMISSIONS,
|
|
460
|
+
EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
|
|
461
|
+
// Form Builder
|
|
462
|
+
FORM: FORM_PERMISSIONS,
|
|
463
|
+
FORM_RESULT: FORM_RESULT_PERMISSIONS,
|
|
464
|
+
// Event Manager
|
|
465
|
+
EVENT: EVENT_PERMISSIONS,
|
|
466
|
+
// Notification
|
|
467
|
+
NOTIFICATION: NOTIFICATION_PERMISSIONS,
|
|
468
|
+
// Localization
|
|
469
|
+
LANGUAGE: LANGUAGE_PERMISSIONS,
|
|
470
|
+
TRANSLATION_KEY: TRANSLATION_KEY_PERMISSIONS,
|
|
471
|
+
TRANSLATION: TRANSLATION_PERMISSIONS,
|
|
472
|
+
};
|
|
429
473
|
|
|
430
474
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
431
475
|
const FILE_TYPE_FILTERS = {
|
|
432
|
-
IMAGES: [
|
|
476
|
+
IMAGES: [
|
|
477
|
+
'image/jpeg',
|
|
478
|
+
'image/png',
|
|
479
|
+
'image/gif',
|
|
480
|
+
'image/webp',
|
|
481
|
+
'image/svg+xml',
|
|
482
|
+
],
|
|
433
483
|
DOCUMENTS: [
|
|
434
484
|
'application/pdf',
|
|
435
485
|
'application/msword',
|
|
@@ -448,7 +498,9 @@ function getAcceptString(contentTypes) {
|
|
|
448
498
|
function isFileTypeAllowed(file, allowedTypes) {
|
|
449
499
|
if (!allowedTypes.length)
|
|
450
500
|
return true;
|
|
451
|
-
return allowedTypes.some((type) => type.endsWith('/*')
|
|
501
|
+
return allowedTypes.some((type) => type.endsWith('/*')
|
|
502
|
+
? file.type.startsWith(type.replace('/*', '/'))
|
|
503
|
+
: file.type === type);
|
|
452
504
|
}
|
|
453
505
|
function getFileIconClass(contentType) {
|
|
454
506
|
if (contentType.startsWith('image/'))
|
|
@@ -521,16 +573,6 @@ class FileUrlService {
|
|
|
521
573
|
appConfig = inject(APP_CONFIG);
|
|
522
574
|
_cache = signal(new Map(), ...(ngDevMode ? [{ debugName: "_cache" }] : []));
|
|
523
575
|
cache = this._cache.asReadonly();
|
|
524
|
-
/** Get file URL by ID from cache (synchronous) */
|
|
525
|
-
getFileUrl(fileId) {
|
|
526
|
-
if (!fileId)
|
|
527
|
-
return null;
|
|
528
|
-
return this._cache().get(fileId)?.url ?? null;
|
|
529
|
-
}
|
|
530
|
-
/** Get file URL as computed signal */
|
|
531
|
-
fileUrlSignal(fileId) {
|
|
532
|
-
return computed(() => this.getFileUrl(fileId));
|
|
533
|
-
}
|
|
534
576
|
/** Fetch file URLs from backend and update cache */
|
|
535
577
|
fetchFileUrls(fileIds, forceRefresh = false) {
|
|
536
578
|
if (!fileIds.length)
|
|
@@ -551,18 +593,6 @@ class FileUrlService {
|
|
|
551
593
|
fetchSingleFileUrl(fileId, forceRefresh = false) {
|
|
552
594
|
return this.fetchFileUrls([fileId], forceRefresh).pipe(map((files) => files[0] ?? null));
|
|
553
595
|
}
|
|
554
|
-
/** Clear entire cache */
|
|
555
|
-
clearCache() {
|
|
556
|
-
this._cache.set(new Map());
|
|
557
|
-
}
|
|
558
|
-
/** Remove specific file from cache */
|
|
559
|
-
removeFromCache(fileId) {
|
|
560
|
-
this._cache.update((cache) => {
|
|
561
|
-
const next = new Map(cache);
|
|
562
|
-
next.delete(fileId);
|
|
563
|
-
return next;
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
596
|
addToCache(files) {
|
|
567
597
|
this._cache.update((cache) => {
|
|
568
598
|
const next = new Map(cache);
|
|
@@ -669,6 +699,10 @@ class PermissionValidatorService {
|
|
|
669
699
|
hasPermission(code) {
|
|
670
700
|
return hasPermission(code, this._permissions());
|
|
671
701
|
}
|
|
702
|
+
/** Check if user has ANY of the specified permissions (OR logic) */
|
|
703
|
+
hasAnyPermission(code) {
|
|
704
|
+
return hasAnyPermission(code, this._permissions());
|
|
705
|
+
}
|
|
672
706
|
isPermissionsLoaded() {
|
|
673
707
|
return this._isLoaded();
|
|
674
708
|
}
|
|
@@ -680,6 +714,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
680
714
|
args: [{ providedIn: 'root' }]
|
|
681
715
|
}] });
|
|
682
716
|
|
|
717
|
+
class TranslateService {
|
|
718
|
+
localizationState = inject(LOCALIZATION_STATE_SERVICE);
|
|
719
|
+
translate(key, params) {
|
|
720
|
+
const translations = this.localizationState.translations();
|
|
721
|
+
const value = translations[key];
|
|
722
|
+
if (!value) {
|
|
723
|
+
return this.getFromFallback(key, params);
|
|
724
|
+
}
|
|
725
|
+
return this.interpolate(value, params);
|
|
726
|
+
}
|
|
727
|
+
translateSignal(key, params) {
|
|
728
|
+
return computed(() => {
|
|
729
|
+
this.localizationState.currentLanguage();
|
|
730
|
+
return this.translate(key, params);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
getFromFallback(key, params) {
|
|
734
|
+
const fallbacks = this.localizationState.fallbackMessages();
|
|
735
|
+
const value = fallbacks[key] ?? key;
|
|
736
|
+
return this.interpolate(value, params);
|
|
737
|
+
}
|
|
738
|
+
interpolate(template, params) {
|
|
739
|
+
if (!params || !template)
|
|
740
|
+
return template;
|
|
741
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
|
742
|
+
const value = params[paramKey];
|
|
743
|
+
return value !== undefined ? String(value) : match;
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
747
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslateService, providedIn: 'root' });
|
|
748
|
+
}
|
|
749
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslateService, decorators: [{
|
|
750
|
+
type: Injectable,
|
|
751
|
+
args: [{ providedIn: 'root' }]
|
|
752
|
+
}] });
|
|
753
|
+
|
|
683
754
|
class EditModeElementChangerDirective {
|
|
684
755
|
el = inject(ElementRef);
|
|
685
756
|
ngControl = inject(NgControl, { optional: true });
|
|
@@ -939,38 +1010,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
939
1010
|
}], 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"] }] } });
|
|
940
1011
|
|
|
941
1012
|
class TranslatePipe {
|
|
942
|
-
translateAdapter = inject(
|
|
943
|
-
fallbackMessages = inject(FALLBACK_MESSAGES_REGISTRY, { optional: true });
|
|
1013
|
+
translateAdapter = inject(TranslateService);
|
|
944
1014
|
transform(key, params, fallback) {
|
|
945
1015
|
if (!key)
|
|
946
1016
|
return fallback ?? '';
|
|
947
|
-
if (!this.translateAdapter) {
|
|
948
|
-
const result = this.getFromFallback(key, params ?? undefined);
|
|
949
|
-
return fallback !== undefined && result === key ? fallback : result;
|
|
950
|
-
}
|
|
951
|
-
this.translateAdapter.languageCode();
|
|
952
1017
|
const result = this.translateAdapter.translate(key, params ?? undefined);
|
|
953
1018
|
return fallback !== undefined && result === key ? fallback : result;
|
|
954
1019
|
}
|
|
955
|
-
getFromFallback(template, params) {
|
|
956
|
-
// Try to get from fallback messages registry first
|
|
957
|
-
if (this.fallbackMessages) {
|
|
958
|
-
const value = this.fallbackMessages[template];
|
|
959
|
-
if (value) {
|
|
960
|
-
return this.interpolate(value, params);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
// Fall back to interpolating the key itself
|
|
964
|
-
return this.interpolate(template, params);
|
|
965
|
-
}
|
|
966
|
-
interpolate(template, params) {
|
|
967
|
-
if (!params)
|
|
968
|
-
return template;
|
|
969
|
-
return template.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
|
970
|
-
const value = params[paramKey];
|
|
971
|
-
return value !== undefined ? String(value) : match;
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
1020
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
975
1021
|
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
|
|
976
1022
|
}
|
|
@@ -982,21 +1028,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
982
1028
|
}]
|
|
983
1029
|
}] });
|
|
984
1030
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* HTTP context token to skip global error toast.
|
|
1033
|
+
* Use when component handles error display locally.
|
|
1034
|
+
*
|
|
1035
|
+
* @example
|
|
1036
|
+
* ```typescript
|
|
1037
|
+
* this.http.post(url, data, {
|
|
1038
|
+
* context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
1039
|
+
* });
|
|
1040
|
+
* ```
|
|
1041
|
+
*/
|
|
1042
|
+
const SKIP_ERROR_TOAST = new HttpContextToken(() => false);
|
|
1043
|
+
/**
|
|
1044
|
+
* Error Catching Interceptor
|
|
1045
|
+
* Displays error messages for failed HTTP requests using backend response.
|
|
1046
|
+
*
|
|
1047
|
+
* Features:
|
|
1048
|
+
* - Uses backend's error title (error.error.error) as summary
|
|
1049
|
+
* - Uses backend's error message (error.error.message) as detail
|
|
1050
|
+
* - Falls back to sensible defaults based on status code
|
|
1051
|
+
* - Skip with SKIP_ERROR_TOAST context token for local handling
|
|
1052
|
+
*
|
|
1053
|
+
* NOTE: This interceptor only handles error display.
|
|
1054
|
+
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
1055
|
+
*
|
|
1056
|
+
* @example
|
|
1057
|
+
* ```typescript
|
|
1058
|
+
* // In app.config.ts
|
|
1059
|
+
* provideHttpClient(
|
|
1060
|
+
* withFetch(),
|
|
1061
|
+
* withInterceptors([
|
|
1062
|
+
* authInterceptor, // from @flusys/ng-auth
|
|
1063
|
+
* tokenRefreshInterceptor, // from @flusys/ng-auth
|
|
1064
|
+
* errorCatchingInterceptor, // from @flusys/ng-core
|
|
1065
|
+
* apiLoaderInterceptor, // from @flusys/ng-core
|
|
1066
|
+
* ])
|
|
1067
|
+
* )
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
function errorCatchingInterceptor(req, next) {
|
|
1071
|
+
const messageService = inject(MessageService);
|
|
1072
|
+
const platformId = inject(PLATFORM_ID);
|
|
1073
|
+
const translateAdapter = inject(TranslateService, { optional: true });
|
|
1074
|
+
const isBrowser = isPlatformBrowser(platformId);
|
|
1075
|
+
// Skip error display for check-login (expected to fail when not logged in)
|
|
1076
|
+
const isCheckLogin = req.url.includes('/check-login');
|
|
1077
|
+
// Skip if component handles error locally
|
|
1078
|
+
const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);
|
|
1079
|
+
return next(req).pipe(catchError$1((error) => {
|
|
1080
|
+
// Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth
|
|
1081
|
+
// Skip 403 errors - handled by permission denied UI in components
|
|
1082
|
+
// Skip check-login errors - expected behavior
|
|
1083
|
+
// Skip if component handles error locally
|
|
1084
|
+
if (error.status === 401 ||
|
|
1085
|
+
error.status === 403 ||
|
|
1086
|
+
isCheckLogin ||
|
|
1087
|
+
skipErrorToast) {
|
|
1088
|
+
return throwError(() => error);
|
|
1089
|
+
}
|
|
1090
|
+
// Display error message for other errors
|
|
1091
|
+
if (isBrowser) {
|
|
1092
|
+
const { summary, detail } = extractErrorInfo(error, translateAdapter);
|
|
1093
|
+
messageService.add({
|
|
1094
|
+
severity: 'error',
|
|
1095
|
+
summary,
|
|
1096
|
+
detail,
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
return throwError(() => error);
|
|
1100
|
+
}));
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Extract error summary and detail from HTTP error response.
|
|
1104
|
+
* Uses backend response format: { message, messageKey, messageVariables, error, statusCode }
|
|
1105
|
+
* If messageKey is present and translate adapter is available, translates the message.
|
|
1106
|
+
*/
|
|
1107
|
+
function extractErrorInfo(error, translateAdapter) {
|
|
1108
|
+
// Check if we have a messageKey for translation
|
|
1109
|
+
const messageKey = error.error?.messageKey;
|
|
1110
|
+
const messageVariables = error.error?.messageVariables;
|
|
1111
|
+
let detail;
|
|
1112
|
+
if (messageKey && translateAdapter) {
|
|
1113
|
+
// Use translated message if available
|
|
1114
|
+
detail = translateAdapter.translate(messageKey, messageVariables);
|
|
1115
|
+
}
|
|
1116
|
+
else if (typeof error.error?.message === 'string') {
|
|
1117
|
+
detail = error.error.message;
|
|
1118
|
+
}
|
|
1119
|
+
else if (Array.isArray(error.error?.message)) {
|
|
1120
|
+
// NestJS validation errors come as array
|
|
1121
|
+
detail = error.error.message[0];
|
|
1122
|
+
}
|
|
1123
|
+
else if (error.message) {
|
|
1124
|
+
detail = error.message;
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
detail = translateAdapter
|
|
1128
|
+
? translateAdapter.translate('shared.unexpected.error')
|
|
1129
|
+
: 'shared.unexpected.error';
|
|
1130
|
+
}
|
|
1131
|
+
// Extract summary/title from backend response or use default based on status
|
|
1132
|
+
let summary;
|
|
1133
|
+
if (typeof error.error?.error === 'string') {
|
|
1134
|
+
// Backend sends error type (e.g., "Not Found", "Bad Request", "Validation Error")
|
|
1135
|
+
summary = error.error.error;
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
// Fallback to status-based title
|
|
1139
|
+
summary = getDefaultSummary(error.status, translateAdapter);
|
|
1140
|
+
}
|
|
1141
|
+
return { summary, detail };
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Get default error summary based on HTTP status code.
|
|
1145
|
+
*/
|
|
1146
|
+
function getDefaultSummary(status, translateAdapter) {
|
|
1147
|
+
const keyMap = {
|
|
1148
|
+
400: 'shared.error.bad.request',
|
|
1149
|
+
404: 'shared.error.not.found',
|
|
1150
|
+
409: 'shared.error.conflict',
|
|
1151
|
+
422: 'shared.error.validation.error',
|
|
1152
|
+
500: 'shared.error.server.error',
|
|
1153
|
+
502: 'shared.error.service.unavailable',
|
|
1154
|
+
503: 'shared.error.service.unavailable',
|
|
1155
|
+
504: 'shared.error.service.unavailable',
|
|
1156
|
+
};
|
|
1157
|
+
const key = keyMap[status] || 'shared.error';
|
|
1158
|
+
if (translateAdapter) {
|
|
1159
|
+
return translateAdapter.translate(key);
|
|
996
1160
|
}
|
|
997
|
-
|
|
1161
|
+
// Fallback to key when no translator available
|
|
1162
|
+
return key;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Resolver to load translations for specific modules.
|
|
1167
|
+
* Works with or without @flusys/ng-localization provider.
|
|
1168
|
+
*
|
|
1169
|
+
* When localization provider is enabled:
|
|
1170
|
+
* - Registers fallback messages in state
|
|
1171
|
+
* - Fetches translations from API
|
|
1172
|
+
* - Merges them into state
|
|
1173
|
+
*
|
|
1174
|
+
* When localization provider is not enabled:
|
|
1175
|
+
* - Registers fallback messages in FALLBACK_MESSAGES_REGISTRY
|
|
1176
|
+
* - TranslatePipe uses registry for translations
|
|
1177
|
+
* - No API calls
|
|
1178
|
+
*
|
|
1179
|
+
* @param config - Module config with modules array and optional fallback messages
|
|
1180
|
+
* @returns ResolveFn that loads translations before route activation
|
|
1181
|
+
*
|
|
1182
|
+
* @example
|
|
1183
|
+
* // With fallback messages (recommended)
|
|
1184
|
+
* { resolve: { t: resolveTranslationModule({ modules: ['auth'], fallbackMessages: AUTH_MESSAGES }) } }
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* // Without fallback (API translations only, requires localization provider)
|
|
1188
|
+
* { resolve: { t: resolveTranslationModule({ modules: ['auth', 'iam'] }) } }
|
|
1189
|
+
*/
|
|
1190
|
+
async function tryLoadLocalizationServices(injector) {
|
|
1191
|
+
const stateService = injector.get(LOCALIZATION_STATE_SERVICE, null);
|
|
1192
|
+
const apiService = injector.get(LOCALIZATION_API_SERVICE, null);
|
|
1193
|
+
if (!stateService || !apiService)
|
|
998
1194
|
return null;
|
|
999
|
-
|
|
1195
|
+
return {
|
|
1196
|
+
stateService,
|
|
1197
|
+
apiService,
|
|
1198
|
+
};
|
|
1000
1199
|
}
|
|
1001
1200
|
function resolveTranslationModule(config) {
|
|
1002
1201
|
return async () => {
|
|
@@ -1018,6 +1217,10 @@ function resolveTranslationModule(config) {
|
|
|
1018
1217
|
if (registry) {
|
|
1019
1218
|
Object.assign(registry, fallbackMessages);
|
|
1020
1219
|
}
|
|
1220
|
+
else if (isDevMode()) {
|
|
1221
|
+
console.warn('resolveTranslationModule: FALLBACK_MESSAGES_REGISTRY not provided. ' +
|
|
1222
|
+
'Add provideFallbackMessagesRegistry() to your app providers.');
|
|
1223
|
+
}
|
|
1021
1224
|
}
|
|
1022
1225
|
}
|
|
1023
1226
|
if (!locServices)
|
|
@@ -1235,19 +1438,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1235
1438
|
}]
|
|
1236
1439
|
}] });
|
|
1237
1440
|
|
|
1441
|
+
// ── Separate default filter into a named constant so it's sharable/testable ──
|
|
1442
|
+
const DEFAULT_FILTER = {
|
|
1443
|
+
pagination: { currentPage: 0, pageSize: 10 },
|
|
1444
|
+
filter: {},
|
|
1445
|
+
select: [],
|
|
1446
|
+
sort: {},
|
|
1447
|
+
};
|
|
1238
1448
|
class ApiResourceService {
|
|
1449
|
+
// ── Injected once via `inject()` ──────────────────────────────────────────
|
|
1450
|
+
http = inject(HttpClient);
|
|
1451
|
+
_config = inject(APP_CONFIG);
|
|
1239
1452
|
baseUrl;
|
|
1240
|
-
http;
|
|
1241
1453
|
moduleApiName;
|
|
1454
|
+
// ── Public writable signals ───────────────────────────────────────────────
|
|
1242
1455
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1243
|
-
filterData = signal({
|
|
1244
|
-
|
|
1245
|
-
filter: {},
|
|
1246
|
-
select: [],
|
|
1247
|
-
sort: {},
|
|
1248
|
-
}, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
1456
|
+
filterData = signal({ ...DEFAULT_FILTER }, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
1457
|
+
// ── Private activation gate: avoids an initial fetch before fetchList() ──
|
|
1249
1458
|
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
|
|
1459
|
+
// ── Resource (reactive list fetching with abort support) ──────────────────
|
|
1250
1460
|
listResource;
|
|
1461
|
+
// ── Derived/computed signals (pure reads, no side-effects) ────────────────
|
|
1251
1462
|
isLoading = computed(() => this.listResource.isLoading(), ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1252
1463
|
data = computed(() => this.listResource.value()?.data ?? [], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1253
1464
|
total = computed(() => this.listResource.value()?.meta?.total ?? 0, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
@@ -1260,52 +1471,64 @@ class ApiResourceService {
|
|
|
1260
1471
|
}, ...(ngDevMode ? [{ debugName: "hasMore" }] : []));
|
|
1261
1472
|
error = computed(() => this.listResource.error(), ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
1262
1473
|
status = computed(() => this.listResource.status(), ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
1263
|
-
constructor(moduleApiName,
|
|
1264
|
-
this.moduleApiName = moduleApiName
|
|
1265
|
-
|
|
1266
|
-
const serviceBaseUrl = serviceName
|
|
1267
|
-
|
|
1268
|
-
|
|
1474
|
+
constructor(moduleApiName, serviceName) {
|
|
1475
|
+
this.moduleApiName = moduleApiName;
|
|
1476
|
+
// ── Resolve base URL once at construction, not per-request ───────────────
|
|
1477
|
+
const serviceBaseUrl = serviceName
|
|
1478
|
+
? getServiceUrl(this._config, serviceName)
|
|
1479
|
+
: this._config.apiBaseUrl;
|
|
1480
|
+
this.baseUrl = moduleApiName
|
|
1481
|
+
? `${serviceBaseUrl}/${moduleApiName}`
|
|
1482
|
+
: serviceBaseUrl;
|
|
1269
1483
|
this.listResource = resource({ ...(ngDevMode ? { debugName: "listResource" } : {}), params: () => {
|
|
1484
|
+
// Short-circuit: skip the loader entirely when not yet activated
|
|
1270
1485
|
if (!this._active())
|
|
1271
1486
|
return undefined;
|
|
1272
1487
|
return { search: this.searchTerm(), filter: this.filterData() };
|
|
1273
1488
|
},
|
|
1274
|
-
loader: ({ params, abortSignal }) =>
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1489
|
+
loader: ({ params, abortSignal }) =>
|
|
1490
|
+
// ── Inline promise keeps the closure tight; no temp variable needed ─
|
|
1491
|
+
new Promise((resolve, reject) => {
|
|
1492
|
+
const sub = this._getAllObs(params.filter, params.search).subscribe({
|
|
1493
|
+
next: resolve,
|
|
1494
|
+
error: reject,
|
|
1279
1495
|
});
|
|
1280
|
-
|
|
1496
|
+
abortSignal.addEventListener('abort', () => sub.unsubscribe());
|
|
1497
|
+
}) });
|
|
1281
1498
|
}
|
|
1499
|
+
// ── Single, memoised header builder – avoids object churn per call ────────
|
|
1282
1500
|
getHttpOptions(endpoint, params) {
|
|
1283
1501
|
return {
|
|
1284
|
-
headers: new HttpHeaders({
|
|
1502
|
+
headers: new HttpHeaders({
|
|
1503
|
+
'x-loader-tag': `${this.moduleApiName}/${endpoint}`,
|
|
1504
|
+
}),
|
|
1285
1505
|
...(params ? { params } : {}),
|
|
1286
1506
|
};
|
|
1287
1507
|
}
|
|
1508
|
+
// ── Private observable – shared by the resource loader and the public getAll() ──
|
|
1288
1509
|
_getAllObs(filter, search = '') {
|
|
1289
|
-
|
|
1290
|
-
if (search)
|
|
1291
|
-
params = params.append('q', search);
|
|
1510
|
+
const params = search ? new HttpParams().append('q', search) : undefined;
|
|
1292
1511
|
return this.http.post(`${this.baseUrl}/get-all`, filter, this.getHttpOptions('get-all', params));
|
|
1293
1512
|
}
|
|
1513
|
+
// ── Activate guard: all list-management helpers call this first ───────────
|
|
1514
|
+
_activate() {
|
|
1515
|
+
this._active.set(true);
|
|
1516
|
+
}
|
|
1294
1517
|
// ==========================================================================
|
|
1295
|
-
// List
|
|
1518
|
+
// List management
|
|
1296
1519
|
// ==========================================================================
|
|
1297
1520
|
fetchList(search = '', filter) {
|
|
1298
1521
|
this.searchTerm.set(search);
|
|
1299
1522
|
if (filter)
|
|
1300
1523
|
this.filterData.update((prev) => ({ ...prev, ...filter }));
|
|
1301
|
-
this.
|
|
1524
|
+
this._activate();
|
|
1302
1525
|
}
|
|
1303
1526
|
setPagination(pagination) {
|
|
1304
|
-
this.
|
|
1527
|
+
this._activate();
|
|
1305
1528
|
this.filterData.update((prev) => ({ ...prev, pagination }));
|
|
1306
1529
|
}
|
|
1307
1530
|
nextPage() {
|
|
1308
|
-
this.
|
|
1531
|
+
this._activate();
|
|
1309
1532
|
this.filterData.update((prev) => ({
|
|
1310
1533
|
...prev,
|
|
1311
1534
|
pagination: {
|
|
@@ -1315,7 +1538,7 @@ class ApiResourceService {
|
|
|
1315
1538
|
}));
|
|
1316
1539
|
}
|
|
1317
1540
|
resetPagination() {
|
|
1318
|
-
this.
|
|
1541
|
+
this._activate();
|
|
1319
1542
|
this.filterData.update((prev) => ({
|
|
1320
1543
|
...prev,
|
|
1321
1544
|
pagination: { currentPage: 0, pageSize: prev.pagination?.pageSize ?? 10 },
|
|
@@ -1325,131 +1548,107 @@ class ApiResourceService {
|
|
|
1325
1548
|
this.listResource.reload();
|
|
1326
1549
|
}
|
|
1327
1550
|
// ==========================================================================
|
|
1328
|
-
// Promise API
|
|
1551
|
+
// Promise API (IApiService implementation)
|
|
1329
1552
|
// ==========================================================================
|
|
1553
|
+
// ── DRY helper: all promise methods are one-liners via this ───────────────
|
|
1554
|
+
_post(endpoint, body, params) {
|
|
1555
|
+
return firstValueFrom(this.http.post(`${this.baseUrl}/${endpoint}`, body, this.getHttpOptions(endpoint, params)));
|
|
1556
|
+
}
|
|
1330
1557
|
insert(dto) {
|
|
1331
|
-
return
|
|
1558
|
+
return this._post('insert', dto);
|
|
1332
1559
|
}
|
|
1333
1560
|
insertMany(dtos) {
|
|
1334
|
-
return
|
|
1561
|
+
return this._post('insert-many', dtos);
|
|
1335
1562
|
}
|
|
1336
1563
|
findById(id, select) {
|
|
1337
|
-
return
|
|
1564
|
+
return this._post(`get/${id}`, { select });
|
|
1338
1565
|
}
|
|
1339
1566
|
getAll(filter, search = '') {
|
|
1340
1567
|
return firstValueFrom(this._getAllObs(filter, search));
|
|
1341
1568
|
}
|
|
1342
1569
|
update(dto) {
|
|
1343
|
-
return
|
|
1570
|
+
return this._post('update', dto);
|
|
1344
1571
|
}
|
|
1345
1572
|
updateMany(dtos) {
|
|
1346
|
-
return
|
|
1573
|
+
return this._post('update-many', dtos);
|
|
1347
1574
|
}
|
|
1348
|
-
|
|
1349
|
-
return
|
|
1575
|
+
delete(deleteDto) {
|
|
1576
|
+
return this._post('delete', deleteDto);
|
|
1350
1577
|
}
|
|
1351
|
-
|
|
1352
|
-
return
|
|
1578
|
+
findByIds(ids, select) {
|
|
1579
|
+
return this._post('get-by-ids', { ids, select });
|
|
1353
1580
|
}
|
|
1354
1581
|
getByFilter(filter) {
|
|
1355
|
-
return
|
|
1582
|
+
return this._post('get-by-filter', filter);
|
|
1356
1583
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1584
|
+
async bulkUpsert(toInsert, toUpdate) {
|
|
1585
|
+
const res = await this._post('bulk-upsert', [
|
|
1586
|
+
...toInsert,
|
|
1587
|
+
...toUpdate,
|
|
1588
|
+
]);
|
|
1589
|
+
return res.data ?? [];
|
|
1359
1590
|
}
|
|
1360
1591
|
}
|
|
1361
1592
|
|
|
1362
|
-
/**
|
|
1363
|
-
* Base class for form controls that support ALL Angular form patterns:
|
|
1364
|
-
*
|
|
1365
|
-
* 1. **Template-driven forms:** `[(value)]="mySignal"` or `[(ngModel)]="myValue"`
|
|
1366
|
-
* 2. **Reactive forms:** `[formControl]="ctrl"` or `formControlName="field"`
|
|
1367
|
-
* 3. **Signal forms (Angular 21+):** `[formField]="formTree.field"`
|
|
1368
|
-
*
|
|
1369
|
-
* Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
|
|
1370
|
-
*
|
|
1371
|
-
* @example
|
|
1372
|
-
* ```typescript
|
|
1373
|
-
* @Component({
|
|
1374
|
-
* providers: [provideValueAccessor(MySelectComponent)]
|
|
1375
|
-
* })
|
|
1376
|
-
* export class MySelectComponent extends BaseFormControl<string | null> {
|
|
1377
|
-
* override readonly value = model<string | null>(null);
|
|
1378
|
-
*
|
|
1379
|
-
* constructor() {
|
|
1380
|
-
* super();
|
|
1381
|
-
* this.initializeFormControl();
|
|
1382
|
-
* }
|
|
1383
|
-
* }
|
|
1384
|
-
*
|
|
1385
|
-
* // Usage:
|
|
1386
|
-
* // Template-driven: <my-select [(value)]="selectedId" />
|
|
1387
|
-
* // Reactive forms: <my-select [formControl]="myControl" />
|
|
1388
|
-
* // Signal forms: <my-select [formField]="formTree.myField" />
|
|
1389
|
-
* ```
|
|
1390
|
-
*/
|
|
1391
1593
|
class BaseFormControl {
|
|
1392
1594
|
injector = inject(Injector);
|
|
1393
|
-
/**
|
|
1394
|
-
* Disabled state model - bound to form control disabled state.
|
|
1395
|
-
* Using model() to satisfy FormValueControl interface requirements.
|
|
1396
|
-
*/
|
|
1397
1595
|
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1398
|
-
/**
|
|
1399
|
-
* Touched state model - for validation styling.
|
|
1400
|
-
* Using model() to satisfy FormValueControl interface requirements.
|
|
1401
|
-
*/
|
|
1402
1596
|
touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : []));
|
|
1597
|
+
// ── CVA callbacks ─────────────────────────────────────────────────────────
|
|
1598
|
+
// Initialised to no-ops so subclasses never need null-guards
|
|
1403
1599
|
onChange = () => { };
|
|
1404
1600
|
onTouched = () => { };
|
|
1405
|
-
|
|
1601
|
+
// ── Write-guard: prevents echo loop (writeValue → effect → onChange) ──────
|
|
1602
|
+
// Using a counter instead of a boolean handles the (rare) re-entrant case
|
|
1603
|
+
writeDepth = 0;
|
|
1406
1604
|
/**
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1605
|
+
* Wire up the ControlValueAccessor ↔ signal bridge.
|
|
1606
|
+
* Call once at the end of the subclass constructor.
|
|
1409
1607
|
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1608
|
+
* Signal-form consumers ([formField]) don't need this — Angular's
|
|
1609
|
+
* FormField directive binds directly to `value`.
|
|
1610
|
+
*
|
|
1611
|
+
* @example
|
|
1612
|
+
* constructor() { super(); this.initializeFormControl(); }
|
|
1412
1613
|
*/
|
|
1413
1614
|
initializeFormControl() {
|
|
1414
1615
|
runInInjectionContext(this.injector, () => {
|
|
1415
1616
|
effect(() => {
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
1617
|
+
const next = this.value();
|
|
1618
|
+
// Skip propagation while writeValue() is on the call stack
|
|
1619
|
+
if (this.writeDepth === 0) {
|
|
1620
|
+
untracked(() => this.onChange(next));
|
|
1419
1621
|
}
|
|
1420
1622
|
});
|
|
1421
1623
|
});
|
|
1422
1624
|
}
|
|
1423
|
-
//
|
|
1424
|
-
// ControlValueAccessor
|
|
1425
|
-
//
|
|
1426
|
-
// ============================================
|
|
1427
|
-
/** ControlValueAccessor: Write value from form control to signal */
|
|
1625
|
+
// ==========================================================================
|
|
1626
|
+
// ControlValueAccessor
|
|
1627
|
+
// ==========================================================================
|
|
1428
1628
|
writeValue(value) {
|
|
1429
|
-
this.
|
|
1430
|
-
|
|
1431
|
-
|
|
1629
|
+
this.writeDepth++;
|
|
1630
|
+
try {
|
|
1631
|
+
this.value.set(value);
|
|
1632
|
+
}
|
|
1633
|
+
finally {
|
|
1634
|
+
// Always decrement — even if set() throws — so the guard never sticks
|
|
1635
|
+
this.writeDepth--;
|
|
1636
|
+
}
|
|
1432
1637
|
}
|
|
1433
|
-
/** ControlValueAccessor: Register change callback */
|
|
1434
1638
|
registerOnChange(fn) {
|
|
1435
1639
|
this.onChange = fn;
|
|
1436
1640
|
}
|
|
1437
|
-
/** ControlValueAccessor: Register touched callback */
|
|
1438
1641
|
registerOnTouched(fn) {
|
|
1439
1642
|
this.onTouched = fn;
|
|
1440
1643
|
}
|
|
1441
|
-
/** ControlValueAccessor: Set disabled state from form control */
|
|
1442
1644
|
setDisabledState(isDisabled) {
|
|
1443
1645
|
this.disabled.set(isDisabled);
|
|
1444
1646
|
}
|
|
1445
|
-
//
|
|
1446
|
-
// FormValueControl
|
|
1447
|
-
//
|
|
1448
|
-
//
|
|
1449
|
-
|
|
1450
|
-
// - value: ModelSignal<T> (already declared as abstract above)
|
|
1451
|
-
// The FormField directive automatically binds to the value model.
|
|
1452
|
-
/** Mark the control as touched (call on blur or panel close) */
|
|
1647
|
+
// ==========================================================================
|
|
1648
|
+
// FormValueControl (signal forms)
|
|
1649
|
+
// `value: ModelSignal<T>` satisfies the interface — nothing else needed.
|
|
1650
|
+
// ==========================================================================
|
|
1651
|
+
/** Call on blur / panel-close to propagate touched state. */
|
|
1453
1652
|
markAsTouched() {
|
|
1454
1653
|
if (!this.touched()) {
|
|
1455
1654
|
this.touched.set(true);
|
|
@@ -1478,7 +1677,7 @@ function provideValueAccessor(component) {
|
|
|
1478
1677
|
return {
|
|
1479
1678
|
provide: NG_VALUE_ACCESSOR,
|
|
1480
1679
|
useExisting: forwardRef(() => component),
|
|
1481
|
-
multi: true
|
|
1680
|
+
multi: true,
|
|
1482
1681
|
};
|
|
1483
1682
|
}
|
|
1484
1683
|
|
|
@@ -1606,7 +1805,7 @@ function checkScrollPagination(event, config) {
|
|
|
1606
1805
|
|
|
1607
1806
|
class FileUploaderComponent {
|
|
1608
1807
|
messageService = inject(MessageService);
|
|
1609
|
-
translateAdapter = inject(
|
|
1808
|
+
translateAdapter = inject(TranslateService, {
|
|
1610
1809
|
optional: true,
|
|
1611
1810
|
});
|
|
1612
1811
|
fileProvider = inject(FILE_PROVIDER, { optional: true });
|
|
@@ -2068,7 +2267,7 @@ const DEFAULT_PAGE_SIZE$1 = 20;
|
|
|
2068
2267
|
const DEFAULT_SELECTOR_PAGE_SIZE = 50;
|
|
2069
2268
|
class FileSelectorDialogComponent {
|
|
2070
2269
|
destroyRef = inject(DestroyRef);
|
|
2071
|
-
translateAdapter = inject(
|
|
2270
|
+
translateAdapter = inject(TranslateService, {
|
|
2072
2271
|
optional: true,
|
|
2073
2272
|
});
|
|
2074
2273
|
fileProvider = inject(FILE_PROVIDER, { optional: true });
|
|
@@ -2964,7 +3163,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2964
3163
|
class LazyMultiSelectComponent extends BaseFormControl {
|
|
2965
3164
|
document = inject(DOCUMENT$1);
|
|
2966
3165
|
appRef = inject(ApplicationRef);
|
|
2967
|
-
translateAdapter = inject(
|
|
3166
|
+
translateAdapter = inject(TranslateService, {
|
|
2968
3167
|
optional: true,
|
|
2969
3168
|
});
|
|
2970
3169
|
onDocumentClickBound = this.handleDocumentClick.bind(this);
|
|
@@ -3170,7 +3369,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3170
3369
|
*/
|
|
3171
3370
|
class LazySelectComponent extends BaseFormControl {
|
|
3172
3371
|
destroyRef = inject(DestroyRef);
|
|
3173
|
-
translateAdapter = inject(
|
|
3372
|
+
translateAdapter = inject(TranslateService, {
|
|
3373
|
+
optional: true,
|
|
3374
|
+
});
|
|
3174
3375
|
onScrollBound = this.onScroll.bind(this);
|
|
3175
3376
|
scrollTargetEl = null;
|
|
3176
3377
|
isDestroyed = false;
|
|
@@ -3294,7 +3495,7 @@ class BaseUserSelectComponent {
|
|
|
3294
3495
|
destroyRef = inject(DestroyRef);
|
|
3295
3496
|
injector = inject(Injector);
|
|
3296
3497
|
userProvider = inject(USER_PROVIDER);
|
|
3297
|
-
translateAdapter = inject(
|
|
3498
|
+
translateAdapter = inject(TranslateService);
|
|
3298
3499
|
abortController = null;
|
|
3299
3500
|
// Inputs
|
|
3300
3501
|
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
@@ -3309,7 +3510,10 @@ class BaseUserSelectComponent {
|
|
|
3309
3510
|
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
3310
3511
|
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
3311
3512
|
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
3312
|
-
pagination = signal({
|
|
3513
|
+
pagination = signal({
|
|
3514
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
3515
|
+
currentPage: 0,
|
|
3516
|
+
}, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
3313
3517
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
3314
3518
|
// Computed dropdown data
|
|
3315
3519
|
dropdownUsers = computed(() => this.users().map((user) => ({
|
|
@@ -3399,12 +3603,16 @@ class BaseUserSelectComponent {
|
|
|
3399
3603
|
}
|
|
3400
3604
|
/** Load users from USER_PROVIDER with active filter */
|
|
3401
3605
|
loadUsersFromProvider(filter) {
|
|
3606
|
+
const extraFilters = {
|
|
3607
|
+
isActive: this.filterActive() ? true : undefined,
|
|
3608
|
+
...this.additionalFilters(),
|
|
3609
|
+
};
|
|
3402
3610
|
return this.userProvider
|
|
3403
3611
|
.getUsers({
|
|
3404
3612
|
page: filter.page,
|
|
3405
3613
|
pageSize: filter.pageSize,
|
|
3406
3614
|
search: filter.search,
|
|
3407
|
-
|
|
3615
|
+
...extraFilters,
|
|
3408
3616
|
})
|
|
3409
3617
|
.pipe(map$1((res) => ({
|
|
3410
3618
|
...res,
|
|
@@ -3765,7 +3973,7 @@ class BaseFormPage {
|
|
|
3765
3973
|
route = inject(ActivatedRoute);
|
|
3766
3974
|
messageService = inject(MessageService);
|
|
3767
3975
|
destroyRef = inject(DestroyRef);
|
|
3768
|
-
translateAdapter = inject(
|
|
3976
|
+
translateAdapter = inject(TranslateService, {
|
|
3769
3977
|
optional: true,
|
|
3770
3978
|
});
|
|
3771
3979
|
routeParams = toSignal(this.route.paramMap);
|
|
@@ -3901,7 +4109,9 @@ class BaseListPage {
|
|
|
3901
4109
|
appConfig = inject(APP_CONFIG);
|
|
3902
4110
|
confirmationService = inject(ConfirmationService);
|
|
3903
4111
|
destroyRef = inject(DestroyRef);
|
|
3904
|
-
translateAdapter = inject(
|
|
4112
|
+
translateAdapter = inject(TranslateService, {
|
|
4113
|
+
optional: true,
|
|
4114
|
+
});
|
|
3905
4115
|
/** Items list */
|
|
3906
4116
|
items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
3907
4117
|
/** Loading state */
|
|
@@ -3951,25 +4161,41 @@ class BaseListPage {
|
|
|
3951
4161
|
* Show success toast message
|
|
3952
4162
|
*/
|
|
3953
4163
|
showSuccess(detail, summary) {
|
|
3954
|
-
this.messageService.add({
|
|
4164
|
+
this.messageService.add({
|
|
4165
|
+
severity: 'success',
|
|
4166
|
+
summary: summary ?? this.t('shared.success'),
|
|
4167
|
+
detail,
|
|
4168
|
+
});
|
|
3955
4169
|
}
|
|
3956
4170
|
/**
|
|
3957
4171
|
* Show error toast message
|
|
3958
4172
|
*/
|
|
3959
4173
|
showError(detail, summary) {
|
|
3960
|
-
this.messageService.add({
|
|
4174
|
+
this.messageService.add({
|
|
4175
|
+
severity: 'error',
|
|
4176
|
+
summary: summary ?? this.t('shared.error'),
|
|
4177
|
+
detail,
|
|
4178
|
+
});
|
|
3961
4179
|
}
|
|
3962
4180
|
/**
|
|
3963
4181
|
* Show info toast message
|
|
3964
4182
|
*/
|
|
3965
4183
|
showInfo(detail, summary) {
|
|
3966
|
-
this.messageService.add({
|
|
4184
|
+
this.messageService.add({
|
|
4185
|
+
severity: 'info',
|
|
4186
|
+
summary: summary ?? this.t('shared.info'),
|
|
4187
|
+
detail,
|
|
4188
|
+
});
|
|
3967
4189
|
}
|
|
3968
4190
|
/**
|
|
3969
4191
|
* Show warning toast message
|
|
3970
4192
|
*/
|
|
3971
4193
|
showWarn(detail, summary) {
|
|
3972
|
-
this.messageService.add({
|
|
4194
|
+
this.messageService.add({
|
|
4195
|
+
severity: 'warn',
|
|
4196
|
+
summary: summary ?? this.t('shared.warning'),
|
|
4197
|
+
detail,
|
|
4198
|
+
});
|
|
3973
4199
|
}
|
|
3974
4200
|
/**
|
|
3975
4201
|
* Translate a key using the adapter, fallback to key if not available.
|
|
@@ -3996,7 +4222,9 @@ class BaseListPage {
|
|
|
3996
4222
|
.subscribe({
|
|
3997
4223
|
next: (res) => {
|
|
3998
4224
|
const r = res;
|
|
3999
|
-
this.showSuccess(r?.messageKey
|
|
4225
|
+
this.showSuccess(r?.messageKey
|
|
4226
|
+
? this.t(r.messageKey, r.messageVariables)
|
|
4227
|
+
: (r?.message ?? ''));
|
|
4000
4228
|
this.loadData();
|
|
4001
4229
|
},
|
|
4002
4230
|
});
|
|
@@ -4019,7 +4247,9 @@ class BaseListPage {
|
|
|
4019
4247
|
accept: async () => {
|
|
4020
4248
|
const res = await deleteApiCall(idGetter(item));
|
|
4021
4249
|
const r = res;
|
|
4022
|
-
this.showSuccess(r?.messageKey
|
|
4250
|
+
this.showSuccess(r?.messageKey
|
|
4251
|
+
? this.t(r.messageKey, r.messageVariables)
|
|
4252
|
+
: (r?.message ?? ''));
|
|
4023
4253
|
await this.loadData();
|
|
4024
4254
|
},
|
|
4025
4255
|
});
|
|
@@ -4104,5 +4334,5 @@ function getUserTimezone() {
|
|
|
4104
4334
|
* Generated bundle index. Do not edit.
|
|
4105
4335
|
*/
|
|
4106
4336
|
|
|
4107
|
-
export { ACTION_PERMISSIONS, AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, BRANCH_PERMISSIONS, BaseFormControl, BaseFormPage, BaseListPage, BaseUserSelectComponent, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, COMPANY_PERMISSIONS, CookieService, EMAIL_CONFIG_PERMISSIONS, EMAIL_TEMPLATE_PERMISSIONS,
|
|
4337
|
+
export { ACTION_PERMISSIONS, AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, BRANCH_PERMISSIONS, BaseFormControl, BaseFormPage, BaseListPage, BaseUserSelectComponent, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, COMPANY_PERMISSIONS, CookieService, EMAIL_CONFIG_PERMISSIONS, EMAIL_TEMPLATE_PERMISSIONS, EVENT_PERMISSIONS, EditModeElementChangerDirective, FILE_PERMISSIONS, FILE_PROVIDER, FILE_TYPE_FILTERS, FOLDER_PERMISSIONS, FORM_PERMISSIONS, FORM_RESULT_PERMISSIONS, FileSelectorDialogComponent, FileUploaderComponent, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LANGUAGE_PERMISSIONS, LazyMultiSelectComponent, LazySelectComponent, NOTIFICATION_PERMISSIONS, PERMISSIONS, PROFILE_PERMISSION_PROVIDER, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, ROLE_ACTION_PERMISSIONS, ROLE_PERMISSIONS, SHARED_MESSAGES, SKIP_ERROR_TOAST, STORAGE_CONFIG_PERMISSIONS, TRANSLATION_KEY_PERMISSIONS, TRANSLATION_PERMISSIONS, TranslatePipe, TranslateService, UPLOAD_CONFIG_PERMISSIONS, USER_ACTION_PERMISSIONS, USER_PERMISSIONS, USER_PERMISSION_PROVIDER, USER_PROVIDER, USER_ROLE_PERMISSIONS, UserMultiSelectComponent, UserSelectComponent, allPermissionsGuard, anyPermissionGuard, checkScrollPagination, errorCatchingInterceptor, evaluateLogicNode, evaluatePermission, formatFileSize, formatInTimezone, getAcceptString, getFileIconClass, getUserTimezone, hasAllPermissions, hasAnyPermission, hasPermission, isFileTypeAllowed, localToUtc, permissionGuard, provideValueAccessor, resolveTranslationModule, utcToLocal };
|
|
4108
4338
|
//# sourceMappingURL=flusys-ng-shared.mjs.map
|