WIP on notifications
- replaced mock notifications with the real ones from database; - grouped notifications using the same format as for the comments; - called API to mark the notification as read/unread; - added messages with links for notification targets;
This commit is contained in:
parent
90e8f5c826
commit
f576c7bb33
@ -36,6 +36,7 @@ import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight
|
|||||||
import { PruningTranslationLoader } from '@utils/pruning-translation-loader';
|
import { PruningTranslationLoader } from '@utils/pruning-translation-loader';
|
||||||
import { HelpModeComponent } from '@components/help-mode/help-mode.component';
|
import { HelpModeComponent } from '@components/help-mode/help-mode.component';
|
||||||
import { HelpModeDialogComponent } from '@components/help-mode-dialog/help-mode-dialog.component';
|
import { HelpModeDialogComponent } from '@components/help-mode-dialog/help-mode-dialog.component';
|
||||||
|
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||||
|
|
||||||
export function httpLoaderFactory(httpClient: HttpClient) {
|
export function httpLoaderFactory(httpClient: HttpClient) {
|
||||||
return new PruningTranslationLoader(httpClient, '/assets/i18n/', '.json');
|
return new PruningTranslationLoader(httpClient, '/assets/i18n/', '.json');
|
||||||
@ -134,7 +135,8 @@ const components = [
|
|||||||
{
|
{
|
||||||
provide: MissingTranslationHandler,
|
provide: MissingTranslationHandler,
|
||||||
useClass: REDMissingTranslationHandler
|
useClass: REDMissingTranslationHandler
|
||||||
}
|
},
|
||||||
|
DatePipe
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
<iqser-circle-button [matMenuTriggerFor]="overlay" [showDot]="hasUnread" icon="red:notification"></iqser-circle-button>
|
<iqser-circle-button [matMenuTriggerFor]="overlay" [showDot]="!!notifications?.length" icon="red:notification"></iqser-circle-button>
|
||||||
<mat-menu #overlay="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
<mat-menu #overlay="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
||||||
<div *ngFor="let group of groupedNotifications | sortBy: 'desc':'dateString'">
|
<div *ngFor="let group of groupedNotifications">
|
||||||
<div class="all-caps-label">{{ day(group) }}</div>
|
<div class="all-caps-label">{{ group.dateString }}</div>
|
||||||
<div
|
<div
|
||||||
*ngFor="let notification of group.notifications | sortBy: 'desc':'eventTime'"
|
*ngFor="let notification of group.notifications | sortBy: 'desc':'creationDate'"
|
||||||
[class.unread]="!notification.read"
|
[class.unread]="!notification.readDate"
|
||||||
class="notification"
|
class="notification"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
>
|
>
|
||||||
<redaction-initials-avatar></redaction-initials-avatar>
|
<redaction-initials-avatar></redaction-initials-avatar>
|
||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
<div [innerHTML]="notification.message"></div>
|
<div [innerHTML]="getNotificationMessage(notification)"></div>
|
||||||
<div class="small-label mt-2">{{ eventTime(notification.eventTime) }}</div>
|
<div class="small-label mt-2">{{ getTime(notification.creationDate) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
(click)="toggleRead(notification, $event)"
|
(click)="toggleRead(notification, $event)"
|
||||||
[matTooltip]="(notification.read ? 'notifications.mark-unread' : 'notifications.mark-read') | translate"
|
[matTooltip]="(notification.readDate ? 'notifications.mark-unread' : 'notifications.mark-read') | translate"
|
||||||
class="dot"
|
class="dot"
|
||||||
matTooltipPosition="before"
|
matTooltipPosition="before"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { NotificationControllerService } from '@redaction/red-ui-http';
|
import { NotificationControllerService, Notification, NotificationResponse } from '@redaction/red-ui-http';
|
||||||
|
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||||
|
|
||||||
interface Notification {
|
enum NotificationType {
|
||||||
message: string;
|
ASSIGN_REVIEWER = 'ASSIGN_REVIEWER',
|
||||||
eventTime: number;
|
ASSIGN_APPROVER = 'ASSIGN_APPROVER',
|
||||||
read: boolean;
|
UNASSIGNED_FROM_FILE = 'UNASSIGNED_FROM_FILE',
|
||||||
|
DOCUMENT_APPROVED = 'DOCUMENT_APPROVED',
|
||||||
|
DOSSIER_OWNER_SET = 'DOSSIER_OWNER_SET',
|
||||||
|
DOSSIER_OWNER_REMOVED = 'DOSSIER_OWNER_REMOVED',
|
||||||
|
USER_BECOMES_DOSSIER_MEMBER = 'USER_BECOMES_DOSSIER_MEMBER',
|
||||||
|
USER_REMOVED_AS_DOSSIER_MEMBER = 'USER_REMOVED_AS_DOSSIER_MEMBER',
|
||||||
|
USER_PROMOTED_TO_APPROVER = 'USER_PROMOTED_TO_APPROVER',
|
||||||
|
USER_DEGRADED_TO_REVIEWER = 'USER_DEGRADED_TO_REVIEWER',
|
||||||
|
DOSSIER_OWNER_DELETED = 'DOSSIER_OWNER_DELETED'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current Notification Types:
|
|
||||||
// ASSIGN_REVIEWER,
|
|
||||||
// ASSIGN_APPROVER,
|
|
||||||
// UNASSIGNED_FROM_FILE,
|
|
||||||
// DOCUMENT_APPROVED,
|
|
||||||
// DOSSIER_OWNER_SET,
|
|
||||||
// DOSSIER_OWNER_REMOVED,
|
|
||||||
// USER_BECOMES_DOSSIER_MEMBER,
|
|
||||||
// USER_REMOVED_AS_DOSSIER_MEMBER,
|
|
||||||
// USER_PROMOTED_TO_APPROVER,
|
|
||||||
// USER_DEGRADED_TO_REVIEWER,
|
|
||||||
// DOSSIER_OWNER_DELETED
|
|
||||||
// For each you have a different set of target Ids - I.E. a projectId, fileId or userId of the user who did the action
|
// For each you have a different set of target Ids - I.E. a projectId, fileId or userId of the user who did the action
|
||||||
// we need to create some templated i18n message which also include links
|
// we need to create some templated i18n message which also include links
|
||||||
// to the file or project that was affected ( see mock notifications )
|
// to the file or project that was affected ( see mock notifications )
|
||||||
@ -31,67 +27,79 @@ interface Notification {
|
|||||||
styleUrls: ['./notifications.component.scss']
|
styleUrls: ['./notifications.component.scss']
|
||||||
})
|
})
|
||||||
export class NotificationsComponent {
|
export class NotificationsComponent {
|
||||||
notifications: Notification[] = [
|
notifications: Notification[];
|
||||||
{
|
|
||||||
message: 'This is a notification with longer text wrapping on multiple lines',
|
|
||||||
eventTime: 1607340971000,
|
|
||||||
read: false
|
|
||||||
},
|
|
||||||
{ message: 'This is a <a>link</a>', eventTime: 1607254981000, read: true },
|
|
||||||
{ message: 'This is a <b>notification 1</b>', eventTime: 1607254571000, read: false },
|
|
||||||
{ message: 'Notification', eventTime: 1607385727000, read: true },
|
|
||||||
{ message: 'Another notification', eventTime: 1606829412000, read: false }
|
|
||||||
];
|
|
||||||
groupedNotifications: { dateString: string; notifications: Notification[] }[] = [];
|
groupedNotifications: { dateString: string; notifications: Notification[] }[] = [];
|
||||||
|
|
||||||
constructor(private _translateService: TranslateService, private _notificationControllerService: NotificationControllerService) {
|
constructor(
|
||||||
this._groupNotifications();
|
private _translateService: TranslateService,
|
||||||
// TODO group notifications by date using same date-format as comments in workload
|
private _notificationControllerService: NotificationControllerService,
|
||||||
//
|
private readonly _datePipe: DatePipe
|
||||||
// this._notificationControllerService.getNotifications(false).subscribe(response => {
|
) {
|
||||||
// console.log(response);
|
this._notificationControllerService.getNotifications(false).subscribe((response: NotificationResponse) => {
|
||||||
// });
|
this.notifications = response.notifications;
|
||||||
}
|
console.log(this.notifications);
|
||||||
|
console.log(JSON.stringify(this.notifications));
|
||||||
get hasUnread() {
|
this._groupNotifications(this.notifications);
|
||||||
return this.notifications.filter(notification => !notification.read).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
day(group: { dateString: string; notifications: Notification[] }): string {
|
|
||||||
moment.locale(this._translateService.currentLang);
|
|
||||||
return moment(group.notifications[0].eventTime).calendar({
|
|
||||||
sameDay: `[${this._translateService.instant('notifications.today')}]`,
|
|
||||||
lastDay: `[${this._translateService.instant('notifications.yesterday')}]`,
|
|
||||||
nextDay: `[${this._translateService.instant('notifications.tomorrow')}]`,
|
|
||||||
nextWeek: 'D MMMM',
|
|
||||||
lastWeek: 'D MMMM',
|
|
||||||
sameElse: 'D MMMM'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
eventTime(eventTime: number): string {
|
getTime(date: string): string {
|
||||||
moment.locale(this._translateService.currentLang);
|
moment.locale(this._translateService.currentLang);
|
||||||
if (moment().isSame(eventTime, 'day')) {
|
return moment(date).format('hh:mm A');
|
||||||
return moment(eventTime).fromNow();
|
}
|
||||||
} else {
|
|
||||||
return moment(eventTime).format('hh:mm A');
|
getNotificationMessage(notification: Notification) {
|
||||||
|
switch (notification.notificationType) {
|
||||||
|
case NotificationType.ASSIGN_REVIEWER:
|
||||||
|
return this._translate(notification, 'notification.assign-reviewer');
|
||||||
|
case NotificationType.ASSIGN_APPROVER:
|
||||||
|
return this._translate(notification, 'notification.assign-approver');
|
||||||
|
case NotificationType.UNASSIGNED_FROM_FILE:
|
||||||
|
return this._translate(notification, 'notification.unassigned-from-file');
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleRead(notification: Notification, $event) {
|
toggleRead(notification: Notification, $event) {
|
||||||
|
console.log('notification: ', notification);
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
notification.read = !notification.read;
|
notification.readDate = !notification.readDate ? moment().format('YYYY-MM-DDTHH:mm:ss Z') : null;
|
||||||
|
const ids = [notification.notificationId];
|
||||||
|
const isRead = !!notification.readDate;
|
||||||
|
this._notificationControllerService.toggleNotificationRead(ids, isRead).subscribe(response => {
|
||||||
|
console.log('response: ', response);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _groupNotifications() {
|
private _getDossierHref(dossierId: string) {
|
||||||
|
return `main/dossiers/${dossierId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getFileHref(dossierId: string, fileId: string) {
|
||||||
|
const dossierUrl = this._getDossierHref(dossierId);
|
||||||
|
return `${dossierUrl}/file/${fileId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _translate(notification: Notification, translation: string) {
|
||||||
|
const fileId = notification.target.fileId;
|
||||||
|
const dossierId = notification.target.dossierId;
|
||||||
|
return this._translateService.instant(translation, {
|
||||||
|
fileHref: this._getFileHref(dossierId, fileId),
|
||||||
|
dossierHref: this._getDossierHref(dossierId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _groupNotifications(notifications: Notification[]) {
|
||||||
const res = {};
|
const res = {};
|
||||||
for (const notification of this.notifications) {
|
for (const notification of notifications) {
|
||||||
const date = moment(notification.eventTime).format('YYYY-MM-DD');
|
const date = this._datePipe.transform(notification.creationDate, 'sophisticatedDate');
|
||||||
if (!res[date]) res[date] = [];
|
if (!res[date]) res[date] = [];
|
||||||
res[date].push(notification);
|
res[date].push(notification);
|
||||||
}
|
}
|
||||||
for (const key of Object.keys(res)) {
|
for (const key of Object.keys(res)) {
|
||||||
this.groupedNotifications.push({ dateString: key, notifications: res[key] });
|
this.groupedNotifications.push({ dateString: key, notifications: res[key] });
|
||||||
}
|
}
|
||||||
|
this.groupedNotifications = this.groupedNotifications.reverse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="comment-details-wrapper">
|
<div class="comment-details-wrapper">
|
||||||
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
|
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
|
||||||
<b> {{ getOwnerName(comment) }} </b>
|
<b> {{ getOwnerName(comment) }} </b>
|
||||||
{{ comment.date | date: 'commentDate' }}
|
{{ comment.date | date: 'sophisticatedDate' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-actions">
|
<div class="comment-actions">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export class DatePipe extends BaseDatePipe implements PipeTransform {
|
|||||||
transform(value: Date | string | number | null | undefined, format?: string, timezone?: string, locale?: string): string | null;
|
transform(value: Date | string | number | null | undefined, format?: string, timezone?: string, locale?: string): string | null;
|
||||||
transform(value: any, format?: string, timezone?: string, locale?: string): string {
|
transform(value: any, format?: string, timezone?: string, locale?: string): string {
|
||||||
if (format === 'timeFromNow') return this._getTimeFromNow(value);
|
if (format === 'timeFromNow') return this._getTimeFromNow(value);
|
||||||
if (format === 'commentDate') return this._getCommentDate(value);
|
if (format === 'sophisticatedDate') return this._getSophisticatedDate(value);
|
||||||
if (format === 'exactDate') return this._getExactDate(value);
|
if (format === 'exactDate') return this._getExactDate(value);
|
||||||
return super.transform(value, format, timezone, locale);
|
return super.transform(value, format, timezone, locale);
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ export class DatePipe extends BaseDatePipe implements PipeTransform {
|
|||||||
return this._translateService.instant(`time.no-time-left`);
|
return this._translateService.instant(`time.no-time-left`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getCommentDate(item: string) {
|
private _getSophisticatedDate(item: string) {
|
||||||
const date = moment(item);
|
const date = moment(item);
|
||||||
const day = date.date();
|
const day = date.date();
|
||||||
const month = date.month();
|
const month = date.month();
|
||||||
|
|||||||
@ -1166,6 +1166,11 @@
|
|||||||
"oct": "Oct.",
|
"oct": "Oct.",
|
||||||
"sep": "Sep."
|
"sep": "Sep."
|
||||||
},
|
},
|
||||||
|
"notification": {
|
||||||
|
"assign-approver": "You have been assigned as approver for <b><a href=\"{fileHref}\" target=\"_blank\">file</a><b> in the <b><a href=\"{dossierHref}\" target=\"_blank\">dossier</a><b>!",
|
||||||
|
"assign-reviewer": "You have been assigned as reviewer for <b><a href=\"{fileHref}\" target=\"_blank\">file</a><b> in the <b><a href=\"{dossierHref}\" target=\"_blank\">dossier</a><b>!",
|
||||||
|
"unassigned-from-file": "You have been unassigned from <b><a href=\"{fileHref}\" target=\"_blank\">file</a><b> in the <b><a href=\"{dossierHref}\" target=\"_blank\">dossier</a><b>!"
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"mark-read": "Mark as read",
|
"mark-read": "Mark as read",
|
||||||
"mark-unread": "Mark as unread",
|
"mark-unread": "Mark as unread",
|
||||||
|
|||||||
@ -80,3 +80,5 @@ export * from './searchRequest';
|
|||||||
export * from './searchResult';
|
export * from './searchResult';
|
||||||
export * from './matchedDocument';
|
export * from './matchedDocument';
|
||||||
export * from './placeholdersResponse';
|
export * from './placeholdersResponse';
|
||||||
|
export * from './notification';
|
||||||
|
export * from './notificationResponse';
|
||||||
|
|||||||
@ -18,6 +18,6 @@ export interface Notification {
|
|||||||
readDate?: string;
|
readDate?: string;
|
||||||
seenDate?: string;
|
seenDate?: string;
|
||||||
softDeleted?: string;
|
softDeleted?: string;
|
||||||
target?: string;
|
target?: any;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user