Merge branch 'master' into VM/RED-2853
# Conflicts: # apps/red-ui/src/app/modules/admin/admin.module.ts # apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts
This commit is contained in:
commit
83c698b2da
37
README.md
37
README.md
@ -1,20 +1,5 @@
|
||||
# Redaction
|
||||
|
||||
## Swagger Generated Code
|
||||
|
||||
To re-generate http rune swagger
|
||||
|
||||
YOu need swagger-codegen installed `brew install swagger-codegen`
|
||||
|
||||
```
|
||||
BASE=https://dev-06.iqser.cloud/
|
||||
URL="$BASE"redaction-gateway-v1/v2/api-docs?group=redaction-gateway-v1
|
||||
rm -Rf /tmp/swagger
|
||||
mkdir -p /tmp/swagger
|
||||
swagger-codegen generate -i "$URL" -l typescript-angular -o /tmp/swagger
|
||||
cd /tmp/swagger
|
||||
```
|
||||
|
||||
## To Create a new Stack in rancher
|
||||
|
||||
Goto rancher.iqser.com: Select Cluster `Development`, go to apps, click launch and select `Redaction` from the `dev`
|
||||
@ -25,17 +10,25 @@ Add cloudflare certificate and specify a hostname to use `timo-redaction-dev.iqs
|
||||
|
||||
## Keycloak Staging Config
|
||||
|
||||
keycloak:
|
||||
authServerUrl: 'https://redkc-staging.iqser.cloud/auth'
|
||||
client:
|
||||
secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4'
|
||||
- keycloak:
|
||||
- authServerUrl: 'https://redkc-staging.iqser.cloud/auth'
|
||||
- client:
|
||||
- secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4'
|
||||
|
||||
## Default Testing URL
|
||||
|
||||
`https://timo-redaction-dev.iqser.cloud/`
|
||||
Hostname:
|
||||
`https://dev-04.iqser.cloud/`
|
||||
|
||||
timo-redaction-dev.iqser.cloud
|
||||
## Known errors
|
||||
|
||||
- In case of CORS or redirect_uri errors follow these steps:
|
||||
- Go to `<HOST>.iqser.cloud/auth/admin/master/console`
|
||||
- Login with `admin` and `admin1234`
|
||||
- In the left menu go to `Clients`
|
||||
- In the table click `redaction`
|
||||
- Find `Valid Redirect URIs` input
|
||||
- Under `/ui/*` add new value `http://localhost:4200/*`
|
||||
- **Save**
|
||||
|
||||
## Test Users
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"cli": {
|
||||
"analytics": "4b8eed12-a1e6-4b7a-9ea2-925b27941271"
|
||||
"analytics": false
|
||||
},
|
||||
"version": 1,
|
||||
"projects": {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
<router-outlet></router-outlet>
|
||||
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
||||
<iqser-connection-status></iqser-connection-status>
|
||||
<iqser-full-page-error></iqser-full-page-error>
|
||||
|
||||
@ -36,9 +36,10 @@ import { KeycloakService } from 'keycloak-angular';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
|
||||
export function httpLoaderFactory(httpClient: HttpClient): PruningTranslationLoader {
|
||||
return new PruningTranslationLoader(httpClient, '/assets/i18n/', '.json');
|
||||
export function httpLoaderFactory(httpClient: HttpClient, configService: ConfigService): PruningTranslationLoader {
|
||||
return new PruningTranslationLoader(httpClient, '/assets/i18n/', `.json?version=${configService.values.FRONTEND_APP_VERSION}`);
|
||||
}
|
||||
|
||||
function cleanupBaseUrl(baseUrl: string) {
|
||||
@ -76,7 +77,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: httpLoaderFactory,
|
||||
deps: [HttpClient],
|
||||
deps: [HttpClient, ConfigService],
|
||||
},
|
||||
compiler: {
|
||||
provide: TranslateCompiler,
|
||||
@ -114,7 +115,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useFactory: configurationInitializer,
|
||||
deps: [KeycloakService, Title, ConfigService, GeneralSettingsService, LanguageService, UserPreferenceService],
|
||||
deps: [KeycloakService, Title, ConfigService, GeneralSettingsService, LanguageService, UserService, UserPreferenceService],
|
||||
},
|
||||
{
|
||||
provide: MissingTranslationHandler,
|
||||
@ -136,6 +137,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
|
||||
export class AppModule {
|
||||
constructor(private readonly _router: Router, private readonly _route: ActivatedRoute) {
|
||||
this._configureKeyCloakRouteHandling();
|
||||
// this._test();
|
||||
}
|
||||
|
||||
private _configureKeyCloakRouteHandling() {
|
||||
@ -152,4 +154,60 @@ export class AppModule {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// private _test(){
|
||||
//
|
||||
// const flatGerman = flatten(german);
|
||||
//
|
||||
//
|
||||
// const flatEnglish = flatten(english);
|
||||
//
|
||||
// const tmfc = new TranslateMessageFormatCompiler();
|
||||
//
|
||||
//
|
||||
//
|
||||
// for (const key of Object.keys(flatGerman)) {
|
||||
// try {
|
||||
// const result = tmfc.compile(flatGerman[key], 'de');
|
||||
// //console.log(result);
|
||||
// } catch (e) {
|
||||
// console.error('ERROR AT: ', flatGerman[key]);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (const key of Object.keys(flatEnglish)) {
|
||||
// try {
|
||||
// const result = tmfc.compile(flatEnglish[key], 'de');
|
||||
// //console.log(result);
|
||||
// } catch (e) {
|
||||
// console.error('ERROR AT: ', flatEnglish[key]);
|
||||
// }
|
||||
// }
|
||||
// console.log('done');
|
||||
// }
|
||||
}
|
||||
|
||||
//
|
||||
// function flatten(data: any) {
|
||||
// const result: any = {};
|
||||
//
|
||||
// function recurse(cur: any, prop: any) {
|
||||
// if (Object(cur) !== cur) {
|
||||
// result[prop] = cur;
|
||||
// } else if (Array.isArray(cur)) {
|
||||
// let l = 0;
|
||||
// for (let i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + '[' + i + ']');
|
||||
// if (l === 0) result[prop] = [];
|
||||
// } else {
|
||||
// let isEmpty = true;
|
||||
// for (const p in cur) {
|
||||
// isEmpty = false;
|
||||
// recurse(cur[p], prop ? prop + '.' + p : p);
|
||||
// }
|
||||
// if (isEmpty && prop) result[prop] = {};
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// recurse(data, '');
|
||||
// return result;
|
||||
// }
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
</div>
|
||||
<redaction-user-button
|
||||
[matMenuTriggerFor]="userMenu"
|
||||
[showDot]="fileDownloadService.hasPendingDownloads"
|
||||
[userId]="currentUser.id"
|
||||
iqserHelpMode="open-usermenu"
|
||||
></redaction-user-button>
|
||||
@ -36,7 +35,6 @@
|
||||
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
|
||||
<button (click)="(item.action)" *ngIf="item.show" [routerLink]="item.routerLink" mat-menu-item translate>
|
||||
{{ item.name }}
|
||||
<span *ngIf="item.showDot()" class="dot"></span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
|
||||
@ -36,26 +35,22 @@ export class BaseScreenComponent {
|
||||
routerLink: '/main/account',
|
||||
show: true,
|
||||
action: this.appStateService.reset,
|
||||
showDot: () => false,
|
||||
},
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.admin'),
|
||||
routerLink: '/main/admin',
|
||||
show: this.currentUser.isManager || this.currentUser.isUserAdmin,
|
||||
action: this.appStateService.reset,
|
||||
showDot: () => false,
|
||||
},
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.downloads'),
|
||||
routerLink: '/main/downloads',
|
||||
show: this.currentUser.isUser,
|
||||
showDot: () => this.fileDownloadService.hasPendingDownloads,
|
||||
},
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.trash'),
|
||||
routerLink: '/main/admin/trash',
|
||||
show: this.currentUser.isManager,
|
||||
showDot: () => false,
|
||||
},
|
||||
];
|
||||
readonly searchActions: readonly SpotlightSearchAction[] = [
|
||||
@ -81,11 +76,9 @@ export class BaseScreenComponent {
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
readonly dossiersService: DossiersService,
|
||||
readonly userService: UserService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly titleService: Title,
|
||||
readonly fileDownloadService: FileDownloadService,
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
readonly breadcrumbsService: BreadcrumbsService,
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<mat-icon *ngIf="!first" svgIcon="iqser:arrow-right"></mat-icon>
|
||||
|
||||
<a
|
||||
[routerLinkActiveOptions]="breadcrumb.routerLinkActiveOptions ?? { exact: false }"
|
||||
[routerLinkActiveOptions]="breadcrumb.routerLinkActiveOptions || { exact: false }"
|
||||
[routerLink]="breadcrumb.routerLink"
|
||||
class="breadcrumb"
|
||||
routerLinkActive="active"
|
||||
|
||||
@ -11,6 +11,8 @@ import {
|
||||
} from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { firstValueFrom, interval } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-downloads-list-screen',
|
||||
@ -43,6 +45,9 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
||||
) {
|
||||
super(_injector);
|
||||
this._loadingService.loadWhile(this._loadData());
|
||||
this.addSubscription = interval(5000)
|
||||
.pipe(switchMap(() => this._loadData()))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
downloadItem(download: DownloadStatus) {
|
||||
@ -55,11 +60,11 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
||||
|
||||
private async _deleteItems(downloads?: DownloadStatus[]) {
|
||||
const storageIds = (downloads || this.listingService.selected).map(d => d.storageId);
|
||||
await this.fileDownloadService.delete({ storageIds }).toPromise();
|
||||
await firstValueFrom(this.fileDownloadService.delete({ storageIds }));
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
await this.fileDownloadService.loadAll().toPromise();
|
||||
await firstValueFrom(this.fileDownloadService.loadAll());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
@use 'variables';
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box {
|
||||
right: 0 !important;
|
||||
|
||||
@ -20,6 +16,7 @@
|
||||
|
||||
.view-all {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--iqser-primary);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { NotificationsService } from '@services/notifications.service';
|
||||
import { Notification } from '@red/domain';
|
||||
import { distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, timer } from 'rxjs';
|
||||
import { BehaviorSubject, firstValueFrom, Observable, timer } from 'rxjs';
|
||||
import { AutoUnsubscribe, List, shareLast } from '@iqser/common-ui';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
|
||||
@ -63,12 +63,12 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit {
|
||||
|
||||
async markRead($event, notifications: List<string> = this._notifications$.getValue().map(n => n.id), isRead = true): Promise<void> {
|
||||
$event.stopPropagation();
|
||||
await this._notificationsService.toggleNotificationRead(notifications, isRead).toPromise();
|
||||
await firstValueFrom(this._notificationsService.toggleNotificationRead(notifications, isRead));
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
const notifications = await this._notificationsService.getNotifications(INCLUDE_SEEN).toPromise();
|
||||
const notifications = await firstValueFrom(this._notificationsService.getNotifications(INCLUDE_SEEN));
|
||||
this._notifications$.next(notifications);
|
||||
}
|
||||
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
<form (submit)="executeCurrentAction()">
|
||||
<iqser-input-with-action
|
||||
(click)="openMenuIfValue()"
|
||||
(valueChange)="valueChanges$.next($event)"
|
||||
[placeholder]="placeholder"
|
||||
></iqser-input-with-action>
|
||||
<iqser-input-with-action
|
||||
(action)="executeCurrentAction()"
|
||||
(click)="openMenuIfValue()"
|
||||
(valueChange)="valueChanges$.next($event)"
|
||||
[placeholder]="placeholder"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<mat-menu #menu="matMenu" class="search-menu" xPosition="after">
|
||||
<ng-template matMenuContent>
|
||||
<div class="wrapper">
|
||||
<button
|
||||
(click)="item.action(valueChanges$.getValue())"
|
||||
*ngFor="let item of shownActions; let index = index"
|
||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||
class="spotlight-row pointer"
|
||||
>
|
||||
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-menu>
|
||||
<mat-menu #menu="matMenu" class="search-menu" xPosition="after">
|
||||
<ng-template matMenuContent>
|
||||
<div class="wrapper">
|
||||
<button
|
||||
(click)="item.action(valueChanges$.getValue())"
|
||||
*ngFor="let item of shownActions; let index = index"
|
||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||
class="spotlight-row pointer"
|
||||
>
|
||||
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-menu>
|
||||
|
||||
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
||||
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
||||
<div [matMenuTriggerFor]="menu"></div>
|
||||
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
||||
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
||||
<div [matMenuTriggerFor]="menu"></div>
|
||||
|
||||
<!-- A hack to avoid subscribing in component -->
|
||||
<ng-container *ngIf="showActions$ | async"></ng-container>
|
||||
</form>
|
||||
<!-- A hack to avoid subscribing in component -->
|
||||
<ng-container *ngIf="showActions$ | async"></ng-container>
|
||||
|
||||
@ -1,40 +1,34 @@
|
||||
import { CanDeactivate } from '@angular/router';
|
||||
import { Directive, HostListener, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { ConfirmationDialogService, ConfirmOptions } from '@iqser/common-ui';
|
||||
|
||||
export interface ComponentCanDeactivate {
|
||||
hasChanges: boolean;
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export abstract class ComponentHasChanges implements ComponentCanDeactivate {
|
||||
abstract hasChanges: boolean;
|
||||
|
||||
protected constructor(protected _translateService: TranslateService) {}
|
||||
|
||||
@HostListener('window:beforeunload', ['$event'])
|
||||
unloadNotification($event: any) {
|
||||
if (this.hasChanges) {
|
||||
// This message will be displayed in IE/Edge
|
||||
$event.returnValue = this._translateService.instant('pending-changes-guard');
|
||||
}
|
||||
}
|
||||
changed: boolean;
|
||||
valid?: boolean;
|
||||
isLeavingPage?: boolean;
|
||||
save: () => Promise<void>;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
|
||||
constructor(private readonly _translateService: TranslateService) {}
|
||||
constructor(private _dialogService: ConfirmationDialogService) {}
|
||||
|
||||
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
|
||||
// if there are no pending changes, just allow deactivation; else confirm first
|
||||
return !component.hasChanges
|
||||
? true
|
||||
: // NOTE: this warning message will only be shown when navigating elsewhere
|
||||
// within your angular app;
|
||||
// when navigating away from your angular app,
|
||||
// the browser will show a generic warning message
|
||||
// see http://stackoverflow.com/a/42207299/7307355
|
||||
confirm(this._translateService.instant('pending-changes-guard'));
|
||||
if (component.changed) {
|
||||
component.isLeavingPage = true;
|
||||
|
||||
const dialogRef = this._dialogService.openDialog({ disableConfirm: component.valid === false });
|
||||
return dialogRef.afterClosed().pipe(
|
||||
map(result => {
|
||||
if (result === ConfirmOptions.CONFIRM) {
|
||||
component.save();
|
||||
}
|
||||
component.isLeavingPage = false;
|
||||
return !!result;
|
||||
}),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierFilesGuard implements CanActivate {
|
||||
@ -22,7 +23,7 @@ export class DossierFilesGuard implements CanActivate {
|
||||
}
|
||||
|
||||
if (!this._filesMapService.has(dossierId)) {
|
||||
await this._filesService.loadAll(dossierId).toPromise();
|
||||
await firstValueFrom(this._filesService.loadAll(dossierId));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossiersGuard implements CanActivate {
|
||||
@ -12,7 +13,7 @@ export class DossiersGuard implements CanActivate {
|
||||
) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
await this._dossiersService.loadAll().toPromise();
|
||||
await firstValueFrom(this._dossiersService.loadAll());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -8,6 +9,7 @@ import { UserPreferenceService } from '@services/user-preference.service';
|
||||
export class LanguageService {
|
||||
constructor(private readonly _translateService: TranslateService, private readonly _userPreferenceService: UserPreferenceService) {
|
||||
_translateService.addLangs(['en', 'de']);
|
||||
_translateService.setDefaultLang('en');
|
||||
}
|
||||
|
||||
get currentLanguage() {
|
||||
@ -26,12 +28,12 @@ export class LanguageService {
|
||||
}
|
||||
document.documentElement.lang = defaultLang;
|
||||
this._translateService.setDefaultLang(defaultLang);
|
||||
this._translateService.use(defaultLang).toPromise().then();
|
||||
firstValueFrom(this._translateService.use(defaultLang)).then();
|
||||
}
|
||||
|
||||
async changeLanguage(language: string) {
|
||||
await this._userPreferenceService.saveLanguage(language);
|
||||
document.documentElement.lang = language;
|
||||
await this._translateService.use(language).toPromise();
|
||||
await firstValueFrom(this._translateService.use(language));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { AnnotationWrapper } from './annotation.wrapper';
|
||||
import { isArray } from 'rxjs/internal-compatibility';
|
||||
import { User } from '@red/domain';
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
export class AnnotationPermissions {
|
||||
canUndo = true;
|
||||
@ -14,6 +14,7 @@ export class AnnotationPermissions {
|
||||
canChangeLegalBasis = true;
|
||||
canResizeAnnotation = true;
|
||||
canRecategorizeImage = true;
|
||||
canForceHint = true;
|
||||
|
||||
static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) {
|
||||
if (!isArray(annotations)) {
|
||||
@ -29,12 +30,13 @@ export class AnnotationPermissions {
|
||||
permissions.canAcceptSuggestion = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
|
||||
permissions.canRejectSuggestion = isApprover && annotation.isSuggestion;
|
||||
|
||||
permissions.canForceHint = annotation.isIgnoredHint;
|
||||
permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive;
|
||||
permissions.canAcceptRecommendation = annotation.isRecommendation;
|
||||
|
||||
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive;
|
||||
|
||||
permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted;
|
||||
permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted || annotation.isHint;
|
||||
permissions.canRemoveOrSuggestToRemoveFromDictionary =
|
||||
annotation.isModifyDictionary && (annotation.isRedacted || annotation.isSkipped || annotation.isHint);
|
||||
|
||||
@ -42,7 +44,9 @@ export class AnnotationPermissions {
|
||||
|
||||
permissions.canRecategorizeImage = (annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage;
|
||||
permissions.canResizeAnnotation =
|
||||
((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) || annotation.isSuggestionResize;
|
||||
((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) ||
|
||||
annotation.isSuggestionResize ||
|
||||
annotation.isRecommendation;
|
||||
|
||||
summedPermissions._merge(permissions);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ export type AnnotationSuperType =
|
||||
| 'suggestion-remove-dictionary'
|
||||
| 'suggestion-add'
|
||||
| 'suggestion-remove'
|
||||
| 'ignored-hint'
|
||||
| 'skipped'
|
||||
| 'redaction'
|
||||
| 'manual-redaction'
|
||||
@ -43,7 +44,9 @@ export class AnnotationWrapper {
|
||||
legalBasisChangeValue?: string;
|
||||
resizing?: boolean;
|
||||
rectangle?: boolean;
|
||||
hintDictionary?: boolean;
|
||||
section?: string;
|
||||
reference: Array<string>;
|
||||
|
||||
manual?: boolean;
|
||||
|
||||
@ -87,7 +90,7 @@ export class AnnotationWrapper {
|
||||
}
|
||||
|
||||
get isSuperTypeBasedColor() {
|
||||
return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion;
|
||||
return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion || this.isIgnoredHint;
|
||||
}
|
||||
|
||||
get isSkipped() {
|
||||
@ -106,6 +109,20 @@ export class AnnotationWrapper {
|
||||
return this.recategorizationType || this.typeValue;
|
||||
}
|
||||
|
||||
get topLevelFilter() {
|
||||
return (
|
||||
this.superType !== 'hint' &&
|
||||
this.superType !== 'redaction' &&
|
||||
this.superType !== 'recommendation' &&
|
||||
this.superType !== 'ignored-hint' &&
|
||||
this.superType !== 'skipped'
|
||||
);
|
||||
}
|
||||
|
||||
get filterKey() {
|
||||
return this.topLevelFilter ? this.superType : this.superType + this.type;
|
||||
}
|
||||
|
||||
get isManuallySkipped() {
|
||||
return this.isSkipped && this.manual;
|
||||
}
|
||||
@ -113,7 +130,10 @@ export class AnnotationWrapper {
|
||||
get isFalsePositive() {
|
||||
return (
|
||||
this.type?.toLowerCase() === 'false_positive' &&
|
||||
(this.superType === 'skipped' || this.superType === 'hint' || this.superType === 'redaction')
|
||||
(this.superType === 'skipped' ||
|
||||
this.superType === 'hint' ||
|
||||
this.superType === 'ignored-hint' ||
|
||||
this.superType === 'redaction')
|
||||
);
|
||||
}
|
||||
|
||||
@ -133,6 +153,10 @@ export class AnnotationWrapper {
|
||||
return this.superType === 'hint';
|
||||
}
|
||||
|
||||
get isIgnoredHint() {
|
||||
return this.superType === 'ignored-hint';
|
||||
}
|
||||
|
||||
get isRedacted() {
|
||||
return this.superType === 'redaction' || this.superType === 'manual-redaction';
|
||||
}
|
||||
@ -236,12 +260,14 @@ export class AnnotationWrapper {
|
||||
annotationWrapper.manual = redactionLogEntry.manual;
|
||||
annotationWrapper.engines = redactionLogEntry.engines;
|
||||
annotationWrapper.section = redactionLogEntry.section;
|
||||
annotationWrapper.reference = redactionLogEntry.reference || [];
|
||||
annotationWrapper.rectangle = redactionLogEntry.rectangle;
|
||||
annotationWrapper.hasBeenResized = redactionLogEntry.hasBeenResized;
|
||||
annotationWrapper.hasBeenRecategorized = redactionLogEntry.hasBeenRecategorized;
|
||||
annotationWrapper.hasLegalBasisChanged = redactionLogEntry.hasLegalBasisChanged;
|
||||
annotationWrapper.hasBeenForced = redactionLogEntry.hasBeenForced;
|
||||
annotationWrapper.hasBeenRemovedByManualOverride = redactionLogEntry.hasBeenRemovedByManualOverride;
|
||||
annotationWrapper.hintDictionary = redactionLogEntry.hintDictionary;
|
||||
|
||||
this._createContent(annotationWrapper, redactionLogEntry);
|
||||
this._setSuperType(annotationWrapper, redactionLogEntry);
|
||||
@ -259,11 +285,7 @@ export class AnnotationWrapper {
|
||||
|
||||
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
|
||||
if (redactionLogEntryWrapper.recommendation) {
|
||||
if (redactionLogEntryWrapper.redacted) {
|
||||
annotationWrapper.superType = 'recommendation';
|
||||
} else {
|
||||
annotationWrapper.superType = 'skipped';
|
||||
}
|
||||
annotationWrapper.superType = 'recommendation';
|
||||
return;
|
||||
}
|
||||
|
||||
@ -280,7 +302,7 @@ export class AnnotationWrapper {
|
||||
if (redactionLogEntryWrapper.status === 'REQUESTED') {
|
||||
annotationWrapper.superType = 'suggestion-force-redaction';
|
||||
} else if (redactionLogEntryWrapper.status === 'APPROVED') {
|
||||
annotationWrapper.superType = 'redaction';
|
||||
annotationWrapper.superType = redactionLogEntryWrapper.hint ? 'hint' : 'redaction';
|
||||
} else {
|
||||
annotationWrapper.superType = 'skipped';
|
||||
}
|
||||
@ -408,6 +430,11 @@ export class AnnotationWrapper {
|
||||
if (!annotationWrapper.superType) {
|
||||
annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'skipped';
|
||||
}
|
||||
if (annotationWrapper.superType === 'skipped') {
|
||||
if (redactionLogEntryWrapper.hintDictionary) {
|
||||
annotationWrapper.superType = 'ignored-hint';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static _createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntryWrapper) {
|
||||
|
||||
@ -1,36 +1,48 @@
|
||||
import { Dictionary, File, IRedactionLog, IRedactionLogEntry, IViewedPage, User, ViewMode } from '@red/domain';
|
||||
import { Dictionary, File, IRedactionLog, IRedactionLogEntry, IViewedPage, ViewMode } from '@red/domain';
|
||||
import { AnnotationWrapper } from './annotation.wrapper';
|
||||
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class AnnotationData {
|
||||
visibleAnnotations: AnnotationWrapper[];
|
||||
allAnnotations: AnnotationWrapper[];
|
||||
}
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export class FileDataModel {
|
||||
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
||||
allAnnotations: AnnotationWrapper[];
|
||||
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
|
||||
readonly blob$ = new BehaviorSubject<Blob>(undefined);
|
||||
readonly file$ = new BehaviorSubject<File>(undefined);
|
||||
|
||||
hasChangeLog: boolean;
|
||||
constructor(
|
||||
private readonly _file: File,
|
||||
private readonly _blob: Blob,
|
||||
private _redactionLog: IRedactionLog,
|
||||
public viewedPages?: IViewedPage[],
|
||||
private _dictionaryData?: { [p: string]: Dictionary },
|
||||
private _areDevFeaturesEnabled?: boolean,
|
||||
) {
|
||||
this.file$.next(_file);
|
||||
this.blob$.next(_blob);
|
||||
this._buildAllAnnotations();
|
||||
}
|
||||
|
||||
constructor(public file: File, public fileData: Blob, public redactionLog: IRedactionLog, public viewedPages?: IViewedPage[]) {}
|
||||
get file(): File {
|
||||
return this.file$.value;
|
||||
}
|
||||
|
||||
getAnnotations(
|
||||
dictionaryData: { [p: string]: Dictionary },
|
||||
currentUser: User,
|
||||
viewMode: ViewMode,
|
||||
areDevFeaturesEnabled: boolean,
|
||||
): AnnotationData {
|
||||
const entries: RedactionLogEntryWrapper[] = this._convertData();
|
||||
let allAnnotations = entries
|
||||
.map(entry => AnnotationWrapper.fromData(entry))
|
||||
.filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber));
|
||||
set file(file: File) {
|
||||
this.file$.next(file);
|
||||
}
|
||||
|
||||
if (!areDevFeaturesEnabled) {
|
||||
allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive);
|
||||
}
|
||||
get redactionLog(): IRedactionLog {
|
||||
return this._redactionLog;
|
||||
}
|
||||
|
||||
const visibleAnnotations = allAnnotations.filter(annotation => {
|
||||
set redactionLog(redactionLog: IRedactionLog) {
|
||||
this._redactionLog = redactionLog;
|
||||
this._buildAllAnnotations();
|
||||
}
|
||||
|
||||
getVisibleAnnotations(viewMode: ViewMode) {
|
||||
return this.allAnnotations.filter(annotation => {
|
||||
if (viewMode === 'STANDARD') {
|
||||
return !annotation.isChangeLogRemoved;
|
||||
} else if (viewMode === 'DELTA') {
|
||||
@ -39,11 +51,30 @@ export class FileDataModel {
|
||||
return annotation.isRedacted;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
visibleAnnotations: visibleAnnotations,
|
||||
allAnnotations: allAnnotations,
|
||||
};
|
||||
private _buildAllAnnotations() {
|
||||
const entries: RedactionLogEntryWrapper[] = this._convertData();
|
||||
|
||||
const previousAnnotations = this.allAnnotations || [];
|
||||
this.allAnnotations = entries
|
||||
.map(entry => AnnotationWrapper.fromData(entry))
|
||||
.filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber));
|
||||
|
||||
if (!this._areDevFeaturesEnabled) {
|
||||
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
|
||||
}
|
||||
|
||||
this._setHiddenPropertyToNewAnnotations(this.allAnnotations, previousAnnotations);
|
||||
}
|
||||
|
||||
private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) {
|
||||
newAnnotations.forEach(newAnnotation => {
|
||||
const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId);
|
||||
if (oldAnnotation) {
|
||||
newAnnotation.hidden = oldAnnotation.hidden;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _convertData(): RedactionLogEntryWrapper[] {
|
||||
@ -55,6 +86,7 @@ export class FileDataModel {
|
||||
const redactionLogEntryWrapper: RedactionLogEntryWrapper = {};
|
||||
Object.assign(redactionLogEntryWrapper, redactionLogEntry);
|
||||
redactionLogEntryWrapper.type = redactionLogEntry.type;
|
||||
redactionLogEntryWrapper.hintDictionary = this._dictionaryData[redactionLogEntry.type].hint;
|
||||
|
||||
this._isChangeLogEntry(redactionLogEntry, redactionLogEntryWrapper);
|
||||
|
||||
@ -101,11 +133,11 @@ export class FileDataModel {
|
||||
}
|
||||
|
||||
private _isChangeLogEntry(redactionLogEntry: IRedactionLogEntry, wrapper: RedactionLogEntryWrapper) {
|
||||
if (this.file.numberOfAnalyses > 1) {
|
||||
redactionLogEntry.changes.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());
|
||||
if (this._file.numberOfAnalyses > 1) {
|
||||
const viableChanges = redactionLogEntry.changes.filter(c => c.analysisNumber > 1);
|
||||
viableChanges.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());
|
||||
|
||||
const lastChange =
|
||||
redactionLogEntry.changes.length >= 1 ? redactionLogEntry.changes[redactionLogEntry.changes.length - 1] : undefined;
|
||||
const lastChange = viableChanges.length >= 1 ? viableChanges[viableChanges.length - 1] : undefined;
|
||||
const page = redactionLogEntry.positions?.[0].page;
|
||||
|
||||
const viewedPage = this.viewedPages.filter(p => p.page === page).pop();
|
||||
@ -114,14 +146,14 @@ export class FileDataModel {
|
||||
if (viewedPage) {
|
||||
const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataModel.DELTA_VIEW_TIME;
|
||||
// these are all unseen changes
|
||||
const relevantChanges = redactionLogEntry.changes.filter(change => moment(change.dateTime).valueOf() > viewTime);
|
||||
const relevantChanges = viableChanges.filter(change => moment(change.dateTime).valueOf() > viewTime);
|
||||
// at least one unseen change
|
||||
if (relevantChanges.length > 0) {
|
||||
// at least 1 relevant change
|
||||
wrapper.changeLogType = relevantChanges[relevantChanges.length - 1].type;
|
||||
wrapper.isChangeLogEntry = true;
|
||||
viewedPage.showAsUnseen = moment(viewedPage.viewedTime).valueOf() < moment(lastChange.dateTime).valueOf();
|
||||
this.hasChangeLog = true;
|
||||
this.hasChangeLog$.next(true);
|
||||
} else {
|
||||
// no relevant changes - hide removed anyway
|
||||
wrapper.isChangeLogEntry = false;
|
||||
|
||||
@ -11,6 +11,7 @@ export interface RedactionLogEntryWrapper {
|
||||
startOffset?: number;
|
||||
type?: string;
|
||||
rectangle?: boolean;
|
||||
hintDictionary?: boolean;
|
||||
|
||||
color?: Array<number>;
|
||||
dictionaryEntry?: boolean;
|
||||
|
||||
@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||
import { AppStateGuard } from '../../state/app-state.guard';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||
|
||||
const routes = [
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
export const NotificationCategories = {
|
||||
inAppNotifications: 'inAppNotifications',
|
||||
emailNotifications: 'emailNotifications',
|
||||
} as const;
|
||||
|
||||
export const NotificationCategoriesValues = Object.values(NotificationCategories);
|
||||
|
||||
export const DossierNotificationsTypes = {
|
||||
dossierOwnerSet: 'DOSSIER_OWNER_SET',
|
||||
dossierOwnerRemoved: 'DOSSIER_OWNER_REMOVED',
|
||||
userBecomseDossierMember: 'USER_BECOMES_DOSSIER_MEMBER',
|
||||
userRemovedAsDossierMember: 'USER_REMOVED_AS_DOSSIER_MEMBER',
|
||||
userPromotedToApprover: 'USER_PROMOTED_TO_APPROVER',
|
||||
userDegradedToReviewer: 'USER_DEGRADED_TO_REVIEWER',
|
||||
dossierOwnerDeleted: 'DOSSIER_OWNER_DELETED',
|
||||
dossierDeleted: 'DOSSIER_DELETED',
|
||||
} as const;
|
||||
|
||||
export const DossierNotificationsTypesValues = Object.values(DossierNotificationsTypes);
|
||||
|
||||
export const DocumentNotificationsTypes = {
|
||||
assignReviewer: 'ASSIGN_REVIEWER',
|
||||
assignApprover: 'ASSIGN_APPROVER',
|
||||
unassignedFromFile: 'UNASSIGNED_FROM_FILE',
|
||||
// documentUnderReview: 'DOCUMENT_UNDER_REVIEW',
|
||||
// documentUnderApproval: 'DOCUMENT_UNDER_APPROVAL',
|
||||
documentApproved: 'DOCUMENT_APPROVED',
|
||||
} as const;
|
||||
|
||||
export const DocumentNotificationsTypesValues = Object.values(DocumentNotificationsTypes);
|
||||
|
||||
export const OtherNotificationsTypes = {
|
||||
downloadReady: 'DOWNLOAD_READY',
|
||||
} as const;
|
||||
|
||||
export const OtherNotificationsTypesValues = Object.values(OtherNotificationsTypes);
|
||||
|
||||
export const NotificationGroupsKeys = ['dossier', 'document', 'other'] as const;
|
||||
export const NotificationGroupsValues = [
|
||||
DossierNotificationsTypesValues,
|
||||
DocumentNotificationsTypesValues,
|
||||
OtherNotificationsTypesValues,
|
||||
] as const;
|
||||
@ -1,4 +1,4 @@
|
||||
<form (submit)="save()" [formGroup]="formGroup">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-content">
|
||||
<div *ngFor="let category of notificationCategories">
|
||||
<div class="iqser-input-group header w-full">
|
||||
@ -7,25 +7,25 @@
|
||||
}}</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="options-content" *ngIf="isCategoryActive(category)">
|
||||
<div class="radio-container" *ngIf="category === 'emailNotifications'">
|
||||
<div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">
|
||||
<iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">
|
||||
</iqser-round-checkbox>
|
||||
<span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isCategoryActive(category)" class="options-content">
|
||||
<!-- <div class="radio-container" *ngIf="category === 'emailNotifications'">-->
|
||||
<!-- <div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">-->
|
||||
<!-- <iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">-->
|
||||
<!-- </iqser-round-checkbox>-->
|
||||
<!-- <span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="statement" translate="notifications-screen.options-title"></div>
|
||||
|
||||
<div class="group" *ngFor="let key of notificationGroupsKeys; let i = index">
|
||||
<div class="group-title" [translate]="translations[key]"></div>
|
||||
<div *ngFor="let key of notificationGroupsKeys; let i = index" class="group">
|
||||
<div [translate]="translations[key]" class="group-title"></div>
|
||||
<div class="iqser-input-group">
|
||||
<mat-checkbox
|
||||
*ngFor="let preference of notificationGroupsValues[i]"
|
||||
color="primary"
|
||||
[checked]="isPreferenceChecked(category, preference)"
|
||||
(change)="addRemovePreference($event.checked, category, preference)"
|
||||
*ngFor="let preference of notificationGroupsValues[i]"
|
||||
[checked]="isPreferenceChecked(category, preference)"
|
||||
color="primary"
|
||||
>
|
||||
{{ translations[preference] | translate }}
|
||||
</mat-checkbox>
|
||||
@ -36,7 +36,7 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="formGroup.invalid" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
|
||||
{{ 'user-profile-screen.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -2,10 +2,15 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { notificationsTranslations } from '../../../translations/notifications-translations';
|
||||
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
||||
import { LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { NotificationCategoriesValues, NotificationGroupsKeys, NotificationGroupsValues } from '../constants';
|
||||
import { EmailNotificationScheduleTypesValues } from '@red/domain';
|
||||
import {
|
||||
EmailNotificationScheduleTypesValues,
|
||||
NotificationCategoriesValues,
|
||||
NotificationGroupsKeys,
|
||||
NotificationGroupsValues,
|
||||
} from '@red/domain';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-notifications-screen',
|
||||
@ -13,26 +18,63 @@ import { EmailNotificationScheduleTypesValues } from '@red/domain';
|
||||
styleUrls: ['./notifications-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NotificationsScreenComponent implements OnInit {
|
||||
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {
|
||||
readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues;
|
||||
readonly notificationCategories = NotificationCategoriesValues;
|
||||
readonly notificationGroupsKeys = NotificationGroupsKeys;
|
||||
readonly notificationGroupsValues = NotificationGroupsValues;
|
||||
readonly translations = notificationsTranslations;
|
||||
|
||||
readonly formGroup: FormGroup = this._getForm();
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _notificationPreferencesService: NotificationPreferencesService,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._initializeForm();
|
||||
}
|
||||
|
||||
isCategoryActive(category: string) {
|
||||
return this.form.get(`${category}Enabled`).value;
|
||||
}
|
||||
|
||||
setEmailNotificationType(type: string) {
|
||||
this.form.get('emailNotificationType').setValue(type);
|
||||
}
|
||||
|
||||
getEmailNotificationType() {
|
||||
return this.form.get('emailNotificationType').value;
|
||||
}
|
||||
|
||||
isPreferenceChecked(category: string, preference: string) {
|
||||
return this.form.get(category).value.includes(preference);
|
||||
}
|
||||
|
||||
addRemovePreference(checked: boolean, category: string, preference: string) {
|
||||
const preferences = this.form.get(category).value;
|
||||
if (checked) {
|
||||
preferences.push(preference);
|
||||
} else {
|
||||
const indexOfPreference = preferences.indexOf(preference);
|
||||
preferences.splice(indexOfPreference, 1);
|
||||
}
|
||||
this.form.get(category).setValue(preferences);
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this._notificationPreferencesService.update(this.form.value));
|
||||
} catch (e) {
|
||||
this._toaster.error(_('notifications-screen.error.generic'));
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
inAppNotificationsEnabled: [undefined],
|
||||
@ -43,48 +85,13 @@ export class NotificationsScreenComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
isCategoryActive(category: string) {
|
||||
return this.formGroup.get(`${category}Enabled`).value;
|
||||
}
|
||||
|
||||
setEmailNotificationType(type: string) {
|
||||
this.formGroup.get('emailNotificationType').setValue(type);
|
||||
}
|
||||
|
||||
getEmailNotificationType() {
|
||||
return this.formGroup.get('emailNotificationType').value;
|
||||
}
|
||||
|
||||
isPreferenceChecked(category: string, preference: string) {
|
||||
return this.formGroup.get(category).value.includes(preference);
|
||||
}
|
||||
|
||||
addRemovePreference(checked: boolean, category: string, preference: string) {
|
||||
const preferences = this.formGroup.get(category).value;
|
||||
if (checked) {
|
||||
preferences.push(preference);
|
||||
} else {
|
||||
const indexOfPreference = preferences.indexOf(preference);
|
||||
preferences.splice(indexOfPreference, 1);
|
||||
}
|
||||
this.formGroup.get(category).setValue(preferences);
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await this._notificationPreferencesService.update(this.formGroup.value).toPromise();
|
||||
} catch (e) {
|
||||
this._toaster.error(_('notifications-screen.error.generic'));
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private async _initializeForm() {
|
||||
this._loadingService.start();
|
||||
|
||||
const notificationPreferences = await this._notificationPreferencesService.get().toPromise();
|
||||
this.formGroup.patchValue(notificationPreferences);
|
||||
this.form = this._getForm();
|
||||
const notificationPreferences = await firstValueFrom(this._notificationPreferencesService.get());
|
||||
this.form.patchValue(notificationPreferences);
|
||||
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
|
||||
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
|
||||
|
||||
const routes = [{ path: '', component: NotificationsScreenComponent }];
|
||||
const routes = [{ path: '', component: NotificationsScreenComponent, canDeactivate: [PendingChangesGuard] }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [NotificationsScreenComponent],
|
||||
|
||||
@ -2,13 +2,14 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LoadingService } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, LoadingService } from '@iqser/common-ui';
|
||||
import { IProfile } from '@red/domain';
|
||||
import { languagesTranslations } from '../../../translations/languages-translations';
|
||||
import { PermissionsService } from '../../../../../services/permissions.service';
|
||||
import { UserService } from '../../../../../services/user.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ConfigService } from '../../../../../services/config.service';
|
||||
import { LanguageService } from '../../../../../i18n/language.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-profile-screen',
|
||||
@ -16,8 +17,7 @@ import { LanguageService } from '../../../../../i18n/language.service';
|
||||
styleUrls: ['./user-profile-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class UserProfileScreenComponent implements OnInit {
|
||||
readonly form: FormGroup = this._getForm();
|
||||
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
|
||||
changePasswordUrl: SafeResourceUrl;
|
||||
translations = languagesTranslations;
|
||||
|
||||
@ -30,25 +30,16 @@ export class UserProfileScreenComponent implements OnInit {
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _languageService: LanguageService,
|
||||
private readonly _domSanitizer: DomSanitizer,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
protected readonly _translateService: TranslateService,
|
||||
) {
|
||||
super();
|
||||
this._loadingService.start();
|
||||
|
||||
this.changePasswordUrl = this._domSanitizer.bypassSecurityTrustResourceUrl(
|
||||
`${this._configService.values.OAUTH_URL}/account/password`,
|
||||
);
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
email: [undefined, [Validators.required, Validators.email]],
|
||||
firstName: [undefined],
|
||||
lastName: [undefined],
|
||||
language: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
get languageChanged(): boolean {
|
||||
return this._profileModel['language'] !== this.form.get('language').value;
|
||||
}
|
||||
@ -82,24 +73,34 @@ export class UserProfileScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.profileChanged) {
|
||||
const value = this.form.value as IProfile;
|
||||
const value = this.form.getRawValue() as IProfile;
|
||||
delete value.language;
|
||||
|
||||
await this._userService
|
||||
.updateMyProfile({
|
||||
await firstValueFrom(
|
||||
this._userService.updateMyProfile({
|
||||
...value,
|
||||
})
|
||||
.toPromise();
|
||||
}),
|
||||
);
|
||||
|
||||
await this._userService.loadCurrentUser();
|
||||
await this._userService.loadAll().toPromise();
|
||||
await firstValueFrom(this._userService.loadAll());
|
||||
}
|
||||
|
||||
this._initializeForm();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
email: [undefined, [Validators.required, Validators.email]],
|
||||
firstName: [undefined],
|
||||
lastName: [undefined],
|
||||
language: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
private _initializeForm(): void {
|
||||
try {
|
||||
this.form = this._getForm();
|
||||
this._profileModel = {
|
||||
email: this._userService.currentUser.email,
|
||||
firstName: this._userService.currentUser.firstName,
|
||||
@ -111,6 +112,7 @@ export class UserProfileScreenComponent implements OnInit {
|
||||
this.form.get('email').disable();
|
||||
}
|
||||
this.form.patchValue(this._profileModel, { emitEvent: false });
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
|
||||
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
|
||||
|
||||
const routes = [{ path: '', component: UserProfileScreenComponent }];
|
||||
const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [UserProfileScreenComponent],
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { EmailNotificationScheduleTypes, INotificationPreferences } from '@red/domain';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@ -11,14 +11,6 @@ export class NotificationPreferencesService extends GenericService<INotification
|
||||
super(_injector, 'notification-preferences');
|
||||
}
|
||||
|
||||
get(): Observable<INotificationPreferences> {
|
||||
return super.get<INotificationPreferences>().pipe(catchError(() => of(this._defaultPreferences)));
|
||||
}
|
||||
|
||||
update(notificationPreferences: INotificationPreferences): Observable<void> {
|
||||
return super._post(notificationPreferences);
|
||||
}
|
||||
|
||||
private get _defaultPreferences(): INotificationPreferences {
|
||||
return {
|
||||
emailNotificationType: EmailNotificationScheduleTypes.INSTANT,
|
||||
@ -28,4 +20,12 @@ export class NotificationPreferencesService extends GenericService<INotification
|
||||
inAppNotificationsEnabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
get(): Observable<INotificationPreferences> {
|
||||
return super.get<INotificationPreferences>().pipe(catchError(() => of(this._defaultPreferences)));
|
||||
}
|
||||
|
||||
update(notificationPreferences: INotificationPreferences): Observable<void> {
|
||||
return super._post(notificationPreferences);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import { AuthGuard } from '../auth/auth.guard';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { DossierTemplatesListingScreenComponent } from './screens/dossier-template-listing/dossier-templates-listing-screen.component';
|
||||
import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component';
|
||||
import { DictionaryOverviewScreenComponent } from './screens/dictionary-overview/dictionary-overview-screen.component';
|
||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||
@ -21,6 +20,7 @@ import { DossierAttributesListingScreenComponent } from './screens/dossier-attri
|
||||
import { TrashScreenComponent } from './screens/trash/trash-screen.component';
|
||||
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
||||
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
|
||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
||||
@ -29,15 +29,25 @@ const routes: Routes = [
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: DossierTemplatesListingScreenComponent,
|
||||
component: BaseAdminScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
},
|
||||
loadChildren: () =>
|
||||
import('./screens/dossier-templates-listing/dossier-templates-listing.module').then(
|
||||
m => m.DossierTemplatesListingModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':dossierTemplateId',
|
||||
children: [
|
||||
{
|
||||
path: 'info',
|
||||
canActivate: [CompositeRouteGuard],
|
||||
component: BaseDossierTemplateScreenComponent,
|
||||
loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule),
|
||||
},
|
||||
{
|
||||
path: 'dictionaries',
|
||||
children: [
|
||||
@ -111,11 +121,11 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'justifications',
|
||||
component: BaseAdminScreenComponent,
|
||||
component: BaseDossierTemplateScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
loadChildren: () => import('./screens/justifications/justifications.module').then(m => m.JustificationsModule),
|
||||
},
|
||||
{ path: '', redirectTo: 'dictionaries', pathMatch: 'full' },
|
||||
{ path: '', redirectTo: 'info', pathMatch: 'full' },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -164,6 +174,7 @@ const routes: Routes = [
|
||||
path: 'general-config',
|
||||
component: GeneralConfigScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [PendingChangesGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
requiredRoles: ['RED_ADMIN'],
|
||||
|
||||
@ -28,7 +28,7 @@ export class AdminSideNavComponent implements OnInit {
|
||||
settings: [
|
||||
{
|
||||
screen: 'dossier-templates',
|
||||
label: _('dossier-templates'),
|
||||
label: _('dossier-templates.label'),
|
||||
hideIf: !this.currentUser.isManager && !this.currentUser.isAdmin,
|
||||
},
|
||||
{
|
||||
@ -50,6 +50,7 @@ export class AdminSideNavComponent implements OnInit {
|
||||
},
|
||||
],
|
||||
dossierTemplates: [
|
||||
{ screen: 'info', label: _('dossier-template-info') },
|
||||
{ screen: 'dictionaries', label: _('dictionaries') },
|
||||
{
|
||||
screen: 'rules',
|
||||
|
||||
@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common';
|
||||
import { AdminRoutingModule } from './admin-routing.module';
|
||||
import { RulesScreenComponent } from './screens/rules/rules-screen.component';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { DossierTemplatesListingScreenComponent } from './screens/dossier-template-listing/dossier-templates-listing-screen.component';
|
||||
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
|
||||
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
|
||||
import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component';
|
||||
@ -13,13 +12,12 @@ import { FileAttributesListingScreenComponent } from './screens/file-attributes-
|
||||
import { LicenseInformationScreenComponent } from './screens/license-information/license-information-screen.component';
|
||||
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
|
||||
import { WatermarkScreenComponent } from './screens/watermark/watermark-screen.component';
|
||||
import { AdminBreadcrumbsComponent } from './components/breadcrumbs/admin-breadcrumbs.component';
|
||||
import { DossierTemplateActionsComponent } from './components/dossier-template-actions/dossier-template-actions.component';
|
||||
import { DossierTemplateBreadcrumbsComponent } from './components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
|
||||
import { ColorPickerModule } from 'ngx-color-picker';
|
||||
import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
|
||||
import { AddEditDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component';
|
||||
import { AddEditDictionaryDialogComponent } from './dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
|
||||
import { ConfirmDeleteFileAttributeDialogComponent } from './dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component';
|
||||
import { ConfirmDeleteAttributeDialogComponent } from './dialogs/confirm-delete-attribute-dialog/confirm-delete-attribute-dialog.component';
|
||||
import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component';
|
||||
import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart';
|
||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
@ -49,12 +47,14 @@ import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dia
|
||||
import { GeneralConfigFormComponent } from './screens/general-config/general-config-form/general-config-form.component';
|
||||
import { SmtpFormComponent } from './screens/general-config/smtp-form/smtp-form.component';
|
||||
import { FileAttributesConfigurationsDialogComponent } from './dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component';
|
||||
import { SharedAdminModule } from './shared/shared-admin.module';
|
||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||
|
||||
const dialogs = [
|
||||
AddEditDossierTemplateDialogComponent,
|
||||
AddEditDictionaryDialogComponent,
|
||||
AddEditFileAttributeDialogComponent,
|
||||
ConfirmDeleteFileAttributeDialogComponent,
|
||||
ConfirmDeleteAttributeDialogComponent,
|
||||
EditColorDialogComponent,
|
||||
SmtpAuthDialogComponent,
|
||||
AddEditUserDialogComponent,
|
||||
@ -66,7 +66,6 @@ const dialogs = [
|
||||
];
|
||||
|
||||
const screens = [
|
||||
DossierTemplatesListingScreenComponent,
|
||||
RulesScreenComponent,
|
||||
AuditScreenComponent,
|
||||
DefaultColorsScreenComponent,
|
||||
@ -84,8 +83,7 @@ const screens = [
|
||||
];
|
||||
|
||||
const components = [
|
||||
AdminBreadcrumbsComponent,
|
||||
DossierTemplateActionsComponent,
|
||||
DossierTemplateBreadcrumbsComponent,
|
||||
ComboChartComponent,
|
||||
ComboSeriesVerticalComponent,
|
||||
UsersStatsComponent,
|
||||
@ -94,14 +92,17 @@ const components = [
|
||||
ResetPasswordComponent,
|
||||
UserDetailsComponent,
|
||||
BaseAdminScreenComponent,
|
||||
BaseDossierTemplateScreenComponent,
|
||||
GeneralConfigFormComponent,
|
||||
SmtpFormComponent,
|
||||
|
||||
...dialogs,
|
||||
...screens,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, GeneralConfigFormComponent, SmtpFormComponent],
|
||||
declarations: [...components],
|
||||
providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService],
|
||||
imports: [CommonModule, SharedModule, AdminRoutingModule, NgxChartsModule, ColorPickerModule, MonacoEditorModule],
|
||||
imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, NgxChartsModule, ColorPickerModule, MonacoEditorModule],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -1,26 +1,5 @@
|
||||
<!--TODO: This is only used for justifications for now, should be used for all admin screens-->
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
|
||||
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
|
||||
|
||||
<div class="flex-1 actions">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
|
||||
<iqser-circle-button
|
||||
[routerLink]="['../..']"
|
||||
[tooltip]="'common.close' | translate"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</section>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-base-admin-screen',
|
||||
templateUrl: './base-admin-screen.component.html',
|
||||
styleUrls: ['./base-admin-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
<!--TODO: Use this for all dossier template screens -->
|
||||
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="flex-1 actions">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
|
||||
<iqser-circle-button
|
||||
[routerLink]="['../..']"
|
||||
[tooltip]="'common.close' | translate"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</section>
|
||||
@ -0,0 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
templateUrl: './base-dossier-template-screen.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BaseDossierTemplateScreenComponent {}
|
||||
@ -1,21 +0,0 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-admin-breadcrumbs',
|
||||
templateUrl: './admin-breadcrumbs.component.html',
|
||||
styleUrls: ['./admin-breadcrumbs.component.scss'],
|
||||
})
|
||||
export class AdminBreadcrumbsComponent {
|
||||
@Input() root = false;
|
||||
|
||||
constructor(
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly permissionService: PermissionsService,
|
||||
readonly appStateService: AppStateService,
|
||||
readonly dossierTemplatesService: DossierTemplatesService,
|
||||
) {}
|
||||
}
|
||||
@ -12,7 +12,18 @@ import {
|
||||
|
||||
import { curveLinear } from 'd3-shape';
|
||||
import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
|
||||
import { BaseChartComponent, calculateViewDimensions, ColorHelper, LineSeriesComponent, ViewDimensions } from '@swimlane/ngx-charts';
|
||||
import {
|
||||
BaseChartComponent,
|
||||
calculateViewDimensions,
|
||||
Color,
|
||||
ColorHelper,
|
||||
LegendPosition,
|
||||
LineSeriesComponent,
|
||||
Orientation,
|
||||
ScaleType,
|
||||
ViewDimensions,
|
||||
} from '@swimlane/ngx-charts';
|
||||
import { ILineChartSeries } from './models';
|
||||
|
||||
@Component({
|
||||
// eslint-disable-next-line @angular-eslint/component-selector
|
||||
@ -25,7 +36,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
@Input() curve: any = curveLinear;
|
||||
@Input() legend = false;
|
||||
@Input() legendTitle = 'Legend';
|
||||
@Input() legendPosition = 'right';
|
||||
@Input() legendPosition: LegendPosition = LegendPosition.Right;
|
||||
@Input() xAxis;
|
||||
@Input() yAxis;
|
||||
@Input() showXAxisLabel;
|
||||
@ -38,33 +49,33 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
@Input() gradient: boolean;
|
||||
@Input() showGridLines = true;
|
||||
@Input() activeEntries: any[] = [];
|
||||
@Input() schemeType: string;
|
||||
@Input() schemeType: ScaleType;
|
||||
@Input() xAxisTickFormatting: any;
|
||||
@Input() yAxisTickFormatting: any;
|
||||
@Input() yRightAxisTickFormatting: any;
|
||||
@Input() roundDomains = false;
|
||||
@Input() colorSchemeLine: any;
|
||||
@Input() colorSchemeLine: Color;
|
||||
@Input() autoScale;
|
||||
@Input() lineChart: any;
|
||||
@Input() lineChart: ILineChartSeries[];
|
||||
@Input() yLeftAxisScaleFactor: any;
|
||||
@Input() yRightAxisScaleFactor: any;
|
||||
@Input() rangeFillOpacity: number;
|
||||
@Input() animations = true;
|
||||
@Input() noBarWhenZero = true;
|
||||
|
||||
@Output() activate: EventEmitter<any> = new EventEmitter();
|
||||
@Output() deactivate: EventEmitter<any> = new EventEmitter();
|
||||
@Output() activate = new EventEmitter<{ value; entries: unknown[] }>();
|
||||
@Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>();
|
||||
|
||||
@ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef<any>;
|
||||
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<any>;
|
||||
@ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef<unknown>;
|
||||
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<unknown>;
|
||||
|
||||
@ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent;
|
||||
|
||||
dims: ViewDimensions;
|
||||
xScale: any;
|
||||
yScale: any;
|
||||
xDomain: any;
|
||||
yDomain: any;
|
||||
xDomain: string[] | number[];
|
||||
yDomain: string[] | number[];
|
||||
transform: string;
|
||||
colors: ColorHelper;
|
||||
colorsLine: ColorHelper;
|
||||
@ -72,19 +83,19 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
xAxisHeight = 0;
|
||||
yAxisWidth = 0;
|
||||
legendOptions: any;
|
||||
scaleType = 'linear';
|
||||
scaleType: ScaleType = ScaleType.Linear;
|
||||
xScaleLine;
|
||||
yScaleLine;
|
||||
xDomainLine;
|
||||
yDomainLine;
|
||||
seriesDomain;
|
||||
scaledAxis;
|
||||
combinedSeries;
|
||||
combinedSeries: ILineChartSeries[];
|
||||
xSet;
|
||||
filteredDomain;
|
||||
hoveredVertical;
|
||||
yOrientLeft = 'left';
|
||||
yOrientRight = 'right';
|
||||
yOrientLeft: Orientation = Orientation.Left;
|
||||
yOrientRight: Orientation = Orientation.Right;
|
||||
legendSpacing = 0;
|
||||
bandwidth: number;
|
||||
barPadding = 8;
|
||||
@ -176,15 +187,11 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
return this.combinedSeries.map(d => d.name);
|
||||
}
|
||||
|
||||
isDate(value): boolean {
|
||||
if (value instanceof Date) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
isDate(value): value is Date {
|
||||
return value instanceof Date;
|
||||
}
|
||||
|
||||
getScaleType(values): string {
|
||||
getScaleType(values): ScaleType {
|
||||
let date = true;
|
||||
let num = true;
|
||||
|
||||
@ -199,16 +206,16 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
if (date) {
|
||||
return 'time';
|
||||
return ScaleType.Time;
|
||||
}
|
||||
if (num) {
|
||||
return 'linear';
|
||||
return ScaleType.Linear;
|
||||
}
|
||||
return 'ordinal';
|
||||
return ScaleType.Ordinal;
|
||||
}
|
||||
|
||||
getXDomainLine(): any[] {
|
||||
let values = [];
|
||||
let values: number[] = [];
|
||||
|
||||
for (const results of this.lineChart) {
|
||||
for (const d of results.series) {
|
||||
@ -239,7 +246,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
getYDomainLine(): any[] {
|
||||
const domain = [];
|
||||
const domain: number[] = [];
|
||||
|
||||
for (const results of this.lineChart) {
|
||||
for (const d of results.series) {
|
||||
@ -263,7 +270,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
const max = Math.max(...domain);
|
||||
if (this.yRightAxisScaleFactor) {
|
||||
const minMax = this.yRightAxisScaleFactor(min, max);
|
||||
return [Math.min(0, minMax.min), minMax.max];
|
||||
return [Math.min(0, minMax.min as number), minMax.max];
|
||||
} else {
|
||||
min = Math.min(0, min);
|
||||
return [min, max];
|
||||
@ -317,12 +324,12 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
getYDomain() {
|
||||
const values = this.results.map(d => d.value);
|
||||
const values: number[] = this.results.map(d => d.value);
|
||||
const min = Math.min(0, ...values);
|
||||
const max = Math.max(...values);
|
||||
if (this.yLeftAxisScaleFactor) {
|
||||
const minMax = this.yLeftAxisScaleFactor(min, max);
|
||||
return [Math.min(0, minMax.min), minMax.max];
|
||||
return [Math.min(0, minMax.min as number), minMax.max];
|
||||
} else {
|
||||
return [min, max];
|
||||
}
|
||||
@ -333,7 +340,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
setColors(): void {
|
||||
let domain;
|
||||
let domain: number[] | string[];
|
||||
if (this.schemeType === 'ordinal') {
|
||||
domain = this.xDomain;
|
||||
} else {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { formatLabel } from '@swimlane/ngx-charts';
|
||||
import { Bar, BarOrientation, formatLabel, PlacementTypes, StyleTypes } from '@swimlane/ngx-charts';
|
||||
|
||||
@Component({
|
||||
// eslint-disable-next-line @angular-eslint/component-selector
|
||||
@ -17,7 +17,7 @@ import { formatLabel } from '@swimlane/ngx-charts';
|
||||
[fill]="bar.color"
|
||||
[stops]="bar.gradientStops"
|
||||
[data]="bar.data"
|
||||
[orientation]="'vertical'"
|
||||
[orientation]="orientations.Vertical"
|
||||
[roundEdges]="bar.roundEdges"
|
||||
[gradient]="gradient"
|
||||
[isActive]="isActive(bar.data)"
|
||||
@ -27,8 +27,8 @@ import { formatLabel } from '@swimlane/ngx-charts';
|
||||
(deactivate)="deactivate.emit($event)"
|
||||
ngx-tooltip
|
||||
[tooltipDisabled]="tooltipDisabled"
|
||||
[tooltipPlacement]="0"
|
||||
[tooltipType]="1"
|
||||
[tooltipPlacement]="tooltipPlacements.Top"
|
||||
[tooltipType]="tooltipTypes.tooltip"
|
||||
[tooltipTitle]="bar.tooltipText"
|
||||
></svg:g>
|
||||
`,
|
||||
@ -67,6 +67,9 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bars: any;
|
||||
x: any;
|
||||
y: any;
|
||||
readonly tooltipTypes = StyleTypes;
|
||||
readonly tooltipPlacements = PlacementTypes;
|
||||
readonly orientations = BarOrientation;
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.update();
|
||||
@ -91,7 +94,7 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
const formattedLabel = formatLabel(label);
|
||||
const roundEdges = this.type === 'standard';
|
||||
|
||||
const bar: any = {
|
||||
const bar: Bar = {
|
||||
value,
|
||||
label,
|
||||
roundEdges,
|
||||
@ -101,8 +104,15 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
ariaLabel: label,
|
||||
tooltipText: label,
|
||||
color: undefined,
|
||||
gradientStops: undefined,
|
||||
};
|
||||
|
||||
let offset0 = d0;
|
||||
let offset1 = offset0 + value;
|
||||
|
||||
if (this.type === 'standard') {
|
||||
bar.height = Math.abs(this.yScale(value) - this.yScale(0));
|
||||
bar.x = this.xScale(label);
|
||||
@ -113,18 +123,14 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bar.y = this.yScale(value);
|
||||
}
|
||||
} else if (this.type === 'stacked') {
|
||||
const offset0 = d0;
|
||||
const offset1 = offset0 + value;
|
||||
d0 += value;
|
||||
|
||||
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
||||
bar.x = 0;
|
||||
bar.y = this.yScale(offset1);
|
||||
bar.offset0 = offset0;
|
||||
bar.offset1 = offset1;
|
||||
// bar.offset0 = offset0;
|
||||
// bar.offset1 = offset1;
|
||||
} else if (this.type === 'normalized') {
|
||||
let offset0 = d0;
|
||||
let offset1 = offset0 + value;
|
||||
d0 += value;
|
||||
|
||||
if (total > 0) {
|
||||
@ -138,8 +144,8 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
||||
bar.x = 0;
|
||||
bar.y = this.yScale(offset1);
|
||||
bar.offset0 = offset0;
|
||||
bar.offset1 = offset1;
|
||||
// bar.offset0 = offset0;
|
||||
// bar.offset1 = offset1;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
value = (offset1 - offset0).toFixed(2) + '%';
|
||||
@ -152,8 +158,8 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bar.color = this.colors.getColor(value);
|
||||
bar.gradientStops = this.colors.getLinearGradientStops(value);
|
||||
} else {
|
||||
bar.color = this.colors.getColor(bar.offset1);
|
||||
bar.gradientStops = this.colors.getLinearGradientStops(bar.offset1, bar.offset0);
|
||||
bar.color = this.colors.getColor(offset1);
|
||||
bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
export interface ISeries {
|
||||
name: number;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface ILineChartSeries {
|
||||
name: string;
|
||||
series: ISeries[];
|
||||
}
|
||||
@ -3,13 +3,14 @@
|
||||
*ngIf="root || !!dossierTemplatesService.activeDossierTemplate"
|
||||
[routerLink]="'/main/admin/dossier-templates'"
|
||||
class="breadcrumb"
|
||||
translate="dossier-templates"
|
||||
translate="dossier-templates.label"
|
||||
></a>
|
||||
|
||||
<ng-container *ngIf="dossierTemplatesService.activeDossierTemplate$ | async as activeDossierTemplate">
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
<a [class.active]="!appStateService.activeDictionaryType" [routerLink]="activeDossierTemplate.routerLink" class="breadcrumb ml-0">
|
||||
{{ activeDossierTemplate.name }}
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
|
||||
<ng-container *ngIf="dossierTemplate$ | async as dossierTemplate">
|
||||
<a [class.active]="!appStateService.activeDictionaryType" [routerLink]="dossierTemplate.routerLink" class="breadcrumb ml-0">
|
||||
{{ dossierTemplate.name }}
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="appStateService.activeDictionary">
|
||||
@ -17,7 +18,7 @@
|
||||
<a
|
||||
[routerLink]="
|
||||
'/main/admin/dossier-templates/' +
|
||||
activeDossierTemplate.dossierTemplateId +
|
||||
dossierTemplate.dossierTemplateId +
|
||||
'/dictionaries/' +
|
||||
appStateService.activeDictionaryType
|
||||
"
|
||||
@ -0,0 +1,32 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DossierTemplate } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-template-breadcrumbs',
|
||||
templateUrl: './dossier-template-breadcrumbs.component.html',
|
||||
styleUrls: ['./dossier-template-breadcrumbs.component.scss'],
|
||||
})
|
||||
export class DossierTemplateBreadcrumbsComponent {
|
||||
@Input() root = false;
|
||||
readonly dossierTemplate$: Observable<DossierTemplate>;
|
||||
|
||||
constructor(
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly permissionService: PermissionsService,
|
||||
readonly appStateService: AppStateService,
|
||||
readonly dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.dossierTemplate$ = _route.paramMap.pipe(
|
||||
map(params => params.get('dossierTemplateId')),
|
||||
switchMap((dossierTemplateId: string) => this.dossierTemplatesService.getEntityChanged$(dossierTemplateId)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,3 @@
|
||||
left: 270px;
|
||||
}
|
||||
}
|
||||
|
||||
.mt-44 {
|
||||
margin-top: 44px;
|
||||
}
|
||||
|
||||
@ -95,11 +95,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button>
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dictionary.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -10,10 +10,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mb-14 {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.technical-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Observable } from 'rxjs';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { BaseDialogComponent, shareDistinctLast, Toaster } from '@iqser/common-ui';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { toKebabCase } from '@utils/functions';
|
||||
import { toSnakeCase } from '@utils/functions';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { Dictionary, IDictionary } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -21,7 +21,6 @@ import { HttpStatusCode } from '@angular/common/http';
|
||||
})
|
||||
export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
readonly dictionary = this._data.dictionary;
|
||||
readonly form: FormGroup = this._getForm(this.dictionary);
|
||||
readonly canEditLabel$ = this._canEditLabel$;
|
||||
readonly technicalName$: Observable<string>;
|
||||
readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', {
|
||||
@ -29,7 +28,6 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
name: this._data.dictionary?.label,
|
||||
});
|
||||
readonly hasColor$: Observable<boolean>;
|
||||
readonly disabled = false;
|
||||
private readonly _dossierTemplateId = this._data.dossierTemplateId;
|
||||
|
||||
constructor(
|
||||
@ -39,37 +37,18 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string },
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm(this.dictionary);
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this.hasColor$ = this._colorEmpty$;
|
||||
this.technicalName$ = this.form.get('label').valueChanges.pipe(map(value => this._toTechnicalName(value)));
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.dictionary) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
if (key === 'caseSensitive') {
|
||||
if (this.getDictCaseSensitive(this.dictionary) !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.dictionary[key] !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private get _canEditLabel$() {
|
||||
return this.userService.currentUser$.pipe(
|
||||
map(user => user.isAdmin || !this._data.dictionary),
|
||||
@ -98,8 +77,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
observable = this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId });
|
||||
}
|
||||
|
||||
return observable
|
||||
.toPromise()
|
||||
return firstValueFrom(observable)
|
||||
.then(() => this._dialogRef.close(true))
|
||||
.catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
@ -126,11 +104,11 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
|
||||
private _toTechnicalName(value: string) {
|
||||
const existingTechnicalNames = Object.keys(this._appStateService.dictionaryData[this._dossierTemplateId]);
|
||||
const baseTechnicalName = toKebabCase(value.trim());
|
||||
const baseTechnicalName = toSnakeCase(value.trim());
|
||||
let technicalName = baseTechnicalName;
|
||||
let suffix = 1;
|
||||
while (existingTechnicalNames.includes(technicalName)) {
|
||||
technicalName = [baseTechnicalName, suffix++].join('-');
|
||||
technicalName = [baseTechnicalName, suffix++].join('_');
|
||||
}
|
||||
return technicalName;
|
||||
}
|
||||
|
||||
@ -35,11 +35,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="form.invalid || !changed" color="primary" mat-flat-button>
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dossier-attribute.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component, HostListener, Inject, OnDestroy } from '@angular/core';
|
||||
import { Component, HostListener, Inject, Injector, OnDestroy } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AutoUnsubscribe, IqserEventTarget, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, IqserEventTarget, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||
import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations';
|
||||
@ -12,9 +12,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
||||
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
|
||||
})
|
||||
export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy {
|
||||
dossierAttribute: IDossierAttributeConfig = this.data.dossierAttribute;
|
||||
readonly form: FormGroup = this._getForm(this.dossierAttribute);
|
||||
readonly translations = dossierAttributeTypesTranslations;
|
||||
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
|
||||
|
||||
@ -23,11 +22,14 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierAttributesService: DossierAttributesService,
|
||||
private readonly _toaster: Toaster,
|
||||
readonly dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: { readonly dossierAttribute: IDossierAttributeConfig },
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm(this.dossierAttribute);
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
@ -55,7 +57,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
|
||||
|
||||
this._dossierAttributesService.createOrUpdate(attribute).subscribe(
|
||||
() => {
|
||||
this.dialogRef.close(true);
|
||||
this._dialogRef.close(true);
|
||||
},
|
||||
(error: HttpErrorResponse) => {
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -33,16 +33,11 @@
|
||||
|
||||
<div class="validity">
|
||||
<div>
|
||||
<mat-checkbox
|
||||
(change)="hasValidFrom = !hasValidFrom"
|
||||
[checked]="hasValidFrom"
|
||||
class="filter-menu-checkbox"
|
||||
color="primary"
|
||||
>
|
||||
<mat-checkbox (change)="toggleHasValid('from')" [checked]="hasValidFrom" class="filter-menu-checkbox" color="primary">
|
||||
{{ 'add-edit-dossier-template.form.valid-from' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-checkbox (change)="hasValidTo = !hasValidTo" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
|
||||
<mat-checkbox (change)="toggleHasValid('to')" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
|
||||
{{ 'add-edit-dossier-template.form.valid-to' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
@ -87,11 +82,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button>
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dossier-template.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
||||
import { downloadTypesTranslations } from '../../../../translations/download-types-translations';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DownloadFileType, IDossierTemplate } from '@red/domain';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
templateUrl: './add-edit-dossier-template-dialog.component.html',
|
||||
styleUrls: ['./add-edit-dossier-template-dialog.component.scss'],
|
||||
})
|
||||
export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
readonly form: FormGroup = this._getForm();
|
||||
hasValidFrom: boolean;
|
||||
hasValidTo: boolean;
|
||||
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
|
||||
@ -25,79 +25,64 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
key: type,
|
||||
label: downloadTypesTranslations[type],
|
||||
}));
|
||||
readonly disabled = false;
|
||||
private _previousValidFrom: Moment;
|
||||
private _previousValidTo: Moment;
|
||||
private _lastValidFrom: Moment;
|
||||
private _lastValidTo: Moment;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
public dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
||||
private readonly _loadingService: LoadingService,
|
||||
@Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate,
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this.hasValidFrom = !!this.dossierTemplate?.validFrom;
|
||||
this.hasValidTo = !!this.dossierTemplate?.validTo;
|
||||
|
||||
this._previousValidFrom = this.form.get('validFrom').value;
|
||||
this._previousValidTo = this.form.get('validTo').value;
|
||||
this._previousValidFrom = this._lastValidFrom = this.form.get('validFrom').value;
|
||||
this._previousValidTo = this._lastValidTo = this.form.get('validTo').value;
|
||||
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.addSubscription = this.form.valueChanges.subscribe(value => {
|
||||
this._applyValidityIntervalConstraints(value);
|
||||
});
|
||||
|
||||
this.addSubscription = this.form.controls['validFrom'].valueChanges.subscribe(value => {
|
||||
this._lastValidFrom = value ? value : this._lastValidFrom;
|
||||
});
|
||||
this.addSubscription = this.form.controls['validTo'].valueChanges.subscribe(value => {
|
||||
this._lastValidFrom = value ? value : this._lastValidFrom;
|
||||
});
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.dossierTemplate) {
|
||||
return true;
|
||||
toggleHasValid(extremity: string) {
|
||||
if (extremity === 'from') {
|
||||
this.hasValidFrom = !this.hasValidFrom;
|
||||
this.form.controls['validFrom'].setValue(this.hasValidFrom ? this._lastValidFrom : null);
|
||||
} else {
|
||||
this.hasValidTo = !this.hasValidTo;
|
||||
this.form.controls['validTo'].setValue(this.hasValidTo ? this._lastValidTo : null);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
const formValue = this.form.get(key).value;
|
||||
const objectValue = this.dossierTemplate[key];
|
||||
if (key === 'validFrom') {
|
||||
if (this.hasValidFrom !== !!objectValue || (this.hasValidFrom && !moment(objectValue).isSame(moment(formValue)))) {
|
||||
return true;
|
||||
}
|
||||
} else if (key === 'validTo') {
|
||||
if (this.hasValidTo !== !!objectValue || (this.hasValidTo && !moment(objectValue).isSame(moment(formValue)))) {
|
||||
return true;
|
||||
}
|
||||
} else if (formValue instanceof Array) {
|
||||
if (objectValue.length !== formValue.length) {
|
||||
return true;
|
||||
}
|
||||
for (const item of objectValue) {
|
||||
if (!formValue.includes(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (objectValue !== formValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
const dossierTemplate = {
|
||||
dossierTemplateId: this.dossierTemplate?.dossierTemplateId,
|
||||
...this.form.getRawValue(),
|
||||
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
||||
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
||||
};
|
||||
} as IDossierTemplate;
|
||||
await this._dossierTemplatesService.createOrUpdate(dossierTemplate).toPromise();
|
||||
await this._dossierTemplatesService.loadAll().toPromise();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this.dialogRef.close(true);
|
||||
this._dialogRef.close(true);
|
||||
} catch (error: any) {
|
||||
const message =
|
||||
error.status === HttpStatusCode.Conflict
|
||||
@ -105,6 +90,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
: _('add-edit-dossier-template.error.generic');
|
||||
this._toaster.error(message, { error });
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
@ -134,7 +120,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
}
|
||||
|
||||
private _requiredIfValidator(predicate) {
|
||||
return formControl => {
|
||||
return (formControl: AbstractControl) => {
|
||||
if (!formControl.parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -84,11 +84,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button>
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-file-attribute.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent } from '../../../../../../../../libs/common-ui/src';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-file-attribute-dialog',
|
||||
@ -13,12 +13,10 @@ import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
readonly disabled = false;
|
||||
DISPLAYED_FILTERABLE_LIMIT = 3;
|
||||
translations = fileAttributeTypesTranslations;
|
||||
fileAttribute: IFileAttributeConfig = this.data.fileAttribute;
|
||||
dossierTemplateId: string = this.data.dossierTemplateId;
|
||||
readonly form!: FormGroup;
|
||||
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
|
||||
readonly canSetDisplayed!: boolean;
|
||||
readonly canSetFilterable!: boolean;
|
||||
@ -26,7 +24,8 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
public dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: {
|
||||
fileAttribute: IFileAttributeConfig;
|
||||
@ -35,32 +34,11 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
numberOfFilterableAttrs: number;
|
||||
},
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList;
|
||||
this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable;
|
||||
this.form = this._getForm(this.fileAttribute);
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.fileAttribute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
if (key === 'readonly') {
|
||||
if (this.fileAttribute.editable === this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.fileAttribute[key] !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
save() {
|
||||
@ -69,7 +47,7 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
editable: !this.form.get('readonly').value,
|
||||
...this.form.getRawValue(),
|
||||
};
|
||||
this.dialogRef.close(fileAttribute);
|
||||
this._dialogRef.close(fileAttribute);
|
||||
}
|
||||
|
||||
private _getForm(fileAttribute: IFileAttributeConfig): FormGroup {
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
<section class="dialog">
|
||||
<redaction-user-details
|
||||
(closeDialog)="dialogRef.close($event)"
|
||||
(closeDialog)="closeDialog($event)"
|
||||
(cancel)="close()"
|
||||
(toggleResetPassword)="toggleResetPassword()"
|
||||
*ngIf="!resettingPassword"
|
||||
[hidden]="resettingPassword"
|
||||
[user]="user"
|
||||
></redaction-user-details>
|
||||
|
||||
<redaction-reset-password
|
||||
(close)="dialogRef.close($event)"
|
||||
(close)="closeDialog($event)"
|
||||
(toggleResetPassword)="toggleResetPassword()"
|
||||
*ngIf="resettingPassword"
|
||||
[hidden]="!resettingPassword"
|
||||
[user]="user"
|
||||
></redaction-reset-password>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,18 +1,44 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { User } from '@red/domain';
|
||||
import { UserDetailsComponent } from './user-details/user-details.component';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-user-dialog',
|
||||
templateUrl: './add-edit-user-dialog.component.html',
|
||||
styleUrls: ['./add-edit-user-dialog.component.scss'],
|
||||
})
|
||||
export class AddEditUserDialogComponent {
|
||||
export class AddEditUserDialogComponent extends BaseDialogComponent {
|
||||
@ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent;
|
||||
|
||||
resettingPassword = false;
|
||||
|
||||
constructor(readonly dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) readonly user: User) {}
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditUserDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly user: User,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
}
|
||||
|
||||
toggleResetPassword() {
|
||||
this.resettingPassword = !this.resettingPassword;
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
await this._userDetailsComponent.save();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
return this._userDetailsComponent.changed;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this._userDetailsComponent.valid;
|
||||
}
|
||||
|
||||
closeDialog(event) {
|
||||
this._dialogRef.close(event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { UserService } from '@services/user.service';
|
||||
import { LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { User } from '@red/domain';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-reset-password',
|
||||
@ -25,15 +26,15 @@ export class ResetPasswordComponent {
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await this._userService
|
||||
.resetPassword(
|
||||
await firstValueFrom(
|
||||
this._userService.resetPassword(
|
||||
{
|
||||
password: this.form.get('temporaryPassword').value,
|
||||
temporary: true,
|
||||
},
|
||||
this.user.id,
|
||||
)
|
||||
.toPromise();
|
||||
),
|
||||
);
|
||||
this.toggleResetPassword.emit();
|
||||
} catch (error) {
|
||||
this._toaster.error(_('reset-password-dialog.error.password-policy'));
|
||||
|
||||
@ -53,6 +53,6 @@
|
||||
icon="iqser:trash"
|
||||
></iqser-icon-button>
|
||||
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="add-edit-user.actions.cancel"></div>
|
||||
<div class="all-caps-label cancel" translate="add-edit-user.actions.cancel" (click)="cancel.emit()"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { rolesTranslations } from '../../../../../translations/roles-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { User } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-details',
|
||||
templateUrl: './user-details.component.html',
|
||||
styleUrls: ['./user-details.component.scss'],
|
||||
})
|
||||
export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, OnDestroy {
|
||||
export class UserDetailsComponent extends BaseFormComponent implements OnChanges, OnDestroy {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
@Input() user: User;
|
||||
@Output() readonly toggleResetPassword = new EventEmitter();
|
||||
@Output() readonly closeDialog = new EventEmitter();
|
||||
@Output() readonly cancel = new EventEmitter();
|
||||
|
||||
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
|
||||
readonly translations = rolesTranslations;
|
||||
form: FormGroup;
|
||||
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
|
||||
|
||||
constructor(
|
||||
@ -35,29 +36,6 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
||||
super();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.user.roles.length !== this.activeRoles.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
const keyValue = this.form.get(key).value;
|
||||
if (key.startsWith('RED_')) {
|
||||
if (this.user.roles.includes(key) !== keyValue) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.user[key] !== keyValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get activeRoles(): string[] {
|
||||
return this.ROLES.reduce((acc, role) => {
|
||||
if (this.form.get(role).value) {
|
||||
@ -85,6 +63,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
||||
ngOnChanges() {
|
||||
this.form = this._getForm();
|
||||
this._setRolesRequirements();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
shouldBeDisabled(role: string): boolean {
|
||||
@ -107,9 +86,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
||||
const userData = { ...this.form.getRawValue(), roles: this.activeRoles };
|
||||
|
||||
if (!this.user) {
|
||||
await this.userService
|
||||
.create(userData)
|
||||
.toPromise()
|
||||
await firstValueFrom(this.userService.create(userData))
|
||||
.then(() => {
|
||||
this.closeDialog.emit(true);
|
||||
})
|
||||
@ -122,7 +99,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
||||
this._loadingService.stop();
|
||||
});
|
||||
} else {
|
||||
await this.userService.updateProfile(userData, this.user.id).toPromise();
|
||||
await firstValueFrom(this.userService.updateProfile(userData, this.user.id));
|
||||
this.closeDialog.emit(true);
|
||||
}
|
||||
}
|
||||
@ -140,7 +117,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
||||
email: [
|
||||
{
|
||||
value: this.user?.email,
|
||||
disabled: !!this.user,
|
||||
disabled: !!this.user?.email,
|
||||
},
|
||||
[Validators.required, Validators.email],
|
||||
],
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
<section class="dialog">
|
||||
<div class="dialog-header heading-l">
|
||||
{{
|
||||
'confirm-delete-file-attribute.title'
|
||||
| translate
|
||||
: {
|
||||
type: type,
|
||||
name: fileAttribute?.label
|
||||
}
|
||||
}}
|
||||
{{ 'confirm-delete-file-attribute.title' | translate: translateArgs }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="showToast" class="inline-dialog-toast toast-error">
|
||||
@ -20,14 +13,11 @@
|
||||
<div class="dialog-content">
|
||||
<div class="heading" translate="confirm-delete-file-attribute.warning"></div>
|
||||
|
||||
<mat-checkbox
|
||||
*ngFor="let checkbox of checkboxes; let idx = index"
|
||||
[(ngModel)]="checkbox.value"
|
||||
[class.error]="!checkbox.value && showToast"
|
||||
color="primary"
|
||||
>
|
||||
{{ checkbox.label | translate: { type: type } }}
|
||||
</mat-checkbox>
|
||||
<ng-container *ngFor="let checkbox of checkboxes; let idx = index">
|
||||
<mat-checkbox [(ngModel)]="checkbox.value" [class.error]="!checkbox.value && showToast" color="primary">
|
||||
{{ checkbox.label }}
|
||||
</mat-checkbox>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
@ -35,7 +25,7 @@
|
||||
{{ 'confirm-delete-file-attribute.delete' | translate: { type: type } }}
|
||||
</button>
|
||||
<div
|
||||
(click)="cancel()"
|
||||
(click)="this.dialogRef.close()"
|
||||
[translateParams]="{ type: type }"
|
||||
[translate]="'confirm-delete-file-attribute.cancel'"
|
||||
class="all-caps-label cancel"
|
||||
@ -0,0 +1,87 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { DossierAttributeConfig, FileAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
const isFileAttributeConfig = (value: DossierAttributeConfig | FileAttributeConfig): value is FileAttributeConfig =>
|
||||
value instanceof FileAttributeConfig;
|
||||
|
||||
interface CheckBox {
|
||||
value: boolean;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface DialogData {
|
||||
attribute: FileAttributeConfig | DossierAttributeConfig;
|
||||
count: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-confirm-delete-attribute-dialog',
|
||||
templateUrl: './confirm-delete-attribute-dialog.component.html',
|
||||
styleUrls: ['./confirm-delete-attribute-dialog.component.scss'],
|
||||
})
|
||||
export class ConfirmDeleteAttributeDialogComponent {
|
||||
checkboxes: CheckBox[];
|
||||
showToast = false;
|
||||
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
readonly dialogRef: MatDialogRef<ConfirmDeleteAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData,
|
||||
) {
|
||||
this.checkboxes = this.checkBoxConfig;
|
||||
}
|
||||
|
||||
get checkBoxConfig(): CheckBox[] {
|
||||
const checkBoxes = isFileAttributeConfig(this.data.attribute) ? this._fileAttributeCheckboxes : this._dossierAttributeCheckboxes;
|
||||
if (this.data.count !== 0) {
|
||||
checkBoxes.push({
|
||||
value: false,
|
||||
label: this._translateService.instant('confirm-delete-file-attribute.impacted-report', { count: this.data.count }),
|
||||
});
|
||||
}
|
||||
|
||||
return checkBoxes;
|
||||
}
|
||||
|
||||
get valid() {
|
||||
return this.checkboxes.reduce((acc, currentValue) => acc && currentValue.value, true);
|
||||
}
|
||||
|
||||
get type(): 'bulk' | 'single' {
|
||||
return this.data.attribute ? 'single' : 'bulk';
|
||||
}
|
||||
|
||||
get translateArgs() {
|
||||
return {
|
||||
type: this.type,
|
||||
name: this.data.attribute?.label,
|
||||
};
|
||||
}
|
||||
|
||||
private get _fileAttributeCheckboxes(): CheckBox[] {
|
||||
return [
|
||||
{
|
||||
value: false,
|
||||
label: this._translateService.instant('confirm-delete-file-attribute.file-impacted-documents', { type: this.type }),
|
||||
},
|
||||
{ value: false, label: this._translateService.instant('confirm-delete-file-attribute.file-lost-details') },
|
||||
];
|
||||
}
|
||||
|
||||
private get _dossierAttributeCheckboxes(): CheckBox[] {
|
||||
return [
|
||||
{ value: false, label: this._translateService.instant('confirm-delete-file-attribute.dossier-impacted-documents') },
|
||||
{ value: false, label: this._translateService.instant('confirm-delete-file-attribute.dossier-lost-details') },
|
||||
];
|
||||
}
|
||||
|
||||
deleteFileAttribute() {
|
||||
if (this.valid) {
|
||||
this.dialogRef.close(true);
|
||||
} else {
|
||||
this.showToast = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { IFileAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-confirm-delete-file-attribute-dialog',
|
||||
templateUrl: './confirm-delete-file-attribute-dialog.component.html',
|
||||
styleUrls: ['./confirm-delete-file-attribute-dialog.component.scss'],
|
||||
})
|
||||
export class ConfirmDeleteFileAttributeDialogComponent {
|
||||
fileAttribute: IFileAttributeConfig;
|
||||
checkboxes = [
|
||||
{ value: false, label: _('confirm-delete-file-attribute.impacted-documents') },
|
||||
{ value: false, label: _('confirm-delete-file-attribute.lost-details') },
|
||||
];
|
||||
showToast = false;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ConfirmDeleteFileAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: IFileAttributeConfig,
|
||||
) {
|
||||
this.fileAttribute = data;
|
||||
}
|
||||
|
||||
get valid() {
|
||||
return this.checkboxes[0].value && this.checkboxes[1].value;
|
||||
}
|
||||
|
||||
get type(): 'bulk' | 'single' {
|
||||
return this.fileAttribute ? 'single' : 'bulk';
|
||||
}
|
||||
|
||||
deleteFileAttribute() {
|
||||
if (this.valid) {
|
||||
this.dialogRef.close(true);
|
||||
} else {
|
||||
this.showToast = true;
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { List, LoadingService } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-confirm-delete-users-dialog',
|
||||
@ -40,7 +41,7 @@ export class ConfirmDeleteUsersDialogComponent {
|
||||
async deleteUser() {
|
||||
if (this.valid) {
|
||||
this._loadingService.start();
|
||||
await this._userService.delete(this.userIds).toPromise();
|
||||
await firstValueFrom(this._userService.delete(this.userIds));
|
||||
this.dialogRef.close(true);
|
||||
} else {
|
||||
this.showToast = true;
|
||||
|
||||
@ -28,11 +28,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button>
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'edit-color-dialog.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DefaultColorType, IColors } from '@red/domain';
|
||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||
@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { defaultColorsTranslations } from '../../translations/default-colors-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
interface IEditColorData {
|
||||
colors: IColors;
|
||||
@ -19,10 +20,7 @@ interface IEditColorData {
|
||||
styleUrls: ['./edit-color-dialog.component.scss'],
|
||||
})
|
||||
export class EditColorDialogComponent extends BaseDialogComponent {
|
||||
readonly form: FormGroup;
|
||||
translations = defaultColorsTranslations;
|
||||
readonly disabled = false;
|
||||
private readonly _initialColor: string;
|
||||
private readonly _dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
@ -30,23 +28,16 @@ export class EditColorDialogComponent extends BaseDialogComponent {
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: IEditColorData,
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this._dossierTemplateId = data.dossierTemplateId;
|
||||
this._initialColor = data.colors[data.colorKey];
|
||||
|
||||
this.form = this._getForm();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
return this.form.get('color').value !== this._initialColor;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
async save() {
|
||||
@ -56,7 +47,7 @@ export class EditColorDialogComponent extends BaseDialogComponent {
|
||||
};
|
||||
|
||||
try {
|
||||
await this._dictionaryService.setColors(colors, this._dossierTemplateId).toPromise();
|
||||
await firstValueFrom(this._dictionaryService.setColors(colors, this._dossierTemplateId));
|
||||
this._dialogRef.close(true);
|
||||
const color = this._translateService.instant(defaultColorsTranslations[this.data.colorKey]);
|
||||
this._toaster.info(_('edit-color-dialog.success'), { params: { color: color } });
|
||||
|
||||
@ -2,9 +2,9 @@ import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/c
|
||||
import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import * as Papa from 'papaparse';
|
||||
import { Observable } from 'rxjs';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { DefaultListingServices, ListingComponent, TableColumnConfig, Toaster, trackBy } from '@iqser/common-ui';
|
||||
import { DefaultListingServices, ListingComponent, TableColumnConfig, Toaster, trackByFactory } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FileAttributeConfig, FileAttributeConfigTypes, IField, IFileAttributesConfig } from '@red/domain';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
@ -34,7 +34,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
|
||||
columnSample = [];
|
||||
initialParseConfig: { delimiter?: string; encoding?: string } = {};
|
||||
readonly tableHeaderLabel = '';
|
||||
readonly trackBy = trackBy();
|
||||
readonly trackBy = trackByFactory();
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
@ -56,14 +56,6 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
|
||||
);
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]],
|
||||
delimiter: [undefined, Validators.required],
|
||||
encoding: ['UTF-8', Validators.required],
|
||||
});
|
||||
}
|
||||
|
||||
readFile() {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', event => {
|
||||
@ -183,7 +175,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
|
||||
};
|
||||
|
||||
try {
|
||||
await this._fileAttributesService.setFileAttributeConfig(fileAttributes, this.data.dossierTemplateId).toPromise();
|
||||
await firstValueFrom(this._fileAttributesService.setFileAttributeConfig(fileAttributes, this.data.dossierTemplateId));
|
||||
this._toaster.success(_('file-attributes-csv-import.save.success'), { params: { count: this.activeFields.length } });
|
||||
} catch (e) {
|
||||
this._toaster.error(_('file-attributes-csv-import.save.error'));
|
||||
@ -207,6 +199,14 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]],
|
||||
delimiter: [undefined, Validators.required],
|
||||
encoding: ['UTF-8', Validators.required],
|
||||
});
|
||||
}
|
||||
|
||||
private _autocompleteStringValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): { [key: string]: any } | null => {
|
||||
if ((this.parseResult?.meta?.fields || []).indexOf(control.value) !== -1) {
|
||||
|
||||
@ -27,5 +27,5 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,23 +1,27 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ISmtpConfiguration } from '@red/domain';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-smtp-auth-dialog',
|
||||
templateUrl: './smtp-auth-dialog.component.html',
|
||||
styleUrls: ['./smtp-auth-dialog.component.scss'],
|
||||
})
|
||||
export class SmtpAuthDialogComponent {
|
||||
readonly form: FormGroup = this._getForm();
|
||||
|
||||
export class SmtpAuthDialogComponent extends BaseDialogComponent {
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _userService: UserService,
|
||||
public dialogRef: MatDialogRef<SmtpAuthDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<SmtpAuthDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: ISmtpConfiguration,
|
||||
) {}
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
@ -27,6 +31,6 @@ export class SmtpAuthDialogComponent {
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dialogRef.close(this.form.getRawValue());
|
||||
this._dialogRef.close(this.form.getRawValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,6 @@
|
||||
*ngIf="form.get('userId').value !== ALL_USERS"
|
||||
[user]="form.get('userId').value"
|
||||
[withName]="true"
|
||||
size="small"
|
||||
></redaction-initials-avatar>
|
||||
<div *ngIf="form.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-select-trigger>
|
||||
@ -69,7 +68,6 @@
|
||||
*ngIf="userId !== ALL_USERS"
|
||||
[user]="userId"
|
||||
[withName]="true"
|
||||
size="small"
|
||||
></redaction-initials-avatar>
|
||||
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-option>
|
||||
@ -109,7 +107,7 @@
|
||||
</div>
|
||||
|
||||
<div class="user-column cell">
|
||||
<redaction-initials-avatar [user]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
|
||||
<redaction-initials-avatar [user]="log.userId" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div [translate]="translations[log.category]" class="cell"></div>
|
||||
|
||||
@ -17,11 +17,3 @@ form {
|
||||
font-size: 16px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.mr-0 {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.mr-20 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { Audit, IAudit, IAuditResponse, IAuditSearchRequest } from '@red/domain';
|
||||
import { AuditService } from '../../services/audit.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
@ -52,15 +53,6 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
|
||||
});
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
category: [this.ALL_CATEGORIES],
|
||||
userId: [this.ALL_USERS],
|
||||
from: [],
|
||||
to: [],
|
||||
});
|
||||
}
|
||||
|
||||
get totalPages(): number {
|
||||
if (!this.logs) {
|
||||
return 0;
|
||||
@ -76,6 +68,15 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
|
||||
await this._fetchData();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
category: [this.ALL_CATEGORIES],
|
||||
userId: [this.ALL_USERS],
|
||||
from: [],
|
||||
to: [],
|
||||
});
|
||||
}
|
||||
|
||||
private _updateDateFilters(value): boolean {
|
||||
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.form, 'from', 'to')) {
|
||||
return true;
|
||||
@ -105,8 +106,8 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
|
||||
to,
|
||||
};
|
||||
|
||||
promises.push(this._auditService.getCategories().toPromise());
|
||||
promises.push(this._auditService.searchAuditLog(logsRequestBody).toPromise());
|
||||
promises.push(firstValueFrom(this._auditService.getCategories()));
|
||||
promises.push(firstValueFrom(this._auditService.searchAuditLog(logsRequestBody)));
|
||||
|
||||
const data = await Promise.all(promises);
|
||||
this.categories = data[0].map(c => c.category);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
|
||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="flex-1 actions">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
|
||||
@ -15,6 +15,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
interface ListItem extends IListable {
|
||||
readonly key: string;
|
||||
@ -72,7 +73,7 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
||||
|
||||
private async _loadColors() {
|
||||
this._loadingService.start();
|
||||
const data = await this._appStateService.loadColors(this._dossierTemplatesService.activeDossierTemplateId).toPromise();
|
||||
const data = await firstValueFrom(this._appStateService.loadColors(this._dossierTemplatesService.activeDossierTemplateId));
|
||||
this._colorsObj = data;
|
||||
const entities = Object.keys(data)
|
||||
.map(key => ({
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
|
||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="flex-1 actions">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
@ -78,51 +78,53 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tableItemTemplate let-entity="entity">
|
||||
<div *ngIf="cast(entity) as dict">
|
||||
<div class="cell">
|
||||
<div [ngStyle]="{ 'background-color': dict.hexColor }" class="color-square"></div>
|
||||
<div class="dict-name">
|
||||
<div class="table-item-title heading">
|
||||
{{ dict.label }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ dict.entries?.length }}
|
||||
<ng-container *ngIf="templateStats$ | async as templateStats">
|
||||
<ng-template #tableItemTemplate let-entity="entity">
|
||||
<div *ngIf="cast(entity) as dict">
|
||||
<div class="cell">
|
||||
<div [ngStyle]="{ 'background-color': dict.hexColor }" class="color-square"></div>
|
||||
<div class="dict-name">
|
||||
<div class="table-item-title heading">
|
||||
{{ dict.label }}
|
||||
</div>
|
||||
<div *ngIf="!dict.caseInsensitive">
|
||||
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
|
||||
{{ 'dictionary-listing.case-sensitive' | translate }}
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ templateStats.dictionarySummary(dict.type)?.entriesCount || 0 }}
|
||||
</div>
|
||||
<div *ngIf="!dict.caseInsensitive">
|
||||
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
|
||||
{{ 'dictionary-listing.case-sensitive' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell center small-label">
|
||||
{{ dict.rank }}
|
||||
</div>
|
||||
<div class="cell center small-label">
|
||||
{{ dict.rank }}
|
||||
</div>
|
||||
|
||||
<div class="cell center">
|
||||
<redaction-annotation-icon [dictionary]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
|
||||
</div>
|
||||
<div class="cell center">
|
||||
<redaction-annotation-icon [dictionary]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openDeleteDictionariesDialog($event, [dict])"
|
||||
[tooltip]="'dictionary-listing.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
<div class="cell">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openDeleteDictionariesDialog($event, [dict])"
|
||||
[tooltip]="'dictionary-listing.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditDictionaryDialog($event, dict)"
|
||||
[tooltip]="'dictionary-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditDictionaryDialog($event, dict)"
|
||||
[tooltip]="'dictionary-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { catchError, defaultIfEmpty, tap } from 'rxjs/operators';
|
||||
import { forkJoin, of } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
@ -17,14 +15,11 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { Dictionary } from '@red/domain';
|
||||
|
||||
const toChartConfig = (dict: Dictionary): DoughnutChartConfig => ({
|
||||
value: dict.entries?.length ?? 0,
|
||||
color: dict.hexColor,
|
||||
label: dict.label,
|
||||
key: dict.type,
|
||||
});
|
||||
import { Dictionary, DossierTemplateStats } from '@red/domain';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dictionary-listing-screen.component.html',
|
||||
@ -42,6 +37,8 @@ export class DictionaryListingScreenComponent extends ListingComponent<Dictionar
|
||||
{ label: _('dictionary-listing.table-col-names.hint-redaction'), class: 'flex-center' },
|
||||
];
|
||||
chartData: DoughnutChartConfig[] = [];
|
||||
readonly templateStats$: Observable<DossierTemplateStats>;
|
||||
templateStats: DossierTemplateStats;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
@ -52,27 +49,30 @@ export class DictionaryListingScreenComponent extends ListingComponent<Dictionar
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
_loadingService.start();
|
||||
const dossierTemplateId = _route.snapshot.paramMap.get('dossierTemplateId');
|
||||
this.templateStats = this._dossierTemplateStatsService.get(dossierTemplateId);
|
||||
this.templateStats$ = this._dossierTemplateStatsService.watch$(dossierTemplateId).pipe(tap(stats => (this.templateStats = stats)));
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._loadDictionaryData();
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._loadDictionaryData(false);
|
||||
}
|
||||
|
||||
openDeleteDictionariesDialog($event?: MouseEvent, types = this.listingService.selected) {
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
this._loadingService.start();
|
||||
await this._dictionaryService
|
||||
.deleteDictionaries(
|
||||
await firstValueFrom(
|
||||
this._dictionaryService.deleteDictionaries(
|
||||
types.map(t => t.type),
|
||||
this._dossierTemplatesService.activeDossierTemplateId,
|
||||
)
|
||||
.toPromise();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this._loadDictionaryData(false);
|
||||
this._calculateData();
|
||||
),
|
||||
);
|
||||
await this._loadDictionaryData();
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
@ -87,50 +87,31 @@ export class DictionaryListingScreenComponent extends ListingComponent<Dictionar
|
||||
},
|
||||
async () => {
|
||||
this._loadingService.start();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this._loadDictionaryData(false);
|
||||
this._calculateData();
|
||||
await this._loadDictionaryData();
|
||||
this._loadingService.stop();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private _loadDictionaryData(loadEntries = true): void {
|
||||
private async _loadDictionaryData(refresh = true): Promise<void> {
|
||||
if (refresh) {
|
||||
await this._appStateService.loadDictionaryData();
|
||||
}
|
||||
|
||||
const appStateDictionaryData = this._appStateService.dictionaryData[this._dossierTemplatesService.activeDossierTemplateId];
|
||||
const entities = Object.values(appStateDictionaryData).filter(d => !d.virtual);
|
||||
this.entitiesService.setEntities(entities);
|
||||
|
||||
if (!loadEntries) {
|
||||
this.entitiesService.setEntities(
|
||||
entities.map(dict => {
|
||||
dict.entries = this.allEntities.find(d => d.type === dict.type)?.entries || [];
|
||||
return dict;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.entitiesService.setEntities(entities);
|
||||
}
|
||||
|
||||
if (!loadEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataObs = this.allEntities.map(dict =>
|
||||
this._dictionaryService.getForType(this._dossierTemplatesService.activeDossierTemplateId, dict.type).pipe(
|
||||
tap(values => (dict.entries = [...values.entries] ?? [])),
|
||||
catchError(() => {
|
||||
dict.entries = [];
|
||||
return of({});
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
forkJoin(dataObs)
|
||||
.pipe(defaultIfEmpty(null))
|
||||
.subscribe(() => this._calculateData());
|
||||
this._calculateData();
|
||||
}
|
||||
|
||||
private _calculateData(): void {
|
||||
this.chartData = this.allEntities.map(dict => toChartConfig(dict));
|
||||
this.chartData = this.allEntities.map(dict => ({
|
||||
value: this.templateStats.dictionarySummary(dict.type).entriesCount ?? 0,
|
||||
color: dict.hexColor,
|
||||
label: dict.label,
|
||||
key: dict.type,
|
||||
}));
|
||||
this.chartData.sort((a, b) => (a.label < b.label ? -1 : 1));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs></redaction-admin-breadcrumbs>
|
||||
<redaction-dossier-template-breadcrumbs></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="actions">
|
||||
<iqser-circle-button
|
||||
@ -55,8 +55,9 @@
|
||||
|
||||
<redaction-dictionary-manager
|
||||
#dictionaryManager
|
||||
(saveDictionary)="saveEntries($event)"
|
||||
(saveDictionary)="save()"
|
||||
[canEdit]="currentUser.isAdmin"
|
||||
[isLeavingPage]="isLeavingPage"
|
||||
[filterByDossierTemplate]="true"
|
||||
[initialEntries]="initialEntries"
|
||||
></redaction-dictionary-manager>
|
||||
|
||||
@ -3,7 +3,6 @@ import { AppStateService } from '@state/app-state.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
@ -11,17 +10,19 @@ import { CircleButtonTypes, LoadingService } from '@iqser/common-ui';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { Dictionary } from '@red/domain';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dictionary-overview-screen.component.html',
|
||||
styleUrls: ['./dictionary-overview-screen.component.scss'],
|
||||
})
|
||||
export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit, OnDestroy {
|
||||
export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
|
||||
initialEntries: string[] = [];
|
||||
dictionary: Dictionary;
|
||||
isLeavingPage = false;
|
||||
|
||||
@ViewChild('dictionaryManager', { static: false })
|
||||
private readonly _dictionaryManager: DictionaryManagerComponent;
|
||||
@ -37,11 +38,9 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
protected readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {
|
||||
super(_translateService);
|
||||
}
|
||||
) {}
|
||||
|
||||
get hasChanges() {
|
||||
get changed() {
|
||||
return this._dictionaryManager.editor.hasChanges;
|
||||
}
|
||||
|
||||
@ -72,7 +71,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
|
||||
$event?.stopPropagation();
|
||||
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
await this._dictionaryService.deleteDictionaries([this.dictionary.type], this.dictionary.dossierTemplateId).toPromise();
|
||||
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.dictionary.type], this.dictionary.dossierTemplateId));
|
||||
await this._appStateService.loadDictionaryData();
|
||||
await this._router.navigate([
|
||||
'/main',
|
||||
@ -116,7 +115,9 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
|
||||
}
|
||||
}
|
||||
|
||||
saveEntries(entries: string[]) {
|
||||
save() {
|
||||
const entries = this._dictionaryManager.editor?.currentEntries;
|
||||
|
||||
this._loadingService.start();
|
||||
this._dictionaryService
|
||||
.saveEntries(entries, this.initialEntries, this.dictionary.dossierTemplateId, this.dictionary.type, null)
|
||||
@ -149,9 +150,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
|
||||
|
||||
private async _loadEntries() {
|
||||
this._loadingService.start();
|
||||
await this._dictionaryService
|
||||
.getForType(this.dictionary.dossierTemplateId, this.dictionary.type)
|
||||
.toPromise()
|
||||
await firstValueFrom(this._dictionaryService.getForType(this.dictionary.dossierTemplateId, this.dictionary.type))
|
||||
.then(
|
||||
data => {
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -6,7 +6,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { DigitalSignatureService } from '../../services/digital-signature.service';
|
||||
import { IDigitalSignature } from '@red/domain';
|
||||
import { IDigitalSignature, IDigitalSignatureRequest } from '@red/domain';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
@ -40,29 +40,30 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
|
||||
}
|
||||
|
||||
saveDigitalSignature() {
|
||||
const digitalSignature = {
|
||||
...this.form.getRawValue(),
|
||||
const formValue = this.form.getRawValue();
|
||||
const digitalSignature: IDigitalSignature = {
|
||||
...formValue,
|
||||
};
|
||||
//adjusted for chrome auto-complete / password manager
|
||||
digitalSignature.password = digitalSignature.keySecret;
|
||||
digitalSignature.password = formValue.keySecret;
|
||||
|
||||
const observable = this.digitalSignatureExists
|
||||
? this._digitalSignatureService.update(digitalSignature)
|
||||
: this._digitalSignatureService.save(digitalSignature);
|
||||
|
||||
this.addSubscription = observable.subscribe(
|
||||
() => {
|
||||
this.addSubscription = observable.subscribe({
|
||||
next: () => {
|
||||
this.loadDigitalSignatureAndInitializeForm();
|
||||
this._toaster.success(_('digital-signature-screen.action.save-success'));
|
||||
},
|
||||
error => {
|
||||
error: error => {
|
||||
if (error.status === HttpStatusCode.BadRequest) {
|
||||
this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
|
||||
} else {
|
||||
this._toaster.error(_('digital-signature-screen.action.save-error'));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
removeDigitalSignature() {
|
||||
@ -85,23 +86,23 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
|
||||
this.form.get('certificateName').setValue(file.name);
|
||||
input.value = null;
|
||||
};
|
||||
fileReader.readAsDataURL(file);
|
||||
fileReader.readAsDataURL(file as Blob);
|
||||
}
|
||||
|
||||
loadDigitalSignatureAndInitializeForm() {
|
||||
this._loadingService.start();
|
||||
this.addSubscription = this._digitalSignatureService
|
||||
this._digitalSignatureService
|
||||
.getSignature()
|
||||
.subscribe(
|
||||
digitalSignature => {
|
||||
.subscribe({
|
||||
next: digitalSignature => {
|
||||
this.digitalSignatureExists = true;
|
||||
this.digitalSignature = digitalSignature;
|
||||
},
|
||||
() => {
|
||||
error: () => {
|
||||
this.digitalSignatureExists = false;
|
||||
this.digitalSignature = {};
|
||||
},
|
||||
)
|
||||
})
|
||||
.add(() => {
|
||||
this.form = this._getForm();
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
|
||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="actions flex-1">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
|
||||
@ -15,6 +15,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossierAttributeConfig, IDossierAttributeConfig } from '@red/domain';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ReportTemplateService } from '../../../../services/report-template.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-attributes-listing-screen.component.html',
|
||||
@ -44,6 +46,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierAttributesService: DossierAttributesService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _reportTemplateService: ReportTemplateService,
|
||||
) {
|
||||
super(_injector);
|
||||
}
|
||||
@ -52,11 +55,16 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: IDossierAttributeConfig) {
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
async openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: DossierAttributeConfig) {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const resp = await firstValueFrom(
|
||||
this._reportTemplateService.getTemplatesByPlaceholder(dossierTemplateId, dossierAttribute.placeholder),
|
||||
);
|
||||
|
||||
this._dialogService.openDialog('deleteAttribute', $event, { attribute: dossierAttribute, count: resp.length }, async () => {
|
||||
this._loadingService.start();
|
||||
const ids = dossierAttribute ? [dossierAttribute.id] : this.listingService.selected.map(item => item.id);
|
||||
await this._dossierAttributesService.delete(ids).toPromise();
|
||||
await firstValueFrom(this._dossierAttributesService.delete(ids));
|
||||
await this._loadData();
|
||||
});
|
||||
}
|
||||
@ -71,7 +79,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
|
||||
private async _loadData() {
|
||||
this._loadingService.start();
|
||||
await this._dossierAttributesService.loadAll().toPromise();
|
||||
await firstValueFrom(this._dossierAttributesService.loadAll());
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
<section class="settings">
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
|
||||
|
||||
<div>
|
||||
<iqser-page-header
|
||||
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
|
||||
[pageLabel]="'dossier-templates' | translate"
|
||||
[showCloseButton]="currentUser.isUser"
|
||||
></iqser-page-header>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="content-container">
|
||||
<iqser-table
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'dossier-templates-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-templates-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
noDataIcon="red:template"
|
||||
></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #bulkActions>
|
||||
<iqser-circle-button
|
||||
(action)="openBulkDeleteTemplatesDialog($event)"
|
||||
*ngIf="currentUser.isAdmin && (listingService.areSomeSelected$ | async)"
|
||||
[tooltip]="'dossier-templates-listing.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-dossierTemplate="entity">
|
||||
<redaction-dossier-template-actions
|
||||
(loadDossierTemplatesData)="loadDossierTemplateStats()"
|
||||
[dossierTemplateId]="dossierTemplate.dossierTemplateId"
|
||||
class="actions-container"
|
||||
></redaction-dossier-template-actions>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dossier-templates-listing.search' | translate"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="openAddDossierTemplateDialog()"
|
||||
*ngIf="currentUser.isAdmin && userPreferenceService.areDevFeaturesEnabled"
|
||||
[label]="'dossier-templates-listing.add-new' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tableItemTemplate let-entity="entity">
|
||||
<div *ngIf="cast(entity) as dossierTemplate">
|
||||
<div class="cell">
|
||||
<div class="table-item-title heading">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
{{ 'dossier-templates-listing.dictionaries' | translate: { length: dossierTemplate.dictionariesCount } }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell user-column">
|
||||
<redaction-initials-avatar
|
||||
[defaultValue]="'unknown' | translate"
|
||||
[user]="dossierTemplate.createdBy || 'system'"
|
||||
[withName]="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateAdded | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateModified | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="actionsTemplate; context: { entity: dossierTemplate }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -0,0 +1,47 @@
|
||||
<iqser-page-header
|
||||
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
|
||||
[pageLabel]="'dossier-templates' | translate"
|
||||
[showCloseButton]="currentUser.isUser"
|
||||
></iqser-page-header>
|
||||
|
||||
<iqser-table
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'dossier-templates-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-templates-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
noDataIcon="red:template"
|
||||
></iqser-table>
|
||||
|
||||
<ng-template #bulkActions>
|
||||
<iqser-circle-button
|
||||
(action)="openBulkDeleteTemplatesDialog($event)"
|
||||
*ngIf="currentUser.isAdmin && (listingService.areSomeSelected$ | async)"
|
||||
[tooltip]="'dossier-templates-listing.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dossier-templates-listing.search' | translate"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="openAddDossierTemplateDialog()"
|
||||
*ngIf="currentUser.isAdmin && userPreferenceService.areDevFeaturesEnabled"
|
||||
[label]="'dossier-templates-listing.add-new' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tableItemTemplate let-entity="entity">
|
||||
<redaction-table-item [dossierTemplate]="entity"></redaction-table-item>
|
||||
</ng-template>
|
||||
@ -0,0 +1,6 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { DossierTemplate } from '@red/domain';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
@ -18,6 +18,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-templates-listing-screen.component.html',
|
||||
@ -29,7 +30,7 @@ import { HttpStatusCode } from '@angular/common/http';
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => DossierTemplatesListingScreenComponent) },
|
||||
],
|
||||
})
|
||||
export class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplate> implements OnInit {
|
||||
export class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplate> {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
@ -39,6 +40,7 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
{ label: _('dossier-templates-listing.table-col-names.created-by'), class: 'user-column' },
|
||||
{ label: _('dossier-templates-listing.table-col-names.created-on'), sortByKey: 'dateAdded' },
|
||||
{ label: _('dossier-templates-listing.table-col-names.modified-on'), sortByKey: 'dateModified' },
|
||||
{ label: _('dossier-templates-listing.table-col-names.status'), sortByKey: 'dossierTemplateStatus' },
|
||||
];
|
||||
|
||||
constructor(
|
||||
@ -55,10 +57,6 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
super(_injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadDossierTemplateStats();
|
||||
}
|
||||
|
||||
openBulkDeleteTemplatesDialog($event?: MouseEvent) {
|
||||
return this._dialogService.openDialog('confirm', $event, null, () => {
|
||||
this._loadingService.loadWhile(this._deleteTemplates());
|
||||
@ -66,38 +64,17 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
}
|
||||
|
||||
openAddDossierTemplateDialog() {
|
||||
this._dialogService.openDialog('addEditDossierTemplate', null, null, () => {
|
||||
this.loadDossierTemplateStats();
|
||||
});
|
||||
}
|
||||
|
||||
loadDossierTemplateStats() {
|
||||
this.entitiesService.all.forEach(rs => {
|
||||
const dictionaries = this._appStateService.dictionaryData[rs.dossierTemplateId];
|
||||
if (dictionaries) {
|
||||
rs.dictionariesCount = Object.keys(dictionaries)
|
||||
.map(key => dictionaries[key])
|
||||
.filter(d => !d.virtual || d.type === 'false_positive').length;
|
||||
} else {
|
||||
rs.dictionariesCount = 0;
|
||||
rs.totalDictionaryEntries = 0;
|
||||
}
|
||||
});
|
||||
this._dialogService.openDialog('addEditDossierTemplate', null, null);
|
||||
}
|
||||
|
||||
private async _deleteTemplates(templateIds = this.listingService.selected.map(d => d.dossierTemplateId)) {
|
||||
await this._dossierTemplatesService
|
||||
.delete(templateIds)
|
||||
.toPromise()
|
||||
.catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('dossier-templates-listing.error.conflict'));
|
||||
} else {
|
||||
this._toaster.error(_('dossier-templates-listing.error.generic'));
|
||||
}
|
||||
});
|
||||
await this._dossierTemplatesService.loadAll().toPromise();
|
||||
await firstValueFrom(this._dossierTemplatesService.delete(templateIds)).catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('dossier-templates-listing.error.conflict'));
|
||||
} else {
|
||||
this._toaster.error(_('dossier-templates-listing.error.generic'));
|
||||
}
|
||||
});
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this.loadDossierTemplateStats();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { TableItemComponent } from './table-item/table-item.component';
|
||||
import { DossierTemplatesListingScreenComponent } from './dossier-templates-listing-screen/dossier-templates-listing-screen.component';
|
||||
import { SharedAdminModule } from '../../shared/shared-admin.module';
|
||||
|
||||
const routes = [{ path: '', component: DossierTemplatesListingScreenComponent }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [TableItemComponent, DossierTemplatesListingScreenComponent],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedAdminModule],
|
||||
})
|
||||
export class DossierTemplatesListingModule {}
|
||||
@ -0,0 +1,41 @@
|
||||
<div class="cell">
|
||||
<div class="table-item-title heading">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div *ngIf="stats$ | async as stats">
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
{{ 'dossier-templates-listing.dictionaries' | translate: { length: stats.numberOfDictionaries } }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell user-column">
|
||||
<redaction-initials-avatar
|
||||
[defaultValue]="'unknown' | translate"
|
||||
[user]="dossierTemplate.createdBy || 'system'"
|
||||
[withName]="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateAdded | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateModified | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div class="small-label">
|
||||
{{ translations[dossierTemplate.dossierTemplateStatus] | translate }}
|
||||
</div>
|
||||
<redaction-dossier-template-actions
|
||||
[dossierTemplateId]="dossierTemplate.dossierTemplateId"
|
||||
class="actions-container"
|
||||
></redaction-dossier-template-actions>
|
||||
</div>
|
||||
@ -0,0 +1,30 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||
import { DossierTemplate, DossierTemplateStats } from '@red/domain';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { dossierTemplateStatusTranslations } from '../../../translations/dossier-template-status-translations';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-table-item [dossierTemplate]',
|
||||
templateUrl: './table-item.component.html',
|
||||
styleUrls: ['./table-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TableItemComponent implements OnChanges {
|
||||
@Input() dossierTemplate!: DossierTemplate;
|
||||
|
||||
readonly translations = dossierTemplateStatusTranslations;
|
||||
readonly stats$: Observable<DossierTemplateStats>;
|
||||
private readonly _ngOnChanges$ = new BehaviorSubject<string>(undefined);
|
||||
|
||||
constructor(readonly dossierTemplateStatsService: DossierTemplateStatsService) {
|
||||
this.stats$ = this._ngOnChanges$.pipe(switchMap(id => this.dossierTemplateStatsService.watch$(id)));
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.dossierTemplate) {
|
||||
this._ngOnChanges$.next(this.dossierTemplate.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
|
||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="actions flex-1">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
|
||||
@ -17,6 +17,8 @@ import { FileAttributeConfig, IFileAttributeConfig, IFileAttributesConfig } from
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ReportTemplateService } from '../../../../services/report-template.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './file-attributes-listing-screen.component.html',
|
||||
@ -59,6 +61,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
private readonly _reportTemplateService: ReportTemplateService,
|
||||
) {
|
||||
super(_injector);
|
||||
}
|
||||
@ -87,23 +90,23 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
});
|
||||
}
|
||||
|
||||
openConfigurationsDialog($event: MouseEvent) {
|
||||
this._dialogService.openDialog('fileAttributesConfigurations', $event, this._existingConfiguration);
|
||||
}
|
||||
async openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const resp = await firstValueFrom(
|
||||
this._reportTemplateService.getTemplatesByPlaceholder(dossierTemplateId, fileAttribute.placeholder),
|
||||
);
|
||||
|
||||
openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: IFileAttributeConfig) {
|
||||
this._dialogService.openDialog('deleteFileAttribute', $event, fileAttribute, async () => {
|
||||
this._dialogService.openDialog('deleteAttribute', $event, { attribute: fileAttribute, count: resp.length }, async () => {
|
||||
this._loadingService.start();
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
if (fileAttribute) {
|
||||
await this._fileAttributesService.deleteFileAttributes([fileAttribute.id], dossierTemplateId).toPromise();
|
||||
await firstValueFrom(this._fileAttributesService.deleteFileAttributes([fileAttribute.id], dossierTemplateId));
|
||||
} else {
|
||||
await this._fileAttributesService
|
||||
.deleteFileAttributes(
|
||||
await firstValueFrom(
|
||||
this._fileAttributesService.deleteFileAttributes(
|
||||
this.listingService.selected.map(f => f.id),
|
||||
dossierTemplateId,
|
||||
)
|
||||
.toPromise();
|
||||
),
|
||||
);
|
||||
}
|
||||
await this._appStateService.refreshDossierTemplate(dossierTemplateId);
|
||||
await this._loadData();
|
||||
@ -126,18 +129,21 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
);
|
||||
}
|
||||
|
||||
openConfigurationsDialog($event: MouseEvent) {
|
||||
this._dialogService.openDialog('fileAttributesConfigurations', $event, this._existingConfiguration);
|
||||
}
|
||||
|
||||
private async _createNewFileAttributeAndRefreshView(newValue: IFileAttributeConfig): Promise<void> {
|
||||
await this._fileAttributesService
|
||||
.setFileAttributesConfig(newValue, this._dossierTemplatesService.activeDossierTemplateId)
|
||||
.toPromise()
|
||||
.catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('file-attributes-listing.error.conflict'));
|
||||
} else {
|
||||
this._toaster.error(_('file-attributes-listing.error.generic'));
|
||||
}
|
||||
this._loadingService.stop();
|
||||
});
|
||||
await firstValueFrom(
|
||||
this._fileAttributesService.setFileAttributesConfig(newValue, this._dossierTemplatesService.activeDossierTemplateId),
|
||||
).catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('file-attributes-listing.error.conflict'));
|
||||
} else {
|
||||
this._toaster.error(_('file-attributes-listing.error.generic'));
|
||||
}
|
||||
this._loadingService.stop();
|
||||
});
|
||||
await this._appStateService.refreshDossierTemplate(this._dossierTemplatesService.activeDossierTemplateId);
|
||||
await this._loadData();
|
||||
}
|
||||
@ -146,9 +152,9 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
this._loadingService.start();
|
||||
|
||||
try {
|
||||
const response = await this._fileAttributesService
|
||||
.getFileAttributesConfig(this._dossierTemplatesService.activeDossierTemplateId)
|
||||
.toPromise();
|
||||
const response = await firstValueFrom(
|
||||
this._fileAttributesService.getFileAttributesConfig(this._dossierTemplatesService.activeDossierTemplateId),
|
||||
);
|
||||
this._existingConfiguration = response;
|
||||
const fileAttributeConfig = response?.fileAttributeConfigs.map(item => new FileAttributeConfig(item)) || [];
|
||||
this.entitiesService.setEntities(fileAttributeConfig);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="heading-l" translate="general-config-screen.general.title"></div>
|
||||
<div translate="general-config-screen.general.subtitle"></div>
|
||||
</div>
|
||||
<form (submit)="saveGeneralConfig()" [formGroup]="form">
|
||||
<form (submit)="save()" [formGroup]="form" *ngIf="form">
|
||||
<div class="dialog-content">
|
||||
<div class="dialog-content-left">
|
||||
<div class="iqser-input-group">
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="form.invalid || !generalConfigurationChanged" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="form?.invalid || !changed" color="primary" mat-flat-button type="submit">
|
||||
{{ 'general-config-screen.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { AutoUnsubscribe, LoadingService } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, LoadingService } from '@iqser/common-ui';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { IGeneralConfiguration } from '@red/domain';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-general-config-form',
|
||||
templateUrl: './general-config-form.component.html',
|
||||
styleUrls: ['./general-config-form.component.scss'],
|
||||
})
|
||||
export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
|
||||
export class GeneralConfigFormComponent extends BaseFormComponent implements OnInit, OnDestroy {
|
||||
private _initialConfiguration: IGeneralConfiguration;
|
||||
readonly form: FormGroup = this._getForm();
|
||||
|
||||
constructor(
|
||||
private readonly _loadingService: LoadingService,
|
||||
@ -21,28 +21,7 @@ export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
forgotPasswordFunctionEnabled: [false],
|
||||
auxiliaryName: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
async saveGeneralConfig() {
|
||||
this._loadingService.start();
|
||||
|
||||
const configFormValues = this.form.getRawValue();
|
||||
|
||||
await this._generalSettingsService.updateGeneralConfigurations(configFormValues).toPromise();
|
||||
this._initialConfiguration = await this._generalSettingsService.getGeneralConfigurations().toPromise();
|
||||
this._configService.updateDisplayName(this._initialConfiguration.displayName);
|
||||
this._loadingService.stop();
|
||||
this.form = this._getForm();
|
||||
}
|
||||
|
||||
get generalConfigurationChanged(): boolean {
|
||||
@ -59,11 +38,35 @@ export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnIni
|
||||
return false;
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
|
||||
const configFormValues = this.form.getRawValue();
|
||||
|
||||
await firstValueFrom(this._generalSettingsService.updateGeneralConfigurations(configFormValues));
|
||||
this._initialConfiguration = await firstValueFrom(this._generalSettingsService.getGeneralConfigurations());
|
||||
this._configService.updateDisplayName(this._initialConfiguration.displayName);
|
||||
this._loadingService.stop();
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
forgotPasswordFunctionEnabled: [false],
|
||||
auxiliaryName: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
this._initialConfiguration = await this._generalSettingsService.getGeneralConfigurations().toPromise();
|
||||
this._initialConfiguration = await firstValueFrom(this._generalSettingsService.getGeneralConfigurations());
|
||||
this.form.patchValue(this._initialConfiguration, { emitEvent: false });
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
} catch (e) {}
|
||||
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -1,13 +1,52 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { GeneralConfigFormComponent } from './general-config-form/general-config-form.component';
|
||||
import { SmtpFormComponent } from './smtp-form/smtp-form.component';
|
||||
import { BaseFormComponent } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-general-config-screen',
|
||||
templateUrl: './general-config-screen.component.html',
|
||||
styleUrls: ['./general-config-screen.component.scss'],
|
||||
})
|
||||
export class GeneralConfigScreenComponent {
|
||||
export class GeneralConfigScreenComponent extends BaseFormComponent implements AfterViewInit {
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
|
||||
constructor(private readonly _userService: UserService) {}
|
||||
@ViewChild(GeneralConfigFormComponent) generalConfigFormComponent: GeneralConfigFormComponent;
|
||||
@ViewChild(SmtpFormComponent) smtpFormComponent: SmtpFormComponent;
|
||||
children: BaseFormComponent[];
|
||||
|
||||
constructor(private readonly _userService: UserService) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.children = [this.generalConfigFormComponent, this.smtpFormComponent];
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
for (const child of this.children) {
|
||||
if (child.changed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get valid() {
|
||||
for (const child of this.children) {
|
||||
if (!child.valid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
for (const child of this.children) {
|
||||
if (child.changed) {
|
||||
await child.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="form.invalid || !smtpConfigurationChanged" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="submit">
|
||||
{{ 'general-config-screen.actions.save' | translate }}
|
||||
</button>
|
||||
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ISmtpConfiguration } from '@red/domain';
|
||||
import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { SmtpConfigService } from '../../../services/smtp-config.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-smtp-form',
|
||||
templateUrl: './smtp-form.component.html',
|
||||
styleUrls: ['./smtp-form.component.scss'],
|
||||
})
|
||||
export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
|
||||
export class SmtpFormComponent extends BaseFormComponent implements OnInit, OnDestroy {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
private _initialConfiguration: ISmtpConfiguration;
|
||||
readonly form: FormGroup = this._getForm();
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
@ -24,6 +24,7 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
|
||||
private readonly _toaster: Toaster,
|
||||
) {
|
||||
super();
|
||||
this.form = this._getForm();
|
||||
this.addSubscription = this.form.controls.auth.valueChanges.subscribe(auth => {
|
||||
if (auth) {
|
||||
this.openAuthConfigDialog();
|
||||
@ -35,6 +36,36 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
openAuthConfigDialog(skipDisableOnCancel?: boolean) {
|
||||
this._dialogService.openDialog('smtpAuthConfig', null, this.form.getRawValue(), null, authConfig => {
|
||||
if (authConfig) {
|
||||
this.form.patchValue(authConfig);
|
||||
} else if (!skipDisableOnCancel) {
|
||||
this.form.patchValue({ auth: false }, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._smtpConfigService.updateSMTPConfiguration(this.form.getRawValue()));
|
||||
this._initialConfiguration = this.form.getRawValue();
|
||||
this._loadingService.stop();
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this._smtpConfigService.testSMTPConfiguration(this.form.getRawValue()));
|
||||
this._toaster.success(_('general-config-screen.test.success'));
|
||||
} catch (e) {
|
||||
this._toaster.error(_('general-config-screen.test.error'));
|
||||
} finally {
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
host: [undefined, Validators.required],
|
||||
@ -52,57 +83,15 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
|
||||
});
|
||||
}
|
||||
|
||||
openAuthConfigDialog(skipDisableOnCancel?: boolean) {
|
||||
this._dialogService.openDialog('smtpAuthConfig', null, this.form.getRawValue(), null, authConfig => {
|
||||
if (authConfig) {
|
||||
this.form.patchValue(authConfig);
|
||||
} else if (!skipDisableOnCancel) {
|
||||
this.form.patchValue({ auth: false }, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
await this._smtpConfigService.updateSMTPConfiguration(this.form.getRawValue()).toPromise();
|
||||
this._initialConfiguration = this.form.getRawValue();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
get smtpConfigurationChanged(): boolean {
|
||||
if (!this._initialConfiguration) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
if (this._initialConfiguration[key] !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await this._smtpConfigService.testSMTPConfiguration(this.form.getRawValue()).toPromise();
|
||||
this._toaster.success(_('general-config-screen.test.success'));
|
||||
} catch (e) {
|
||||
this._toaster.error(_('general-config-screen.test.error'));
|
||||
} finally {
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
this._loadingService.start();
|
||||
|
||||
try {
|
||||
this._initialConfiguration = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise();
|
||||
this._initialConfiguration = await firstValueFrom(this._smtpConfigService.getCurrentSMTPConfiguration());
|
||||
this.form.patchValue(this._initialConfiguration, { emitEvent: false });
|
||||
} catch (e) {}
|
||||
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { DossierTemplateInfoScreenComponent } from './info-screen/dossier-template-info-screen.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
|
||||
const routes = [{ path: '', component: DossierTemplateInfoScreenComponent }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [DossierTemplateInfoScreenComponent],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule],
|
||||
})
|
||||
export class DossierTemplateInfoModule {}
|
||||
@ -0,0 +1,56 @@
|
||||
<div *ngIf="dossierTemplate$ | async as dossierTemplate" class="content-container" iqserHasScrollbar>
|
||||
<ng-container *ngIf="dossierTemplateStats$ | async as stats">
|
||||
<div class="heading-xl">{{ dossierTemplate.name }}</div>
|
||||
|
||||
<div class="all-caps-label mt-24 mb-8" translate="dossier-template-info-screen.created-by"></div>
|
||||
|
||||
<redaction-initials-avatar
|
||||
[user]="dossierTemplate.createdBy || 'system'"
|
||||
[withName]="true"
|
||||
size="large"
|
||||
></redaction-initials-avatar>
|
||||
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
{{ 'dossier-template-info-screen.dictionaries' | translate: { count: stats.numberOfDictionaries } }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossierTemplate.validTo && dossierTemplate.validFrom">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
{{ 'dossier-template-info-screen.valid-from' | translate: { date: dossierTemplate.validFrom | date: 'd MMM. yyyy' } }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
{{ 'dossier-template-info-screen.created-on' | translate: { date: dossierTemplate.dateAdded | date: 'd MMM. yyyy' } }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ 'dossier-template-info-screen.entries' | translate: { count: stats.numberOfEntries } }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossierTemplate.validTo && dossierTemplate.validFrom">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
{{ 'dossier-template-info-screen.valid-to' | translate: { date: dossierTemplate.validTo | date: 'd MMM. yyyy' } }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossierTemplate.dateModified">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
{{ 'dossier-template-info-screen.modified-on' | translate: { date: dossierTemplate.dateModified | date: 'd MMM. yyyy' } }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="heading mt-40" translate="dossier-template-info-screen.description">
|
||||
<iqser-circle-button
|
||||
(action)="openEditDossierTemplateDialog($event, dossierTemplate)"
|
||||
*ngIf="permissionsService.isAdmin()"
|
||||
class="ml-8"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
|
||||
<div>{{ dossierTemplate.description }}</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@ -0,0 +1,30 @@
|
||||
@use 'variables';
|
||||
@use 'common-mixins';
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
overflow: auto;
|
||||
@include common-mixins.scroll-bar;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stats-subtitle {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, max-content);
|
||||
grid-row-gap: 8px;
|
||||
grid-column-gap: 40px;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DossierTemplate, DossierTemplateStats } from '@red/domain';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-template-info-screen.component.html',
|
||||
styleUrls: ['./dossier-template-info-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossierTemplateInfoScreenComponent {
|
||||
readonly dossierTemplate$: Observable<DossierTemplate>;
|
||||
readonly dossierTemplateStats$: Observable<DossierTemplateStats>;
|
||||
|
||||
constructor(
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
readonly permissionsService: PermissionsService,
|
||||
) {
|
||||
const dossierTemplateId = _route.snapshot.paramMap.get('dossierTemplateId');
|
||||
this.dossierTemplate$ = this._dossierTemplatesService.getEntityChanged$(dossierTemplateId);
|
||||
this.dossierTemplateStats$ = this._dossierTemplateStatsService.watch$(dossierTemplateId);
|
||||
}
|
||||
|
||||
openEditDossierTemplateDialog($event: MouseEvent, dossierTemplate: DossierTemplate) {
|
||||
this._dialogService.openDialog('addEditDossierTemplate', $event, dossierTemplate);
|
||||
}
|
||||
}
|
||||
@ -43,13 +43,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'add-edit-justification.actions.save' | translate }}
|
||||
</button>
|
||||
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="add-edit-justification.actions.cancel"></div>
|
||||
<div class="all-caps-label cancel" translate="add-edit-justification.actions.cancel" (click)="close()"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Justification } from '@red/domain';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { LoadingService } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, LoadingService } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-justification-dialog',
|
||||
@ -12,33 +13,31 @@ import { LoadingService } from '@iqser/common-ui';
|
||||
styleUrls: ['./add-edit-justification-dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddEditJustificationDialogComponent {
|
||||
readonly form: FormGroup = this._getForm();
|
||||
export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _justificationService: JustificationsService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
public dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public justification: Justification,
|
||||
) {}
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
|
||||
get changed(): boolean {
|
||||
return (
|
||||
!this.justification ||
|
||||
Object.keys(this.form.getRawValue()).reduce((prev, key) => prev || this.justification[key] !== this.form.get(key).value, false)
|
||||
);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
async save() {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
|
||||
this._loadingService.start();
|
||||
await this._justificationService.createOrUpdate(this.form.getRawValue(), dossierTemplateId).toPromise();
|
||||
await this._justificationService.loadAll(dossierTemplateId).toPromise();
|
||||
await firstValueFrom(this._justificationService.createOrUpdate(this.form.getRawValue(), dossierTemplateId));
|
||||
await firstValueFrom(this._justificationService.loadAll(dossierTemplateId));
|
||||
this._loadingService.stop();
|
||||
this.dialogRef.close(true);
|
||||
this._dialogRef.close(true);
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user