Merge branch 'master' into VM/RED-2539

This commit is contained in:
Valentin 2021-11-22 20:41:04 +02:00
commit 256d9b7172
91 changed files with 995 additions and 921 deletions

View File

@ -40,11 +40,7 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit {
super();
this.notifications$ = this._notifications$.asObservable().pipe(shareLast());
this.groupedNotifications$ = this.notifications$.pipe(map(notifications => this._groupNotifications(notifications)));
this.hasUnreadNotifications$ = this.notifications$.pipe(
map(notifications => notifications.filter(n => !n.readDate).length > 0),
distinctUntilChanged(),
shareLast(),
);
this.hasUnreadNotifications$ = this._hasUnreadNotifications$;
}
async ngOnInit(): Promise<void> {
@ -58,6 +54,14 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit {
.subscribe();
}
private get _hasUnreadNotifications$(): Observable<boolean> {
return this.notifications$.pipe(
map(notifications => notifications.filter(n => !n.readDate).length > 0),
distinctUntilChanged(),
shareLast(),
);
}
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();

View File

@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router';
import { DossiersService } from '../services/entity-services/dossiers.service';
import { BreadcrumbsService } from '../services/breadcrumbs.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { pluck } from 'rxjs/operators';
import { AppStateService } from '../state/app-state.service';
import { FilesMapService } from '../services/entity-services/files-map.service';
import { AppStateService } from '@state/app-state.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { FilesService } from '@services/entity-services/files.service';
@Injectable({ providedIn: 'root' })
export class DossierFilesGuard implements CanActivate, CanDeactivate<unknown> {
@ -13,20 +14,20 @@ export class DossierFilesGuard implements CanActivate, CanDeactivate<unknown> {
private readonly _appStateService: AppStateService,
private readonly _breadcrumbsService: BreadcrumbsService,
private readonly _filesMapService: FilesMapService,
private readonly _filesService: FilesService,
private readonly _router: Router,
) {}
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
const dossierId = route.paramMap.get('dossierId');
const dossier = this._dossiersService.find(dossierId);
if (!dossier) {
if (!this._dossiersService.has(dossierId)) {
await this._router.navigate(['/main', 'dossiers']);
return false;
}
if (!this._filesMapService.has(dossierId)) {
await this._appStateService.getFiles(dossier);
await this._filesService.loadAll(dossierId).toPromise();
}
this._breadcrumbsService.append({

View File

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router';
import { FilesMapService } from '../services/entity-services/files-map.service';
import { AppStateService } from '../state/app-state.service';
import { DossiersService } from '../services/entity-services/dossiers.service';
import { BreadcrumbsService } from '../services/breadcrumbs.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { AppStateService } from '@state/app-state.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { pluck } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })

View File

@ -20,15 +20,21 @@ export class NotificationsScreenComponent implements OnInit {
readonly notificationGroupsValues = NotificationGroupsValues;
readonly translations = notificationsTranslations;
formGroup: FormGroup;
formGroup: FormGroup = this._getForm();
constructor(
private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
private readonly _notificationPreferencesService: NotificationPreferencesService,
) {
this.formGroup = this._formBuilder.group({
) {}
async ngOnInit(): Promise<void> {
await this._initializeForm();
}
private _getForm(): FormGroup {
return this._formBuilder.group({
inAppNotificationsEnabled: [undefined],
emailNotificationsEnabled: [undefined],
emailNotificationType: [undefined],
@ -37,10 +43,6 @@ export class NotificationsScreenComponent implements OnInit {
});
}
async ngOnInit(): Promise<void> {
await this._initializeForm();
}
isCategoryActive(category: string) {
return this.formGroup.get(`${category}Enabled`).value;
}

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../../shared/shared.module';
import { SharedModule } from '@shared/shared.module';
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
const routes = [{ path: '', component: NotificationsScreenComponent }];

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DomSanitizer } from '@angular/platform-browser';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '@iqser/common-ui';
import { IProfile } from '@red/domain';
@ -17,8 +17,8 @@ import { LanguageService } from '../../../../../i18n/language.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserProfileScreenComponent implements OnInit {
formGroup: FormGroup;
changePasswordUrl: any;
formGroup: FormGroup = this._getForm();
changePasswordUrl: SafeResourceUrl;
translations = languagesTranslations;
private _profileModel: IProfile;
@ -34,16 +34,19 @@ export class UserProfileScreenComponent implements OnInit {
private readonly _loadingService: LoadingService,
) {
this._loadingService.start();
this.formGroup = this._formBuilder.group({
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],
});
this.changePasswordUrl = this._domSanitizer.bypassSecurityTrustResourceUrl(
`${this._configService.values.OAUTH_URL}/account/password`,
);
}
get languageChanged(): boolean {

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../../shared/shared.module';
import { SharedModule } from '@shared/shared.module';
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
const routes = [{ path: '', component: UserProfileScreenComponent }];

View File

@ -46,6 +46,8 @@ import { LicenseReportService } from './services/licence-report.service';
import { RulesService } from './services/rules.service';
import { SmtpConfigService } from './services/smtp-config.service';
import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
import { GeneralConfigFormComponent } from './screens/general-config/general-config-form/general-config-form.component';
import { SmtpFormComponent } from './screens/general-config/smtp-form/smtp-form.component';
const dialogs = [
AddEditDossierTemplateDialogComponent,
@ -96,7 +98,7 @@ const components = [
];
@NgModule({
declarations: [...components],
declarations: [...components, GeneralConfigFormComponent, SmtpFormComponent],
providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService],
imports: [CommonModule, SharedModule, AdminRoutingModule, NgxChartsModule, ColorPickerModule, MonacoEditorModule],
})

View File

@ -20,8 +20,8 @@ import { HttpStatusCode } from '@angular/common/http';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
readonly form: FormGroup;
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', {
@ -43,21 +43,24 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string },
) {
super();
this.form = _formBuilder.group({
label: [this.dictionary?.label, [Validators.required, Validators.minLength(3)]],
description: [this.dictionary?.description],
rank: [this.dictionary?.rank, Validators.required],
hexColor: [this.dictionary?.hexColor, [Validators.required, Validators.minLength(7)]],
hint: [!!this.dictionary?.hint],
addToDictionaryAction: [!!this.dictionary?.addToDictionaryAction],
caseSensitive: [this.dictCaseSensitive],
});
this.hasColor$ = this._colorEmpty$;
this.technicalName$ = this.form.get('label').valueChanges.pipe(map(value => this._toTechnicalName(value)));
}
get dictCaseSensitive(): boolean {
return this.dictionary ? !this.dictionary.caseInsensitive : false;
private _getForm(dictionary: Dictionary): FormGroup {
return this._formBuilder.group({
label: [dictionary?.label, [Validators.required, Validators.minLength(3)]],
description: [dictionary?.description],
rank: [dictionary?.rank, Validators.required],
hexColor: [dictionary?.hexColor, [Validators.required, Validators.minLength(7)]],
hint: [!!dictionary?.hint],
addToDictionaryAction: [!!dictionary?.addToDictionaryAction],
caseSensitive: [this.getDictCaseSensitive(dictionary)],
});
}
getDictCaseSensitive(dictionary: Dictionary): boolean {
return dictionary ? !dictionary.caseInsensitive : false;
}
get changed(): boolean {
@ -67,7 +70,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
for (const key of Object.keys(this.form.getRawValue())) {
if (key === 'caseSensitive') {
if (this.dictCaseSensitive !== this.form.get(key).value) {
if (this.getDictCaseSensitive(this.dictionary) !== this.form.get(key).value) {
return true;
}
} else if (this.dictionary[key] !== this.form.get(key).value) {

View File

@ -8,7 +8,7 @@
class="dialog-header heading-l"
></div>
<form (submit)="saveDossierAttribute()" [formGroup]="dossierAttributeForm">
<form (submit)="saveDossierAttribute()" [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group required w-300">
<label translate="add-edit-dossier-attribute.form.label"></label>
@ -35,7 +35,7 @@
</div>
</div>
<div class="dialog-actions">
<button [disabled]="dossierAttributeForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ 'add-edit-dossier-attribute.save' | translate }}
</button>
</div>

View File

@ -13,8 +13,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
})
export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe implements OnDestroy {
dossierAttributeForm: FormGroup;
dossierAttribute: IDossierAttributeConfig;
dossierAttribute: IDossierAttributeConfig = this.data.dossierAttribute;
readonly form: FormGroup = this._getForm(this.dossierAttribute);
readonly translations = dossierAttributeTypesTranslations;
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
@ -28,17 +28,18 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
readonly data: { readonly dossierAttribute: IDossierAttributeConfig },
) {
super();
this.dossierAttribute = data.dossierAttribute;
}
this.dossierAttributeForm = this._formBuilder.group({
label: [this.dossierAttribute?.label, Validators.required],
...(!!this.dossierAttribute && {
private _getForm(dossierAttribute: IDossierAttributeConfig): FormGroup {
return this._formBuilder.group({
label: [dossierAttribute?.label, Validators.required],
...(!!dossierAttribute && {
placeholder: {
value: this.dossierAttribute.placeholder,
value: dossierAttribute.placeholder,
disabled: true,
},
}),
type: [this.dossierAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
type: [dossierAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
});
}
@ -47,8 +48,8 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
return true;
}
for (const key of Object.keys(this.dossierAttributeForm.getRawValue())) {
if (this.dossierAttribute[key] !== this.dossierAttributeForm.get(key).value) {
for (const key of Object.keys(this.form.getRawValue())) {
if (this.dossierAttribute[key] !== this.form.get(key).value) {
return true;
}
}
@ -62,7 +63,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
const attribute: IDossierAttributeConfig = {
id: this.dossierAttribute?.id,
editable: true,
...this.dossierAttributeForm.getRawValue(),
...this.form.getRawValue(),
};
this._dossierAttributesService.createOrUpdate(attribute).subscribe(
@ -79,7 +80,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
@HostListener('window:keydown.Enter', ['$event'])
onEnter(event: KeyboardEvent): void {
const node = (event.target as IqserEventTarget).localName;
if (this.dossierAttributeForm.valid && this.changed && node !== 'textarea') {
if (this.form.valid && this.changed && node !== 'textarea') {
this.saveDossierAttribute();
}
}

View File

@ -17,7 +17,7 @@ import { HttpStatusCode } from '@angular/common/http';
styleUrls: ['./add-edit-dossier-template-dialog.component.scss'],
})
export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
form: FormGroup;
readonly form: FormGroup = this._getForm();
hasValidFrom: boolean;
hasValidTo: boolean;
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
@ -38,7 +38,19 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
@Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate,
) {
super();
this.form = this._formBuilder.group({
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.form.valueChanges.subscribe(value => {
this._applyValidityIntervalConstraints(value);
});
}
private _getForm(): FormGroup {
return this._formBuilder.group({
name: [this.dossierTemplate?.name, Validators.required],
description: [this.dossierTemplate?.description],
validFrom: [
@ -51,15 +63,6 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
],
downloadFileTypes: [this.dossierTemplate?.downloadFileTypes || ['PREVIEW', 'REDACTED']],
});
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.form.valueChanges.subscribe(value => {
this._applyValidityIntervalConstraints(value);
});
}
get changed(): boolean {

View File

@ -12,9 +12,9 @@ import { BaseDialogComponent } from '@iqser/common-ui';
styleUrls: ['./add-edit-file-attribute-dialog.component.scss'],
})
export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
form: FormGroup;
fileAttribute: IFileAttributeConfig;
dossierTemplateId: string;
fileAttribute: IFileAttributeConfig = this.data.fileAttribute;
dossierTemplateId: string = this.data.dossierTemplateId;
readonly form: FormGroup = this._getForm(this.fileAttribute);
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
translations = fileAttributeTypesTranslations;
@ -26,17 +26,17 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
public data: { fileAttribute: IFileAttributeConfig; dossierTemplateId: string },
) {
super();
this.fileAttribute = data.fileAttribute;
this.dossierTemplateId = data.dossierTemplateId;
}
this.form = this._formBuilder.group({
label: [this.fileAttribute?.label, Validators.required],
csvColumnHeader: [this.fileAttribute?.csvColumnHeader],
type: [this.fileAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
readonly: [this.fileAttribute ? !this.fileAttribute.editable : false],
primaryAttribute: [this.fileAttribute?.primaryAttribute],
filterable: [this.fileAttribute?.filterable],
displayedInFileList: [this.fileAttribute?.displayedInFileList],
private _getForm(fileAttribute: IFileAttributeConfig): FormGroup {
return this._formBuilder.group({
label: [fileAttribute?.label, Validators.required],
csvColumnHeader: [fileAttribute?.csvColumnHeader],
type: [fileAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
readonly: [fileAttribute ? !fileAttribute.editable : false],
primaryAttribute: [fileAttribute?.primaryAttribute],
filterable: [fileAttribute?.filterable],
displayedInFileList: [fileAttribute?.displayedInFileList],
});
}

View File

@ -1,6 +1,6 @@
<div [translateParams]="{ userName: user | name }" [translate]="'reset-password-dialog.header'" class="dialog-header heading-l"></div>
<form (submit)="save()" [formGroup]="passwordForm">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group required w-300">
<label translate="reset-password-dialog.form.password"></label>
@ -9,7 +9,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="passwordForm.invalid" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'reset-password-dialog.actions.save' | translate }}
</button>

View File

@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { UserService } from '@services/user.service';
import { LoadingService } from '@iqser/common-ui';
import { User } from '@red/domain';
@ -10,9 +10,7 @@ import { User } from '@red/domain';
styleUrls: ['./reset-password.component.scss'],
})
export class ResetPasswordComponent {
readonly passwordForm = this._formBuilder.group({
temporaryPassword: [null, Validators.required],
});
readonly form = this._getForm();
@Input() user: User;
@Output() readonly toggleResetPassword = new EventEmitter();
@ -27,7 +25,7 @@ export class ResetPasswordComponent {
await this._userService
.resetPassword(
{
password: this.passwordForm.get('temporaryPassword').value,
password: this.form.get('temporaryPassword').value,
temporary: true,
},
this.user.id,
@ -36,4 +34,10 @@ export class ResetPasswordComponent {
this._loadingService.stop();
this.toggleResetPassword.emit();
}
private _getForm(): FormGroup {
return this._formBuilder.group({
temporaryPassword: [null, Validators.required],
});
}
}

View File

@ -6,7 +6,7 @@
class="dialog-header heading-l"
></div>
<form (submit)="save()" [formGroup]="userForm">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group required w-300">
<label translate="add-edit-user.form.first-name"></label>
@ -41,7 +41,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="userForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ (user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate }}
</button>

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
@ -13,17 +13,17 @@ import { HttpStatusCode } from '@angular/common/http';
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.scss'],
})
export class UserDetailsComponent implements OnInit {
export class UserDetailsComponent {
readonly iconButtonTypes = IconButtonTypes;
@Input() user: User;
@Output() readonly toggleResetPassword = new EventEmitter();
@Output() readonly closeDialog = new EventEmitter();
userForm: FormGroup;
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
readonly translations = rolesTranslations;
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
readonly form: FormGroup = this._getForm();
constructor(
private readonly _formBuilder: FormBuilder,
@ -31,7 +31,9 @@ export class UserDetailsComponent implements OnInit {
private readonly _dialogService: AdminDialogService,
private readonly _loadingService: LoadingService,
readonly userService: UserService,
) {}
) {
this._setRolesRequirements();
}
get changed(): boolean {
if (!this.user) {
@ -42,8 +44,8 @@ export class UserDetailsComponent implements OnInit {
return true;
}
for (const key of Object.keys(this.userForm.getRawValue())) {
const keyValue = this.userForm.get(key).value;
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;
@ -58,7 +60,7 @@ export class UserDetailsComponent implements OnInit {
get activeRoles(): string[] {
return this.ROLES.reduce((acc, role) => {
if (this.userForm.get(role).value) {
if (this.form.get(role).value) {
acc.push(role);
}
return acc;
@ -80,8 +82,23 @@ export class UserDetailsComponent implements OnInit {
);
}
ngOnInit() {
const rolesControls = this.ROLES.reduce(
private _getForm(): FormGroup {
return this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required],
email: [
{
value: this.user?.email,
disabled: !!this.user,
},
[Validators.required, Validators.email],
],
...this._rolesControls,
});
}
private get _rolesControls(): any {
return this.ROLES.reduce(
(prev, role) => ({
...prev,
[role]: [
@ -93,24 +110,11 @@ export class UserDetailsComponent implements OnInit {
}),
{},
);
this.userForm = this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required],
email: [
{
value: this.user?.email,
disabled: !!this.user,
},
[Validators.required, Validators.email],
],
...rolesControls,
});
this._setRolesRequirements();
}
async save() {
this._loadingService.start();
const userData = { ...this.userForm.getRawValue(), roles: this.activeRoles };
const userData = { ...this.form.getRawValue(), roles: this.activeRoles };
if (!this.user) {
await this.userService
@ -141,12 +145,12 @@ export class UserDetailsComponent implements OnInit {
private _setRolesRequirements() {
for (const key of Object.keys(this._ROLE_REQUIREMENTS)) {
this.userForm.controls[key].valueChanges.subscribe(checked => {
this.form.controls[key].valueChanges.subscribe(checked => {
if (checked) {
this.userForm.patchValue({ [this._ROLE_REQUIREMENTS[key]]: true });
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].disable();
this.form.patchValue({ [this._ROLE_REQUIREMENTS[key]]: true });
this.form.controls[this._ROLE_REQUIREMENTS[key]].disable();
} else {
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].enable();
this.form.controls[this._ROLE_REQUIREMENTS[key]].enable();
}
});
}

View File

@ -25,15 +25,18 @@ export class ConfirmDeleteUsersDialogComponent {
@Inject(MAT_DIALOG_DATA) readonly userIds: List<string>,
readonly dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>,
) {
this.dossiersCount = this._dossiersService.all.filter(dw =>
this.userIds.reduce((prev, userId) => prev || dw.memberIds.includes(userId), false),
).length;
this.dossiersCount = this._dossierCount;
}
get valid() {
return this.checkboxes[0].value && this.checkboxes[1].value;
}
private get _dossierCount(): number {
return this._dossiersService.all.filter(dw => this.userIds.reduce((prev, userId) => prev || dw.memberIds.includes(userId), false))
.length;
}
async deleteUser() {
if (this.valid) {
this._loadingService.start();

View File

@ -1,5 +1,5 @@
<section class="dialog">
<div [translate]="translations[colorKey]" class="dialog-header heading-l"></div>
<div [translate]="translations[data.colorKey]" class="dialog-header heading-l"></div>
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">

View File

@ -8,15 +8,18 @@ import { defaultColorsTranslations } from '../../translations/default-colors-tra
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@shared/services/dictionary.service';
interface IEditColorData {
colors: IColors;
colorKey: DefaultColorType;
dossierTemplateId: string;
}
@Component({
selector: 'redaction-edit-color-dialog',
templateUrl: './edit-color-dialog.component.html',
styleUrls: ['./edit-color-dialog.component.scss'],
})
export class EditColorDialogComponent extends BaseDialogComponent {
readonly colors: IColors;
readonly colorKey: DefaultColorType;
form: FormGroup;
readonly form: FormGroup;
translations = defaultColorsTranslations;
private readonly _initialColor: string;
private readonly _dossierTemplateId: string;
@ -28,33 +31,35 @@ export class EditColorDialogComponent extends BaseDialogComponent {
private readonly _translateService: TranslateService,
private readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
@Inject(MAT_DIALOG_DATA)
private readonly _data: { colors: IColors; colorKey: DefaultColorType; dossierTemplateId: string },
readonly data: IEditColorData,
) {
super();
this.colors = _data.colors;
this.colorKey = _data.colorKey;
this._dossierTemplateId = _data.dossierTemplateId;
this._initialColor = _data.colors[this.colorKey];
this._dossierTemplateId = data.dossierTemplateId;
this._initialColor = data.colors[data.colorKey];
this.form = this._formBuilder.group({
color: [this.colors[this.colorKey], [Validators.required, Validators.minLength(7)]],
});
this.form = this._getForm();
}
get changed(): boolean {
return this.form.get('color').value !== this._initialColor;
}
private _getForm(): FormGroup {
return this._formBuilder.group({
color: [this.data.colors[this.data.colorKey], [Validators.required, Validators.minLength(7)]],
});
}
async save() {
const colors = {
...this.colors,
[this.colorKey]: this.form.get('color').value,
...this.data.colors,
[this.data.colorKey]: this.form.get('color').value,
};
try {
await this._dictionaryService.setColors(colors, this._dossierTemplateId).toPromise();
this._dialogRef.close(true);
const color = this._translateService.instant(defaultColorsTranslations[this.colorKey]);
const color = this._translateService.instant(defaultColorsTranslations[this.data.colorKey]);
this._toaster.info(_('edit-color-dialog.success'), { params: { color: color } });
} catch (e) {
this._toaster.error(_('edit-color-dialog.error'));

View File

@ -10,7 +10,7 @@
</div>
</div>
<div class="right">
<form (submit)="changedParseConfig && readFile()" [formGroup]="baseConfigForm">
<form (submit)="changedParseConfig && readFile()" [formGroup]="form">
<div class="iqser-input-group required w-250">
<mat-form-field floatLabel="always">
<mat-label>{{ 'file-attributes-csv-import.key-column' | translate }}</mat-label>
@ -154,7 +154,7 @@
</div>
<div class="dialog-actions">
<button (click)="save()" [disabled]="changedParseConfig || baseConfigForm.invalid" color="primary" mat-flat-button>
<button (click)="save()" [disabled]="changedParseConfig || form.invalid" color="primary" mat-flat-button>
{{ 'file-attributes-csv-import.save.label' | translate }}
</button>

View File

@ -9,6 +9,12 @@ 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';
interface IFileAttributesCSVImportData {
readonly csv: File;
readonly dossierTemplateId: string;
readonly existingConfiguration: IFileAttributesConfig;
}
@Component({
templateUrl: './file-attributes-csv-import-dialog.component.html',
styleUrls: ['./file-attributes-csv-import-dialog.component.scss'],
@ -20,7 +26,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
parseResult: { data: any[]; errors: any[]; meta: any; fields: IField[] };
hoveredColumn: string;
activeFields: IField[] = [];
readonly baseConfigForm: FormGroup;
readonly form: FormGroup;
isSearchOpen = false;
previewExpanded = true;
filteredKeyOptions: Observable<string[]>;
@ -36,40 +42,37 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
private readonly _formBuilder: FormBuilder,
readonly dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
private readonly _fileAttributesService: FileAttributesService,
@Inject(MAT_DIALOG_DATA)
readonly data: {
readonly csv: File;
readonly dossierTemplateId: string;
readonly existingConfiguration: IFileAttributesConfig;
},
@Inject(MAT_DIALOG_DATA) readonly data: IFileAttributesCSVImportData,
) {
super(_injector);
this.baseConfigForm = this._formBuilder.group({
filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]],
delimiter: [undefined, Validators.required],
encoding: ['UTF-8', Validators.required],
});
this.form = this._getForm();
this.readFile();
}
get changedParseConfig(): boolean {
return (
this.initialParseConfig.delimiter !== this.baseConfigForm.get('delimiter').value ||
this.initialParseConfig.encoding !== this.baseConfigForm.get('encoding').value
this.initialParseConfig.delimiter !== this.form.get('delimiter').value ||
this.initialParseConfig.encoding !== this.form.get('encoding').value
);
}
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 => {
const parsedCsv = <any>event.target.result;
this.parseResult = Papa.parse(parsedCsv, {
header: true,
delimiter: this.baseConfigForm.get('delimiter').value,
delimiter: this.form.get('delimiter').value,
});
this.baseConfigForm.patchValue({ delimiter: this.parseResult.meta.delimiter });
this.form.patchValue({ delimiter: this.parseResult.meta.delimiter });
// Filter duplicate columns
if (this.parseResult?.data?.length) {
@ -91,8 +94,8 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
}
}
this.filteredKeyOptions = this.baseConfigForm.get('filenameMappingColumnHeaderName').valueChanges.pipe(
startWith(this.baseConfigForm.get('filenameMappingColumnHeaderName').value as string),
this.filteredKeyOptions = this.form.get('filenameMappingColumnHeaderName').valueChanges.pipe(
startWith(this.form.get('filenameMappingColumnHeaderName').value as string),
map((value: string) =>
this.allEntities
.filter(field => field.csvColumn.toLowerCase().indexOf(value.toLowerCase()) !== -1)
@ -104,17 +107,17 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
this.data.existingConfiguration &&
this.allEntities.find(entity => entity.csvColumn === this.data.existingConfiguration.filenameMappingColumnHeaderName)
) {
this.baseConfigForm.patchValue({
this.form.patchValue({
filenameMappingColumnHeaderName: this.data.existingConfiguration.filenameMappingColumnHeaderName,
});
}
this.initialParseConfig = {
delimiter: this.baseConfigForm.get('delimiter').value,
encoding: this.baseConfigForm.get('encoding').value,
delimiter: this.form.get('delimiter').value,
encoding: this.form.get('encoding').value,
};
});
reader.readAsText(this.data.csv, this.baseConfigForm.get('encoding').value);
reader.readAsText(this.data.csv, this.form.get('encoding').value);
}
getSample(csvColumn: string) {
@ -163,7 +166,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
}
const fileAttributes: IFileAttributesConfig = {
...this.baseConfigForm.getRawValue(),
...this.form.getRawValue(),
fileAttributeConfigs: [
...fileAttributeConfigs.filter(a => !this.allEntities.find(entity => entity.csvColumn === a.csvColumnHeader)),
...this.activeFields.map(field => ({

View File

@ -1,7 +1,7 @@
<section class="dialog">
<div class="dialog-header heading-l" translate="smtp-auth-config.title"></div>
<form (submit)="save()" [formGroup]="authForm">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group required w-300">
<label translate="smtp-auth-config.form.username"></label>
@ -20,7 +20,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="authForm.invalid" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'smtp-auth-config.actions.save' | translate }}
</button>
<div class="all-caps-label cancel" mat-dialog-close translate="smtp-auth-config.actions.cancel"></div>

View File

@ -10,21 +10,23 @@ import { ISmtpConfiguration } from '@red/domain';
styleUrls: ['./smtp-auth-dialog.component.scss'],
})
export class SmtpAuthDialogComponent {
authForm: FormGroup;
readonly form: FormGroup = this._getForm();
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _userService: UserService,
public dialogRef: MatDialogRef<SmtpAuthDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ISmtpConfiguration,
) {
this.authForm = this._formBuilder.group({
user: [data?.user || this._userService.currentUser.email, [Validators.required]],
password: [data?.password, Validators.required],
) {}
private _getForm(): FormGroup {
return this._formBuilder.group({
user: [this.data?.user || this._userService.currentUser.email, [Validators.required]],
password: [this.data?.password, Validators.required],
});
}
save() {
this.dialogRef.close(this.authForm.getRawValue());
this.dialogRef.close(this.form.getRawValue());
}
}

View File

@ -41,7 +41,7 @@
class="mr-0"
></redaction-pagination>
<div class="separator">·</div>
<form [formGroup]="filterForm">
<form [formGroup]="form">
<div class="iqser-input-group w-150 mr-20">
<mat-form-field class="no-label">
<mat-select formControlName="category">
@ -56,12 +56,12 @@
<mat-select formControlName="userId">
<mat-select-trigger>
<redaction-initials-avatar
*ngIf="filterForm.get('userId').value !== ALL_USERS"
[user]="filterForm.get('userId').value"
*ngIf="form.get('userId').value !== ALL_USERS"
[user]="form.get('userId').value"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
<div *ngIf="form.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-select-trigger>
<mat-option *ngFor="let userId of userIds" [value]="userId">
<redaction-initials-avatar

View File

@ -22,7 +22,7 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
readonly ALL_USERS = _('audit-screen.all-users');
readonly translations = auditCategoriesTranslations;
readonly currentUser = this._userService.currentUser;
filterForm: FormGroup;
readonly form: FormGroup = this._getForm();
categories: string[] = [];
userIds: Set<string>;
logs: IAuditResponse;
@ -44,18 +44,21 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
private readonly _auditService: AuditService,
) {
super(_injector);
this.filterForm = this._formBuilder.group({
this.addSubscription = this.form.valueChanges.subscribe(async value => {
if (!this._updateDateFilters(value)) {
await this._fetchData();
}
});
}
private _getForm(): FormGroup {
return this._formBuilder.group({
category: [this.ALL_CATEGORIES],
userId: [this.ALL_USERS],
from: [],
to: [],
});
this.addSubscription = this.filterForm.valueChanges.subscribe(async value => {
if (!this._updateDateFilters(value)) {
await this._fetchData();
}
});
}
get totalPages(): number {
@ -74,22 +77,22 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
}
private _updateDateFilters(value): boolean {
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.form, 'from', 'to')) {
return true;
}
this._previousFrom = this.filterForm.get('from').value;
this._previousTo = this.filterForm.get('to').value;
this._previousFrom = this.form.get('from').value;
this._previousTo = this.form.get('to').value;
return false;
}
private async _fetchData(page?: number) {
this._loadingService.start();
const promises = [];
const category = this.filterForm.get('category').value;
const userId = this.filterForm.get('userId').value;
const from = this.filterForm.get('from').value;
let to = this.filterForm.get('to').value;
const category = this.form.get('category').value;
const userId = this.form.get('userId').value;
const from = this.form.get('from').value;
let to = this.form.get('to').value;
if (to) {
to = to.clone().add(1, 'd');
}

View File

@ -13,7 +13,7 @@
<div class="content-inner">
<div class="content-container">
<div class="content-container-content">
<form *ngIf="digitalSignatureForm" [formGroup]="digitalSignatureForm" autocomplete="off">
<form *ngIf="form" [formGroup]="form" autocomplete="off">
<input #fileInput (change)="fileChanged($event, fileInput)" class="file-upload-input" hidden type="file" />
<iqser-empty-state
@ -76,7 +76,7 @@
<div [class.hidden]="!hasDigitalSignatureSet" class="changes-box">
<iqser-icon-button
(action)="saveDigitalSignature()"
[disabled]="digitalSignatureForm.invalid"
[disabled]="form.invalid"
[label]="'digital-signature-screen.action.save' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"

View File

@ -19,7 +19,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
readonly currentUser = this._userService.currentUser;
digitalSignature: IDigitalSignature;
digitalSignatureForm: FormGroup;
form: FormGroup;
digitalSignatureExists = false;
@ -36,12 +36,12 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
}
get hasDigitalSignatureSet() {
return this.digitalSignatureExists || !!this.digitalSignatureForm.get('base64EncodedPrivateKey').value;
return this.digitalSignatureExists || !!this.form.get('base64EncodedPrivateKey').value;
}
saveDigitalSignature() {
const digitalSignature = {
...this.digitalSignatureForm.getRawValue(),
...this.form.getRawValue(),
};
//adjusted for chrome auto-complete / password manager
digitalSignature.password = digitalSignature.keySecret;
@ -81,8 +81,8 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
fileReader.onload = () => {
const dataUrl = <string>fileReader.result;
const actualBase64Value = dataUrl.substring(lastIndexOfEnd(dataUrl, ';base64,'));
this.digitalSignatureForm.get('base64EncodedPrivateKey').setValue(actualBase64Value);
this.digitalSignatureForm.get('certificateName').setValue(file.name);
this.form.get('base64EncodedPrivateKey').setValue(actualBase64Value);
this.form.get('certificateName').setValue(file.name);
input.value = null;
};
fileReader.readAsDataURL(file);
@ -103,13 +103,13 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
},
)
.add(() => {
this._initForm();
this.form = this._getForm();
this._loadingService.stop();
});
}
private _initForm() {
this.digitalSignatureForm = this._formBuilder.group({
private _getForm(): FormGroup {
return this._formBuilder.group({
certificateName: [this.digitalSignature.certificateName, Validators.required],
contactInfo: this.digitalSignature.contactInfo,
location: this.digitalSignature.location,

View File

@ -0,0 +1,29 @@
<div class="dialog-header">
<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">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group">
<label translate="general-config-screen.app-name.label"></label>
<input
formControlName="auxiliaryName"
name="auxiliaryName"
placeholder="{{ 'general-config-screen.app-name.placeholder' | translate }}"
type="text"
/>
</div>
<div class="inline-input-group flex-align-items-center">
<mat-slide-toggle color="primary" formControlName="forgotPasswordFunctionEnabled"></mat-slide-toggle>
<span class="ml-8" translate="general-config-screen.general.form.forgot-password"></span>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid || !generalConfigurationChanged" color="primary" mat-flat-button type="submit">
{{ 'general-config-screen.actions.save' | translate }}
</button>
</div>
</form>

View File

@ -0,0 +1,71 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { AutoUnsubscribe, 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';
@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 {
private _initialConfiguration: IGeneralConfiguration;
readonly form: FormGroup = this._getForm();
constructor(
private readonly _loadingService: LoadingService,
private readonly _generalSettingsService: GeneralSettingsService,
private readonly _configService: ConfigService,
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();
}
get generalConfigurationChanged(): 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;
}
private async _loadData() {
this._loadingService.start();
try {
this._initialConfiguration = await this._generalSettingsService.getGeneralConfigurations().toPromise();
this.form.patchValue(this._initialConfiguration, { emitEvent: false });
} catch (e) {}
this._loadingService.stop();
}
}

View File

@ -22,156 +22,10 @@
<div class="content-inner">
<div class="content-container">
<div class="dialog mb-8">
<div class="dialog-header">
<div class="heading-l" translate="general-config-screen.general.title"></div>
<div translate="general-config-screen.general.subtitle"></div>
</div>
<form (submit)="saveGeneralConfig()" [formGroup]="configForm">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group">
<label translate="general-config-screen.app-name.label"></label>
<input
formControlName="auxiliaryName"
name="auxiliaryName"
placeholder="{{ 'general-config-screen.app-name.placeholder' | translate }}"
type="text"
/>
</div>
<div class="inline-input-group flex-align-items-center">
<mat-slide-toggle color="primary" formControlName="forgotPasswordFunctionEnabled"></mat-slide-toggle>
<span class="ml-8" translate="general-config-screen.general.form.forgot-password"></span>
</div>
</div>
</div>
<div class="dialog-actions">
<button
[disabled]="configForm.invalid || !generalConfigurationChanged"
color="primary"
mat-flat-button
type="submit"
>
{{ 'general-config-screen.actions.save' | translate }}
</button>
</div>
</form>
<redaction-general-config-form></redaction-general-config-form>
</div>
<div class="dialog mt-16">
<div class="dialog-header">
<div class="heading-l" translate="general-config-screen.title"></div>
<div translate="general-config-screen.subtitle"></div>
</div>
<form (submit)="save()" [formGroup]="smtpForm">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group required">
<label translate="general-config-screen.form.host"></label>
<input
formControlName="host"
name="host"
placeholder="{{ 'general-config-screen.form.host-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group w-100">
<label translate="general-config-screen.form.port"></label>
<input formControlName="port" name="port" type="number" />
</div>
<div class="iqser-input-group required">
<label translate="general-config-screen.form.from"></label>
<input
formControlName="from"
name="from"
placeholder="{{ 'general-config-screen.form.from-placeholder' | translate }}"
type="email"
/>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.from-display-name"></label>
<input
formControlName="fromDisplayName"
name="fromDisplayName"
placeholder="{{ 'general-config-screen.form.from-display-name-placeholder' | translate }}"
type="text"
/>
<span class="hint" translate="general-config-screen.form.from-display-name-hint"></span>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.reply-to"></label>
<input
formControlName="replyTo"
name="replyTo"
placeholder="{{ 'general-config-screen.form.reply-to-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.reply-to-display-name"></label>
<input
formControlName="replyToDisplayName"
name="replyToDisplayName"
placeholder="{{ 'general-config-screen.form.reply-to-display-name-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.envelope-from"></label>
<input
formControlName="envelopeFrom"
name="envelopeFrom"
placeholder="{{ 'general-config-screen.form.envelope-from-placeholder' | translate }}"
type="text"
/>
<span class="hint" translate="general-config-screen.form.envelope-from-hint"></span>
</div>
</div>
<div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.ssl"></label>
<mat-slide-toggle color="primary" formControlName="ssl"></mat-slide-toggle>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.starttls"></label>
<mat-slide-toggle color="primary" formControlName="starttls"></mat-slide-toggle>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.auth"></label>
<mat-slide-toggle color="primary" formControlName="auth"></mat-slide-toggle>
</div>
<div
(click)="openAuthConfigDialog(true)"
*ngIf="smtpForm.get('auth').value"
class="link-action"
translate="general-config-screen.form.change-credentials"
></div>
</div>
</div>
<div class="dialog-actions">
<button
[disabled]="smtpForm.invalid || !smtpConfigurationChanged"
color="primary"
mat-flat-button
type="submit"
>
{{ 'general-config-screen.actions.save' | translate }}
</button>
<iqser-icon-button
(action)="testConnection()"
[disabled]="smtpForm.invalid"
[label]="'general-config-screen.actions.test-connection' | translate"
[type]="iconButtonTypes.dark"
></iqser-icon-button>
</div>
</form>
<redaction-smtp-form></redaction-smtp-form>
</div>
</div>
</div>

View File

@ -1,152 +1,13 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { IGeneralConfiguration, ISmtpConfiguration } from '@red/domain';
import { ConfigService } from '@services/config.service';
import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Component } from '@angular/core';
import { UserService } from '@services/user.service';
import { GeneralSettingsService } from '@services/general-settings.service';
import { SmtpConfigService } from '../../services/smtp-config.service';
@Component({
selector: 'redaction-general-config-screen',
templateUrl: './general-config-screen.component.html',
styleUrls: ['./general-config-screen.component.scss'],
})
export class GeneralConfigScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
readonly iconButtonTypes = IconButtonTypes;
export class GeneralConfigScreenComponent {
readonly currentUser = this._userService.currentUser;
readonly configForm: FormGroup;
readonly smtpForm: FormGroup;
private _initialGeneralConfiguration: IGeneralConfiguration;
private _initialSMTPConfiguration: ISmtpConfiguration;
constructor(
private readonly _toaster: Toaster,
private readonly _userService: UserService,
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
private readonly _dialogService: AdminDialogService,
private readonly _configService: ConfigService,
private readonly _smtpConfigService: SmtpConfigService,
private readonly _generalSettingsService: GeneralSettingsService,
) {
super();
this.configForm = this._formBuilder.group({
forgotPasswordFunctionEnabled: [false],
auxiliaryName: [undefined],
});
this.smtpForm = this._formBuilder.group({
host: [undefined, Validators.required],
port: [25],
from: [undefined, [Validators.required, Validators.email]],
fromDisplayName: [undefined],
replyTo: [undefined],
replyToDisplayName: [undefined],
envelopeFrom: [undefined],
ssl: [false],
starttls: [false],
auth: [false],
user: [undefined],
password: [undefined],
});
this.addSubscription = this.smtpForm.controls.auth.valueChanges.subscribe(auth => {
if (auth) {
this.openAuthConfigDialog();
}
});
}
get smtpConfigurationChanged(): boolean {
if (!this._initialSMTPConfiguration) {
return true;
}
for (const key of Object.keys(this.smtpForm.getRawValue())) {
if (this._initialSMTPConfiguration[key] !== this.smtpForm.get(key).value) {
return true;
}
}
return false;
}
get generalConfigurationChanged(): boolean {
if (!this._initialGeneralConfiguration) {
return true;
}
for (const key of Object.keys(this.configForm.getRawValue())) {
if (this._initialGeneralConfiguration[key] !== this.configForm.get(key).value) {
return true;
}
}
return false;
}
async ngOnInit() {
await this._loadData();
}
async save() {
this._loadingService.start();
await this._smtpConfigService.updateSMTPConfiguration(this.smtpForm.getRawValue()).toPromise();
this._initialSMTPConfiguration = this.smtpForm.getRawValue();
this._loadingService.stop();
}
async saveGeneralConfig() {
this._loadingService.start();
const configFormValues = this.configForm.getRawValue();
await this._generalSettingsService.updateGeneralConfigurations(configFormValues).toPromise();
this._initialGeneralConfiguration = await this._generalSettingsService.getGeneralConfigurations().toPromise();
this._configService.updateDisplayName(this._initialGeneralConfiguration.displayName);
this._loadingService.stop();
}
openAuthConfigDialog(skipDisableOnCancel?: boolean) {
this._dialogService.openDialog('smtpAuthConfig', null, this.smtpForm.getRawValue(), null, authConfig => {
if (authConfig) {
this.smtpForm.patchValue(authConfig);
} else if (!skipDisableOnCancel) {
this.smtpForm.patchValue({ auth: false }, { emitEvent: false });
}
});
}
async testConnection() {
this._loadingService.start();
try {
await this._smtpConfigService.testSMTPConfiguration(this.smtpForm.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._initialGeneralConfiguration = await this._generalSettingsService.getGeneralConfigurations().toPromise();
this.configForm.patchValue(this._initialGeneralConfiguration, { emitEvent: false });
} catch (e) {}
try {
this._initialSMTPConfiguration = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise();
this.smtpForm.patchValue(this._initialSMTPConfiguration, { emitEvent: false });
} catch (e) {}
this._loadingService.stop();
}
constructor(private readonly _userService: UserService) {}
}

View File

@ -0,0 +1,108 @@
<div class="dialog-header">
<div class="heading-l" translate="general-config-screen.title"></div>
<div translate="general-config-screen.subtitle"></div>
</div>
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group required">
<label translate="general-config-screen.form.host"></label>
<input
formControlName="host"
name="host"
placeholder="{{ 'general-config-screen.form.host-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group w-100">
<label translate="general-config-screen.form.port"></label>
<input formControlName="port" name="port" type="number" />
</div>
<div class="iqser-input-group required">
<label translate="general-config-screen.form.from"></label>
<input
formControlName="from"
name="from"
placeholder="{{ 'general-config-screen.form.from-placeholder' | translate }}"
type="email"
/>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.from-display-name"></label>
<input
formControlName="fromDisplayName"
name="fromDisplayName"
placeholder="{{ 'general-config-screen.form.from-display-name-placeholder' | translate }}"
type="text"
/>
<span class="hint" translate="general-config-screen.form.from-display-name-hint"></span>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.reply-to"></label>
<input
formControlName="replyTo"
name="replyTo"
placeholder="{{ 'general-config-screen.form.reply-to-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.reply-to-display-name"></label>
<input
formControlName="replyToDisplayName"
name="replyToDisplayName"
placeholder="{{ 'general-config-screen.form.reply-to-display-name-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.envelope-from"></label>
<input
formControlName="envelopeFrom"
name="envelopeFrom"
placeholder="{{ 'general-config-screen.form.envelope-from-placeholder' | translate }}"
type="text"
/>
<span class="hint" translate="general-config-screen.form.envelope-from-hint"></span>
</div>
</div>
<div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.ssl"></label>
<mat-slide-toggle color="primary" formControlName="ssl"></mat-slide-toggle>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.starttls"></label>
<mat-slide-toggle color="primary" formControlName="starttls"></mat-slide-toggle>
</div>
<div class="iqser-input-group">
<label translate="general-config-screen.form.auth"></label>
<mat-slide-toggle color="primary" formControlName="auth"></mat-slide-toggle>
</div>
<div
(click)="openAuthConfigDialog(true)"
*ngIf="form.get('auth').value"
class="link-action"
translate="general-config-screen.form.change-credentials"
></div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid || !smtpConfigurationChanged" color="primary" mat-flat-button type="submit">
{{ 'general-config-screen.actions.save' | translate }}
</button>
<iqser-icon-button
(action)="testConnection()"
[disabled]="form.invalid"
[label]="'general-config-screen.actions.test-connection' | translate"
[type]="iconButtonTypes.dark"
></iqser-icon-button>
</div>
</form>

View File

@ -0,0 +1,108 @@
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 { AdminDialogService } from '../../../services/admin-dialog.service';
import { SmtpConfigService } from '../../../services/smtp-config.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
selector: 'redaction-smtp-form',
templateUrl: './smtp-form.component.html',
styleUrls: ['./smtp-form.component.scss'],
})
export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
readonly iconButtonTypes = IconButtonTypes;
private _initialConfiguration: ISmtpConfiguration;
readonly form: FormGroup = this._getForm();
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
private readonly _smtpConfigService: SmtpConfigService,
private readonly _loadingService: LoadingService,
private readonly _toaster: Toaster,
) {
super();
this.addSubscription = this.form.controls.auth.valueChanges.subscribe(auth => {
if (auth) {
this.openAuthConfigDialog();
}
});
}
async ngOnInit(): Promise<void> {
await this._loadData();
}
private _getForm(): FormGroup {
return this._formBuilder.group({
host: [undefined, Validators.required],
port: [25],
from: [undefined, [Validators.required, Validators.email]],
fromDisplayName: [undefined],
replyTo: [undefined],
replyToDisplayName: [undefined],
envelopeFrom: [undefined],
ssl: [false],
starttls: [false],
auth: [false],
user: [undefined],
password: [undefined],
});
}
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.form.patchValue(this._initialConfiguration, { emitEvent: false });
} catch (e) {}
this._loadingService.stop();
}
}

View File

@ -13,7 +13,7 @@ import { BaseDialogComponent, LoadingService } from '@iqser/common-ui';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddEditJustificationDialogComponent extends BaseDialogComponent {
form: FormGroup;
readonly form: FormGroup = this._getForm();
constructor(
private readonly _formBuilder: FormBuilder,
@ -24,7 +24,10 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
@Inject(MAT_DIALOG_DATA) public justification: Justification,
) {
super();
this.form = this._formBuilder.group({
}
private _getForm(): FormGroup {
return this._formBuilder.group({
name: [{ value: this.justification?.name, disabled: !!this.justification }, Validators.required],
reason: [this.justification?.reason, Validators.required],
description: [this.justification?.description, Validators.required],

View File

@ -24,7 +24,7 @@
<div *ngIf="changed && permissionsService.isAdmin()" class="changes-box">
<iqser-icon-button
(action)="save()"
[disabled]="configForm.invalid"
[disabled]="form.invalid"
[label]="'watermark-screen.action.save' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
@ -35,7 +35,7 @@
<div class="right-container" iqserHasScrollbar>
<div class="heading-xl" translate="watermark-screen.title"></div>
<form (keyup)="configChanged()" [formGroup]="configForm">
<form (keyup)="configChanged()" [formGroup]="form">
<div class="iqser-input-group w-300">
<textarea
(mousemove)="triggerChanges()"
@ -55,8 +55,8 @@
<div
(click)="setValue('orientation', option)"
*ngFor="let option of ['VERTICAL', 'HORIZONTAL', 'DIAGONAL']"
[class.active]="configForm.get('orientation').value === option"
[class.disabled]="configForm.get('orientation').disabled"
[class.active]="form.get('orientation').value === option"
[class.disabled]="form.get('orientation').disabled"
[ngClass]="option"
>
<span>ABC</span>
@ -85,17 +85,17 @@
/>
<div
(colorPickerChange)="setValue('hexColor', $event)"
[class.disabled]="configForm.get('hexColor').disabled"
[colorPicker]="configForm.get('hexColor').value"
[cpDisabled]="configForm.get('hexColor').disabled"
[class.disabled]="form.get('hexColor').disabled"
[colorPicker]="form.get('hexColor').value"
[cpDisabled]="form.get('hexColor').disabled"
[cpOutputFormat]="'hex'"
[cpPosition]="'top-right'"
[cpUseRootViewContainer]="true"
[style.background]="configForm.get('hexColor').value"
[style.background]="form.get('hexColor').value"
class="input-icon"
>
<mat-icon
*ngIf="!configForm.get('hexColor')?.value || configForm.get('hexColor').value?.length === 0"
*ngIf="!form.get('hexColor')?.value || form.get('hexColor').value?.length === 0"
svgIcon="red:color-picker"
></mat-icon>
</div>
@ -113,8 +113,8 @@
{ value: 'courier', display: 'Courier' }
]
"
[class.active]="configForm.get('fontType').value === option.value"
[class.disabled]="configForm.get('fontType').disabled"
[class.active]="form.get('fontType').value === option.value"
[class.disabled]="form.get('fontType').disabled"
[ngClass]="option.value"
>
{{ option.display }}

View File

@ -10,7 +10,7 @@ import { BASE_HREF } from '../../../../tokens';
import { stampPDFPage } from '@utils/page-stamper';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { WatermarkService } from '../../../shared/services/watermark.service';
import { WatermarkService } from '@shared/services/watermark.service';
export const DEFAULT_WATERMARK: IWatermark = {
text: null,
@ -28,7 +28,7 @@ export const DEFAULT_WATERMARK: IWatermark = {
})
export class WatermarkScreenComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
configForm: FormGroup;
readonly form: FormGroup = this._getForm();
private _instance: WebViewerInstance;
private _watermark: IWatermark = {};
@ViewChild('viewer', { static: true })
@ -46,7 +46,6 @@ export class WatermarkScreenComponent implements OnInit {
private readonly _loadingService: LoadingService,
) {
this._loadingService.start();
this._initForm();
}
get changed(): boolean {
@ -54,7 +53,7 @@ export class WatermarkScreenComponent implements OnInit {
return true;
}
for (const key of Object.keys(this._watermark)) {
if (this._watermark[key] !== this.configForm.get(key)?.value) {
if (this._watermark[key] !== this.form.get(key)?.value) {
return true;
}
}
@ -72,7 +71,7 @@ export class WatermarkScreenComponent implements OnInit {
save() {
const watermark = {
...this.configForm.getRawValue(),
...this.form.getRawValue(),
};
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
@ -93,15 +92,15 @@ export class WatermarkScreenComponent implements OnInit {
}
async revert() {
this.configForm.setValue({ ...this._watermark });
this.form.setValue({ ...this._watermark });
await this.configChanged();
}
triggerChanges() {}
async setValue(type: 'fontType' | 'orientation' | 'hexColor', value: any) {
if (!this.configForm.get(type).disabled) {
this.configForm.get(type).setValue(value);
if (!this.form.get(type).disabled) {
this.form.get(type).setValue(value);
await this.configChanged();
}
}
@ -110,12 +109,12 @@ export class WatermarkScreenComponent implements OnInit {
this._watermarkService.getWatermark(this._dossierTemplatesService.activeDossierTemplateId).subscribe(
watermark => {
this._watermark = watermark;
this.configForm.setValue({ ...this._watermark });
this.form.setValue({ ...this._watermark });
this._loadViewer();
},
() => {
this._watermark = DEFAULT_WATERMARK;
this.configForm.setValue({ ...this._watermark });
this.form.setValue({ ...this._watermark });
this._loadViewer();
},
);
@ -162,12 +161,12 @@ export class WatermarkScreenComponent implements OnInit {
const pdfNet = this._instance.Core.PDFNet;
const document = await this._instance.Core.documentViewer.getDocument().getPDFDoc();
const text = this.configForm.get('text').value || '';
const fontSize = this.configForm.get('fontSize').value;
const fontType = this.configForm.get('fontType').value;
const orientation: WatermarkOrientation = this.configForm.get('orientation').value;
const opacity = this.configForm.get('opacity').value;
const color = this.configForm.get('hexColor').value;
const text = this.form.get('text').value || '';
const fontSize = this.form.get('fontSize').value;
const fontType = this.form.get('fontType').value;
const orientation: WatermarkOrientation = this.form.get('orientation').value;
const opacity = this.form.get('opacity').value;
const color = this.form.get('hexColor').value;
await stampPDFPage(document, pdfNet, text, fontSize, fontType, orientation, opacity, color, [1]);
this._instance.Core.documentViewer.refreshAll();
@ -175,8 +174,8 @@ export class WatermarkScreenComponent implements OnInit {
this._changeDetectorRef.detectChanges();
}
private _initForm() {
this.configForm = this._formBuilder.group({
private _getForm(): FormGroup {
return this._formBuilder.group({
text: [{ value: null, disabled: !this.permissionsService.isAdmin() }],
hexColor: [
{

View File

@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnD
import { PermissionsService } from '@services/permissions.service';
import { ConfigService } from '@services/config.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { ViewedPagesService } from '../../shared/services/viewed-pages.service';
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
import { File, IViewedPage } from '@red/domain';
import { AutoUnsubscribe } from '@iqser/common-ui';
import { FilesMapService } from '@services/entity-services/files-map.service';

View File

@ -14,6 +14,7 @@
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="true"
[dossierId]="dossier.dossierId"
[largeSpacing]="true"
[memberIds]="selectedApproversList"
[perLine]="13"
@ -27,6 +28,7 @@
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="true"
[dossierId]="dossier.dossierId"
[largeSpacing]="true"
[memberIds]="selectedReviewers$ | async"
[perLine]="13"

View File

@ -1,5 +1,5 @@
<section class="dialog">
<form (submit)="saveDossier()" [formGroup]="dossierForm">
<form (submit)="saveDossier()" [formGroup]="form">
<div class="dialog-header heading-l" translate="add-dossier-dialog.header-new"></div>
<div class="dialog-content">

View File

@ -16,7 +16,7 @@ import { ReportTemplateService } from '@services/report-template.service';
export class AddDossierDialogComponent {
readonly iconButtonTypes = IconButtonTypes;
dossierForm: FormGroup;
readonly form: FormGroup;
hasDueDate = false;
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'].map((type: DownloadFileType) => ({
key: type,
@ -33,7 +33,11 @@ export class AddDossierDialogComponent {
readonly dialogRef: MatDialogRef<AddDossierDialogComponent>,
) {
this._getDossierTemplates();
this.dossierForm = this._formBuilder.group(
this.form = this._getForm();
}
private _getForm(): FormGroup {
return this._formBuilder.group(
{
dossierName: [null, Validators.required],
dossierTemplateId: [null, Validators.required],
@ -54,19 +58,19 @@ export class AddDossierDialogComponent {
}
get reportTemplateIdsLength() {
return this.dossierForm.controls['reportTemplateIds']?.value?.length || 0;
return this.form.controls['reportTemplateIds']?.value?.length || 0;
}
get downloadFileTypesLength() {
return this.dossierForm.controls['downloadFileTypes']?.value?.length || 0;
return this.form.controls['downloadFileTypes']?.value?.length || 0;
}
get disabled() {
if (this.hasDueDate && this.dossierForm.get('dueDate').value === null) {
if (this.hasDueDate && this.form.get('dueDate').value === null) {
return true;
}
return this.dossierForm.invalid;
return this.form.invalid;
}
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
@ -86,7 +90,7 @@ export class AddDossierDialogComponent {
this.availableReportTypes =
(await this._reportTemplateController.getAvailableReportTemplates(dossierTemplate.dossierTemplateId).toPromise()) || [];
// update dropdown values
this.dossierForm.patchValue(
this.form.patchValue(
{
downloadFileTypes: dossierTemplate.downloadFileTypes,
reportTemplateIds: [], // TODO DEFAULT
@ -95,7 +99,7 @@ export class AddDossierDialogComponent {
);
} else {
this.availableReportTypes = [];
this.dossierForm.patchValue(
this.form.patchValue(
{
downloadFileTypes: [],
reportTemplateIds: [],
@ -117,14 +121,14 @@ export class AddDossierDialogComponent {
private _formToObject(): IDossierRequest {
return {
dossierName: this.dossierForm.get('dossierName').value,
description: this.dossierForm.get('description').value,
dueDate: this.hasDueDate ? this.dossierForm.get('dueDate').value : undefined,
dossierTemplateId: this.dossierForm.get('dossierTemplateId').value,
downloadFileTypes: this.dossierForm.get('downloadFileTypes').value,
reportTemplateIds: this.dossierForm.get('reportTemplateIds').value,
watermarkEnabled: this.dossierForm.get('watermarkEnabled').value,
watermarkPreviewEnabled: this.dossierForm.get('watermarkPreviewEnabled').value,
dossierName: this.form.get('dossierName').value,
description: this.form.get('description').value,
dueDate: this.hasDueDate ? this.form.get('dueDate').value : undefined,
dossierTemplateId: this.form.get('dossierTemplateId').value,
downloadFileTypes: this.form.get('downloadFileTypes').value,
reportTemplateIds: this.form.get('reportTemplateIds').value,
watermarkEnabled: this.form.get('watermarkEnabled').value,
watermarkPreviewEnabled: this.form.get('watermarkPreviewEnabled').value,
};
}
}

View File

@ -22,7 +22,7 @@ class DialogData {
styleUrls: ['./assign-reviewer-approver-dialog.component.scss'],
})
export class AssignReviewerApproverDialogComponent {
form: FormGroup;
readonly form: FormGroup;
dossier: Dossier;
constructor(
@ -37,7 +37,7 @@ export class AssignReviewerApproverDialogComponent {
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
) {
this.dossier = this._dossiersService.find(this.data.files[0].dossierId);
this._loadData();
this.form = this._getForm();
}
get selectedUser(): string {
@ -103,23 +103,30 @@ export class AssignReviewerApproverDialogComponent {
* the id of the current reviewer of the files list if there is only one reviewer for all of them;
* or the id of the current user
**/
private _loadData() {
private get _uniqueReviewers(): Set<string> {
const uniqueReviewers = new Set<string>();
for (const file of this.data.files) {
if (file.currentReviewer) {
uniqueReviewers.add(file.currentReviewer);
}
}
let user: string = uniqueReviewers.size === 1 ? uniqueReviewers.values().next().value : this.userService.currentUser.id;
user = this.userOptions.indexOf(user) >= 0 ? user : this.userOptions[0];
return uniqueReviewers;
}
private get _user(): string {
let user: string = this._uniqueReviewers.size === 1 ? this._uniqueReviewers.values().next().value : this.userService.currentUser.id;
user = this.userOptions.indexOf(user) >= 0 ? user : this.userOptions[0];
if (this.data.withCurrentUserAsDefault && this.userOptions.includes(this.userService.currentUser.id)) {
user = this.userService.currentUser.id;
}
return user;
}
this.form = this._formBuilder.group({
private _getForm(): FormGroup {
return this._formBuilder.group({
// Allow a null reviewer if a previous reviewer exists (= it's not the first assignment) & current user is allowed to unassign
user: [user, this._canUnassignFiles && !user ? Validators.required : null],
user: [this._user, this._canUnassignFiles && !this._user ? Validators.required : null],
});
}
}

View File

@ -1,5 +1,5 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="legalBasisForm">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-header heading-l" translate="change-legal-basis-dialog.header"></div>
<div class="dialog-content">
@ -18,7 +18,7 @@
<div class="iqser-input-group w-400">
<label translate="change-legal-basis-dialog.content.legalBasis"></label>
<input [value]="legalBasisForm.get('reason').value?.legalBasis" disabled type="text" />
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
</div>
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
@ -28,7 +28,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="!legalBasisForm.valid || !changed" color="primary" mat-flat-button type="submit">
<button [disabled]="!form.valid || !changed" color="primary" mat-flat-button type="submit">
{{ 'change-legal-basis-dialog.actions.save' | translate }}
</button>
<div class="all-caps-label cancel" mat-dialog-close translate="change-legal-basis-dialog.actions.cancel"></div>

View File

@ -17,7 +17,7 @@ export interface LegalBasisOption {
templateUrl: './change-legal-basis-dialog.component.html',
})
export class ChangeLegalBasisDialogComponent implements OnInit {
legalBasisForm: FormGroup;
form: FormGroup = this._getForm();
isDocumentAdmin: boolean;
legalOptions: LegalBasisOption[] = [];
@ -31,17 +31,10 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
) {}
get changed(): boolean {
return this.legalBasisForm.get('reason').value.legalBasis !== this._data.annotations[0].legalBasis;
return this.form.get('reason').value.legalBasis !== this._data.annotations[0].legalBasis;
}
async ngOnInit() {
this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier);
this.legalBasisForm = this._formBuilder.group({
reason: [null, Validators.required],
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
});
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
this.legalOptions = data
@ -52,15 +45,23 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
}))
.sort((a, b) => a.label.localeCompare(b.label));
this.legalBasisForm.patchValue({
this.form.patchValue({
reason: this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis),
});
}
private _getForm(): FormGroup {
this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier);
return this._formBuilder.group({
reason: [null, Validators.required],
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
});
}
save() {
this.dialogRef.close({
legalBasis: this.legalBasisForm.get('reason').value.legalBasis,
comment: this.legalBasisForm.get('comment').value,
legalBasis: this.form.get('reason').value.legalBasis,
comment: this.form.get('comment').value,
});
}
}

View File

@ -1,7 +1,7 @@
<section *ngIf="!!documentInfoForm" class="dialog">
<section *ngIf="!!form" class="dialog">
<div class="dialog-header heading-l" translate="document-info.title"></div>
<form (submit)="saveDocumentInfo()" [formGroup]="documentInfoForm">
<form (submit)="saveDocumentInfo()" [formGroup]="form">
<div class="dialog-content">
<div *ngFor="let attr of attributes" class="iqser-input-group w-300">
<label>{{ attr.label }}</label>
@ -9,7 +9,7 @@
</div>
</div>
<div class="dialog-actions">
<button [disabled]="documentInfoForm.invalid" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'document-info.save' | translate }}
</button>
</div>

View File

@ -11,8 +11,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
styleUrls: ['./document-info-dialog.component.scss'],
})
export class DocumentInfoDialogComponent implements OnInit {
documentInfoForm: FormGroup;
file: IFile;
form: FormGroup;
attributes: IFileAttributeConfig[];
private readonly _dossier: Dossier;
@ -25,31 +24,35 @@ export class DocumentInfoDialogComponent implements OnInit {
public dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: IFile,
) {
this.file = this.data;
this._dossier = this._dossiersService.find(this.file.dossierId);
this._dossier = this._dossiersService.find(this.data.dossierId);
}
async ngOnInit() {
this.attributes = (
await this._fileAttributesService.getFileAttributesConfig(this._dossier.dossierTemplateId).toPromise()
).fileAttributeConfigs.filter(attr => attr.editable);
const formConfig = this.attributes.reduce(
(acc, attr) => ({
...acc,
[attr.id]: [this.file.fileAttributes?.attributeIdToValue[attr.id]],
}),
{},
this.form = this._getForm();
}
private _getForm(): FormGroup {
return this._formBuilder.group(
this.attributes.reduce(
(acc, attr) => ({
...acc,
[attr.id]: [this.data.fileAttributes?.attributeIdToValue[attr.id]],
}),
{},
),
);
this.documentInfoForm = this._formBuilder.group(formConfig);
}
async saveDocumentInfo() {
const attributeIdToValue = {
...this.file.fileAttributes?.attributeIdToValue,
...this.documentInfoForm.getRawValue(),
...this.data.fileAttributes?.attributeIdToValue,
...this.form.getRawValue(),
};
await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId).toPromise();
this.file.fileAttributes = { attributeIdToValue };
await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise();
this.data.fileAttributes = { attributeIdToValue };
this.dialogRef.close(true);
}
}

View File

@ -1,4 +1,4 @@
<form (submit)="save()" *ngIf="attributesForm" [formGroup]="attributesForm">
<form (submit)="save()" *ngIf="form" [formGroup]="form">
<div>
<div *ngIf="customAttributes.length" class="heading">
{{ 'edit-dossier-dialog.attributes.custom-attributes' | translate }}
@ -10,16 +10,20 @@
icon="red:attribute"
></iqser-empty-state>
<div *ngFor="let attr of customAttributes" [class.datepicker-wrapper]="isDate(attr)" class="iqser-input-group">
<div
*ngFor="let attr of customAttributes"
[class.datepicker-wrapper]="isSpecificType(attr, dossierAttributeConfigTypes.DATE)"
class="iqser-input-group"
>
<label>{{ attr.label }}</label>
<input
*ngIf="isNumber(attr) || isText(attr)"
*ngIf="isSpecificType(attr, dossierAttributeConfigTypes.NUMBER) || isSpecificType(attr, dossierAttributeConfigTypes.TEXT)"
[formControlName]="attr.id"
[name]="attr.id"
[type]="isNumber(attr) ? 'number' : 'text'"
[type]="isSpecificType(attr, dossierAttributeConfigTypes.NUMBER) ? 'number' : 'text'"
/>
<ng-container *ngIf="isDate(attr)">
<ng-container *ngIf="isSpecificType(attr, dossierAttributeConfigTypes.DATE)">
<input [formControlName]="attr.id" [matDatepicker]="picker" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="picker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>

View File

@ -1,6 +1,6 @@
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren } from '@angular/core';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { Dossier, DossierAttributeWithValue } from '@red/domain';
import { Dossier, DossierAttributeConfigType, DossierAttributeConfigTypes, DossierAttributeWithValue } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { CircleButtonTypes, IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { FormBuilder, FormGroup } from '@angular/forms';
@ -15,6 +15,7 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d
export class EditDossierAttributesComponent implements EditDossierSectionInterface, OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly dossierAttributeConfigTypes = DossierAttributeConfigTypes;
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
@ -22,7 +23,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
imageAttributes: DossierAttributeWithValue[] = [];
attributes: DossierAttributeWithValue[] = [];
attributesForm: FormGroup;
form: FormGroup;
@ViewChildren('fileInput') private _fileInputs: QueryList<ElementRef>;
constructor(
@ -34,7 +35,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
get changed() {
for (const attr of this.attributes) {
if (this.isDate(attr) && attr.value) {
if (this.isSpecificType(attr, this.dossierAttributeConfigTypes.DATE) && attr.value) {
if (!moment(attr.value).isSame(moment(this.currentAttrValue(attr)))) {
return true;
}
@ -57,7 +58,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
async ngOnInit() {
this._loadingService.start();
await this._loadAttributes();
this._initForm();
this.form = this._getForm();
this._loadingService.stop();
}
@ -75,20 +76,8 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
this._getFileInputById(attr.id).nativeElement.click();
}
isNumber(attr: DossierAttributeWithValue): boolean {
return attr.type === 'NUMBER';
}
isDate(attr: DossierAttributeWithValue): boolean {
return attr.type === 'DATE';
}
isImage(attr: DossierAttributeWithValue): boolean {
return attr.type === 'IMAGE';
}
isText(attr: DossierAttributeWithValue): boolean {
return attr.type === 'TEXT';
isSpecificType(attr: DossierAttributeWithValue, type: DossierAttributeConfigType): boolean {
return attr.type === type;
}
async uploadImage($event, attr: DossierAttributeWithValue) {
@ -102,22 +91,22 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
const image = $event.target.files[0];
const result = await toBase64(image);
this.attributesForm.patchValue({
this.form.patchValue({
[attr.id]: result,
});
this._getFileInputById(attr.id).nativeElement.value = null;
}
revert() {
this._initForm();
this.form = this._getForm();
}
currentAttrValue(attr: DossierAttributeWithValue): string {
return this.attributesForm.get(attr.id).value;
return this.form.get(attr.id).value;
}
deleteAttr(attr: DossierAttributeWithValue) {
this.attributesForm.patchValue({
this.form.patchValue({
[attr.id]: null,
});
}
@ -132,15 +121,15 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
private async _loadAttributes() {
this.attributes = await this._dossierAttributesService.getWithValues(this.dossier);
this.customAttributes = this.attributes.filter(attr => !this.isImage(attr));
this.imageAttributes = this.attributes.filter(attr => this.isImage(attr));
this.customAttributes = this.attributes.filter(attr => !this.isSpecificType(attr, this.dossierAttributeConfigTypes.IMAGE));
this.imageAttributes = this.attributes.filter(attr => this.isSpecificType(attr, this.dossierAttributeConfigTypes.IMAGE));
}
private _initForm() {
private _getForm(): FormGroup {
const controlsConfig = {};
for (const attribute of this.attributes) {
controlsConfig[attribute.id] = [{ value: attribute.value, disabled: !this.canEdit }];
}
this.attributesForm = this._formBuilder.group(controlsConfig);
return this._formBuilder.group(controlsConfig);
}
}

View File

@ -19,9 +19,8 @@ import { getLeftDateTime } from '@utils/functions';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { AppStateService } from '@state/app-state.service';
import { FilesService } from '@services/entity-services/files.service';
import { FileManagementService } from '../../../shared/services/file-management.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
interface FileListItem extends IFile, IListable {
readonly canRestore: boolean;
@ -56,7 +55,6 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
constructor(
protected readonly _injector: Injector,
private readonly _fileManagementService: FileManagementService,
private readonly _appStateService: AppStateService,
private readonly _filesService: FilesService,
private readonly _loadingService: LoadingService,
private readonly _configService: ConfigService,
@ -115,7 +113,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
const fileIds = files.map(f => f.fileId);
await this._fileManagementService.restore(fileIds, this.dossier.id).toPromise();
this._removeFromList(fileIds);
await this._appStateService.reloadDossierFiles(files[0].dossierId);
await this._filesService.loadAll(files[0].dossierId).toPromise();
this.updateDossier.emit();
}

View File

@ -4,7 +4,6 @@ import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { PermissionsService } from '@services/permissions.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionaryService } from '@shared/services/dictionary.service';
import { FormBuilder } from '@angular/forms';
import { CircleButtonTypes, LoadingService } from '@iqser/common-ui';
import { DossiersService } from '@services/entity-services/dossiers.service';
@ -27,7 +26,6 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
private readonly _dictionaryService: DictionaryService,
private readonly _permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,
private readonly _formBuilder: FormBuilder,
) {}
get changed() {

View File

@ -67,7 +67,11 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
this.availableReportTypes =
(await this._reportTemplateController.getAvailableReportTemplates(this.dossier.dossierTemplateId).toPromise()) || [];
this.dossierForm = this._formBuilder.group(
this.dossierForm = this._getForm();
}
private _getForm(): FormGroup {
return this._formBuilder.group(
{
reportTemplateIds: [this.dossier.reportTemplateIds],
downloadFileTypes: [this.dossier.downloadFileTypes],

View File

@ -68,7 +68,12 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
ngOnInit() {
this._filterInvalidDossierTemplates();
this.dossierForm = this._formBuilder.group({
this.dossierForm = this._getForm();
this.hasDueDate = !!this.dossier.dueDate;
}
private _getForm(): FormGroup {
return this._formBuilder.group({
dossierName: [this.dossier.dossierName, Validators.required],
dossierTemplateId: [
{
@ -82,7 +87,6 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
watermarkEnabled: [this.dossier.watermarkEnabled],
watermarkPreviewEnabled: [this.dossier.watermarkPreviewEnabled],
});
this.hasDueDate = !!this.dossier.dueDate;
}
revert() {

View File

@ -38,9 +38,13 @@ export class ForceRedactionDialogComponent implements OnInit {
public dialogRef: MatDialogRef<ForceRedactionDialogComponent>,
@Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier },
) {
this.redactionForm = this._getForm();
}
private _getForm(): FormGroup {
this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier);
this.redactionForm = this._formBuilder.group({
return this._formBuilder.group({
reason: [null, Validators.required],
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
});

View File

@ -44,21 +44,33 @@ export class ManualAnnotationDialogComponent implements OnInit {
this.isFalsePositiveRequest = this.data.manualRedactionEntryWrapper.type === 'FALSE_POSITIVE';
this.isDictionaryRequest = this.data.manualRedactionEntryWrapper.type === 'DICTIONARY' || this.isFalsePositiveRequest;
this.redactionForm = this._formBuilder.group({
this.redactionForm = this._getForm();
this.redactionDictionaries = this._redactionDictionaries;
}
private _getForm(): FormGroup {
return this._formBuilder.group({
reason: this.isDictionaryRequest ? [null] : [null, Validators.required],
dictionary: this.isDictionaryRequest
? [this.isFalsePositiveRequest ? 'false_positive' : null, Validators.required]
: ['manual', Validators.required],
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
});
}
for (const key of Object.keys(this._appStateService.dictionaryData[data.dossier.dossierTemplateId])) {
const dictionaryData = this._appStateService.getDictionary(key, data.dossier.dossierTemplateId);
private get _redactionDictionaries(): Dictionary[] {
const redactionDictionaries: Dictionary[] = [];
for (const key of Object.keys(this._appStateService.dictionaryData[this.data.dossier.dossierTemplateId])) {
const dictionaryData = this._appStateService.getDictionary(key, this.data.dossier.dossierTemplateId);
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
this.redactionDictionaries.push(dictionaryData);
redactionDictionaries.push(dictionaryData);
}
}
this.redactionDictionaries.sort((a, b) => a.label.localeCompare(b.label));
redactionDictionaries.sort((a, b) => a.label.localeCompare(b.label));
return redactionDictionaries;
}
get title() {

View File

@ -38,7 +38,6 @@ import { AnnotationsListComponent } from './components/file-workload/components/
import { AnnotationSourceComponent } from './components/file-workload/components/annotation-source/annotation-source.component';
import { OverlayModule } from '@angular/cdk/overlay';
import { SharedDossiersModule } from './shared/shared-dossiers.module';
import { PlatformSearchService } from './shared/services/platform-search.service';
import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { of } from 'rxjs';
@ -88,7 +87,6 @@ const services = [
PdfViewerDataService,
AnnotationDrawService,
AnnotationProcessingService,
PlatformSearchService,
];
@NgModule({

View File

@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { Dossier, File } from '@red/domain';
import { FileActionService } from '../../../../shared/services/file-action.service';
import { FileAssignService } from '../../../../shared/services/file-assign.service';
import { Observable } from 'rxjs';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { CircleButtonTypes, ConfirmationDialogInput, ListingService, LoadingService } from '@iqser/common-ui';
@ -10,8 +10,9 @@ import { TranslateService } from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { LongPressEvent } from '@shared/directives/long-press.directive';
import { UserPreferenceService } from '@services/user-preference.service';
import { FileManagementService } from '../../../../shared/services/file-management.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { ReanalysisService } from '@services/reanalysis.service';
import { FilesService } from '@services/entity-services/files.service';
@Component({
selector: 'redaction-dossier-overview-bulk-actions',
@ -33,11 +34,12 @@ export class DossierOverviewBulkActionsComponent {
private readonly _fileManagementService: FileManagementService,
private readonly _reanalysisService: ReanalysisService,
private readonly _permissionsService: PermissionsService,
private readonly _fileActionService: FileActionService,
private readonly _fileAssignService: FileAssignService,
private readonly _loadingService: LoadingService,
private readonly _translateService: TranslateService,
readonly listingService: ListingService<File>,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _filesService: FilesService,
) {}
get selectedFiles(): File[] {
@ -140,7 +142,13 @@ export class DossierOverviewBulkActionsComponent {
if (this.dossier.approverIds.length > 1) {
this._assignFiles('approver', true);
} else {
this._performBulkAction(this._fileActionService.setFilesUnderApproval(this.selectedFiles, this.dossier.approverIds[0]));
this._performBulkAction(
this._filesService.setUnderApprovalFor(
this.selectedFiles.map(f => f.id),
this.dossier.id,
this.dossier.approverIds[0],
),
);
}
}
@ -150,11 +158,21 @@ export class DossierOverviewBulkActionsComponent {
}
ocr() {
this._performBulkAction(this._fileActionService.ocrFiles(this.selectedFiles));
this._performBulkAction(
this._reanalysisService.ocrFiles(
this.selectedFiles.map(f => f.fileId),
this.dossier.id,
),
);
}
setToUnderReview() {
this._performBulkAction(this._fileActionService.setFilesUnderReview(this.selectedFiles));
this._performBulkAction(
this._filesService.setUnderReviewFor(
this.selectedFiles.map(f => f.id),
this.dossier.id,
),
);
}
approveDocuments() {
@ -168,16 +186,26 @@ export class DossierOverviewBulkActionsComponent {
question: _('confirmation-dialog.approve-multiple-files.question'),
}),
() => {
this._performBulkAction(this._fileActionService.setFilesApproved(this.selectedFiles));
this._performBulkAction(
this._filesService.setApprovedFor(
this.selectedFiles.map(f => f.id),
this.dossier.id,
),
);
},
);
} else {
this._performBulkAction(this._fileActionService.setFilesApproved(this.selectedFiles));
this._performBulkAction(
this._filesService.setApprovedFor(
this.selectedFiles.map(f => f.id),
this.dossier.id,
),
);
}
}
assignToMe() {
this._fileActionService.assignToMe(this.selectedFiles).then(() => {
this._fileAssignService.assignToMe(this.selectedFiles).then(() => {
this._loadingService.start();
this.reload.emit();
this._loadingService.stop();

View File

@ -34,15 +34,15 @@
<div>
<mat-icon svgIcon="red:template"></mat-icon>
<span>{{ dossierTemplate(dossier).name }} </span>
<span>{{ dossierTemplateName }} </span>
</div>
<div (click)="openDossierDictionaryDialog.emit()" class="link-property">
<div (click)="openEditDossierDialog('dossierDictionary')" class="link-property">
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span>{{ 'dossier-overview.dossier-details.dictionary' | translate }} </span>
</div>
<div (click)="openEditDossierAttributesDialog(dossier.dossierId, 'deletedDocuments')" class="link-property">
<div (click)="openEditDossierDialog('deletedDocuments')" class="link-property">
<mat-icon svgIcon="iqser:trash"></mat-icon>
<span>{{ 'dossier-overview.dossier-details.stats.deleted' | translate: { count: deletedFilesCount$ | async } }}</span>
</div>
@ -53,11 +53,7 @@
</div>
<ng-container *ngIf="attributesExpanded">
<div
(click)="openEditDossierAttributesDialog(dossier.dossierId, 'dossierAttributes')"
*ngFor="let attr of dossierAttributes"
class="link-property"
>
<div (click)="openEditDossierDialog('dossierAttributes')" *ngFor="let attr of dossierAttributes" class="link-property">
<mat-icon svgIcon="red:attribute"></mat-icon>
<span *ngIf="!attr.value"> {{ attr.label + ': -' }}</span>
<span *ngIf="attr.value && attr.type === 'DATE'"> {{ attr.label + ': ' + (attr.value | date: 'd MMM. yyyy') }}</span>

View File

@ -1,7 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Dossier, DossierAttributeWithValue, DossierStats, DossierTemplate } from '@red/domain';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { Dossier, DossierAttributeWithValue, DossierStats } from '@red/domain';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { FilesService } from '@services/entity-services/files.service';
import { Observable } from 'rxjs';
@ -13,17 +12,16 @@ import { FilesMapService } from '@services/entity-services/files-map.service';
selector: 'redaction-dossier-details-stats',
templateUrl: './dossier-details-stats.component.html',
styleUrls: ['./dossier-details-stats.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DossierDetailsStatsComponent implements OnInit {
@Input() dossierAttributes: DossierAttributeWithValue[];
@Input() dossier: Dossier;
attributesExpanded = false;
dossierTemplateName: string;
deletedFilesCount$: Observable<number>;
dossierStats$: Observable<DossierStats>;
@Input()
dossierAttributes: DossierAttributeWithValue[];
@Input()
dossier: Dossier;
@Output()
readonly openDossierDictionaryDialog = new EventEmitter();
constructor(
private readonly _dossierTemplatesService: DossierTemplatesService,
@ -31,7 +29,6 @@ export class DossierDetailsStatsComponent implements OnInit {
private readonly _filesService: FilesService,
private readonly _dossierStatsService: DossierStatsService,
private readonly _filesMapService: FilesMapService,
readonly dossiersService: DossiersService,
) {}
ngOnInit() {
@ -40,16 +37,13 @@ export class DossierDetailsStatsComponent implements OnInit {
switchMapTo(this._filesService.getDeletedFilesFor(this.dossier.id)),
map(files => files.length),
);
this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId).name;
}
dossierTemplate(dossier: Dossier): DossierTemplate {
return this._dossierTemplatesService.find(dossier.dossierTemplateId);
}
openEditDossierAttributesDialog(dossierId: string, section: string) {
this._dialogService.openDialog('editDossier', null, {
dossierId,
section: section,
openEditDossierDialog(section: string): void {
const data = { dossierId: this.dossier.dossierId, section };
this._dialogService.openDialog('editDossier', null, data, async () => {
await this._filesService.loadAll(this.dossier.dossierId).toPromise();
});
}
}

View File

@ -32,11 +32,7 @@
<div class="mt-16">
<div class="all-caps-label" translate="dossier-details.members"></div>
<redaction-team-members
(openAssignDossierMembersDialog)="openEditDossierDialog(dossier, 'members')"
[memberIds]="dossier.memberIds"
[perLine]="9"
></redaction-team-members>
<redaction-team-members [dossierId]="dossierId" [memberIds]="dossier.memberIds" [perLine]="9"></redaction-team-members>
</div>
<ng-container *ngIf="dossierStats$ | async as stats">
@ -65,11 +61,7 @@
</div>
<div [class.mt-24]="!stats.hasFiles" class="pb-32">
<redaction-dossier-details-stats
(openDossierDictionaryDialog)="openEditDossierDialog(dossier, 'dossierDictionary')"
[dossierAttributes]="dossierAttributes"
[dossier]="dossier"
></redaction-dossier-details-stats>
<redaction-dossier-details-stats [dossierAttributes]="dossierAttributes" [dossier]="dossier"></redaction-dossier-details-stats>
</div>
</ng-container>

View File

@ -13,6 +13,7 @@ import { Observable } from 'rxjs';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
import { pluck, switchMap } from 'rxjs/operators';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { FilesService } from '@services/entity-services/files.service';
@Component({
selector: 'redaction-dossier-details',
@ -40,6 +41,7 @@ export class DossierDetailsComponent {
readonly filterService: FilterService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _userService: UserService,
private readonly _filesService: FilesService,
private readonly _dossierStatsService: DossierStatsService,
private readonly _toaster: Toaster,
private readonly _dialogService: DossiersDialogService,
@ -77,11 +79,4 @@ export class DossierDetailsComponent {
const dossierName = dossier.dossierName;
this._toaster.info(_('assignment.owner'), { params: { ownerName, dossierName } });
}
openEditDossierDialog(dossier: Dossier, section: string): void {
const data = { dossierId: this.dossierId, section };
this._dialogService.openDialog('editDossier', null, data, async () => {
await this.appStateService.getFiles(dossier);
});
}
}

View File

@ -4,10 +4,10 @@
</div>
</div>
<div *ngIf="file.primaryAttribute" class="small-label">
<div *ngIf="primaryAttribute" class="small-label">
<div class="primary-attribute">
<span [matTooltip]="file.primaryAttribute" matTooltipPosition="above">
{{ file.primaryAttribute }}
<span [matTooltip]="primaryAttribute" matTooltipPosition="above">
{{ primaryAttribute }}
</span>
</div>
</div>

View File

@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { File } from '@red/domain';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
@Component({
selector: 'redaction-file-name-column',
@ -7,6 +8,24 @@ import { File } from '@red/domain';
styleUrls: ['./file-name-column.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileNameColumnComponent {
export class FileNameColumnComponent implements OnChanges {
@Input() file: File;
@Input() dossierTemplateId: string;
primaryAttribute: string;
constructor(private readonly _fileAttributesService: FileAttributesService) {}
ngOnChanges() {
const fileAttributesConfig = this._fileAttributesService.getFileAttributeConfig(this.dossierTemplateId);
if (fileAttributesConfig) {
const primary = fileAttributesConfig.fileAttributeConfigs?.find(c => c.primaryAttribute);
if (primary && this.file.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = this.file.fileAttributes?.attributeIdToValue[primary.id];
}
if (!this.primaryAttribute) {
this.primaryAttribute = '-';
}
}
}
}

View File

@ -1,5 +1,5 @@
<div class="cell">
<redaction-file-name-column [file]="file"></redaction-file-name-column>
<redaction-file-name-column [dossierTemplateId]="dossierTemplateId" [file]="file"></redaction-file-name-column>
</div>
<div class="cell">

View File

@ -11,5 +11,6 @@ import { Required } from '@iqser/common-ui';
export class TableItemComponent {
@Input() @Required() file!: File;
@Input() @Required() displayedAttributes!: IFileAttributeConfig[];
@Input() dossierTemplateId: string;
@Output() readonly calculateData = new EventEmitter<string>();
}

View File

@ -14,7 +14,7 @@ import {
} from '@iqser/common-ui';
import { File, IFileAttributeConfig, StatusSorter, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain';
import { workflowFileStatusTranslations } from '../../translations/file-status-translations';
import { FileActionService } from '../../shared/services/file-action.service';
import { FileAssignService } from '../../shared/services/file-assign.service';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -35,7 +35,7 @@ export class ConfigService {
private readonly _listingMode$ = new BehaviorSubject<ListingMode>(ListingModes.table);
constructor(
private readonly _fileActionService: FileActionService,
private readonly _fileAssignService: FileAssignService,
private readonly _filesService: FilesService,
private readonly _loadingService: LoadingService,
private readonly _appStateService: AppStateService,
@ -320,38 +320,41 @@ export class ConfigService {
_recentlyModifiedChecker = (file: File) =>
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
private _quickFilters(entities: File[]): NestedFilter[] {
let quickFilters: INestedFilter[] = [];
if (entities.filter(this._recentlyModifiedChecker).length > 0) {
const recentPeriod = this._appConfigService.values.RECENT_PERIOD_IN_HOURS;
quickFilters = [
{
id: 'recent',
label: this._translateService.instant('dossier-overview.quick-filters.recent', {
hours: recentPeriod,
}),
required: true,
checker: this._recentlyModifiedChecker,
},
];
}
_assignedToMeChecker = (file: File) => file.currentReviewer === this._userService.currentUser.id;
_unassignedChecker = (file: File) => file.isUnassigned;
_assignedToOthersChecker = (file: File) => !file.isUnassigned && file.currentReviewer !== this._userService.currentUser.id;
private _quickFilters(entities: File[]): NestedFilter[] {
const recentPeriod = this._appConfigService.values.RECENT_PERIOD_IN_HOURS;
return [
...quickFilters,
{
id: 'recent',
label: this._translateService.instant('dossier-overview.quick-filters.recent', {
hours: recentPeriod,
}),
required: true,
checker: this._recentlyModifiedChecker,
disabled: entities.filter(this._recentlyModifiedChecker).length === 0,
},
{
id: 'assigned-to-me',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
checker: (file: File) => file.currentReviewer === this._userService.currentUser.id,
checker: this._recentlyModifiedChecker,
disabled: entities.filter(this._assignedToMeChecker).length === 0,
},
{
id: 'unassigned',
label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
checker: (file: File) => file.isUnassigned,
checker: this._unassignedChecker,
disabled: entities.filter(this._unassignedChecker).length === 0,
},
{
id: 'assigned-to-others',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
checker: (file: File) => !file.isUnassigned && file.currentReviewer !== this._userService.currentUser.id,
checker: this._assignedToOthersChecker,
disabled: entities.filter(this._assignedToOthersChecker).length === 0,
},
].map(filter => new NestedFilter(filter));
}
@ -371,16 +374,16 @@ export class ConfigService {
};
private _underReviewFn = (reloadDossiers: () => Promise<void>) => (file: File) => {
this._fileActionService.assignFile('reviewer', null, file, () => this._loadingService.loadWhile(reloadDossiers()), true);
this._fileAssignService.assignReviewer(null, file, () => this._loadingService.loadWhile(reloadDossiers()), true);
};
private _underApprovalFn = (reloadDossiers: () => Promise<void>) => async (file: File) => {
const dossier = this._dossiersService.find(file.dossierId);
if (dossier.approverIds.length > 1) {
this._fileActionService.assignFile('approver', null, file, () => this._loadingService.loadWhile(reloadDossiers()), true);
this._fileAssignService.assignApprover(null, file, () => this._loadingService.loadWhile(reloadDossiers()), true);
} else {
this._loadingService.start();
await this._fileActionService.setFilesUnderApproval([file]).toPromise();
await this._filesService.setUnderApprovalFor([file.id], dossier.dossierId, dossier.approverIds[0]).toPromise();
await reloadDossiers();
this._loadingService.stop();
}
@ -388,7 +391,7 @@ export class ConfigService {
private _approveFn = (reloadDossiers: () => Promise<void>) => async (file: File) => {
this._loadingService.start();
await this._fileActionService.setFilesApproved([file]).toPromise();
await this._filesService.setApprovedFor([file.id], file.dossierId).toPromise();
await reloadDossiers();
this._loadingService.stop();
};

View File

@ -56,6 +56,15 @@
<ng-template #bulkActions>
<redaction-dossier-overview-bulk-actions (reload)="reloadFiles()" [dossier]="dossier"></redaction-dossier-overview-bulk-actions>
</ng-template>
<ng-template #tableItemTemplate let-file="entity">
<redaction-table-item
(calculateData)="actionPerformed($event)"
[displayedAttributes]="displayedAttributes"
[dossierTemplateId]="dossier.dossierTemplateId"
[file]="file"
></redaction-table-item>
</ng-template>
</ng-container>
<ng-template #needsWorkFilterTemplate let-filter="filter">
@ -64,14 +73,6 @@
<input #fileInput (change)="uploadFiles($event.target['files'])" class="file-upload-input" multiple="true" type="file" />
<ng-template #tableItemTemplate let-file="entity">
<redaction-table-item
(calculateData)="actionPerformed($event)"
[displayedAttributes]="displayedAttributes"
[file]="file"
></redaction-table-item>
</ng-template>
<ng-template #workflowItemTemplate let-entity="entity">
<redaction-workflow-item (actionPerformed)="actionPerformed($event, entity)" [file]="entity"></redaction-workflow-item>
</ng-template>

View File

@ -46,7 +46,6 @@ import { DossierTemplatesService } from '@services/entity-services/dossier-templ
import { LongPressEvent } from '@shared/directives/long-press.directive';
import { UserPreferenceService } from '@services/user-preference.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
import { FilesService } from '@services/entity-services/files.service';
@Component({
@ -94,7 +93,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
readonly configService: ConfigService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileMapService: FilesMapService,
private readonly _dossierStatsService: DossierStatsService,
activatedRoute: ActivatedRoute,
) {
super(_injector);
@ -205,8 +203,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
}
async reloadFiles() {
await this._appStateService.getFiles(this.currentDossier);
await this._dossierStatsService.getFor([this.dossierId]).toPromise();
await this._filesService.loadAll(this.dossierId).toPromise();
this._computeAllFilters();
}

View File

@ -36,28 +36,40 @@ export class ConfigService {
return this._userService.currentUser;
}
private get _quickFilters(): NestedFilter[] {
_myDossiersChecker = (dw: Dossier) => dw.ownerId === this._currentUser.id;
_toApproveChecker = (dw: Dossier) => dw.approverIds.includes(this._currentUser.id);
_toReviewChecker = (dw: Dossier) => dw.memberIds.includes(this._currentUser.id);
_otherChecker = (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id);
private _quickFilters(entities: Dossier[]): NestedFilter[] {
const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers');
const filters = [
{
id: 'my-dossiers',
label: myDossiersLabel,
checker: (dw: Dossier) => dw.ownerId === this._currentUser.id,
checker: this._myDossiersChecker,
disabled: entities.filter(this._myDossiersChecker).length === 0,
},
{
id: 'to-approve',
label: this._translateService.instant('dossier-listing.quick-filters.to-approve'),
checker: (dw: Dossier) => dw.approverIds.includes(this._currentUser.id),
checker: this._toApproveChecker,
disabled: entities.filter(this._toApproveChecker).length === 0,
},
{
id: 'to-review',
label: this._translateService.instant('dossier-listing.quick-filters.to-review'),
checker: (dw: Dossier) => dw.memberIds.includes(this._currentUser.id),
checker: this._toReviewChecker,
disabled: entities.filter(this._toReviewChecker).length === 0,
},
{
id: 'other',
label: this._translateService.instant('dossier-listing.quick-filters.other'),
checker: (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id),
checker: this._otherChecker,
disabled: entities.filter(this._otherChecker).length === 0,
},
].map(filter => new NestedFilter(filter));
@ -177,10 +189,9 @@ export class ConfigService {
checker: dossierTemplateChecker,
});
const quickFilters = this._quickFilters;
filterGroups.push({
slug: 'quickFilters',
filters: quickFilters,
filters: this._quickFilters(entities),
checker: (dw: Dossier, filter: NestedFilter) => filter.checked && filter.checker(dw),
});

View File

@ -30,7 +30,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
import { FileActionService } from '../../shared/services/file-action.service';
import { FileAssignService } from '../../shared/services/file-assign.service';
import { AnnotationDrawService } from '../../services/annotation-draw.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { Dossier, File, User, ViewMode, WorkflowFileStatus } from '@red/domain';
@ -50,10 +50,10 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileActionsComponent } from '../../shared/components/file-actions/file-actions.component';
import { FilesService } from '@services/entity-services/files.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FileManagementService } from '../../shared/services/file-management.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { filter, switchMap, switchMapTo, tap } from 'rxjs/operators';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { WatermarkService } from '../../../shared/services/watermark.service';
import { WatermarkService } from '@shared/services/watermark.service';
import Annotation = Core.Annotations.Annotation;
import PDFNet = Core.PDFNet;
@ -112,7 +112,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _toaster: Toaster,
private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _fileActionService: FileActionService,
private readonly _fileAssignService: FileAssignService,
private readonly _fileDownloadService: PdfViewerDataService,
private readonly _filesService: FilesService,
private readonly _ngZone: NgZone,
@ -367,7 +367,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
response.manualRedactionEntryWrapper.rectId,
);
this._instance.Core.annotationManager.deleteAnnotation(annotation);
await this.appStateService.reloadFile(this.dossierId, this.fileId);
await this._filesService.reload(this.dossierId, this.fileId);
const distinctPages = manualRedactionEntryWrapper.manualRedactionEntry.positions
.map(p => p.page)
.filter((item, pos, self) => self.indexOf(item) === pos);
@ -475,11 +475,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
case 'reanalyse':
await this._loadFileData(true);
this._updateCanPerformActions();
await this.appStateService.reloadDossierFiles(this.dossierId);
await this._filesService.loadAll(this.dossierId).toPromise();
return;
case 'exclude-pages':
await this.appStateService.reloadDossierFiles(this.dossierId);
await this._filesService.loadAll(this.dossierId).toPromise();
await this._loadFileData(true);
this._cleanupAndRedrawManualAnnotations$();
await this._stampPDF();
@ -507,8 +507,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
async assignToMe(file: File) {
await this._fileActionService.assignToMe([file], async () => {
await this.appStateService.reloadFile(this.dossierId, this.fileId);
await this._fileAssignService.assignToMe([file], async () => {
await this._filesService.reload(this.dossierId, this.fileId);
this._updateCanPerformActions();
});
}
@ -521,7 +521,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
await this._filesService.setReviewerFor([fileId], dossierId, reviewerId).toPromise();
this._toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } });
await this.appStateService.reloadFile(this.dossierId, this.fileId);
await this._filesService.reload(this.dossierId, this.fileId);
this._updateCanPerformActions();
this.editingReviewer = false;
}
@ -617,7 +617,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private _subscribeToFileUpdates(): void {
this.addSubscription = timer(0, 5000)
.pipe(switchMap(() => this.appStateService.reloadFile(this.dossierId, this.fileId)))
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
.subscribe();
this.addSubscription = this._filesMapService.fileReanalysed$
.pipe(filter(file => file.fileId === this.fileId))
@ -679,7 +679,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page);
const currentPageAnnotationIds = currentPageAnnotations.map(a => a.id);
await this.appStateService.reloadFile(this.dossierId, this.fileId);
await this._filesService.reload(this.dossierId, this.fileId);
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
this.rebuildFilters();

View File

@ -19,9 +19,9 @@ import { workflowFileStatusTranslations } from '../../translations/file-status-t
import { TranslateService } from '@ngx-translate/core';
import { RouterHistoryService } from '@services/router-history.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { PlatformSearchService } from '../../shared/services/platform-search.service';
import { Dossier, IMatchedDocument, ISearchInput, ISearchListItem, ISearchResponse } from '@red/domain';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { PlatformSearchService } from '@services/entity-services/platform-search.service';
function toSearchInput(query: string, dossierIds: List | string): ISearchInput {
return {

View File

@ -4,9 +4,9 @@ import { catchError, map, tap } from 'rxjs/operators';
import { FileDataModel } from '@models/file/file-data.model';
import { PermissionsService } from '@services/permissions.service';
import { File } from '@red/domain';
import { FileManagementService } from '../shared/services/file-management.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { RedactionLogService } from './redaction-log.service';
import { ViewedPagesService } from '../shared/services/viewed-pages.service';
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
@Injectable()
export class PdfViewerDataService {

View File

@ -17,9 +17,11 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { LongPressEvent } from '@shared/directives/long-press.directive';
import { FileActionService } from '../../services/file-action.service';
import { FileAssignService } from '../../services/file-assign.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FileManagementService } from '../../services/file-management.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { FilesService } from '@services/entity-services/files.service';
import { ReanalysisService } from '@services/reanalysis.service';
@Component({
selector: 'redaction-file-actions',
@ -67,12 +69,14 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
readonly appStateService: AppStateService,
readonly dossiersService: DossiersService,
private readonly _dialogService: DossiersDialogService,
private readonly _fileActionService: FileActionService,
private readonly _fileAssignService: FileAssignService,
private readonly _loadingService: LoadingService,
private readonly _fileManagementService: FileManagementService,
private readonly _filesService: FilesService,
private readonly _userService: UserService,
private readonly _toaster: Toaster,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _reanalysisService: ReanalysisService,
) {
super();
}
@ -117,7 +121,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
.catch(error => {
this._toaster.error(_('error.http.generic'), { params: error });
});
await this.appStateService.reloadDossierFiles(this.file.dossierId);
await this._filesService.loadAll(this.file.dossierId).toPromise();
this.actionPerformed.emit('delete');
this._loadingService.stop();
},
@ -135,7 +139,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
async assignToMe($event: MouseEvent) {
$event.stopPropagation();
await this._fileActionService.assignToMe([this.file], () => {
await this._fileAssignService.assignToMe([this.file], () => {
this.reloadFiles('reanalyse');
});
}
@ -144,18 +148,20 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
if ($event) {
$event.stopPropagation();
}
this.addSubscription = this._fileActionService.reanalyseFile(this.file).subscribe(() => {
this.reloadFiles('reanalyse');
});
this.addSubscription = this._reanalysisService
.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, true)
.subscribe(() => {
this.reloadFiles('reanalyse');
});
}
async setFileUnderApproval($event: MouseEvent) {
$event.stopPropagation();
const dossier = this.dossiersService.find(this.file.dossierId);
if (dossier.approverIds.length > 1) {
await this._fileActionService.assignFile('approver', $event, this.file, () => this.reloadFiles('assign-reviewer'), true);
await this._fileAssignService.assignApprover($event, this.file, () => this.reloadFiles('assign-reviewer'), true);
} else {
await this._fileActionService.setFilesUnderApproval([this.file]).toPromise();
await this._filesService.setUnderApprovalFor([this.file.id], dossier.id, dossier.approverIds[0]).toPromise();
this.reloadFiles('set-under-approval');
}
}
@ -182,29 +188,26 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
async ocrFile($event: MouseEvent) {
$event.stopPropagation();
await this._fileActionService.ocrFiles([this.file]).toPromise();
await this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId).toPromise();
this.reloadFiles('ocr-file');
}
async setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) {
await this._fileActionService.assignFile(
'reviewer',
$event,
this.file,
() => this.reloadFiles('assign-reviewer'),
ignoreDialogChanges,
);
await this._fileAssignService.assignReviewer($event, this.file, () => this.reloadFiles('assign-reviewer'), ignoreDialogChanges);
}
reloadFiles(action: string) {
this.appStateService.getFiles(this.dossiersService.find(this.file.dossierId)).then(() => {
this.actionPerformed.emit(action);
});
this._filesService
.loadAll(this.file.dossierId)
.toPromise()
.then(() => {
this.actionPerformed.emit(action);
});
}
async toggleAnalysis() {
await this._fileActionService.toggleAnalysis(this.file).toPromise();
await this.appStateService.getFiles(this.dossiersService.find(this.file.dossierId));
await this._reanalysisService.toggleAnalysis(this.file.dossierId, this.file.fileId, !this.file.excluded).toPromise();
await this._filesService.loadAll(this.file.dossierId).toPromise();
this.actionPerformed.emit(this.file?.excluded ? 'enable-analysis' : 'disable-analysis');
}
@ -248,7 +251,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
}
private async _setFileApproved() {
await this._fileActionService.setFilesApproved([this.file]).toPromise();
await this._filesService.setApprovedFor([this.file.id], this.file.dossierId).toPromise();
this.reloadFiles('set-approved');
}
}

View File

@ -6,27 +6,17 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FilesService } from '@services/entity-services/files.service';
import { ConfirmationDialogInput, Toaster } from '@iqser/common-ui';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { ReanalysisService } from '@services/reanalysis.service';
@Injectable()
export class FileActionService {
export class FileAssignService {
constructor(
private readonly _dialogService: DossiersDialogService,
private readonly _userService: UserService,
private readonly _filesService: FilesService,
private readonly _reanalysisService: ReanalysisService,
private readonly _dossiersService: DossiersService,
private readonly _toaster: Toaster,
) {}
reanalyseFile(file: File) {
return this._reanalysisService.reanalyzeFilesForDossier([file.fileId], file.dossierId, true);
}
toggleAnalysis(file: File) {
return this._reanalysisService.toggleAnalysis(file.dossierId, file.fileId, !file.excluded);
}
async assignToMe(files: File[], callback?: Function) {
return new Promise<void>((resolve, reject) => {
const atLeastOneFileHasReviewer = files.reduce((acc, fs) => acc || !!fs.currentReviewer, false);
@ -48,42 +38,15 @@ export class FileActionService {
});
}
setFilesUnderApproval(files: File[], approverId?: string) {
const dossier = this._getDossier(files[0]);
if (!approverId) {
approverId = dossier.approverIds[0];
}
return this._filesService.setUnderApprovalFor(
files.map(f => f.fileId),
dossier.id,
approverId,
);
async assignReviewer($event: MouseEvent, file: File, callback?: Function, ignoreChanged = false): Promise<void> {
await this._assignFile('reviewer', $event, file, callback, ignoreChanged);
}
setFilesApproved(files: File[]) {
return this._filesService.setApprovedFor(
files.map(f => f.fileId),
files[0].dossierId,
);
async assignApprover($event: MouseEvent, file: File, callback?: Function, ignoreChanged = false): Promise<void> {
await this._assignFile('approver', $event, file, callback, ignoreChanged);
}
setFilesUnderReview(files: File[]) {
return this._filesService.setUnderReviewFor(
files.map(f => f.fileId),
files[0].dossierId,
);
}
ocrFiles(files: File[]) {
return this._reanalysisService.ocrFiles(
files.map(f => f.fileId),
files[0].dossierId,
);
}
async assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file: File, callback?: Function, ignoreChanged = false) {
private async _assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file: File, callback?: Function, ignoreChanged = false) {
$event?.stopPropagation();
const currentUserId = this._userService.currentUser.id;
@ -91,12 +54,12 @@ export class FileActionService {
const eligibleUsersIds = this._getUserIds(mode, currentDossier);
if (file.isUnassigned) {
await this._assignFile(currentUserId, mode, [file], callback);
await this._makeAssignFileRequest(currentUserId, mode, [file], callback);
} else if (file.currentReviewer === currentUserId) {
if (eligibleUsersIds.includes(currentUserId)) {
await this._assignFile(currentUserId, mode, [file], callback);
await this._makeAssignFileRequest(currentUserId, mode, [file], callback);
} else if (eligibleUsersIds.length === 1) {
await this._assignFile(eligibleUsersIds[0], mode, [file], callback);
await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file], callback);
} else {
const files = [file];
this._dialogService.openDialog('assignFile', null, { mode, files, ignoreChanged }, async () => {
@ -107,7 +70,7 @@ export class FileActionService {
}
} else {
if (eligibleUsersIds.length === 1) {
await this._assignFile(eligibleUsersIds[0], mode, [file], callback);
await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file], callback);
} else {
const files = [file];
this._dialogService.openDialog(
@ -124,11 +87,7 @@ export class FileActionService {
}
}
private _getDossier(file: File): Dossier {
return this._dossiersService.find(file.dossierId);
}
private async _assignFile(userId: string, mode: 'reviewer' | 'approver', files: File[], callback?: Function) {
private async _makeAssignFileRequest(userId: string, mode: 'reviewer' | 'approver', files: File[], callback?: Function) {
try {
if (mode === 'reviewer') {
await this._filesService
@ -147,7 +106,6 @@ export class FileActionService {
)
.toPromise();
}
if (callback) {
await callback();
}

View File

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FileActionService } from './services/file-action.service';
import { FileAssignService } from './services/file-assign.service';
import { FileActionsComponent } from './components/file-actions/file-actions.component';
import { IqserIconsModule } from '@iqser/common-ui';
import { SharedModule } from '@shared/shared.module';
@ -10,7 +10,7 @@ const components = [FileActionsComponent];
@NgModule({
declarations: [...components],
exports: [...components],
providers: [FileActionService],
providers: [FileAssignService],
imports: [CommonModule, IqserIconsModule, SharedModule],
})
export class SharedDossiersModule {}

View File

@ -7,15 +7,18 @@
class="member"
>
<redaction-initials-avatar [user]="userId" color="gray" size="large"></redaction-initials-avatar>
<div *ngIf="canRemoveMember(userId)" class="remove">
<mat-icon svgIcon="iqser:close"></mat-icon>
</div>
</div>
<div *ngIf="overflowCount && !expandedTeam" [class.large-spacing]="largeSpacing" class="member pointer">
<div (click)="toggleExpandedTeam()" class="oval large white-dark">+{{ overflowCount }}</div>
</div>
<iqser-circle-button
(action)="openAssignDossierMembersDialog.emit()"
(action)="openEditDossierDialog()"
*ngIf="currentUser.isManager && canAdd"
[class.large-spacing]="largeSpacing"
[size]="32"
@ -26,4 +29,5 @@
iqserHelpMode="edit-dossier-member"
></iqser-circle-button>
</div>
<div (click)="toggleExpandedTeam()" *ngIf="expandedTeam" class="all-caps-label see-less pointer" translate="dossier-details.see-less"></div>

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { CircleButtonTypes, List } from '@iqser/common-ui';
import { UserService } from '@services/user.service';
import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service';
@Component({
selector: 'redaction-team-members',
@ -13,18 +14,18 @@ export class TeamMembersComponent {
@Input() memberIds: List;
@Input() perLine: number;
@Input() dossierId: string;
@Input() canAdd = true;
@Input() largeSpacing = false;
@Input() canRemove = false;
@Input() unremovableMembers: string[] = [];
@Output() openAssignDossierMembersDialog = new EventEmitter();
@Output() remove = new EventEmitter<string>();
@ViewChild('container', { static: true }) container: ElementRef;
@ViewChild('container', { static: true }) readonly container: ElementRef;
expandedTeam = false;
constructor(private readonly _userService: UserService) {}
constructor(private readonly _userService: UserService, private readonly _dialogService: DossiersDialogService) {}
get maxTeamMembersBeforeExpand(): number {
return this.perLine - (this.canAdd ? 1 : 0);
@ -45,4 +46,9 @@ export class TeamMembersComponent {
canRemoveMember(userId: string) {
return this.canRemove && this.unremovableMembers.indexOf(userId) === -1;
}
openEditDossierDialog(): void {
const data = { dossierId: this.dossierId, section: 'members' };
this._dialogService.openDialog('editDossier', null, data);
}
}

View File

@ -1,6 +1,5 @@
import { ApplicationRef, Injectable, Injector, OnDestroy } from '@angular/core';
import { FileUploadModel } from '../model/file-upload.model';
import { AppStateService } from '@state/app-state.service';
import { HttpErrorResponse, HttpEventType } from '@angular/common/http';
import { interval, Subject, Subscription } from 'rxjs';
import { ConfigService } from '@services/config.service';
@ -11,6 +10,7 @@ import { isCsv } from '@utils/file-drop-utils';
import { ErrorMessageService, GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { switchMap, tap, throttleTime } from 'rxjs/operators';
import { FilesService } from '@services/entity-services/files.service';
export interface ActiveUpload {
subscription: Subscription;
@ -30,7 +30,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
private readonly _subscriptions = new Subscription();
constructor(
private readonly _appStateService: AppStateService,
private readonly _filesService: FilesService,
private readonly _filesMapService: FilesMapService,
private readonly _applicationRef: ApplicationRef,
private readonly _translateService: TranslateService,
@ -42,7 +42,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
super(_injector, 'upload');
const fileFetch$ = this._fetchFiles$.pipe(
throttleTime(1500),
switchMap(dossierId => this._appStateService.reloadDossierFiles(dossierId)),
switchMap(dossierId => this._filesService.loadAll(dossierId)),
);
this._subscriptions.add(fileFetch$.subscribe());
const interval$ = interval(2500).pipe(tap(() => this._handleUploads()));

View File

@ -1,5 +1,4 @@
import { Injectable } from '@angular/core';
import { FilesService } from '@services/entity-services/files.service';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { File } from '@red/domain';
import { filter, startWith } from 'rxjs/operators';
@ -10,8 +9,6 @@ export class FilesMapService {
private readonly _entityChanged$ = new Subject<File>();
private readonly _map = new Map<string, BehaviorSubject<File[]>>();
constructor(private readonly _filesService: FilesService) {}
get$(dossierId: string) {
if (!this._map.has(dossierId)) {
this._map.set(dossierId, new BehaviorSubject<File[]>([]));

View File

@ -1,30 +1,51 @@
import { Injectable, Injector } from '@angular/core';
import { EntitiesService, List, RequiredParam, Validate } from '@iqser/common-ui';
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
import { File, IFile } from '@red/domain';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { UserService } from '../user.service';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class FilesService extends EntitiesService<File, IFile> {
constructor(protected readonly _injector: Injector, private readonly _userService: UserService) {
constructor(
protected readonly _injector: Injector,
private readonly _userService: UserService,
private readonly _fileAttributesService: FileAttributesService,
private readonly _filesMapService: FilesMapService,
) {
super(_injector, File, 'status');
}
fetch() {
this.get().pipe(map(files => files.map(file => new File(file, this._userService.getNameForId(file.currentReviewer)))));
loadAll(dossierId: string) {
const files$ = this.getFor(dossierId).pipe(mapEach(file => new File(file, this._userService.getNameForId(file.currentReviewer))));
return files$.pipe(tap(files => this._filesMapService.set(dossierId, files)));
}
getOne(dossierId: string, fileId: string): Observable<IFile> {
return super._getOne([dossierId, fileId]);
}
async reload(dossierId: string, fileId: string): Promise<File> {
const oldFile = this._filesMapService.get(dossierId, fileId);
if (!oldFile) {
return null;
}
const rawFile = await this.getOne(dossierId, fileId).toPromise();
const newFile = new File(rawFile, this._userService.getNameForId(rawFile.currentReviewer));
this._filesMapService.replace(newFile);
return newFile;
}
@Validate()
setUnderApprovalFor(@RequiredParam() body: List, @RequiredParam() dossierId: string, approverId: string) {
setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, approverId: string) {
const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`;
return this._post<unknown>(body, url, [{ key: 'approverId', value: approverId }]);
return this._post<unknown>(fileIds, url, [{ key: 'approverId', value: approverId }]);
}
/**

View File

@ -3,15 +3,15 @@ import { GenericService } from '@iqser/common-ui';
import { IMatchedDocument, ISearchInput, ISearchRequest, ISearchResponse } from '@red/domain';
import { Observable, of, zip } from 'rxjs';
import { mapTo, switchMap } from 'rxjs/operators';
import { AppStateService } from '@state/app-state.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { DossiersService } from './dossiers.service';
import { FilesMapService } from './files-map.service';
import { FilesService } from './files.service';
@Injectable()
@Injectable({ providedIn: 'root' })
export class PlatformSearchService extends GenericService<ISearchResponse> {
constructor(
protected readonly _injector: Injector,
private readonly _appStateService: AppStateService,
private readonly _filesService: FilesService,
private readonly _dossiersService: DossiersService,
private readonly _filesMapService: FilesMapService,
) {
@ -47,7 +47,6 @@ export class PlatformSearchService extends GenericService<ISearchResponse> {
}
private _loadFilesFor$(dossierIds: string[]) {
const dossiers = dossierIds.map(dossierId => this._dossiersService.find(dossierId));
return zip(...dossiers.map(dossier => this._appStateService.getFiles(dossier)));
return zip(...dossierIds.map(dossierId => this._filesService.loadAll(dossierId)));
}
}

View File

@ -2,9 +2,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { AppStateService } from './app-state.service';
import { UserService } from '@services/user.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
@Injectable({
providedIn: 'root',
@ -12,8 +10,6 @@ import { FilesMapService } from '@services/entity-services/files-map.service';
export class AppStateGuard implements CanActivate {
constructor(
private readonly _appStateService: AppStateService,
private readonly _dossiersService: DossiersService,
private readonly _filesMapService: FilesMapService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _userService: UserService,
private readonly _router: Router,

View File

@ -1,17 +1,13 @@
import { Injectable } from '@angular/core';
import { Dictionary, Dossier, DossierTemplate, File, IColors } from '@red/domain';
import { Dictionary, DossierTemplate, IColors } from '@red/domain';
import { ActivationEnd, Router } from '@angular/router';
import { UserService } from '@services/user.service';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { currentComponentRoute, FALLBACK_COLOR, hexToRgb } from '@utils/functions';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FilesService } from '@services/entity-services/files.service';
import { DictionaryService } from '@shared/services/dictionary.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
export interface AppState {
activeFileId?: string;
@ -27,14 +23,10 @@ export class AppStateService {
constructor(
private readonly _router: Router,
private readonly _userService: UserService,
private readonly _dossiersService: DossiersService,
private readonly _filesService: FilesService,
private readonly _dictionaryService: DictionaryService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _dossierStatsService: DossierStatsService,
private readonly _fileAttributesService: FileAttributesService,
private readonly _filesMapService: FilesMapService,
) {
_router.events.pipe(currentComponentRoute).subscribe((event: ActivationEnd) => {
const fileId = event.snapshot.paramMap.get('fileId');
@ -96,37 +88,6 @@ export class AppStateService {
return data ? data : this._dictionaryData[dossierTemplateId]['default'];
}
async reloadFile(dossierId: string, fileId: string): Promise<File> {
const dossier = this._dossiersService.find(dossierId);
const oldFile = this._filesMapService.get(dossierId, fileId);
if (!oldFile || !dossier) {
return null;
}
const iFile = await this._filesService.getOne(dossierId, fileId).toPromise();
const newFile = new File(
iFile,
this._userService.getNameForId(iFile.currentReviewer),
this._fileAttributesService.getFileAttributeConfig(dossier.dossierTemplateId),
);
this._filesMapService.replace(newFile);
return newFile;
}
async getFiles(dossier: Dossier) {
const files = await this._filesService.getFor(dossier.id).toPromise();
await this._dossierStatsService.getFor([dossier.id]).toPromise();
const fileAttributes = this._fileAttributesService.getFileAttributeConfig(dossier.dossierTemplateId);
const newFiles = files.map(iFile => new File(iFile, this._userService.getNameForId(iFile.currentReviewer), fileAttributes));
this._filesMapService.set(dossier.dossierId, newFiles);
return newFiles;
}
activateDictionary(dictionaryType: string) {
if (this._dossierTemplatesService.activeDossierTemplate) {
this._appState.activeDictionaryType = dictionaryType;
@ -142,12 +103,6 @@ export class AppStateService {
this._appState.activeDictionaryType = null;
}
async reloadDossierFiles(dossierId: string) {
if (dossierId) {
return this.getFiles(this._dossiersService.find(dossierId));
}
}
async refreshDossierTemplate(dossierTemplateId: string) {
const dossierTemplate = await this._dossierTemplatesService.get(dossierTemplateId).toPromise();

View File

@ -1,6 +1,5 @@
FROM node:14.17-alpine as prep
RUN apk add --update jq && rm -rf /var/cache/apk/*
FROM node:14.18-buster as prep
RUN apt-get update && apt-get install -y jq
COPY package.json /tmp
RUN jq '{ dependencies, devDependencies, peerDependencies, scripts: (.scripts | { postinstall }) }' < /tmp/package.json > /tmp/deps.json
@ -9,13 +8,16 @@ RUN jq '{ dependencies, devDependencies, peerDependencies, scripts: (.scripts |
### STAGE 1: Build ###
# We label our stage as builder
FROM node:14.17-alpine as builder
FROM node:14.18-buster as builder
COPY --from=prep /tmp/deps.json ./package.json
COPY yarn.lock ./
## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
RUN yarn install && mkdir /ng-app && mv ./node_modules ./ng-app
WORKDIR /ng-app
COPY apps apps
@ -29,16 +31,19 @@ COPY .eslintrc.json .eslintrc.json
COPY tsconfig.base.json tsconfig.base.json
COPY versions.sh version.sh
COPY paligo-styles paligo-styles
COPY sonar.js sonar.js
## Build the angular app in production mode and store the artifacts in dist folder
RUN node sonar.js
RUN yarn run build-lint-all
RUN yarn run build-paligo-styles
CMD ["/bin/cp", "-r", "/ng-app/dist/paligo-styles", "/tmp/styles-export"]
### STAGE 2: Setup ###
FROM nginx:1.19.2-alpine
FROM nginx:1.21.4-alpine
RUN apk add --update nano && rm -rf /var/cache/apk/*
## Copy our default nginx config

@ -1 +1 @@
Subproject commit aebdfd0f2eaaf6b4551c5c822ca9533a7f37fda2
Subproject commit 5d6e3053fe6a92eb7bc9fe3e44934bd3c6f5943d

View File

@ -2,7 +2,7 @@ import { Entity } from '@iqser/common-ui';
import { StatusSorter } from '../shared';
import { isProcessingStatuses, ProcessingFileStatus, ProcessingFileStatuses, WorkflowFileStatus, WorkflowFileStatuses } from './types';
import { IFile } from './file';
import { FileAttributes, IFileAttributesConfig } from '../file-attributes';
import { FileAttributes } from '../file-attributes';
export class File extends Entity<IFile> implements IFile {
readonly added?: string;
@ -54,7 +54,7 @@ export class File extends Entity<IFile> implements IFile {
readonly canBeOpened: boolean;
readonly canBeOCRed: boolean;
constructor(file: IFile, readonly reviewerName: string, fileAttributesConfig?: IFileAttributesConfig) {
constructor(file: IFile, readonly reviewerName: string) {
super(file);
this.added = file.added;
this.allManualRedactionsApplied = !!file.allManualRedactionsApplied;
@ -106,17 +106,6 @@ export class File extends Entity<IFile> implements IFile {
this.canBeOpened = !this.isError && !this.isPending && this.numberOfAnalyses > 0;
this.canBeOCRed = !this.excluded && !this.lastOCRTime && (this.isUnassigned || this.isUnderReview || this.isUnderApproval);
if (fileAttributesConfig) {
const primary = fileAttributesConfig.fileAttributeConfigs?.find(c => c.primaryAttribute);
if (primary && file.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = file.fileAttributes?.attributeIdToValue[primary.id];
}
if (!this.primaryAttribute) {
// Fallback here
this.primaryAttribute = '-';
}
}
if (!this.fileAttributes || !this.fileAttributes.attributeIdToValue) {
this.fileAttributes = { attributeIdToValue: {} };
}

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.48.0",
"version": "3.53.0",
"private": true,
"license": "MIT",
"scripts": {

Binary file not shown.