|
columnSpan
|
Type : number
|
|
Required : true
|
|
|
|
notifications
|
|
Required : true
|
|
|
|
transformNotifications
|
Type : unknown
|
Default value : () => {...}
|
|
|
import { NxColComponent, NxLayoutComponent, NxRowComponent } from '@allianz/ng-aquila/grid';
import { CHANNEL, CHANNEL_TOKEN, InterpolateFromStorePipe } from '@allianz/taly-core';
import { MarkdownToHtmlComponent } from '@allianz/taly-core/ui';
import {
BaseNotificationConfig,
ErrorNotificationConfig,
NotificationConfig
} from '@allianz/taly-core/schemas';
import { AsyncPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
effect,
inject,
input,
InputSignalWithTransform,
OnDestroy
} from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { TalyFrameTopAreaService } from '../../services/taly-frame-top-area.service';
import { NotificationComponent } from './notification.component';
interface ErrorNotificationWithVisibility extends ErrorNotificationConfig {
visibility$: Observable<boolean>;
}
interface OtherNotificationWithVisibility extends BaseNotificationConfig {
visibility$: BehaviorSubject<boolean>;
}
interface StructuredNotifications {
errors: ErrorNotificationWithVisibility[];
others: OtherNotificationWithVisibility[];
}
@Component({
selector: 'frame-notifications-group',
templateUrl: './notifications-group.component.html',
imports: [
MarkdownToHtmlComponent,
InterpolateFromStorePipe,
AsyncPipe,
NotificationComponent,
NxRowComponent,
NxLayoutComponent,
NxColComponent
],
styleUrl: './notifications-group.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NotificationsGroupComponent implements OnDestroy {
private channel = inject(CHANNEL_TOKEN);
private topAreaService = inject(TalyFrameTopAreaService);
private cdr = inject(ChangeDetectorRef);
private inputChanged$ = new Subject<void>();
protected anyErrorNotificationDisplayed$!: Observable<boolean>;
protected anyNotificationDisplayed$!: Observable<boolean>;
protected errorMessage$!: Observable<string | undefined>;
protected hasConfiguredNotifications = computed(() => {
return this.notifications().errors.length > 0 || this.notifications().others.length > 0;
});
columnSpan = input.required<number>();
notifications: InputSignalWithTransform<StructuredNotifications, NotificationConfig[]> =
input.required({
transform: (notifications) => this.transformNotifications(notifications)
});
constructor() {
this.errorMessage$ = this.topAreaService.networkErrorMessage$;
effect(() => {
this.anyErrorNotificationDisplayed$ = combineLatest([
...this.notifications().errors.map((notification) => notification.visibility$),
this.errorMessage$.pipe(map(Boolean))
]).pipe(
map((visibilityFlags) => visibilityFlags.some((visibilityFlags) => visibilityFlags)),
takeUntil(this.inputChanged$)
);
this.anyNotificationDisplayed$ = combineLatest([
...this.notifications().others.map((notification) => notification.visibility$),
this.anyErrorNotificationDisplayed$
]).pipe(
map((values) => values.some((value) => value)),
takeUntil(this.inputChanged$)
);
this.cdr.markForCheck();
});
}
ngOnDestroy() {
this.inputChanged$.next();
this.inputChanged$.complete();
}
get isExpert() {
return this.channel === CHANNEL.EXPERT;
}
transformNotifications = (notifications: NotificationConfig[]): StructuredNotifications => {
this.inputChanged$.next();
const sortedNotifications = this.sortNotificationsByContext(notifications);
return sortedNotifications.reduce(
(acc: StructuredNotifications, notification) => {
if (notification.context === 'error') {
const notificationWithVisibility = {
...notification,
visibility$: this.topAreaService.getNotificationVisibility$(notification.visibleIf)
};
acc.errors.push(notificationWithVisibility);
} else {
const notificationWithVisibility = this.createToggleableNotification(notification);
acc.others.push(notificationWithVisibility);
}
return acc;
},
{
errors: [],
others: []
}
);
};
private createToggleableNotification(
notification: BaseNotificationConfig
): OtherNotificationWithVisibility {
const visibilitySubject$ = new BehaviorSubject(true);
if (notification.visibleIf) {
this.topAreaService
.getNotificationVisibility$(notification.visibleIf)
.pipe(takeUntil(this.inputChanged$))
.subscribe(visibilitySubject$);
}
return {
...notification,
visibility$: visibilitySubject$
};
}
private sortNotificationsByContext(notifications: NotificationConfig[]) {
const contextOrder = {
error: 0,
warning: 1,
info: 2,
success: 3
};
notifications.sort((a, b) => {
const contextA = a.context;
const contextB = b.context;
return contextOrder[contextA] - contextOrder[contextB];
});
return notifications;
}
}
@if (anyNotificationDisplayed$ | async) {
<div class="notifications-section-wrapper">
<div class="notifications-block-wrapper">
<div nxLayout="grid nopadding" [containerQuery]="true">
<div nxRow [rowJustify]="!isExpert ? 'center' : ''">
<div [nxCol]="'12,12,' + columnSpan()">
<!-- Error Notification -->
@if (anyErrorNotificationDisplayed$ | async) {
<div class="notification-wrapper error-notification-wrapper">
<taly-notification context="error" [closable]="false">
@if (errorMessage$ | async) {
<div class="js-notification-content error-notification-content">
{{ errorMessage$ | async }}
</div>
} @for (notification of notifications().errors; track notification.message) { @if
(notification.visibility$ | async) {
<div class="js-notification-content error-notification-content">
@if (notification?.title) {
<span class="nx-font-weight-bold" data-testid="notification-title">
{{ notification.title | interpolateFromStore | async }}
</span>
<br />
} @defer (when hasConfiguredNotifications()) {
<taly-markdown-to-html
[markdown]="(notification.message | interpolateFromStore | async) || ''"
/>
}
</div>
} }
</taly-notification>
</div>
}
<!-- Other Notifications -->
@for (notification of notifications().others; track notification.message) { @if
(notification.visibility$ | async) {
<div class="notification-wrapper">
<taly-notification
[context]="notification.context"
[closable]="notification.closable ?? true"
(closeNotification)="notification.visibility$.next(false)"
>
<div class="js-notification-content">
@if (notification?.title) {
<span class="nx-font-weight-bold" data-testid="notification-title">
{{ notification.title | interpolateFromStore | async }}
</span>
<br />
} @defer (when hasConfiguredNotifications()) {
<taly-markdown-to-html
[markdown]="(notification.message | interpolateFromStore | async) || ''"
/>
}
</div>
</taly-notification>
</div>
} }
</div>
</div>
</div>
</div>
</div>
}
:host {
display: block;
}
.notification-wrapper + .notification-wrapper {
padding-top: var(--vertical-inner-section-spacing);
}
.error-notification-content + .error-notification-content {
padding-top: var(--vertical-helper-low-spacing);
}
:host-context(.is-stacked),
:host-context(.is-centered) {
.notifications-block-wrapper {
margin-inline: auto;
}
}
Legend
Html element with directive