diff --git a/frontend/src/app/general/notice/notice.component.css b/frontend/src/app/general/notice/notice.component.css
index 7047d8786..bde016226 100644
--- a/frontend/src/app/general/notice/notice.component.css
+++ b/frontend/src/app/general/notice/notice.component.css
@@ -2,20 +2,6 @@
* SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
* SPDX-License-Identifier: Apache-2.0
*/
-
-p {
- margin: 0px;
- padding: 0px;
-}
-
-h3 {
- margin: 0px !important;
-}
-
-.url {
- color: white;
-}
-
.primary {
background-color: #004085;
color: white;
diff --git a/frontend/src/app/general/notice/notice.component.html b/frontend/src/app/general/notice/notice.component.html
index 0e86497de..5f29a6ad8 100644
--- a/frontend/src/app/general/notice/notice.component.html
+++ b/frontend/src/app/general/notice/notice.component.html
@@ -3,9 +3,17 @@
~ SPDX-License-Identifier: Apache-2.0
-->
-
-
+
0,
+ }"
+>
+ @for (notice of noticesWrapperService.notices$ | async; track notice.id) {
+
+ }
diff --git a/frontend/src/app/general/notice/notice.component.ts b/frontend/src/app/general/notice/notice.component.ts
index cccd1e54d..6c9e55e18 100644
--- a/frontend/src/app/general/notice/notice.component.ts
+++ b/frontend/src/app/general/notice/notice.component.ts
@@ -2,9 +2,9 @@
* SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
* SPDX-License-Identifier: Apache-2.0
*/
-import { NgFor } from '@angular/common';
+import { AsyncPipe, NgClass } from '@angular/common';
import { Component, ViewEncapsulation } from '@angular/core';
-import { NoticeService } from '../../services/notice/notice.service';
+import { NoticeWrapperService } from 'src/app/general/notice/notice.service';
@Component({
selector: 'app-notice',
@@ -12,8 +12,8 @@ import { NoticeService } from '../../services/notice/notice.service';
styleUrls: ['./notice.component.css'],
encapsulation: ViewEncapsulation.None,
standalone: true,
- imports: [NgFor],
+ imports: [NgClass, AsyncPipe],
})
export class NoticeComponent {
- constructor(public noticeService: NoticeService) {}
+ constructor(public noticesWrapperService: NoticeWrapperService) {}
}
diff --git a/frontend/src/app/general/notice/notice.service.ts b/frontend/src/app/general/notice/notice.service.ts
new file mode 100644
index 000000000..9c0012a93
--- /dev/null
+++ b/frontend/src/app/general/notice/notice.service.ts
@@ -0,0 +1,28 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import { Injectable } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+import { NoticeResponse, NoticesService } from 'src/app/openapi';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class NoticeWrapperService {
+ private _notices = new BehaviorSubject
(
+ undefined,
+ );
+ public readonly notices$ = this._notices.asObservable();
+
+ constructor(private noticesService: NoticesService) {
+ this.refreshNotices();
+ }
+
+ refreshNotices(): void {
+ this._notices.next(undefined);
+ this.noticesService.getNotices().subscribe((res) => {
+ this._notices.next(res);
+ });
+ }
+}
diff --git a/frontend/src/app/general/notice/notice.stories.ts b/frontend/src/app/general/notice/notice.stories.ts
new file mode 100644
index 000000000..896439ea9
--- /dev/null
+++ b/frontend/src/app/general/notice/notice.stories.ts
@@ -0,0 +1,38 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
+import { NoticeWrapperService } from 'src/app/general/notice/notice.service';
+import { NoticeLevel } from 'src/app/openapi';
+import { mockNotice, MockNoticeWrapperService } from 'src/storybook/notices';
+import { NoticeComponent } from './notice.component';
+
+const meta: Meta = {
+ title: 'General Components / Notices',
+ component: NoticeComponent,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const AllLevels: Story = {
+ args: {},
+ decorators: [
+ moduleMetadata({
+ providers: [
+ {
+ provide: NoticeWrapperService,
+ useFactory: () =>
+ new MockNoticeWrapperService(
+ Object.values(NoticeLevel).map((level) => ({
+ ...mockNotice,
+ title: 'This is an example notice with level ' + level,
+ level,
+ })),
+ ),
+ },
+ ],
+ }),
+ ],
+};
diff --git a/frontend/src/app/services/notice/notice.service.ts b/frontend/src/app/services/notice/notice.service.ts
deleted file mode 100644
index 81eca4975..000000000
--- a/frontend/src/app/services/notice/notice.service.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
- * SPDX-License-Identifier: Apache-2.0
- */
-import { HttpClient } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs';
-import { environment } from 'src/environments/environment';
-
-@Injectable({
- providedIn: 'root',
-})
-export class NoticeService {
- notices: Notice[] = [];
- noticeLevels = [
- 'primary',
- 'secondary',
- 'success',
- 'danger',
- 'warning',
- 'info',
- 'alert',
- ];
-
- constructor(private http: HttpClient) {
- this.refreshNotices();
- }
-
- refreshNotices(): void {
- this.getNotices().subscribe((res) => {
- this.notices = res;
- });
- }
-
- getNotices(): Observable {
- return this.http.get(environment.backend_url + '/notices');
- }
-
- deleteNotice(id: number): Observable {
- return this.http.delete(environment.backend_url + '/notices/' + id);
- }
-
- createNotice(body: CreateNotice): Observable {
- return this.http.post(environment.backend_url + '/notices', body);
- }
-}
-
-export interface Notice extends CreateNotice {
- id: number;
-}
-
-export interface CreateNotice {
- level: NoticeLevel;
- title: string;
- message: string;
-}
-
-export type NoticeLevel =
- | 'primary'
- | 'secondary'
- | 'success'
- | 'danger'
- | 'warning'
- | 'info'
- | 'alert';
diff --git a/frontend/src/app/settings/core/alert-settings/alert-settings.component.html b/frontend/src/app/settings/core/alert-settings/alert-settings.component.html
index 84045901a..5f90dbbea 100644
--- a/frontend/src/app/settings/core/alert-settings/alert-settings.component.html
+++ b/frontend/src/app/settings/core/alert-settings/alert-settings.component.html
@@ -6,7 +6,6 @@
Handle alerts
-
-
-
- {{ notice.title }}
-
- {{ notice.level }}
-
-
- {{ notice.message }}
-
-
-
- @if (!noticeService.notices.length) {
- There are no existing alerts.
+ @if ((noticeWrapperService.notices$ | async) === undefined) {
+ @for (_ of [0, 1, 2]; track $index) {
+
+ }
+ } @else {
+
+ @for (
+ notice of noticeWrapperService.notices$ | async;
+ track notice.id
+ ) {
+
+
+ {{ notice.title }}
+
+ {{ notice.level }}
+
+
+ {{ notice.message }}
+
+
+ } @empty {
+ There are no existing alerts.
+ }
+
}
diff --git a/frontend/src/app/settings/core/alert-settings/alert-settings.component.ts b/frontend/src/app/settings/core/alert-settings/alert-settings.component.ts
index e1dce50ee..09d4975b8 100644
--- a/frontend/src/app/settings/core/alert-settings/alert-settings.component.ts
+++ b/frontend/src/app/settings/core/alert-settings/alert-settings.component.ts
@@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
* SPDX-License-Identifier: Apache-2.0
*/
-import { NgFor, NgIf } from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core';
import {
AbstractControl,
@@ -16,21 +16,17 @@ import {
} from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { MatOption } from '@angular/material/core';
-import {
- MatAccordion,
- MatExpansionPanel,
- MatExpansionPanelHeader,
- MatExpansionPanelTitle,
- MatExpansionPanelDescription,
-} from '@angular/material/expansion';
-import { MatFormField, MatLabel, MatError } from '@angular/material/form-field';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatError, MatFormFieldModule } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { MatSelect } from '@angular/material/select';
-import { ToastService } from 'src/app/helpers/toast/toast.service';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+import { NoticeWrapperService } from 'src/app/general/notice/notice.service';
import {
- CreateNotice,
- NoticeService,
-} from 'src/app/services/notice/notice.service';
+ CreateNoticeRequest,
+ NoticeLevel,
+ NoticesService,
+} from 'src/app/openapi';
@Component({
selector: 'app-alert-settings',
@@ -40,20 +36,15 @@ import {
imports: [
FormsModule,
ReactiveFormsModule,
- MatFormField,
- MatLabel,
+ MatFormFieldModule,
MatInput,
MatSelect,
- NgFor,
MatOption,
MatError,
- NgIf,
MatButton,
- MatAccordion,
- MatExpansionPanel,
- MatExpansionPanelHeader,
- MatExpansionPanelTitle,
- MatExpansionPanelDescription,
+ MatExpansionModule,
+ AsyncPipe,
+ NgxSkeletonLoaderModule,
],
})
export class AlertSettingsComponent {
@@ -70,9 +61,13 @@ export class AlertSettingsComponent {
return this.createAlertForm.get('message') as FormControl;
}
+ get noticeLevels(): string[] {
+ return Object.values(NoticeLevel);
+ }
+
constructor(
- public noticeService: NoticeService,
- private toastService: ToastService,
+ public noticeWrapperService: NoticeWrapperService,
+ private noticeService: NoticesService,
) {}
titleOrDescriptionRequired(): ValidatorFn {
@@ -89,20 +84,10 @@ export class AlertSettingsComponent {
createNotice(): void {
if (this.createAlertForm.valid) {
this.noticeService
- .createNotice(this.createAlertForm.value as CreateNotice)
+ .createNotice(this.createAlertForm.value as CreateNoticeRequest)
.subscribe({
next: () => {
- this.noticeService.refreshNotices();
- this.toastService.showSuccess(
- 'Alert created',
- this.createAlertForm.value.title as string,
- );
- },
- error: () => {
- this.toastService.showError(
- 'Creation of alert failed',
- 'Please try again',
- );
+ this.noticeWrapperService.refreshNotices();
},
});
}
@@ -111,14 +96,7 @@ export class AlertSettingsComponent {
deleteNotice(id: number): void {
this.noticeService.deleteNotice(id).subscribe({
next: () => {
- this.toastService.showSuccess('Alert deleted', 'ID: ' + id);
- this.noticeService.refreshNotices();
- },
- error: () => {
- this.toastService.showSuccess(
- 'Deletion of alert failed',
- 'Please try again',
- );
+ this.noticeWrapperService.refreshNotices();
},
});
}
diff --git a/frontend/src/app/settings/core/alert-settings/alert-settings.stories.ts b/frontend/src/app/settings/core/alert-settings/alert-settings.stories.ts
new file mode 100644
index 000000000..11560e458
--- /dev/null
+++ b/frontend/src/app/settings/core/alert-settings/alert-settings.stories.ts
@@ -0,0 +1,52 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
+import { NoticeWrapperService } from 'src/app/general/notice/notice.service';
+import { mockNotice, MockNoticeWrapperService } from 'src/storybook/notices';
+import { AlertSettingsComponent } from './alert-settings.component';
+
+const meta: Meta = {
+ title: 'Settings Components / Alert Settings',
+ component: AlertSettingsComponent,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Loading: Story = {
+ args: {},
+};
+
+export const NoAlerts: Story = {
+ args: {},
+ decorators: [
+ moduleMetadata({
+ providers: [
+ {
+ provide: NoticeWrapperService,
+ useFactory: () => new MockNoticeWrapperService([]),
+ },
+ ],
+ }),
+ ],
+};
+
+export const SomeAlerts: Story = {
+ args: {},
+ decorators: [
+ moduleMetadata({
+ providers: [
+ {
+ provide: NoticeWrapperService,
+ useFactory: () =>
+ new MockNoticeWrapperService([
+ mockNotice,
+ { ...mockNotice, id: 2 },
+ ]),
+ },
+ ],
+ }),
+ ],
+};
diff --git a/frontend/src/storybook/notices.ts b/frontend/src/storybook/notices.ts
new file mode 100644
index 000000000..3dc37e094
--- /dev/null
+++ b/frontend/src/storybook/notices.ts
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import { BehaviorSubject } from 'rxjs';
+import { NoticeWrapperService } from 'src/app/general/notice/notice.service';
+import { NoticeLevel, NoticeResponse } from 'src/app/openapi';
+
+export const mockNotice: NoticeResponse = {
+ id: 1,
+ title: 'Title of the notice',
+ message:
+ 'This is the message / content of a notice. It can also contain simple HTML like links: ' +
+ "example.com",
+ level: NoticeLevel.Info,
+};
+
+export class MockNoticeWrapperService implements Partial {
+ private _notices = new BehaviorSubject(
+ undefined,
+ );
+ public readonly notices$ = this._notices.asObservable();
+
+ constructor(notices: NoticeResponse[]) {
+ this._notices.next(notices);
+ }
+}