diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.html b/apps/red-ui/src/app/components/notifications/notifications.component.html
index c1d447aab..a744fdf0d 100644
--- a/apps/red-ui/src/app/components/notifications/notifications.component.html
+++ b/apps/red-ui/src/app/components/notifications/notifications.component.html
@@ -13,11 +13,11 @@
{{ group.date }}
-
View all
+
View all
{{ notification.time }}
;
- hasUnreadNotifications$: Observable
;
- groupedNotifications$: Observable;
- private _notifications$ = new BehaviorSubject([]);
+export class NotificationsComponent {
+ readonly hasUnreadNotifications$: Observable;
+ readonly groupedNotifications$: Observable;
constructor(
private readonly _translateService: TranslateService,
@@ -36,40 +31,30 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit {
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _datePipe: DatePipe,
) {
- super();
- this.notifications$ = this._notifications$.asObservable().pipe(shareLast());
- this.groupedNotifications$ = this.notifications$.pipe(map(notifications => this._groupNotifications(notifications)));
+ this.groupedNotifications$ = this._notificationsService.all$.pipe(map(notifications => this._groupNotifications(notifications)));
this.hasUnreadNotifications$ = this._hasUnreadNotifications$;
}
private get _hasUnreadNotifications$(): Observable {
- return this.notifications$.pipe(
+ return this._notificationsService.all$.pipe(
map(notifications => notifications.filter(n => !n.readDate).length > 0),
distinctUntilChanged(),
shareLast(),
);
}
- async ngOnInit(): Promise {
- await this._loadData();
-
- this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
- .pipe(
- switchMap(() => this._notificationsService.getNotificationsIfChanged(INCLUDE_SEEN)),
- tap(notifications => this._notifications$.next(notifications)),
- )
- .subscribe();
- }
-
- async markRead($event, notifications: List = this._notifications$.getValue().map(n => n.id), isRead = true): Promise {
+ async markRead($event, notifications: Notification[] = this._notificationsService.all, isRead = true): Promise {
$event.stopPropagation();
- await firstValueFrom(this._notificationsService.toggleNotificationRead(notifications, isRead));
- await this._loadData();
- }
-
- private async _loadData(): Promise {
- const notifications = await firstValueFrom(this._notificationsService.getNotifications(INCLUDE_SEEN));
- this._notifications$.next(notifications);
+ if (!notifications.find(notification => !!notification.readDate !== isRead)) {
+ // If no notification changes status after the request, abort
+ return;
+ }
+ await firstValueFrom(
+ this._notificationsService.toggleNotificationRead(
+ notifications.map(n => n.id),
+ isRead,
+ ),
+ );
}
private _groupNotifications(notifications: Notification[]): NotificationsGroup[] {
diff --git a/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts
index 9aafdd008..a3405ac88 100644
--- a/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts
+++ b/apps/red-ui/src/app/modules/account/account-side-nav/account-side-nav.component.ts
@@ -22,7 +22,7 @@ export class AccountSideNavComponent {
},
{
screen: 'notifications',
- label: _('notifications'),
+ label: _('notifications.label'),
hideIf: !this._userService.currentUser.isUser,
},
];
diff --git a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts
index 732c73209..ba3adff2c 100644
--- a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts
+++ b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts
@@ -1,12 +1,13 @@
-import { Component, OnDestroy } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { lastIndexOfEnd } from '@utils/functions';
-import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
+import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { RouterHistoryService } from '@services/router-history.service';
import { DigitalSignatureService } from '../../services/digital-signature.service';
-import { IDigitalSignature, IDigitalSignatureRequest } from '@red/domain';
+import { IDigitalSignature } from '@red/domain';
+import { firstValueFrom } from 'rxjs';
import { HttpStatusCode } from '@angular/common/http';
@Component({
@@ -14,7 +15,7 @@ import { HttpStatusCode } from '@angular/common/http';
templateUrl: './digital-signature-screen.component.html',
styleUrls: ['./digital-signature-screen.component.scss'],
})
-export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements OnDestroy {
+export class DigitalSignatureScreenComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly currentUser = this._userService.currentUser;
@@ -30,16 +31,18 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
private readonly _loadingService: LoadingService,
readonly routerHistoryService: RouterHistoryService,
private readonly _digitalSignatureService: DigitalSignatureService,
- ) {
- super();
- this.loadDigitalSignatureAndInitializeForm();
- }
+ ) {}
get hasDigitalSignatureSet() {
return this.digitalSignatureExists || !!this.form.get('base64EncodedPrivateKey').value;
}
- saveDigitalSignature() {
+ async ngOnInit(): Promise {
+ await this.loadDigitalSignatureAndInitializeForm();
+ }
+
+ async saveDigitalSignature(): Promise {
+ this._loadingService.start();
const formValue = this.form.getRawValue();
const digitalSignature: IDigitalSignature = {
...formValue,
@@ -51,29 +54,32 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
? this._digitalSignatureService.update(digitalSignature)
: this._digitalSignatureService.save(digitalSignature);
- this.addSubscription = observable.subscribe({
- next: () => {
- this.loadDigitalSignatureAndInitializeForm();
- this._toaster.success(_('digital-signature-screen.action.save-success'));
- },
- error: error => {
- if (error.status === HttpStatusCode.BadRequest) {
- this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
- } else {
- this._toaster.error(_('digital-signature-screen.action.save-error'));
- }
- },
- });
+ try {
+ await firstValueFrom(observable);
+ await this.loadDigitalSignatureAndInitializeForm();
+ this._toaster.success(_('digital-signature-screen.action.save-success'));
+ } catch (error) {
+ console.error(error);
+ if (error.status === HttpStatusCode.BadRequest) {
+ this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
+ } else {
+ this._toaster.error(_('digital-signature-screen.action.save-error'));
+ }
+ }
+
+ this._loadingService.stop();
}
- removeDigitalSignature() {
- this.addSubscription = this._digitalSignatureService.delete().subscribe(
- () => {
- this.loadDigitalSignatureAndInitializeForm();
- this._toaster.success(_('digital-signature-screen.action.delete-success'));
- },
- () => this._toaster.error(_('digital-signature-screen.action.delete-error')),
- );
+ async removeDigitalSignature(): Promise {
+ this._loadingService.start();
+ try {
+ await firstValueFrom(this._digitalSignatureService.delete());
+ await this.loadDigitalSignatureAndInitializeForm();
+ this._toaster.success(_('digital-signature-screen.action.delete-success'));
+ } catch (error) {
+ console.error(error);
+ this._toaster.error(_('digital-signature-screen.action.delete-error'));
+ }
}
fileChanged(event, input: HTMLInputElement) {
@@ -89,24 +95,19 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
fileReader.readAsDataURL(file as Blob);
}
- loadDigitalSignatureAndInitializeForm() {
+ async loadDigitalSignatureAndInitializeForm(): Promise {
this._loadingService.start();
- this._digitalSignatureService
- .getSignature()
- .subscribe({
- next: digitalSignature => {
- this.digitalSignatureExists = true;
- this.digitalSignature = digitalSignature;
- },
- error: () => {
- this.digitalSignatureExists = false;
- this.digitalSignature = {};
- },
- })
- .add(() => {
- this.form = this._getForm();
- this._loadingService.stop();
- });
+ try {
+ const digitalSignature = await firstValueFrom(this._digitalSignatureService.getSignature());
+ this.digitalSignatureExists = true;
+ this.digitalSignature = digitalSignature;
+ } catch (error) {
+ this.digitalSignatureExists = false;
+ this.digitalSignature = {};
+ }
+
+ this.form = this._getForm();
+ this._loadingService.stop();
}
private _getForm(): FormGroup {
diff --git a/apps/red-ui/src/app/services/notifications.service.ts b/apps/red-ui/src/app/services/notifications.service.ts
index f25f49a88..e68359f12 100644
--- a/apps/red-ui/src/app/services/notifications.service.ts
+++ b/apps/red-ui/src/app/services/notifications.service.ts
@@ -1,56 +1,65 @@
-import { Injectable, Injector } from '@angular/core';
-import { GenericService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
+import { Inject, Injectable, Injector } from '@angular/core';
+import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
-import { EMPTY, iif, Observable } from 'rxjs';
-import { INotification, Notification, NotificationTypes } from '@red/domain';
-import { map, switchMap } from 'rxjs/operators';
+import { EMPTY, iif, Observable, of, timer } from 'rxjs';
+import { Dossier, INotification, Notification, NotificationTypes } from '@red/domain';
+import { map, switchMap, tap } from 'rxjs/operators';
import { notificationsTranslations } from '../translations/notifications-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ActiveDossiersService } from './dossiers/active-dossiers.service';
import { UserService } from '@services/user.service';
-import { FilesMapService } from '@services/entity-services/files-map.service';
import dayjs from 'dayjs';
+import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
+import { BASE_HREF } from '../tokens';
+import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
+
+const INCLUDE_SEEN = false;
@Injectable({
providedIn: 'root',
})
-export class NotificationsService extends GenericService {
+export class NotificationsService extends EntitiesService {
constructor(
+ @Inject(BASE_HREF) private readonly _baseHref: string,
protected readonly _injector: Injector,
private readonly _translateService: TranslateService,
private readonly _activeDossiersService: ActiveDossiersService,
+ private readonly _archivedDossiersService: ArchivedDossiersService,
private readonly _userService: UserService,
- private readonly _filesMapService: FilesMapService,
) {
- super(_injector, 'notification');
+ super(_injector, Notification, 'notification');
+
+ timer(0, CHANGED_CHECK_INTERVAL)
+ .pipe(
+ switchMap(() => (this._activeDossiersService.all.length ? of(null) : this._activeDossiersService.loadAll())),
+ switchMap(() => (this._archivedDossiersService.all.length ? of(null) : this._archivedDossiersService.loadAll())),
+ switchMap(() => this.#loadNotificationsIfChanged()),
+ )
+ .subscribe();
}
@Validate()
- getNotifications(@RequiredParam() includeSeen: boolean): Observable {
- let queryParam: QueryParam;
- if (includeSeen !== undefined && includeSeen !== null) {
- queryParam = { key: 'includeSeen', value: includeSeen };
- }
-
- return this.getAll<{ notifications: Notification[] }>(this._defaultModelPath, [queryParam]).pipe(
- map(response => response.notifications.filter(n => n.notificationType in NotificationTypes)),
- mapEach(notification => this._new(notification)),
- );
- }
-
- @Validate()
- getNotificationsIfChanged(@RequiredParam() includeSeen: boolean): Observable {
- return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.getNotifications(includeSeen), EMPTY)));
- }
-
- @Validate()
- toggleNotificationRead(@RequiredParam() body: List, @RequiredParam() setRead: boolean) {
+ toggleNotificationRead(@RequiredParam() body: List, @RequiredParam() setRead: boolean): Observable {
let queryParam: QueryParam;
if (setRead !== undefined && setRead !== null) {
queryParam = { key: 'setRead', value: setRead };
}
- return this._post(body, `${this._defaultModelPath}/toggle-read`, [queryParam]);
+ return this._post(body, `${this._defaultModelPath}/toggle-read`, [queryParam]).pipe(switchMap(() => this.#loadAll()));
+ }
+
+ #loadAll(includeSeen = INCLUDE_SEEN): Observable {
+ const queryParam: QueryParam = { key: 'includeSeen', value: includeSeen };
+
+ return this.getAll<{ notifications: Notification[] }>(this._defaultModelPath, [queryParam]).pipe(
+ map(response => response.notifications.filter(n => n.notificationType in NotificationTypes)),
+ mapEach(notification => this._new(notification)),
+ tap(notifications => this.setEntities(notifications)),
+ );
+ }
+
+ #loadNotificationsIfChanged(): Observable {
+ return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.#loadAll(), EMPTY)));
}
private _new(notification: INotification) {
@@ -66,25 +75,27 @@ export class NotificationsService extends GenericService {
private _translate(notification: INotification, translation: string): string {
const fileId = notification.target.fileId;
const dossierId = notification.target.dossierId;
- const dossier = this._activeDossiersService.find(dossierId);
+ const dossier = this._activeDossiersService.find(dossierId) || this._archivedDossiersService.find(dossierId);
const fileName = notification.target.fileName;
return this._translateService.instant(translation, {
- fileHref: this._getFileHref(dossierId, fileId),
- dossierHref: this._getDossierHref(dossierId),
- dossierName: notification.target?.dossierName || dossier?.dossierName || this._translateService.instant(_('dossier')),
+ fileHref: this._getFileHref(dossier, fileId),
+ dossierHref: this._getDossierHref(dossier),
+ dossierName:
+ notification.target?.dossierName ||
+ dossier?.dossierName ||
+ this._translateService.instant(_('notifications.deleted-dossier')),
fileName: fileName || this._translateService.instant(_('file')),
user: this._getUsername(notification.userId),
});
}
- private _getFileHref(dossierId: string, fileId: string): string {
- const dossierHref = this._getDossierHref(dossierId);
- return `${dossierHref}/file/${fileId}`;
+ private _getFileHref(dossier: Dossier, fileId: string): string {
+ return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
}
- private _getDossierHref(dossierId: string): string {
- return `/ui/main/dossiers/${dossierId}`;
+ private _getDossierHref(dossier: Dossier): string {
+ return dossier ? `${this._baseHref}${dossier.routerLink}` : null;
}
private _getUsername(userId: string | undefined) {
diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json
index 0bbc03d45..9de143c7d 100644
--- a/apps/red-ui/src/assets/i18n/de.json
+++ b/apps/red-ui/src/assets/i18n/de.json
@@ -665,7 +665,6 @@
"save": "Dokumenteninformation speichern",
"title": "Datei-Attribute anlegen"
},
- "dossier": "Dossier",
"dossier-attribute-types": {
"date": "Datum",
"image": "Bild",
@@ -1530,21 +1529,20 @@
}
},
"notification": {
- "assign-approver": "Sie wurden dem Dokument {fileName} im Dossier {dossierName} als Genehmiger zugewiesen!",
- "assign-reviewer": "Sie wurden dem Dokument {fileName} im Dossier {dossierName} als Reviewer zugewiesen!",
- "document-approved": "{fileName} wurde genehmigt!",
+ "assign-approver": "Sie wurden dem Dokument {fileHref, select, null{{fileName}} other{{fileName}}} im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} als Genehmiger zugewiesen!",
+ "assign-reviewer": "Sie wurden dem Dokument {fileHref, select, null{{fileName}} other{{fileName}}} im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} als Reviewer zugewiesen!",
+ "document-approved": "{fileHref, select, null{{fileName}} other{{fileName}}} wurde genehmigt!",
"dossier-deleted": "Dossier: {dossierName} wurde gelöscht!",
- "dossier-owner-removed": "Der Dossier-Owner von {dossierName} wurde entfernt!",
- "dossier-owner-set": "Eigentümer von {dossierName} geändert zu {user}!",
+ "dossier-owner-removed": "Der Dossier-Owner von {dossierHref, select, null{{dossierName}} other{{dossierName}}} wurde entfernt!",
+ "dossier-owner-set": "Eigentümer von {dossierHref, select, null{{dossierName}} other{{dossierName}}} geändert zu {user}!",
"download-ready": "Ihr Download ist fertig!",
"no-data": "Du hast aktuell keine Benachrichtigungen",
- "unassigned-from-file": "Sie wurden vom Dokument {fileName} im Dossier {dossierName} entfernt!",
- "user-becomes-dossier-member": "{user} ist jetzt Mitglied des Dossiers {dossierName}!",
- "user-demoted-to-reviewer": "{user} wurde im Dossier {dossierName} auf die Reviewer-Berechtigung heruntergestuft!",
- "user-promoted-to-approver": "{user} wurde im Dossier {dossierName} zum Genehmiger ernannt!",
- "user-removed-as-dossier-member": "{user} wurde als Mitglied von: {dossierName} entfernt!"
+ "unassigned-from-file": "Sie wurden vom Dokument {fileHref, select, null{{fileName}} other{{fileName}}} im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} entfernt!",
+ "user-becomes-dossier-member": "{user} ist jetzt Mitglied des Dossiers {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "user-demoted-to-reviewer": "{user} wurde im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} auf die Reviewer-Berechtigung heruntergestuft!",
+ "user-promoted-to-approver": "{user} wurde im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} zum Genehmiger ernannt!",
+ "user-removed-as-dossier-member": "{user} wurde als Mitglied von: {dossierHref, select, null{{dossierName}} other{{dossierName}}} entfernt!"
},
- "notifications": "Benachrichtigungen",
"notifications-screen": {
"category": {
"email-notifications": "E-Mail Benachrichtigungen",
@@ -1583,6 +1581,11 @@
},
"title": "Benachrichtigungseinstellungen"
},
+ "notifications": {
+ "deleted-dossier": "",
+ "label": "Benachrichtigungen",
+ "mark-as": ""
+ },
"ocr": {
"confirmation-dialog": {
"cancel": "",
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index ff7c38da1..c314b77f9 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -665,7 +665,6 @@
"save": "Save Document Info",
"title": "Introduce File Attributes"
},
- "dossier": "Dossier",
"dossier-attribute-types": {
"date": "Date",
"image": "Image",
@@ -1530,21 +1529,20 @@
}
},
"notification": {
- "assign-approver": "You have been assigned as approver for {fileName} in dossier: {dossierName}!",
- "assign-reviewer": "You have been assigned as reviewer for {fileName} in dossier: {dossierName}!",
- "document-approved": " {fileName} has been approved!",
+ "assign-approver": "You have been assigned as approver for {fileHref, select, null{{fileName}} other{{fileName}}} in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "assign-reviewer": "You have been assigned as reviewer for {fileHref, select, null{{fileName}} other{{fileName}}} in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "document-approved": " {fileHref, select, null{{fileName}} other{{fileName}}} has been approved!",
"dossier-deleted": "Dossier: {dossierName} has been deleted!",
- "dossier-owner-removed": "You have been removed as dossier owner from {dossierName}!",
- "dossier-owner-set": "You are now the dossier owner of {dossierName}!",
+ "dossier-owner-removed": "You have been removed as dossier owner from {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "dossier-owner-set": "You are now the dossier owner of {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
"download-ready": "Your download is ready!",
"no-data": "You currently have no notifications",
- "unassigned-from-file": "You have been unassigned from {fileName} in dossier: {dossierName}!",
- "user-becomes-dossier-member": "You have been added to dossier: {dossierName}!",
- "user-demoted-to-reviewer": "You have been demoted to reviewer in dossier: {dossierName}!",
- "user-promoted-to-approver": "You have been promoted to approver in dossier: {dossierName}!",
- "user-removed-as-dossier-member": "You have been removed as a member from dossier: {dossierName} !"
+ "unassigned-from-file": "You have been unassigned from {fileHref, select, null{{fileName}} other{{fileName}}} in dossier: {dossierHref, select, null{{dossierName}} other{{dossierHref, select, null{{dossierName}} other{{dossierName}}}}}!",
+ "user-becomes-dossier-member": "You have been added to dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "user-demoted-to-reviewer": "You have been demoted to reviewer in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "user-promoted-to-approver": "You have been promoted to approver in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!",
+ "user-removed-as-dossier-member": "You have been removed as a member from dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!"
},
- "notifications": "Notifications",
"notifications-screen": {
"category": {
"email-notifications": "Email Notifications",
@@ -1583,6 +1581,11 @@
},
"title": "Notifications Preferences"
},
+ "notifications": {
+ "deleted-dossier": "Deleted Dossier",
+ "label": "Notifications",
+ "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}"
+ },
"ocr": {
"confirmation-dialog": {
"cancel": "Cancel",
diff --git a/package.json b/package.json
index 66ecaf42d..f2d09f236 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "redaction",
- "version": "3.376.0",
+ "version": "3.377.0",
"private": true,
"license": "MIT",
"scripts": {
diff --git a/paligo-theme.tar.gz b/paligo-theme.tar.gz
index 7c89c4820..625912511 100644
Binary files a/paligo-theme.tar.gz and b/paligo-theme.tar.gz differ