@flusys/ng-shared 5.0.3 → 5.1.0
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 +421 -197
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +2 -2
- package/types/flusys-ng-shared.d.ts +209 -179
|
@@ -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, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE } 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 = {
|
|
@@ -309,6 +309,7 @@ const SHARED_MESSAGES = {
|
|
|
309
309
|
'primeng.country.placeholder': 'Choose a country',
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
+
// ==================== AUTH MODULE ====================
|
|
312
313
|
const USER_PERMISSIONS = {
|
|
313
314
|
CREATE: 'user.create',
|
|
314
315
|
READ: 'user.read',
|
|
@@ -327,6 +328,7 @@ const BRANCH_PERMISSIONS = {
|
|
|
327
328
|
UPDATE: 'branch.update',
|
|
328
329
|
DELETE: 'branch.delete',
|
|
329
330
|
};
|
|
331
|
+
// ==================== IAM MODULE ====================
|
|
330
332
|
const ACTION_PERMISSIONS = {
|
|
331
333
|
CREATE: 'action.create',
|
|
332
334
|
READ: 'action.read',
|
|
@@ -355,6 +357,7 @@ const COMPANY_ACTION_PERMISSIONS = {
|
|
|
355
357
|
READ: 'company-action.read',
|
|
356
358
|
ASSIGN: 'company-action.assign',
|
|
357
359
|
};
|
|
360
|
+
// ==================== STORAGE MODULE ====================
|
|
358
361
|
const FILE_PERMISSIONS = {
|
|
359
362
|
CREATE: 'file.create',
|
|
360
363
|
READ: 'file.read',
|
|
@@ -373,6 +376,10 @@ const STORAGE_CONFIG_PERMISSIONS = {
|
|
|
373
376
|
UPDATE: 'storage-config.update',
|
|
374
377
|
DELETE: 'storage-config.delete',
|
|
375
378
|
};
|
|
379
|
+
const UPLOAD_CONFIG_PERMISSIONS = {
|
|
380
|
+
DELETE: 'upload-config.delete',
|
|
381
|
+
};
|
|
382
|
+
// ==================== EMAIL MODULE ====================
|
|
376
383
|
const EMAIL_CONFIG_PERMISSIONS = {
|
|
377
384
|
CREATE: 'email-config.create',
|
|
378
385
|
READ: 'email-config.read',
|
|
@@ -385,30 +392,34 @@ const EMAIL_TEMPLATE_PERMISSIONS = {
|
|
|
385
392
|
UPDATE: 'email-template.update',
|
|
386
393
|
DELETE: 'email-template.delete',
|
|
387
394
|
};
|
|
395
|
+
// ==================== FORM BUILDER MODULE ====================
|
|
388
396
|
const FORM_PERMISSIONS = {
|
|
389
397
|
CREATE: 'form.create',
|
|
390
398
|
READ: 'form.read',
|
|
391
399
|
UPDATE: 'form.update',
|
|
392
400
|
DELETE: 'form.delete',
|
|
393
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 ====================
|
|
394
409
|
const EVENT_PERMISSIONS = {
|
|
395
410
|
CREATE: 'event.create',
|
|
396
411
|
READ: 'event.read',
|
|
397
412
|
UPDATE: 'event.update',
|
|
398
413
|
DELETE: 'event.delete',
|
|
399
414
|
};
|
|
400
|
-
|
|
401
|
-
CREATE: 'event-participant.create',
|
|
402
|
-
READ: 'event-participant.read',
|
|
403
|
-
UPDATE: 'event-participant.update',
|
|
404
|
-
DELETE: 'event-participant.delete',
|
|
405
|
-
};
|
|
415
|
+
// ==================== NOTIFICATION MODULE ====================
|
|
406
416
|
const NOTIFICATION_PERMISSIONS = {
|
|
407
417
|
CREATE: 'notification.create',
|
|
408
418
|
READ: 'notification.read',
|
|
409
419
|
UPDATE: 'notification.update',
|
|
410
420
|
DELETE: 'notification.delete',
|
|
411
421
|
};
|
|
422
|
+
// ==================== LOCALIZATION MODULE ====================
|
|
412
423
|
const LANGUAGE_PERMISSIONS = {
|
|
413
424
|
CREATE: 'language.create',
|
|
414
425
|
READ: 'language.read',
|
|
@@ -427,10 +438,48 @@ const TRANSLATION_PERMISSIONS = {
|
|
|
427
438
|
UPDATE: 'translation.update',
|
|
428
439
|
DELETE: 'translation.delete',
|
|
429
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
|
+
};
|
|
430
473
|
|
|
431
474
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
432
475
|
const FILE_TYPE_FILTERS = {
|
|
433
|
-
IMAGES: [
|
|
476
|
+
IMAGES: [
|
|
477
|
+
'image/jpeg',
|
|
478
|
+
'image/png',
|
|
479
|
+
'image/gif',
|
|
480
|
+
'image/webp',
|
|
481
|
+
'image/svg+xml',
|
|
482
|
+
],
|
|
434
483
|
DOCUMENTS: [
|
|
435
484
|
'application/pdf',
|
|
436
485
|
'application/msword',
|
|
@@ -449,7 +498,9 @@ function getAcceptString(contentTypes) {
|
|
|
449
498
|
function isFileTypeAllowed(file, allowedTypes) {
|
|
450
499
|
if (!allowedTypes.length)
|
|
451
500
|
return true;
|
|
452
|
-
return allowedTypes.some((type) => type.endsWith('/*')
|
|
501
|
+
return allowedTypes.some((type) => type.endsWith('/*')
|
|
502
|
+
? file.type.startsWith(type.replace('/*', '/'))
|
|
503
|
+
: file.type === type);
|
|
453
504
|
}
|
|
454
505
|
function getFileIconClass(contentType) {
|
|
455
506
|
if (contentType.startsWith('image/'))
|
|
@@ -522,16 +573,6 @@ class FileUrlService {
|
|
|
522
573
|
appConfig = inject(APP_CONFIG);
|
|
523
574
|
_cache = signal(new Map(), ...(ngDevMode ? [{ debugName: "_cache" }] : []));
|
|
524
575
|
cache = this._cache.asReadonly();
|
|
525
|
-
/** Get file URL by ID from cache (synchronous) */
|
|
526
|
-
getFileUrl(fileId) {
|
|
527
|
-
if (!fileId)
|
|
528
|
-
return null;
|
|
529
|
-
return this._cache().get(fileId)?.url ?? null;
|
|
530
|
-
}
|
|
531
|
-
/** Get file URL as computed signal */
|
|
532
|
-
fileUrlSignal(fileId) {
|
|
533
|
-
return computed(() => this.getFileUrl(fileId));
|
|
534
|
-
}
|
|
535
576
|
/** Fetch file URLs from backend and update cache */
|
|
536
577
|
fetchFileUrls(fileIds, forceRefresh = false) {
|
|
537
578
|
if (!fileIds.length)
|
|
@@ -552,18 +593,6 @@ class FileUrlService {
|
|
|
552
593
|
fetchSingleFileUrl(fileId, forceRefresh = false) {
|
|
553
594
|
return this.fetchFileUrls([fileId], forceRefresh).pipe(map((files) => files[0] ?? null));
|
|
554
595
|
}
|
|
555
|
-
/** Clear entire cache */
|
|
556
|
-
clearCache() {
|
|
557
|
-
this._cache.set(new Map());
|
|
558
|
-
}
|
|
559
|
-
/** Remove specific file from cache */
|
|
560
|
-
removeFromCache(fileId) {
|
|
561
|
-
this._cache.update((cache) => {
|
|
562
|
-
const next = new Map(cache);
|
|
563
|
-
next.delete(fileId);
|
|
564
|
-
return next;
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
596
|
addToCache(files) {
|
|
568
597
|
this._cache.update((cache) => {
|
|
569
598
|
const next = new Map(cache);
|
|
@@ -670,6 +699,10 @@ class PermissionValidatorService {
|
|
|
670
699
|
hasPermission(code) {
|
|
671
700
|
return hasPermission(code, this._permissions());
|
|
672
701
|
}
|
|
702
|
+
/** Check if user has ANY of the specified permissions (OR logic) */
|
|
703
|
+
hasAnyPermission(code) {
|
|
704
|
+
return hasAnyPermission(code, this._permissions());
|
|
705
|
+
}
|
|
673
706
|
isPermissionsLoaded() {
|
|
674
707
|
return this._isLoaded();
|
|
675
708
|
}
|
|
@@ -681,6 +714,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
681
714
|
args: [{ providedIn: 'root' }]
|
|
682
715
|
}] });
|
|
683
716
|
|
|
717
|
+
class TranslateService {
|
|
718
|
+
localizationState = inject(LOCALIZATION_STATE_SERVICE, {
|
|
719
|
+
optional: true,
|
|
720
|
+
});
|
|
721
|
+
registry = inject(FALLBACK_MESSAGES_REGISTRY, {
|
|
722
|
+
optional: false,
|
|
723
|
+
});
|
|
724
|
+
translate(key, params) {
|
|
725
|
+
if (this.localizationState === null) {
|
|
726
|
+
const value = this.registry[key];
|
|
727
|
+
return this.interpolate(value, params);
|
|
728
|
+
}
|
|
729
|
+
const translations = this.localizationState.translations();
|
|
730
|
+
let value = translations[key];
|
|
731
|
+
if (!value) {
|
|
732
|
+
const fallbacks = this.localizationState.fallbackMessages();
|
|
733
|
+
value = fallbacks[key] ?? key;
|
|
734
|
+
}
|
|
735
|
+
return this.interpolate(value, params);
|
|
736
|
+
}
|
|
737
|
+
translateSignal(key, params) {
|
|
738
|
+
return computed(() => {
|
|
739
|
+
return this.translate(key, params);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
interpolate(template, params) {
|
|
743
|
+
if (!params || !template)
|
|
744
|
+
return template;
|
|
745
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
|
746
|
+
const value = params[paramKey];
|
|
747
|
+
return value !== undefined ? String(value) : match;
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
751
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslateService, providedIn: 'root' });
|
|
752
|
+
}
|
|
753
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslateService, decorators: [{
|
|
754
|
+
type: Injectable,
|
|
755
|
+
args: [{ providedIn: 'root' }]
|
|
756
|
+
}] });
|
|
757
|
+
|
|
684
758
|
class EditModeElementChangerDirective {
|
|
685
759
|
el = inject(ElementRef);
|
|
686
760
|
ngControl = inject(NgControl, { optional: true });
|
|
@@ -940,38 +1014,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
940
1014
|
}], 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"] }] } });
|
|
941
1015
|
|
|
942
1016
|
class TranslatePipe {
|
|
943
|
-
translateAdapter = inject(
|
|
944
|
-
fallbackMessages = inject(FALLBACK_MESSAGES_REGISTRY, { optional: true });
|
|
1017
|
+
translateAdapter = inject(TranslateService);
|
|
945
1018
|
transform(key, params, fallback) {
|
|
946
1019
|
if (!key)
|
|
947
1020
|
return fallback ?? '';
|
|
948
|
-
if (!this.translateAdapter) {
|
|
949
|
-
const result = this.getFromFallback(key, params ?? undefined);
|
|
950
|
-
return fallback !== undefined && result === key ? fallback : result;
|
|
951
|
-
}
|
|
952
|
-
this.translateAdapter.languageCode();
|
|
953
1021
|
const result = this.translateAdapter.translate(key, params ?? undefined);
|
|
954
1022
|
return fallback !== undefined && result === key ? fallback : result;
|
|
955
1023
|
}
|
|
956
|
-
getFromFallback(template, params) {
|
|
957
|
-
// Try to get from fallback messages registry first
|
|
958
|
-
if (this.fallbackMessages) {
|
|
959
|
-
const value = this.fallbackMessages[template];
|
|
960
|
-
if (value) {
|
|
961
|
-
return this.interpolate(value, params);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
// Fall back to interpolating the key itself
|
|
965
|
-
return this.interpolate(template, params);
|
|
966
|
-
}
|
|
967
|
-
interpolate(template, params) {
|
|
968
|
-
if (!params)
|
|
969
|
-
return template;
|
|
970
|
-
return template.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
|
971
|
-
const value = params[paramKey];
|
|
972
|
-
return value !== undefined ? String(value) : match;
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
1024
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
976
1025
|
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
|
|
977
1026
|
}
|
|
@@ -983,21 +1032,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
983
1032
|
}]
|
|
984
1033
|
}] });
|
|
985
1034
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1035
|
+
/**
|
|
1036
|
+
* HTTP context token to skip global error toast.
|
|
1037
|
+
* Use when component handles error display locally.
|
|
1038
|
+
*
|
|
1039
|
+
* @example
|
|
1040
|
+
* ```typescript
|
|
1041
|
+
* this.http.post(url, data, {
|
|
1042
|
+
* context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
1043
|
+
* });
|
|
1044
|
+
* ```
|
|
1045
|
+
*/
|
|
1046
|
+
const SKIP_ERROR_TOAST = new HttpContextToken(() => false);
|
|
1047
|
+
/**
|
|
1048
|
+
* Error Catching Interceptor
|
|
1049
|
+
* Displays error messages for failed HTTP requests using backend response.
|
|
1050
|
+
*
|
|
1051
|
+
* Features:
|
|
1052
|
+
* - Uses backend's error title (error.error.error) as summary
|
|
1053
|
+
* - Uses backend's error message (error.error.message) as detail
|
|
1054
|
+
* - Falls back to sensible defaults based on status code
|
|
1055
|
+
* - Skip with SKIP_ERROR_TOAST context token for local handling
|
|
1056
|
+
*
|
|
1057
|
+
* NOTE: This interceptor only handles error display.
|
|
1058
|
+
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* ```typescript
|
|
1062
|
+
* // In app.config.ts
|
|
1063
|
+
* provideHttpClient(
|
|
1064
|
+
* withFetch(),
|
|
1065
|
+
* withInterceptors([
|
|
1066
|
+
* authInterceptor, // from @flusys/ng-auth
|
|
1067
|
+
* tokenRefreshInterceptor, // from @flusys/ng-auth
|
|
1068
|
+
* errorCatchingInterceptor, // from @flusys/ng-core
|
|
1069
|
+
* apiLoaderInterceptor, // from @flusys/ng-core
|
|
1070
|
+
* ])
|
|
1071
|
+
* )
|
|
1072
|
+
* ```
|
|
1073
|
+
*/
|
|
1074
|
+
function errorCatchingInterceptor(req, next) {
|
|
1075
|
+
const messageService = inject(MessageService);
|
|
1076
|
+
const platformId = inject(PLATFORM_ID);
|
|
1077
|
+
const translateAdapter = inject(TranslateService, { optional: true });
|
|
1078
|
+
const isBrowser = isPlatformBrowser(platformId);
|
|
1079
|
+
// Skip error display for check-login (expected to fail when not logged in)
|
|
1080
|
+
const isCheckLogin = req.url.includes('/check-login');
|
|
1081
|
+
// Skip if component handles error locally
|
|
1082
|
+
const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);
|
|
1083
|
+
return next(req).pipe(catchError$1((error) => {
|
|
1084
|
+
// Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth
|
|
1085
|
+
// Skip 403 errors - handled by permission denied UI in components
|
|
1086
|
+
// Skip check-login errors - expected behavior
|
|
1087
|
+
// Skip if component handles error locally
|
|
1088
|
+
if (error.status === 401 ||
|
|
1089
|
+
error.status === 403 ||
|
|
1090
|
+
isCheckLogin ||
|
|
1091
|
+
skipErrorToast) {
|
|
1092
|
+
return throwError(() => error);
|
|
1093
|
+
}
|
|
1094
|
+
// Display error message for other errors
|
|
1095
|
+
if (isBrowser) {
|
|
1096
|
+
const { summary, detail } = extractErrorInfo(error, translateAdapter);
|
|
1097
|
+
messageService.add({
|
|
1098
|
+
severity: 'error',
|
|
1099
|
+
summary,
|
|
1100
|
+
detail,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return throwError(() => error);
|
|
1104
|
+
}));
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Extract error summary and detail from HTTP error response.
|
|
1108
|
+
* Uses backend response format: { message, messageKey, messageVariables, error, statusCode }
|
|
1109
|
+
* If messageKey is present and translate adapter is available, translates the message.
|
|
1110
|
+
*/
|
|
1111
|
+
function extractErrorInfo(error, translateAdapter) {
|
|
1112
|
+
// Check if we have a messageKey for translation
|
|
1113
|
+
const messageKey = error.error?.messageKey;
|
|
1114
|
+
const messageVariables = error.error?.messageVariables;
|
|
1115
|
+
let detail;
|
|
1116
|
+
if (messageKey && translateAdapter) {
|
|
1117
|
+
// Use translated message if available
|
|
1118
|
+
detail = translateAdapter.translate(messageKey, messageVariables);
|
|
1119
|
+
}
|
|
1120
|
+
else if (typeof error.error?.message === 'string') {
|
|
1121
|
+
detail = error.error.message;
|
|
1122
|
+
}
|
|
1123
|
+
else if (Array.isArray(error.error?.message)) {
|
|
1124
|
+
// NestJS validation errors come as array
|
|
1125
|
+
detail = error.error.message[0];
|
|
1126
|
+
}
|
|
1127
|
+
else if (error.message) {
|
|
1128
|
+
detail = error.message;
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
detail = translateAdapter
|
|
1132
|
+
? translateAdapter.translate('shared.unexpected.error')
|
|
1133
|
+
: 'shared.unexpected.error';
|
|
1134
|
+
}
|
|
1135
|
+
// Extract summary/title from backend response or use default based on status
|
|
1136
|
+
let summary;
|
|
1137
|
+
if (typeof error.error?.error === 'string') {
|
|
1138
|
+
// Backend sends error type (e.g., "Not Found", "Bad Request", "Validation Error")
|
|
1139
|
+
summary = error.error.error;
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
// Fallback to status-based title
|
|
1143
|
+
summary = getDefaultSummary(error.status, translateAdapter);
|
|
1144
|
+
}
|
|
1145
|
+
return { summary, detail };
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Get default error summary based on HTTP status code.
|
|
1149
|
+
*/
|
|
1150
|
+
function getDefaultSummary(status, translateAdapter) {
|
|
1151
|
+
const keyMap = {
|
|
1152
|
+
400: 'shared.error.bad.request',
|
|
1153
|
+
404: 'shared.error.not.found',
|
|
1154
|
+
409: 'shared.error.conflict',
|
|
1155
|
+
422: 'shared.error.validation.error',
|
|
1156
|
+
500: 'shared.error.server.error',
|
|
1157
|
+
502: 'shared.error.service.unavailable',
|
|
1158
|
+
503: 'shared.error.service.unavailable',
|
|
1159
|
+
504: 'shared.error.service.unavailable',
|
|
1160
|
+
};
|
|
1161
|
+
const key = keyMap[status] || 'shared.error';
|
|
1162
|
+
if (translateAdapter) {
|
|
1163
|
+
return translateAdapter.translate(key);
|
|
997
1164
|
}
|
|
998
|
-
|
|
1165
|
+
// Fallback to key when no translator available
|
|
1166
|
+
return key;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Resolver to load translations for specific modules.
|
|
1171
|
+
* Works with or without @flusys/ng-localization provider.
|
|
1172
|
+
*
|
|
1173
|
+
* When localization provider is enabled:
|
|
1174
|
+
* - Registers fallback messages in state
|
|
1175
|
+
* - Fetches translations from API
|
|
1176
|
+
* - Merges them into state
|
|
1177
|
+
*
|
|
1178
|
+
* When localization provider is not enabled:
|
|
1179
|
+
* - Registers fallback messages in FALLBACK_MESSAGES_REGISTRY
|
|
1180
|
+
* - TranslatePipe uses registry for translations
|
|
1181
|
+
* - No API calls
|
|
1182
|
+
*
|
|
1183
|
+
* @param config - Module config with modules array and optional fallback messages
|
|
1184
|
+
* @returns ResolveFn that loads translations before route activation
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* // With fallback messages (recommended)
|
|
1188
|
+
* { resolve: { t: resolveTranslationModule({ modules: ['auth'], fallbackMessages: AUTH_MESSAGES }) } }
|
|
1189
|
+
*
|
|
1190
|
+
* @example
|
|
1191
|
+
* // Without fallback (API translations only, requires localization provider)
|
|
1192
|
+
* { resolve: { t: resolveTranslationModule({ modules: ['auth', 'iam'] }) } }
|
|
1193
|
+
*/
|
|
1194
|
+
async function tryLoadLocalizationServices(injector) {
|
|
1195
|
+
const stateService = injector.get(LOCALIZATION_STATE_SERVICE, null);
|
|
1196
|
+
const apiService = injector.get(LOCALIZATION_API_SERVICE, null);
|
|
1197
|
+
if (!stateService || !apiService)
|
|
999
1198
|
return null;
|
|
1000
|
-
|
|
1199
|
+
return {
|
|
1200
|
+
stateService,
|
|
1201
|
+
apiService,
|
|
1202
|
+
};
|
|
1001
1203
|
}
|
|
1002
1204
|
function resolveTranslationModule(config) {
|
|
1003
1205
|
return async () => {
|
|
@@ -1019,6 +1221,10 @@ function resolveTranslationModule(config) {
|
|
|
1019
1221
|
if (registry) {
|
|
1020
1222
|
Object.assign(registry, fallbackMessages);
|
|
1021
1223
|
}
|
|
1224
|
+
else if (isDevMode()) {
|
|
1225
|
+
console.warn('resolveTranslationModule: FALLBACK_MESSAGES_REGISTRY not provided. ' +
|
|
1226
|
+
'Add provideFallbackMessagesRegistry() to your app providers.');
|
|
1227
|
+
}
|
|
1022
1228
|
}
|
|
1023
1229
|
}
|
|
1024
1230
|
if (!locServices)
|
|
@@ -1236,19 +1442,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1236
1442
|
}]
|
|
1237
1443
|
}] });
|
|
1238
1444
|
|
|
1445
|
+
// ── Separate default filter into a named constant so it's sharable/testable ──
|
|
1446
|
+
const DEFAULT_FILTER = {
|
|
1447
|
+
pagination: { currentPage: 0, pageSize: 10 },
|
|
1448
|
+
filter: {},
|
|
1449
|
+
select: [],
|
|
1450
|
+
sort: {},
|
|
1451
|
+
};
|
|
1239
1452
|
class ApiResourceService {
|
|
1453
|
+
// ── Injected once via `inject()` ──────────────────────────────────────────
|
|
1454
|
+
http = inject(HttpClient);
|
|
1455
|
+
_config = inject(APP_CONFIG);
|
|
1240
1456
|
baseUrl;
|
|
1241
|
-
http;
|
|
1242
1457
|
moduleApiName;
|
|
1458
|
+
// ── Public writable signals ───────────────────────────────────────────────
|
|
1243
1459
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1244
|
-
filterData = signal({
|
|
1245
|
-
|
|
1246
|
-
filter: {},
|
|
1247
|
-
select: [],
|
|
1248
|
-
sort: {},
|
|
1249
|
-
}, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
1460
|
+
filterData = signal({ ...DEFAULT_FILTER }, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
1461
|
+
// ── Private activation gate: avoids an initial fetch before fetchList() ──
|
|
1250
1462
|
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
|
|
1463
|
+
// ── Resource (reactive list fetching with abort support) ──────────────────
|
|
1251
1464
|
listResource;
|
|
1465
|
+
// ── Derived/computed signals (pure reads, no side-effects) ────────────────
|
|
1252
1466
|
isLoading = computed(() => this.listResource.isLoading(), ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1253
1467
|
data = computed(() => this.listResource.value()?.data ?? [], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1254
1468
|
total = computed(() => this.listResource.value()?.meta?.total ?? 0, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
@@ -1261,52 +1475,64 @@ class ApiResourceService {
|
|
|
1261
1475
|
}, ...(ngDevMode ? [{ debugName: "hasMore" }] : []));
|
|
1262
1476
|
error = computed(() => this.listResource.error(), ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
1263
1477
|
status = computed(() => this.listResource.status(), ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
1264
|
-
constructor(moduleApiName,
|
|
1265
|
-
this.moduleApiName = moduleApiName
|
|
1266
|
-
|
|
1267
|
-
const serviceBaseUrl = serviceName
|
|
1268
|
-
|
|
1269
|
-
|
|
1478
|
+
constructor(moduleApiName, serviceName) {
|
|
1479
|
+
this.moduleApiName = moduleApiName;
|
|
1480
|
+
// ── Resolve base URL once at construction, not per-request ───────────────
|
|
1481
|
+
const serviceBaseUrl = serviceName
|
|
1482
|
+
? getServiceUrl(this._config, serviceName)
|
|
1483
|
+
: this._config.apiBaseUrl;
|
|
1484
|
+
this.baseUrl = moduleApiName
|
|
1485
|
+
? `${serviceBaseUrl}/${moduleApiName}`
|
|
1486
|
+
: serviceBaseUrl;
|
|
1270
1487
|
this.listResource = resource({ ...(ngDevMode ? { debugName: "listResource" } : {}), params: () => {
|
|
1488
|
+
// Short-circuit: skip the loader entirely when not yet activated
|
|
1271
1489
|
if (!this._active())
|
|
1272
1490
|
return undefined;
|
|
1273
1491
|
return { search: this.searchTerm(), filter: this.filterData() };
|
|
1274
1492
|
},
|
|
1275
|
-
loader: ({ params, abortSignal }) =>
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1493
|
+
loader: ({ params, abortSignal }) =>
|
|
1494
|
+
// ── Inline promise keeps the closure tight; no temp variable needed ─
|
|
1495
|
+
new Promise((resolve, reject) => {
|
|
1496
|
+
const sub = this._getAllObs(params.filter, params.search).subscribe({
|
|
1497
|
+
next: resolve,
|
|
1498
|
+
error: reject,
|
|
1280
1499
|
});
|
|
1281
|
-
|
|
1500
|
+
abortSignal.addEventListener('abort', () => sub.unsubscribe());
|
|
1501
|
+
}) });
|
|
1282
1502
|
}
|
|
1503
|
+
// ── Single, memoised header builder – avoids object churn per call ────────
|
|
1283
1504
|
getHttpOptions(endpoint, params) {
|
|
1284
1505
|
return {
|
|
1285
|
-
headers: new HttpHeaders({
|
|
1506
|
+
headers: new HttpHeaders({
|
|
1507
|
+
'x-loader-tag': `${this.moduleApiName}/${endpoint}`,
|
|
1508
|
+
}),
|
|
1286
1509
|
...(params ? { params } : {}),
|
|
1287
1510
|
};
|
|
1288
1511
|
}
|
|
1512
|
+
// ── Private observable – shared by the resource loader and the public getAll() ──
|
|
1289
1513
|
_getAllObs(filter, search = '') {
|
|
1290
|
-
|
|
1291
|
-
if (search)
|
|
1292
|
-
params = params.append('q', search);
|
|
1514
|
+
const params = search ? new HttpParams().append('q', search) : undefined;
|
|
1293
1515
|
return this.http.post(`${this.baseUrl}/get-all`, filter, this.getHttpOptions('get-all', params));
|
|
1294
1516
|
}
|
|
1517
|
+
// ── Activate guard: all list-management helpers call this first ───────────
|
|
1518
|
+
_activate() {
|
|
1519
|
+
this._active.set(true);
|
|
1520
|
+
}
|
|
1295
1521
|
// ==========================================================================
|
|
1296
|
-
// List
|
|
1522
|
+
// List management
|
|
1297
1523
|
// ==========================================================================
|
|
1298
1524
|
fetchList(search = '', filter) {
|
|
1299
1525
|
this.searchTerm.set(search);
|
|
1300
1526
|
if (filter)
|
|
1301
1527
|
this.filterData.update((prev) => ({ ...prev, ...filter }));
|
|
1302
|
-
this.
|
|
1528
|
+
this._activate();
|
|
1303
1529
|
}
|
|
1304
1530
|
setPagination(pagination) {
|
|
1305
|
-
this.
|
|
1531
|
+
this._activate();
|
|
1306
1532
|
this.filterData.update((prev) => ({ ...prev, pagination }));
|
|
1307
1533
|
}
|
|
1308
1534
|
nextPage() {
|
|
1309
|
-
this.
|
|
1535
|
+
this._activate();
|
|
1310
1536
|
this.filterData.update((prev) => ({
|
|
1311
1537
|
...prev,
|
|
1312
1538
|
pagination: {
|
|
@@ -1316,7 +1542,7 @@ class ApiResourceService {
|
|
|
1316
1542
|
}));
|
|
1317
1543
|
}
|
|
1318
1544
|
resetPagination() {
|
|
1319
|
-
this.
|
|
1545
|
+
this._activate();
|
|
1320
1546
|
this.filterData.update((prev) => ({
|
|
1321
1547
|
...prev,
|
|
1322
1548
|
pagination: { currentPage: 0, pageSize: prev.pagination?.pageSize ?? 10 },
|
|
@@ -1326,131 +1552,107 @@ class ApiResourceService {
|
|
|
1326
1552
|
this.listResource.reload();
|
|
1327
1553
|
}
|
|
1328
1554
|
// ==========================================================================
|
|
1329
|
-
// Promise API
|
|
1555
|
+
// Promise API (IApiService implementation)
|
|
1330
1556
|
// ==========================================================================
|
|
1557
|
+
// ── DRY helper: all promise methods are one-liners via this ───────────────
|
|
1558
|
+
_post(endpoint, body, params) {
|
|
1559
|
+
return firstValueFrom(this.http.post(`${this.baseUrl}/${endpoint}`, body, this.getHttpOptions(endpoint, params)));
|
|
1560
|
+
}
|
|
1331
1561
|
insert(dto) {
|
|
1332
|
-
return
|
|
1562
|
+
return this._post('insert', dto);
|
|
1333
1563
|
}
|
|
1334
1564
|
insertMany(dtos) {
|
|
1335
|
-
return
|
|
1565
|
+
return this._post('insert-many', dtos);
|
|
1336
1566
|
}
|
|
1337
1567
|
findById(id, select) {
|
|
1338
|
-
return
|
|
1568
|
+
return this._post(`get/${id}`, { select });
|
|
1339
1569
|
}
|
|
1340
1570
|
getAll(filter, search = '') {
|
|
1341
1571
|
return firstValueFrom(this._getAllObs(filter, search));
|
|
1342
1572
|
}
|
|
1343
1573
|
update(dto) {
|
|
1344
|
-
return
|
|
1574
|
+
return this._post('update', dto);
|
|
1345
1575
|
}
|
|
1346
1576
|
updateMany(dtos) {
|
|
1347
|
-
return
|
|
1577
|
+
return this._post('update-many', dtos);
|
|
1348
1578
|
}
|
|
1349
|
-
|
|
1350
|
-
return
|
|
1579
|
+
delete(deleteDto) {
|
|
1580
|
+
return this._post('delete', deleteDto);
|
|
1351
1581
|
}
|
|
1352
|
-
|
|
1353
|
-
return
|
|
1582
|
+
findByIds(ids, select) {
|
|
1583
|
+
return this._post('get-by-ids', { ids, select });
|
|
1354
1584
|
}
|
|
1355
1585
|
getByFilter(filter) {
|
|
1356
|
-
return
|
|
1586
|
+
return this._post('get-by-filter', filter);
|
|
1357
1587
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1588
|
+
async bulkUpsert(toInsert, toUpdate) {
|
|
1589
|
+
const res = await this._post('bulk-upsert', [
|
|
1590
|
+
...toInsert,
|
|
1591
|
+
...toUpdate,
|
|
1592
|
+
]);
|
|
1593
|
+
return res.data ?? [];
|
|
1360
1594
|
}
|
|
1361
1595
|
}
|
|
1362
1596
|
|
|
1363
|
-
/**
|
|
1364
|
-
* Base class for form controls that support ALL Angular form patterns:
|
|
1365
|
-
*
|
|
1366
|
-
* 1. **Template-driven forms:** `[(value)]="mySignal"` or `[(ngModel)]="myValue"`
|
|
1367
|
-
* 2. **Reactive forms:** `[formControl]="ctrl"` or `formControlName="field"`
|
|
1368
|
-
* 3. **Signal forms (Angular 21+):** `[formField]="formTree.field"`
|
|
1369
|
-
*
|
|
1370
|
-
* Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
|
|
1371
|
-
*
|
|
1372
|
-
* @example
|
|
1373
|
-
* ```typescript
|
|
1374
|
-
* @Component({
|
|
1375
|
-
* providers: [provideValueAccessor(MySelectComponent)]
|
|
1376
|
-
* })
|
|
1377
|
-
* export class MySelectComponent extends BaseFormControl<string | null> {
|
|
1378
|
-
* override readonly value = model<string | null>(null);
|
|
1379
|
-
*
|
|
1380
|
-
* constructor() {
|
|
1381
|
-
* super();
|
|
1382
|
-
* this.initializeFormControl();
|
|
1383
|
-
* }
|
|
1384
|
-
* }
|
|
1385
|
-
*
|
|
1386
|
-
* // Usage:
|
|
1387
|
-
* // Template-driven: <my-select [(value)]="selectedId" />
|
|
1388
|
-
* // Reactive forms: <my-select [formControl]="myControl" />
|
|
1389
|
-
* // Signal forms: <my-select [formField]="formTree.myField" />
|
|
1390
|
-
* ```
|
|
1391
|
-
*/
|
|
1392
1597
|
class BaseFormControl {
|
|
1393
1598
|
injector = inject(Injector);
|
|
1394
|
-
/**
|
|
1395
|
-
* Disabled state model - bound to form control disabled state.
|
|
1396
|
-
* Using model() to satisfy FormValueControl interface requirements.
|
|
1397
|
-
*/
|
|
1398
1599
|
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1399
|
-
/**
|
|
1400
|
-
* Touched state model - for validation styling.
|
|
1401
|
-
* Using model() to satisfy FormValueControl interface requirements.
|
|
1402
|
-
*/
|
|
1403
1600
|
touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : []));
|
|
1601
|
+
// ── CVA callbacks ─────────────────────────────────────────────────────────
|
|
1602
|
+
// Initialised to no-ops so subclasses never need null-guards
|
|
1404
1603
|
onChange = () => { };
|
|
1405
1604
|
onTouched = () => { };
|
|
1406
|
-
|
|
1605
|
+
// ── Write-guard: prevents echo loop (writeValue → effect → onChange) ──────
|
|
1606
|
+
// Using a counter instead of a boolean handles the (rare) re-entrant case
|
|
1607
|
+
writeDepth = 0;
|
|
1407
1608
|
/**
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1609
|
+
* Wire up the ControlValueAccessor ↔ signal bridge.
|
|
1610
|
+
* Call once at the end of the subclass constructor.
|
|
1611
|
+
*
|
|
1612
|
+
* Signal-form consumers ([formField]) don't need this — Angular's
|
|
1613
|
+
* FormField directive binds directly to `value`.
|
|
1410
1614
|
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1615
|
+
* @example
|
|
1616
|
+
* constructor() { super(); this.initializeFormControl(); }
|
|
1413
1617
|
*/
|
|
1414
1618
|
initializeFormControl() {
|
|
1415
1619
|
runInInjectionContext(this.injector, () => {
|
|
1416
1620
|
effect(() => {
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1621
|
+
const next = this.value();
|
|
1622
|
+
// Skip propagation while writeValue() is on the call stack
|
|
1623
|
+
if (this.writeDepth === 0) {
|
|
1624
|
+
untracked(() => this.onChange(next));
|
|
1420
1625
|
}
|
|
1421
1626
|
});
|
|
1422
1627
|
});
|
|
1423
1628
|
}
|
|
1424
|
-
//
|
|
1425
|
-
// ControlValueAccessor
|
|
1426
|
-
//
|
|
1427
|
-
// ============================================
|
|
1428
|
-
/** ControlValueAccessor: Write value from form control to signal */
|
|
1629
|
+
// ==========================================================================
|
|
1630
|
+
// ControlValueAccessor
|
|
1631
|
+
// ==========================================================================
|
|
1429
1632
|
writeValue(value) {
|
|
1430
|
-
this.
|
|
1431
|
-
|
|
1432
|
-
|
|
1633
|
+
this.writeDepth++;
|
|
1634
|
+
try {
|
|
1635
|
+
this.value.set(value);
|
|
1636
|
+
}
|
|
1637
|
+
finally {
|
|
1638
|
+
// Always decrement — even if set() throws — so the guard never sticks
|
|
1639
|
+
this.writeDepth--;
|
|
1640
|
+
}
|
|
1433
1641
|
}
|
|
1434
|
-
/** ControlValueAccessor: Register change callback */
|
|
1435
1642
|
registerOnChange(fn) {
|
|
1436
1643
|
this.onChange = fn;
|
|
1437
1644
|
}
|
|
1438
|
-
/** ControlValueAccessor: Register touched callback */
|
|
1439
1645
|
registerOnTouched(fn) {
|
|
1440
1646
|
this.onTouched = fn;
|
|
1441
1647
|
}
|
|
1442
|
-
/** ControlValueAccessor: Set disabled state from form control */
|
|
1443
1648
|
setDisabledState(isDisabled) {
|
|
1444
1649
|
this.disabled.set(isDisabled);
|
|
1445
1650
|
}
|
|
1446
|
-
//
|
|
1447
|
-
// FormValueControl
|
|
1448
|
-
//
|
|
1449
|
-
//
|
|
1450
|
-
|
|
1451
|
-
// - value: ModelSignal<T> (already declared as abstract above)
|
|
1452
|
-
// The FormField directive automatically binds to the value model.
|
|
1453
|
-
/** Mark the control as touched (call on blur or panel close) */
|
|
1651
|
+
// ==========================================================================
|
|
1652
|
+
// FormValueControl (signal forms)
|
|
1653
|
+
// `value: ModelSignal<T>` satisfies the interface — nothing else needed.
|
|
1654
|
+
// ==========================================================================
|
|
1655
|
+
/** Call on blur / panel-close to propagate touched state. */
|
|
1454
1656
|
markAsTouched() {
|
|
1455
1657
|
if (!this.touched()) {
|
|
1456
1658
|
this.touched.set(true);
|
|
@@ -1479,7 +1681,7 @@ function provideValueAccessor(component) {
|
|
|
1479
1681
|
return {
|
|
1480
1682
|
provide: NG_VALUE_ACCESSOR,
|
|
1481
1683
|
useExisting: forwardRef(() => component),
|
|
1482
|
-
multi: true
|
|
1684
|
+
multi: true,
|
|
1483
1685
|
};
|
|
1484
1686
|
}
|
|
1485
1687
|
|
|
@@ -1607,7 +1809,7 @@ function checkScrollPagination(event, config) {
|
|
|
1607
1809
|
|
|
1608
1810
|
class FileUploaderComponent {
|
|
1609
1811
|
messageService = inject(MessageService);
|
|
1610
|
-
translateAdapter = inject(
|
|
1812
|
+
translateAdapter = inject(TranslateService, {
|
|
1611
1813
|
optional: true,
|
|
1612
1814
|
});
|
|
1613
1815
|
fileProvider = inject(FILE_PROVIDER, { optional: true });
|
|
@@ -2069,7 +2271,7 @@ const DEFAULT_PAGE_SIZE$1 = 20;
|
|
|
2069
2271
|
const DEFAULT_SELECTOR_PAGE_SIZE = 50;
|
|
2070
2272
|
class FileSelectorDialogComponent {
|
|
2071
2273
|
destroyRef = inject(DestroyRef);
|
|
2072
|
-
translateAdapter = inject(
|
|
2274
|
+
translateAdapter = inject(TranslateService, {
|
|
2073
2275
|
optional: true,
|
|
2074
2276
|
});
|
|
2075
2277
|
fileProvider = inject(FILE_PROVIDER, { optional: true });
|
|
@@ -2965,7 +3167,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2965
3167
|
class LazyMultiSelectComponent extends BaseFormControl {
|
|
2966
3168
|
document = inject(DOCUMENT$1);
|
|
2967
3169
|
appRef = inject(ApplicationRef);
|
|
2968
|
-
translateAdapter = inject(
|
|
3170
|
+
translateAdapter = inject(TranslateService, {
|
|
2969
3171
|
optional: true,
|
|
2970
3172
|
});
|
|
2971
3173
|
onDocumentClickBound = this.handleDocumentClick.bind(this);
|
|
@@ -3171,7 +3373,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3171
3373
|
*/
|
|
3172
3374
|
class LazySelectComponent extends BaseFormControl {
|
|
3173
3375
|
destroyRef = inject(DestroyRef);
|
|
3174
|
-
translateAdapter = inject(
|
|
3376
|
+
translateAdapter = inject(TranslateService, {
|
|
3377
|
+
optional: true,
|
|
3378
|
+
});
|
|
3175
3379
|
onScrollBound = this.onScroll.bind(this);
|
|
3176
3380
|
scrollTargetEl = null;
|
|
3177
3381
|
isDestroyed = false;
|
|
@@ -3295,9 +3499,7 @@ class BaseUserSelectComponent {
|
|
|
3295
3499
|
destroyRef = inject(DestroyRef);
|
|
3296
3500
|
injector = inject(Injector);
|
|
3297
3501
|
userProvider = inject(USER_PROVIDER);
|
|
3298
|
-
translateAdapter = inject(
|
|
3299
|
-
optional: true,
|
|
3300
|
-
});
|
|
3502
|
+
translateAdapter = inject(TranslateService);
|
|
3301
3503
|
abortController = null;
|
|
3302
3504
|
// Inputs
|
|
3303
3505
|
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
@@ -3775,7 +3977,7 @@ class BaseFormPage {
|
|
|
3775
3977
|
route = inject(ActivatedRoute);
|
|
3776
3978
|
messageService = inject(MessageService);
|
|
3777
3979
|
destroyRef = inject(DestroyRef);
|
|
3778
|
-
translateAdapter = inject(
|
|
3980
|
+
translateAdapter = inject(TranslateService, {
|
|
3779
3981
|
optional: true,
|
|
3780
3982
|
});
|
|
3781
3983
|
routeParams = toSignal(this.route.paramMap);
|
|
@@ -3911,7 +4113,9 @@ class BaseListPage {
|
|
|
3911
4113
|
appConfig = inject(APP_CONFIG);
|
|
3912
4114
|
confirmationService = inject(ConfirmationService);
|
|
3913
4115
|
destroyRef = inject(DestroyRef);
|
|
3914
|
-
translateAdapter = inject(
|
|
4116
|
+
translateAdapter = inject(TranslateService, {
|
|
4117
|
+
optional: true,
|
|
4118
|
+
});
|
|
3915
4119
|
/** Items list */
|
|
3916
4120
|
items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
3917
4121
|
/** Loading state */
|
|
@@ -3961,25 +4165,41 @@ class BaseListPage {
|
|
|
3961
4165
|
* Show success toast message
|
|
3962
4166
|
*/
|
|
3963
4167
|
showSuccess(detail, summary) {
|
|
3964
|
-
this.messageService.add({
|
|
4168
|
+
this.messageService.add({
|
|
4169
|
+
severity: 'success',
|
|
4170
|
+
summary: summary ?? this.t('shared.success'),
|
|
4171
|
+
detail,
|
|
4172
|
+
});
|
|
3965
4173
|
}
|
|
3966
4174
|
/**
|
|
3967
4175
|
* Show error toast message
|
|
3968
4176
|
*/
|
|
3969
4177
|
showError(detail, summary) {
|
|
3970
|
-
this.messageService.add({
|
|
4178
|
+
this.messageService.add({
|
|
4179
|
+
severity: 'error',
|
|
4180
|
+
summary: summary ?? this.t('shared.error'),
|
|
4181
|
+
detail,
|
|
4182
|
+
});
|
|
3971
4183
|
}
|
|
3972
4184
|
/**
|
|
3973
4185
|
* Show info toast message
|
|
3974
4186
|
*/
|
|
3975
4187
|
showInfo(detail, summary) {
|
|
3976
|
-
this.messageService.add({
|
|
4188
|
+
this.messageService.add({
|
|
4189
|
+
severity: 'info',
|
|
4190
|
+
summary: summary ?? this.t('shared.info'),
|
|
4191
|
+
detail,
|
|
4192
|
+
});
|
|
3977
4193
|
}
|
|
3978
4194
|
/**
|
|
3979
4195
|
* Show warning toast message
|
|
3980
4196
|
*/
|
|
3981
4197
|
showWarn(detail, summary) {
|
|
3982
|
-
this.messageService.add({
|
|
4198
|
+
this.messageService.add({
|
|
4199
|
+
severity: 'warn',
|
|
4200
|
+
summary: summary ?? this.t('shared.warning'),
|
|
4201
|
+
detail,
|
|
4202
|
+
});
|
|
3983
4203
|
}
|
|
3984
4204
|
/**
|
|
3985
4205
|
* Translate a key using the adapter, fallback to key if not available.
|
|
@@ -4006,7 +4226,9 @@ class BaseListPage {
|
|
|
4006
4226
|
.subscribe({
|
|
4007
4227
|
next: (res) => {
|
|
4008
4228
|
const r = res;
|
|
4009
|
-
this.showSuccess(r?.messageKey
|
|
4229
|
+
this.showSuccess(r?.messageKey
|
|
4230
|
+
? this.t(r.messageKey, r.messageVariables)
|
|
4231
|
+
: (r?.message ?? ''));
|
|
4010
4232
|
this.loadData();
|
|
4011
4233
|
},
|
|
4012
4234
|
});
|
|
@@ -4029,7 +4251,9 @@ class BaseListPage {
|
|
|
4029
4251
|
accept: async () => {
|
|
4030
4252
|
const res = await deleteApiCall(idGetter(item));
|
|
4031
4253
|
const r = res;
|
|
4032
|
-
this.showSuccess(r?.messageKey
|
|
4254
|
+
this.showSuccess(r?.messageKey
|
|
4255
|
+
? this.t(r.messageKey, r.messageVariables)
|
|
4256
|
+
: (r?.message ?? ''));
|
|
4033
4257
|
await this.loadData();
|
|
4034
4258
|
},
|
|
4035
4259
|
});
|
|
@@ -4114,5 +4338,5 @@ function getUserTimezone() {
|
|
|
4114
4338
|
* Generated bundle index. Do not edit.
|
|
4115
4339
|
*/
|
|
4116
4340
|
|
|
4117
|
-
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,
|
|
4341
|
+
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 };
|
|
4118
4342
|
//# sourceMappingURL=flusys-ng-shared.mjs.map
|