From 80f09b6f180d674e558252ecb6f9510aa9ee68f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 8 Jan 2023 16:57:21 +0100 Subject: [PATCH] custom notifications service --- Frontend/src/app/app.module.ts | 4 +- .../login-page/login-page.component.ts | 19 ++-- .../notifications.component.html | 8 ++ .../notifications.component.scss | 17 ++++ .../notifications.component.spec.ts | 23 +++++ .../notifications/notifications.component.ts | 13 +++ .../app/main-view/main-view.component.scss | 97 ------------------- .../src/app/main-view/main-view.component.ts | 3 +- Frontend/src/app/material.module.ts | 78 +++++++-------- .../layer-detail/layer-detail.component.ts | 18 ++-- .../layers/layer-edit/layer-edit.component.ts | 1 - .../src/app/services/notifications.service.ts | 59 +++++++++++ Frontend/src/styles.scss | 8 -- WebAPI/appsettings.Development.json | 2 +- 14 files changed, 187 insertions(+), 163 deletions(-) create mode 100644 Frontend/src/app/components/notifications/notifications.component.html create mode 100644 Frontend/src/app/components/notifications/notifications.component.scss create mode 100644 Frontend/src/app/components/notifications/notifications.component.spec.ts create mode 100644 Frontend/src/app/components/notifications/notifications.component.ts create mode 100644 Frontend/src/app/services/notifications.service.ts diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index 397f87e..908df17 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -14,6 +14,7 @@ import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MomentDateAdapter } from '@angular/material-moment-adapter'; import { registerLocaleData } from '@angular/common'; import localePl from '@angular/common/locales/pl'; +import { NotificationsComponent } from './components/notifications/notifications.component'; registerLocaleData(localePl, 'pl-PL'); @@ -21,7 +22,8 @@ registerLocaleData(localePl, 'pl-PL'); declarations: [ AppComponent, MainViewComponent, - LoginPageComponent + LoginPageComponent, + NotificationsComponent ], imports: [ BrowserModule, diff --git a/Frontend/src/app/components/login-page/login-page.component.ts b/Frontend/src/app/components/login-page/login-page.component.ts index 90a05cf..b4bb825 100644 --- a/Frontend/src/app/components/login-page/login-page.component.ts +++ b/Frontend/src/app/components/login-page/login-page.component.ts @@ -1,9 +1,9 @@ import { Component, NgZone, OnInit } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; import jwt_decode from "jwt-decode"; import { AuthService } from 'src/app/auth/auth.service'; import { User } from 'src/app/models/user.model'; +import { NotificationsService } from 'src/app/services/notifications.service'; import { environment } from 'src/environments/environment'; @Component({ @@ -17,14 +17,12 @@ export class LoginPageComponent implements OnInit { private router$: Router, private auth$: AuthService, private ngZone$: NgZone, - private snackBar$: MatSnackBar + private notifications$: NotificationsService ) { } loading = false; ngOnInit(): void { - this.snackBar$.open("", "", { duration: 1 }); - console.log('Envs', environment); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore window.onGoogleLibraryLoad = () => { @@ -66,13 +64,20 @@ export class LoginPageComponent implements OnInit { this.ngZone$.run(() => { this.router$.navigate(['/app']); }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { + console.error('handleCredentialResponse', e); this.loading = false; if (e.status === 401) { - this.snackBar$.open("Użytkownik nie istnieje w bazie danych aplikacji DiunaBI.", "", { duration: 3000 }); + this.notifications$.add({ + text: "Użytkownik nie istnieje w bazie danych aplikacji DiunaBI.", + duration: 2500 + }); } else { - this.snackBar$.open("Błąd połączenia z serwerem. Skontaktuj się z administratorem.", "", { duration: 3000 }); + this.notifications$.add({ + text: "Błąd połączenia z serwerem. Skontaktuj się z administratorem.", + duration: 2500 + }); } } finally { this.loading = false; diff --git a/Frontend/src/app/components/notifications/notifications.component.html b/Frontend/src/app/components/notifications/notifications.component.html new file mode 100644 index 0000000..7195276 --- /dev/null +++ b/Frontend/src/app/components/notifications/notifications.component.html @@ -0,0 +1,8 @@ + + + {{msg.text}} + + {{msg.btn}} + + + \ No newline at end of file diff --git a/Frontend/src/app/components/notifications/notifications.component.scss b/Frontend/src/app/components/notifications/notifications.component.scss new file mode 100644 index 0000000..98dcdbf --- /dev/null +++ b/Frontend/src/app/components/notifications/notifications.component.scss @@ -0,0 +1,17 @@ +mat-card { + margin-bottom: 3px; + background-color: rgba(255, 145, 0, 0.4); +} +.action-button { + cursor: pointer; +} +.text { + display: inline-block; + width: 70%; +} +.btn { + display: inline-block; + width: 30%; + text-align: right; + color: red; +} \ No newline at end of file diff --git a/Frontend/src/app/components/notifications/notifications.component.spec.ts b/Frontend/src/app/components/notifications/notifications.component.spec.ts new file mode 100644 index 0000000..07f2f87 --- /dev/null +++ b/Frontend/src/app/components/notifications/notifications.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotificationsComponent } from './notifications.component'; + +describe('NotificationsComponent', () => { + let component: NotificationsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NotificationsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NotificationsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Frontend/src/app/components/notifications/notifications.component.ts b/Frontend/src/app/components/notifications/notifications.component.ts new file mode 100644 index 0000000..ebb4747 --- /dev/null +++ b/Frontend/src/app/components/notifications/notifications.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { NotificationsService } from 'src/app/services/notifications.service'; + +@Component({ + selector: 'app-notifications', + templateUrl: './notifications.component.html', + styleUrls: ['./notifications.component.scss'], +}) +export class NotificationsComponent { + constructor( + public notifications$: NotificationsService + ) {} +} diff --git a/Frontend/src/app/main-view/main-view.component.scss b/Frontend/src/app/main-view/main-view.component.scss index 30d86e0..f21e659 100644 --- a/Frontend/src/app/main-view/main-view.component.scss +++ b/Frontend/src/app/main-view/main-view.component.scss @@ -120,106 +120,9 @@ textarea[disabled] { color: rgb(205, 206, 177); font-size: larger; } -.errorMsg { - font-size: small; - margin-top: 10px; -} /* links */ a:link, a:visited { color: black; -} -.stock-state-error { - color: red; - font-weight: bold; -} -.listContainer { - height: 100%; - width: 100%; - overflow: scroll; -} -.listItem { - height: 40px; - font-size: medium; -} - -.listItem:hover { - width: 100%; - cursor: pointer; - background: rgba(130, 130, 130, 0.2); -} - -/* snack bars */ -.snack-error { - background: #f44336; -} - -.input-right { - display: block; - direction: rtl; -} - -.background-red { - background-color: rgba(255, 0, 0, 0.6); -} - -.background-orange { - background-color: rgba(255, 145, 0, 0.6); -} - -.background-green { - background-color: rgba(0, 255, 0, 0.6); -} - -.background-grey { - background-color: rgba(100, 100, 100, 0.3); -} - -.mat-mdc-dialog-container { - padding: 0px !important; -} - -.tab-loader { - width: 25px; - margin-right: 10px; -} - -.cell-border { - border-left: 1px solid #e0e0e0; - padding-left: 4px; -} - -.cell-background { - background-color: #eeeeee; -} - -.breadcrumbs { - width: 100%; - text-align: right; - font-size: small; - color: gray; - margin-top: 0px; - padding-top: 0px; -} - -.breadcrumbs a { - color: gray; - text-decoration: none; -} - -::ng-deep snack-bar-container.custom-snackbar-error { - background: #ff3452; -} - -::ng-deep snack-bar-container.custom-snackbar-success { - background: #9ad284; -} - -::ng-deep .mat-mdc-simple-snack-bar { - color: #000; -} - -::ng-deep .mat-mdc-snack-bar-action { - color: #000; } \ No newline at end of file diff --git a/Frontend/src/app/main-view/main-view.component.ts b/Frontend/src/app/main-view/main-view.component.ts index 8b771e7..0a7aae5 100644 --- a/Frontend/src/app/main-view/main-view.component.ts +++ b/Frontend/src/app/main-view/main-view.component.ts @@ -9,6 +9,7 @@ import { environment } from 'src/environments/environment'; import { AuthService } from '../auth/auth.service'; import { DataService } from '../services/data.service'; import { DeviceService } from '../services/device.service'; +import { NotificationsService } from '../services/notifications.service'; @Component({ selector: 'app-main-view', @@ -30,7 +31,7 @@ export class MainViewComponent { private router$: Router, private swUpdate$: SwUpdate, private ngZone$: NgZone, - public auth$: AuthService + public auth$: AuthService, ) { this.swUpdate$.versionUpdates.subscribe(() => { this.reloadApp(); diff --git a/Frontend/src/app/material.module.ts b/Frontend/src/app/material.module.ts index 423f680..1b3c04a 100644 --- a/Frontend/src/app/material.module.ts +++ b/Frontend/src/app/material.module.ts @@ -1,72 +1,72 @@ import {NgModule} from '@angular/core'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; +//import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; -import { MatButtonToggleModule } from '@angular/material/button-toggle'; +//import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatStepperModule } from '@angular/material/stepper'; -import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatExpansionModule } from '@angular/material/expansion'; +//import { MatCheckboxModule } from '@angular/material/checkbox'; +//import { MatChipsModule } from '@angular/material/chips'; +//import { MatStepperModule } from '@angular/material/stepper'; +//import { MatDatepickerModule } from '@angular/material/datepicker'; +//import { MatDialogModule } from '@angular/material/dialog'; +//import { MatExpansionModule } from '@angular/material/expansion'; import { MatGridListModule } from '@angular/material/grid-list'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; import { MatMenuModule } from '@angular/material/menu'; import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatRadioModule } from '@angular/material/radio'; -import { MatRippleModule } from '@angular/material/core'; +//import { MatProgressBarModule } from '@angular/material/progress-bar'; +//import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +//import { MatRadioModule } from '@angular/material/radio'; +//import { MatRippleModule } from '@angular/material/core'; import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatSliderModule } from '@angular/material/slider'; -import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; +//import { MatSliderModule } from '@angular/material/slider'; +//import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; -import { MatTabsModule } from '@angular/material/tabs'; +//import { MatTabsModule } from '@angular/material/tabs'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import {CdkTableModule} from '@angular/cdk/table'; -import {MatBadgeModule} from '@angular/material/badge'; +//import { MatTooltipModule } from '@angular/material/tooltip'; +//import {CdkTableModule} from '@angular/cdk/table'; +//import {MatBadgeModule} from '@angular/material/badge'; +import {MatBottomSheetModule} from '@angular/material/bottom-sheet'; @NgModule({ exports: [ - CdkTableModule, - MatAutocompleteModule, + // CdkTableModule, + // MatAutocompleteModule, MatButtonModule, - MatButtonToggleModule, + // MatButtonToggleModule, MatCardModule, - MatCheckboxModule, - MatChipsModule, - MatStepperModule, - MatDatepickerModule, - MatDialogModule, - MatExpansionModule, + // MatCheckboxModule, + // MatChipsModule, + // MatStepperModule, + // MatDatepickerModule, + // MatDialogModule, + // MatExpansionModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatPaginatorModule, - MatProgressBarModule, - MatProgressSpinnerModule, - MatRadioModule, - MatRippleModule, + // MatProgressBarModule, + // MatProgressSpinnerModule, + // MatRadioModule, + // MatRippleModule, MatSelectModule, - MatSidenavModule, - MatSliderModule, - MatSlideToggleModule, - MatSnackBarModule, + // MatSidenavModule, + // MatSliderModule, + // MatSlideToggleModule, MatSortModule, MatTableModule, - MatTabsModule, + // MatTabsModule, MatToolbarModule, - MatTooltipModule, + // MatTooltipModule, MatSidenavModule, - MatBadgeModule + // MatBadgeModule, + MatBottomSheetModule ], providers: [] }) diff --git a/Frontend/src/app/modules/layers/layer-detail/layer-detail.component.ts b/Frontend/src/app/modules/layers/layer-detail/layer-detail.component.ts index ecc7286..85c02eb 100644 --- a/Frontend/src/app/modules/layers/layer-detail/layer-detail.component.ts +++ b/Frontend/src/app/modules/layers/layer-detail/layer-detail.component.ts @@ -2,14 +2,13 @@ import { DatePipe } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { Component, OnInit, ViewChild } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder } from '@angular/forms'; -import { MatPaginator } from '@angular/material/paginator'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSort, MatSortable } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; -import { Router, ActivatedRoute } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { AuthService } from 'src/app/auth/auth.service'; import { Layer } from 'src/app/models/layer.model'; import { Record } from 'src/app/models/record.model'; +import { NotificationsService } from 'src/app/services/notifications.service'; import { environment } from 'src/environments/environment'; @Component({ @@ -29,12 +28,11 @@ export class LayerDetailComponent implements OnInit { constructor( private fb$: UntypedFormBuilder, - private router$: Router, private http$: HttpClient, private route$: ActivatedRoute, private auth$: AuthService, - private snackBar: MatSnackBar, - private datePipe: DatePipe + private datePipe: DatePipe, + private notifications$: NotificationsService ) { } async ngOnInit() { @@ -55,9 +53,13 @@ export class LayerDetailComponent implements OnInit { } async export() { if (await Layer.exportToGoogleSheet(this.document.id || "", this.http$)) { - this.snackBar.open("Plik został zapisany na dysku Google", "OK"); + this.notifications$.add({ + text: "Plik został zapisany na dysku Google.", + }); } else { - this.snackBar.open("Zapis się nie udał.", "OK"); + this.notifications$.add({ + text: "Zapis się nie udał.", + }); } } } diff --git a/Frontend/src/app/modules/layers/layer-edit/layer-edit.component.ts b/Frontend/src/app/modules/layers/layer-edit/layer-edit.component.ts index 87c2225..f804d0d 100644 --- a/Frontend/src/app/modules/layers/layer-edit/layer-edit.component.ts +++ b/Frontend/src/app/modules/layers/layer-edit/layer-edit.component.ts @@ -1,7 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Component, OnInit, ViewChild } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Router, ActivatedRoute } from '@angular/router'; diff --git a/Frontend/src/app/services/notifications.service.ts b/Frontend/src/app/services/notifications.service.ts new file mode 100644 index 0000000..b893880 --- /dev/null +++ b/Frontend/src/app/services/notifications.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@angular/core'; +import { MatBottomSheet, MatBottomSheetRef } from '@angular/material/bottom-sheet'; +import { NotificationsComponent } from '../components/notifications/notifications.component'; +import { v4 as uuidv4 } from 'uuid'; +import moment, { Moment } from 'moment'; + +@Injectable({ + providedIn: 'root' +}) +export class NotificationsService { + + public messages: Message[] = []; + private handler?: MatBottomSheetRef; + + constructor( + private bottomSheet$: MatBottomSheet + ) { } + public add(message: Message) { + message.id = uuidv4(); + message.createdAt = moment(); + this.messages.push(message); + this.sortMessages(); + if (this.messages.length === 1) { + this.handler = this.bottomSheet$.open(NotificationsComponent, { + hasBackdrop: false, + disableClose: true + }); + } + setTimeout(() => { + this.remove(message); + }, message.duration ? message.duration : 5000); + } + private remove(message: Message) { + this.messages = this.messages.filter(x => x.id!==message.id); + this.sortMessages(); + if (this.messages.length === 0 && this.handler) { + this.handler.dismiss(); + } + } + private sortMessages() { + this.messages = this.messages.sort((a, b) => { + return a.createdAt && a.createdAt.isAfter(b.createdAt) ? 1 : -1; + }) + } + doAction(message: Message) { + if (message.action) { message.action(); } + this.remove(message); + } +} + +interface Message { + id?: string; + text: string; + duration?: number; + btn?: string; + // eslint-disable-next-line @typescript-eslint/ban-types + action?: Function; + createdAt?: Moment +} diff --git a/Frontend/src/styles.scss b/Frontend/src/styles.scss index 298db6a..4e60916 100644 --- a/Frontend/src/styles.scss +++ b/Frontend/src/styles.scss @@ -44,14 +44,6 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } z-index: 100; } - .snackbar-success { - background: #BEFF33; - } - - .snackbar-error { - background: #e7334b; - } - :root { --avatar-size: 30px; } diff --git a/WebAPI/appsettings.Development.json b/WebAPI/appsettings.Development.json index 860df4c..93a022c 100644 --- a/WebAPI/appsettings.Development.json +++ b/WebAPI/appsettings.Development.json @@ -8,7 +8,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "SQLDatabase": "Server=tcp:127.0.0.1,1433;Initial Catalog=diuna;Persist Security Info=False;User ID=SA;Password=v](8Lc|RfG;MultipleActiveResultSets=False;Encrypt=False;TrustServerCertificate=False;Connection Timeout=30;" + "SQLDatabase": "Server=tcp:127.0.0.1,1433;Initial Catalog=diunabi-morska;Persist Security Info=False;User ID=SA;Password=v](8Lc|RfG;MultipleActiveResultSets=False;Encrypt=False;TrustServerCertificate=False;Connection Timeout=30;" }, "GoogleClientId": "107631825312-bkfe438ehr9k9ecb2h76g802tj6advma.apps.googleusercontent.com", "Secret": "8393AF8EAEF8478CB738D44858690F9C7E2D19F65896DD9FBAA3EB2A6F493E80",