diff --git a/src/app/app.component.html b/src/app/app.component.html
index 4dd83bd..00031de 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,4 +1,5 @@
+
diff --git a/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.html b/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.html
new file mode 100644
index 0000000..19903b7
--- /dev/null
+++ b/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.html
@@ -0,0 +1,9 @@
+
+
+ {{alert.alert.message}}
+
+
diff --git a/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.sass b/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.sass
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.ts b/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.ts
new file mode 100644
index 0000000..d28c3ba
--- /dev/null
+++ b/src/app/modules/shared/components/global-alert-handler/global-alert-handler.component.ts
@@ -0,0 +1,17 @@
+import {Component} from '@angular/core'
+import {AlertHandlerComponent, AlertService, AlertType} from '../../service/alert.service'
+
+@Component({
+ selector: 'cre-global-alert-handler',
+ templateUrl: './global-alert-handler.component.html',
+ styleUrls: ['./global-alert-handler.component.sass']
+})
+export class GlobalAlertHandlerComponent extends AlertHandlerComponent {
+ readonly SUCCESS_ALERT_TYPE = AlertType.Success
+ readonly WARNING_ALERT_TYPE = AlertType.Warning
+ readonly ERROR_ALERT_TYPE = AlertType.Error
+
+ constructor(alertService: AlertService) {
+ super(alertService)
+ }
+}
diff --git a/src/app/modules/shared/service/alert.service.ts b/src/app/modules/shared/service/alert.service.ts
new file mode 100644
index 0000000..b8e2922
--- /dev/null
+++ b/src/app/modules/shared/service/alert.service.ts
@@ -0,0 +1,106 @@
+import {Injectable} from '@angular/core'
+import {interval} from 'rxjs'
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AlertService {
+ private alertQueue: Alert[] = []
+ private activeHandler: AlertHandlerComponent
+
+ constructor() {
+ // interval(500).subscribe({next: i => this.pushError('Test error ' + i)})
+ }
+
+ pushSuccess(message: String) {
+ this.enqueue(AlertType.Success, message)
+ }
+
+ pushWarning(message: String) {
+ this.enqueue(AlertType.Warning, message)
+ }
+
+ pushError(message: String) {
+ this.enqueue(AlertType.Error, message)
+ }
+
+ set activeAlertHandlerComponent(handler: AlertHandlerComponent) {
+ this.activeHandler = handler
+ }
+
+ private enqueue(type: AlertType, message: String) {
+ const alert = {type, message}
+ if (this.activeHandler && !this.activeHandler.isBufferFull) {
+ if (this.alertQueue.length == 0) {
+ this.activeHandler.pushAlert(alert)
+ } else {
+ this.activeHandler.pushAlert(this.dequeue())
+ this.alertQueue.unshift(alert)
+ }
+ } else {
+ this.alertQueue.unshift(alert)
+ }
+ }
+
+ private dequeue(): Alert {
+ return this.alertQueue.pop()
+ }
+}
+
+/**
+ * An alert handler component is a component that will show the alerts pushed by the alert system to the user.
+ */
+export abstract class AlertHandlerComponent {
+ protected static readonly DEFAULT_ALERT_BUFFER_SIZE = 3
+ protected static readonly DEFAULT_ALERT_DURATION = 5
+
+ alertBuffer = new Array<{ alert: Alert, time: number }>()
+ protected alertBufferSize: number = AlertHandlerComponent.DEFAULT_ALERT_BUFFER_SIZE
+ protected alertDuration: number = AlertHandlerComponent.DEFAULT_ALERT_DURATION
+ protected alertDurationCounter = 0
+
+ protected constructor(
+ protected alertService: AlertService
+ ) {
+ alertService.activeAlertHandlerComponent = this
+
+ interval(1000).subscribe({
+ next: () => {
+ this.alertDurationCounter++
+ if (this.alertBuffer.length > 1) {
+ this.alertBuffer
+ .filter(a => a.time + this.alertDuration < this.alertDurationCounter)
+ .map(a => this.alertBuffer.indexOf(a))
+ .forEach(i => this.alertBuffer.splice(i, 1))
+ }
+ }
+ })
+ }
+
+ pushAlert(alert: Alert) {
+ this.alertBuffer.unshift({alert: alert, time: this.alertDurationCounter})
+ }
+
+ get isBufferFull(): boolean {
+ return this.alertBuffer.length >= this.alertBufferSize
+ }
+}
+
+/**
+ * An alert is a message that will be shown to the user.
+ *
+ * An alert can be a success alert, a warning alert of an error alert.
+ */
+class Alert {
+ constructor(
+ public type: AlertType,
+ public message: String
+ ) {
+ }
+}
+
+export enum AlertType {
+ Success,
+ Warning,
+ Error
+}
diff --git a/src/app/modules/shared/service/error.service.ts b/src/app/modules/shared/service/error.service.ts
new file mode 100644
index 0000000..45c61e7
--- /dev/null
+++ b/src/app/modules/shared/service/error.service.ts
@@ -0,0 +1,107 @@
+import {Injectable} from '@angular/core'
+import {AlertService} from './alert.service'
+import {AppState} from '../app-state'
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ErrorService {
+ private static readonly UNKNOWN_ERROR_MESSAGE = 'Une erreur inconnue est survenue'
+ private defaultHandledErrorModels: ErrorModel[] = [{
+ filter: error => error.status === 0 && error.statusText === 'Unknown Error' || error.status === 502,
+ consumer: () => this.appState.isServerOnline = false
+ }, {
+ filter: error => error.status === 400,
+ consumer: error => console.error(error),
+ messageProducer: () => 'Certaines informations dans la requête étaient invalides'
+ }, {
+ filter: error => error.status === 401,
+ messageProducer: () => 'Vous devez être connecté pour effectuer cette action'
+ }, {
+ filter: error => error.status === 403,
+ messageProducer: () => 'Vous n\'avez pas la permission d\'effectuer cette action'
+ }, {
+ filter: error => error.status === 404,
+ messageProducer: () => 'La resource demandée n\'a pas été trouvée'
+ }, {
+ filter: error => error.status === 500,
+ messageProducer: () => ErrorService.UNKNOWN_ERROR_MESSAGE
+ }]
+
+ private activeHandler: ErrorHandler
+
+ constructor(
+ private alertService: AlertService,
+ private appState: AppState
+ ) {
+ }
+
+ handleError(error: any) {
+ if (!this.activeHandler) {
+ console.error('An error occurred but no handler was set')
+ }
+
+ let matchingModels = this.activeHandler.handledErrorModels.filter(m => m.filter(error)) // Find error models whose filter matches the current error
+
+ if (!matchingModels || matchingModels.length == 0) { // If none are found, search in defaults handlers
+ matchingModels = this.defaultHandledErrorModels.filter(m => m.filter(error))
+ }
+
+ if (!matchingModels || matchingModels.length == 0) { // If still none are found, handle as an unknown error
+ this.consumeUnknownError(error)
+ return
+ }
+
+ matchingModels.forEach(m => {
+ if (m.consumer || m.messageProducer) {
+ if (m.consumer) {
+ m.consumer(error)
+ }
+ if (m.messageProducer) {
+ this.alertService.pushError(m.messageProducer(error))
+ }
+ } else {
+ console.error('An error model has no consumer or message')
+ }
+ })
+ }
+
+ consumeUnknownError(error: any) {
+ console.error(error)
+ this.alertService.pushError(ErrorService.UNKNOWN_ERROR_MESSAGE)
+ }
+
+ set activeErrorHandler(handler: ErrorHandler) {
+ this.activeHandler = handler
+ }
+}
+
+/**
+ * An error handler, defining models of errors that this type will handle
+ */
+export abstract class ErrorHandler {
+ abstract handledErrorModels: ErrorModel[]
+
+ protected constructor(
+ protected errorService: ErrorService
+ ) {
+ this.errorService.activeErrorHandler = this
+ }
+}
+
+/**
+ * An error model define how errors matching its filter will be handled.
+ *
+ * The consumer will consume matching errors when they occurs.
+ * The message producer returns a string that will be pushed to the alert system.
+ *
+ * To work correctly a model must define at least one handler (consumer or message producer).
+ */
+export class ErrorModel {
+ constructor(
+ public filter: (error: any) => Boolean,
+ public consumer?: (error: any) => void,
+ public messageProducer?: (error: any) => String
+ ) {
+ }
+}
diff --git a/src/app/modules/shared/shared.module.ts b/src/app/modules/shared/shared.module.ts
index 62c3d95..8ea2b4a 100644
--- a/src/app/modules/shared/shared.module.ts
+++ b/src/app/modules/shared/shared.module.ts
@@ -27,10 +27,11 @@ import {MatSelectModule} from "@angular/material/select";
import {MatOptionModule} from "@angular/material/core";
import {MaterialFileInputModule} from "ngx-material-file-input";
import { FileButtonComponent } from './file-button/file-button.component';
+import { GlobalAlertHandlerComponent } from './components/global-alert-handler/global-alert-handler.component';
@NgModule({
- declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent],
+ declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent],
exports: [
CommonModule,
HttpClientModule,
@@ -55,7 +56,8 @@ import { FileButtonComponent } from './file-button/file-button.component';
EntityListComponent,
EntityAddComponent,
EntityEditComponent,
- FileButtonComponent
+ FileButtonComponent,
+ GlobalAlertHandlerComponent
],
imports: [
MatTabsModule,