diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index f8e65170c..116926226 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -91,6 +91,7 @@ import { AceEditorModule } from 'ng2-ace-editor'; import { TeamMembersComponent } from './components/team-members/team-members.component'; import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component'; import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component'; +import { NotificationsComponent } from './components/notifications/notifications.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -246,7 +247,8 @@ const matImports = [ DictionaryOverviewScreenComponent, TeamMembersComponent, AdminBreadcrumbsComponent, - UserListingScreenComponent + UserListingScreenComponent, + NotificationsComponent ], imports: [ BrowserModule, diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html index 4743f6ed0..bdad4ece7 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html @@ -6,8 +6,10 @@ [class.warn]="type === 'warn'" [disabled]="disabled" [class.small]="small" + [class.overlay]="showDot" mat-icon-button > +
diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts index ee2280b3c..085885e2a 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts @@ -8,6 +8,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; export class CircleButtonComponent implements OnInit { @Input() icon: string; @Input() tooltip: string; + @Input() showDot = false; @Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above'; @Input() tooltipClass: string; @Input() disabled = false; diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.html b/apps/red-ui/src/app/components/notifications/notifications.component.html new file mode 100644 index 000000000..844de2f90 --- /dev/null +++ b/apps/red-ui/src/app/components/notifications/notifications.component.html @@ -0,0 +1,24 @@ + + +
+
{{ day(group) }}
+
+ +
+
+
{{ eventTime(notification.eventTime) }}
+
+
+
+
+
diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.scss b/apps/red-ui/src/app/components/notifications/notifications.component.scss new file mode 100644 index 000000000..b34fb71a1 --- /dev/null +++ b/apps/red-ui/src/app/components/notifications/notifications.component.scss @@ -0,0 +1,73 @@ +@import '../../../assets/styles/red-variables'; + +.mt-2 { + margin-top: 2px; +} + +::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box { + right: 0 !important; + + .notifications-menu { + min-width: 400px !important; + padding: 0 8px 16px; + max-height: calc(100vh - 200px); + + .all-caps-label { + margin: 16px 0 6px 8px; + } + + .mat-menu-item.notification { + padding: 8px 26px 10px 8px; + margin: 2px 0 0 0; + height: fit-content; + position: relative; + line-height: 18px; + align-items: flex-start; + + .notification-content { + margin-top: 4px; + margin-left: 8px; + max-width: 320px; + + > div { + white-space: normal; + + a { + color: $accent; + font-weight: bold; + } + } + } + + .dot { + height: 8px; + width: 8px; + border-radius: 50%; + background-color: $grey-4; + position: absolute; + top: 8px; + right: 8px; + } + + &:hover { + background-color: $grey-6; + + .dot { + background-color: $grey-5; + } + + a { + text-decoration: underline; + } + } + + &.unread { + background-color: rgba($primary, 0.1); + + .dot { + background-color: $primary; + } + } + } + } +} diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.ts b/apps/red-ui/src/app/components/notifications/notifications.component.ts new file mode 100644 index 000000000..d6c4b04bb --- /dev/null +++ b/apps/red-ui/src/app/components/notifications/notifications.component.ts @@ -0,0 +1,73 @@ +import { Component, OnInit } from '@angular/core'; +import * as moment from 'moment'; +import { TranslateService } from '@ngx-translate/core'; + +interface Notification { + message: string; + eventTime: number; + read: boolean; +} + +@Component({ + selector: 'redaction-notifications', + templateUrl: './notifications.component.html', + styleUrls: ['./notifications.component.scss'] +}) +export class NotificationsComponent implements OnInit { + public notifications: Notification[] = [ + { message: 'This is a notification with longer text wrapping on multiple lines', eventTime: 1607340971000, read: false }, + { message: 'This is a link', eventTime: 1607254981000, read: true }, + { message: 'This is a notification 1', eventTime: 1607254571000, read: false }, + { message: 'Notification', eventTime: 1607385727000, read: true }, + { message: 'Another notification', eventTime: 1606829412000, read: false } + ]; + public groupedNotifications: { dateString: string; notifications: Notification[] }[] = []; + + constructor(private _translateService: TranslateService) { + this._groupNotifications(); + } + + ngOnInit(): void {} + + public get hasUnread() { + return this.notifications.filter((notification) => !notification.read).length > 0; + } + + private _groupNotifications() { + const res = {}; + for (const notification of this.notifications) { + const date = moment(notification.eventTime).format('YYYY-MM-DD'); + if (!res[date]) res[date] = []; + res[date].push(notification); + } + for (const key of Object.keys(res)) { + this.groupedNotifications.push({ dateString: key, notifications: res[key] }); + } + } + + public 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' + }); + } + + public eventTime(eventTime: number): string { + moment.locale(this._translateService.currentLang); + if (moment().isSame(eventTime, 'day')) { + return moment(eventTime).fromNow(); + } else { + return moment(eventTime).format('hh:mm A'); + } + } + + public toggleRead(notification: Notification, $event) { + $event.stopPropagation(); + notification.read = !notification.read; + } +} diff --git a/apps/red-ui/src/app/icons/icons.module.ts b/apps/red-ui/src/app/icons/icons.module.ts index f8ef97199..8f3a5b189 100644 --- a/apps/red-ui/src/app/icons/icons.module.ts +++ b/apps/red-ui/src/app/icons/icons.module.ts @@ -41,6 +41,7 @@ export class IconsModule { 'logout', 'menu', 'needs-work', + 'notification', 'pages', 'plus', 'preview', diff --git a/apps/red-ui/src/app/screens/base-screen/base-screen.component.html b/apps/red-ui/src/app/screens/base-screen/base-screen.component.html index 2071ed4f6..c26833d98 100644 --- a/apps/red-ui/src/app/screens/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/screens/base-screen/base-screen.component.html @@ -56,8 +56,9 @@