From 0b3d4dd3b9e45e6a06aadff2dc856666945ed518 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Fri, 25 Jun 2021 16:23:39 +0200
Subject: [PATCH] Admin settings => done

---
 client/src/app/admin/components/index.ts      |  4 +-
 .../settings/form-option.component.html       |  8 +-
 .../settings/form-option.component.ts         | 33 +++++--
 .../settings/form-select.component.html       |  6 +-
 .../settings/form-select.component.ts         | 30 ++++---
 .../app/admin/components/settings/index.ts    |  4 +-
 .../settings/option-list.component.ts         | 32 -------
 ...onent.html => option-table.component.html} | 27 ++----
 .../settings/option-table.component.ts        | 14 +++
 .../settings/select-buttons.component.html    | 54 ++++--------
 .../settings/select-buttons.component.ts      | 23 ++---
 .../settings/select-list.component.html       |  8 +-
 .../settings/select-list.component.scss       |  8 --
 .../settings/select-list.component.ts         |  1 -
 .../components/shared/add-btn.component.html  | 12 +++
 .../components/shared/add-btn.component.ts    | 23 +++++
 .../components/shared/edit-btn.component.html | 12 +++
 .../components/shared/edit-btn.component.ts   | 23 +++++
 .../src/app/admin/components/shared/index.ts  |  9 ++
 .../settings/settings.component.html          | 21 +++--
 .../containers/settings/settings.component.ts | 24 +++---
 .../store/actions/select-option.actions.ts    |  9 ++
 .../metamodel/store/actions/select.actions.ts |  9 ++
 .../store/effects/select-option.effects.ts    | 85 +++++++++++++++++-
 .../metamodel/store/effects/select.effects.ts | 86 ++++++++++++++++++-
 .../store/reducers/select-option.reducer.ts   |  9 ++
 .../store/reducers/select.reducer.ts          |  9 ++
 client/src/app/shared/shared.module.ts        |  3 +
 client/src/styles.scss                        | 14 +++
 29 files changed, 429 insertions(+), 171 deletions(-)
 delete mode 100644 client/src/app/admin/components/settings/option-list.component.ts
 rename client/src/app/admin/components/settings/{option-list.component.html => option-table.component.html} (52%)
 create mode 100644 client/src/app/admin/components/settings/option-table.component.ts
 delete mode 100644 client/src/app/admin/components/settings/select-list.component.scss
 create mode 100644 client/src/app/admin/components/shared/add-btn.component.html
 create mode 100644 client/src/app/admin/components/shared/add-btn.component.ts
 create mode 100644 client/src/app/admin/components/shared/edit-btn.component.html
 create mode 100644 client/src/app/admin/components/shared/edit-btn.component.ts
 create mode 100644 client/src/app/admin/components/shared/index.ts

diff --git a/client/src/app/admin/components/index.ts b/client/src/app/admin/components/index.ts
index 585ffe5d..f1a04357 100644
--- a/client/src/app/admin/components/index.ts
+++ b/client/src/app/admin/components/index.ts
@@ -1,12 +1,12 @@
+import { sharedComponents } from "./shared";
 import { InstanceCardComponent } from "./instance/instance-card.component";
-import { DeleteBtnComponent } from "./shared/delete-btn.component";
 import { SurveyTableComponent } from "./survey/survey-table.component";
 import { DatabaseTableComponent } from "./database/database-table.component";
 import { settingsComponents } from './settings';
 
 export const dummiesComponents = [
+    sharedComponents,
     InstanceCardComponent,
-    DeleteBtnComponent,
     SurveyTableComponent,
     DatabaseTableComponent,
     settingsComponents
diff --git a/client/src/app/admin/components/settings/form-option.component.html b/client/src/app/admin/components/settings/form-option.component.html
index 54407fdf..d957337c 100644
--- a/client/src/app/admin/components/settings/form-option.component.html
+++ b/client/src/app/admin/components/settings/form-option.component.html
@@ -1,15 +1,15 @@
-<form name="form" (ngSubmit)="f.form.valid && emit(f.form.value)" #f="ngForm" novalidate>
+<form [formGroup]="selectOptionForm" (ngSubmit)="submit()" novalidate>
     <div class="form-group">
         <label for="label">Label</label>
-        <input type="text" class="form-control" name="label" [ngModel]="model.label" #label="ngModel" required>
+        <input type="text" class="form-control" id="label" name="label" formControlName="label">
     </div>
     <div class="form-group">
         <label for="value">Value</label>
-        <input type="text" class="form-control" name="value" [ngModel]="model.value" #value="ngModel" required>
+        <input type="text" class="form-control" id="value" name="value" formControlName="value">
     </div>
     <div class="form-group">
         <label for="display">Display</label>
-        <input type="number" class="form-control" name="display" [ngModel]="model.display" #displays="ngModel" required>
+        <input type="number" class="form-control" id="display" name="display" formControlName="display">
     </div>
     <div class="form-group">
         <ng-content></ng-content>
diff --git a/client/src/app/admin/components/settings/form-option.component.ts b/client/src/app/admin/components/settings/form-option.component.ts
index 1ff1b0fd..62655a0b 100644
--- a/client/src/app/admin/components/settings/form-option.component.ts
+++ b/client/src/app/admin/components/settings/form-option.component.ts
@@ -1,5 +1,5 @@
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { NgForm } from '@angular/forms';
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
 
 import { SelectOption } from 'src/app/metamodel/store/models';
 
@@ -8,11 +8,30 @@ import { SelectOption } from 'src/app/metamodel/store/models';
     templateUrl: 'form-option.component.html'
 })
 export class FormOptionComponent {
-    @ViewChild(NgForm, {static: true}) ngForm: NgForm;
-    @Input() model: SelectOption;
-    @Output() submitted: EventEmitter<SelectOption> = new EventEmitter();
+    @Input() selectOption: SelectOption;
+    @Output() onSubmit: EventEmitter<SelectOption> = new EventEmitter();
 
-    emit(option: SelectOption) {
-        this.submitted.emit({id: this.model.id, ...option});
+    public selectOptionForm = new FormGroup({
+        label: new FormControl('', [Validators.required]),
+        value: new FormControl('', [Validators.required]),
+        display: new FormControl('', [Validators.required])
+    });
+
+    ngOnInit() {
+        if (this.selectOption) {
+            this.selectOptionForm.patchValue(this.selectOption);
+        }
+    }
+
+    submit() {
+        if (this.selectOption) {
+            this.onSubmit.emit({
+                ...this.selectOption,
+                ...this.selectOptionForm.value
+            });
+        } else {
+            this.onSubmit.emit(this.selectOptionForm.value);
+            this.selectOptionForm.reset(); 
+        }
     }
 }
diff --git a/client/src/app/admin/components/settings/form-select.component.html b/client/src/app/admin/components/settings/form-select.component.html
index f5957ca0..e53bb31a 100644
--- a/client/src/app/admin/components/settings/form-select.component.html
+++ b/client/src/app/admin/components/settings/form-select.component.html
@@ -1,11 +1,11 @@
-<form [formGroup]="selectForm">
+<form [formGroup]="selectForm" (ngSubmit)="onSubmit.emit(selectForm.getRawValue())" novalidate>
     <div class="form-group">
         <label for="name">Name</label>
-        <input type="text" class="form-control" name="name" [ngModel]="model.name" #name="ngModel" [disabled]="model.name" required>
+        <input type="text" class="form-control" id="name" name="name" formControlName="name">
     </div>
     <div class="form-group">
         <label for="label">Label</label>
-        <input type="text" class="form-control" name="label" [ngModel]="model.label" #label="ngModel" required>
+        <input type="text" class="form-control" id="label" name="label" formControlName="label">
     </div>
     <div class="form-group">
         <ng-content></ng-content>
diff --git a/client/src/app/admin/components/settings/form-select.component.ts b/client/src/app/admin/components/settings/form-select.component.ts
index a27114cc..a3dfc0fc 100644
--- a/client/src/app/admin/components/settings/form-select.component.ts
+++ b/client/src/app/admin/components/settings/form-select.component.ts
@@ -1,5 +1,5 @@
-import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
-import { NgForm, FormGroup, FormControl } from '@angular/forms';
+import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
 
 import { Select } from 'src/app/metamodel/store/models';
 
@@ -7,19 +7,23 @@ import { Select } from 'src/app/metamodel/store/models';
     selector: 'app-form-select',
     templateUrl: 'form-select.component.html'
 })
-export class FormSelectComponent {
-    @ViewChild(NgForm, {static: true}) ngForm: NgForm;
-    @Input() model: Select;
-    @Output() submitted: EventEmitter<Select> = new EventEmitter();
+export class FormSelectComponent implements OnInit, OnChanges {
+    @Input() select: Select;
+    @Output() onSubmit: EventEmitter<Select> = new EventEmitter();
 
-    selectForm = new FormGroup({
-        name: new FormControl(''),
-        label: new FormControl('')
+    public selectForm = new FormGroup({
+        name: new FormControl('', [Validators.required, Validators.pattern('[a-z1-9_]*')]),
+        label: new FormControl('', [Validators.required])
     });
 
-    emit(select: Select) {
-        let selectEmitted: Select;
-        (this.model.name) ? selectEmitted = {name: this.model.name, ...select} : selectEmitted = select;
-        this.submitted.emit(selectEmitted);
+    ngOnInit() {
+        if (this.select) {
+            this.selectForm.setValue(this.select);
+            this.selectForm.controls.name.disable();
+        }
+    }
+
+    ngOnChanges(changes: SimpleChanges) {
+        this.selectForm.setValue(changes.select.currentValue);
     }
 }
diff --git a/client/src/app/admin/components/settings/index.ts b/client/src/app/admin/components/settings/index.ts
index 9364ca7e..ed1b9a88 100644
--- a/client/src/app/admin/components/settings/index.ts
+++ b/client/src/app/admin/components/settings/index.ts
@@ -1,13 +1,13 @@
 import { SelectListComponent } from "./select-list.component";
 import { SelectButtonsComponent } from "./select-buttons.component";
-import { OptionListComponent } from "./option-list.component";
+import { OptionTableComponent } from "./option-table.component";
 import { FormSelectComponent } from "./form-select.component";
 import { FormOptionComponent } from "./form-option.component";
 
 export const settingsComponents = [
     SelectListComponent,
     SelectButtonsComponent,
-    OptionListComponent,
+    OptionTableComponent,
     FormSelectComponent,
     FormOptionComponent
 ];
diff --git a/client/src/app/admin/components/settings/option-list.component.ts b/client/src/app/admin/components/settings/option-list.component.ts
deleted file mode 100644
index c1cfb8ed..00000000
--- a/client/src/app/admin/components/settings/option-list.component.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core';
-
-import { BsModalService } from 'ngx-bootstrap/modal';
-import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
-
-import { SelectOption } from 'src/app/metamodel/store/models';
-
-@Component({
-    selector: 'app-option-list',
-    templateUrl: 'option-list.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class OptionListComponent {
-    @Input() optionList: SelectOption[];
-    @Output() editOption: EventEmitter<SelectOption> = new EventEmitter();
-    @Output() deleteOption: EventEmitter<SelectOption> = new EventEmitter();
-
-    modalRef: BsModalRef;
-    optionSelected: SelectOption;
-
-    constructor(private modalService: BsModalService) { }
-
-    openModal(template: TemplateRef<any>, option: SelectOption) {
-        this.optionSelected = option;
-        this.modalRef = this.modalService.show(template);
-    }
-
-    confirmEdit(option: SelectOption) {
-        this.editOption.emit(option);
-        this.modalRef.hide();
-    }
-}
diff --git a/client/src/app/admin/components/settings/option-list.component.html b/client/src/app/admin/components/settings/option-table.component.html
similarity index 52%
rename from client/src/app/admin/components/settings/option-list.component.html
rename to client/src/app/admin/components/settings/option-table.component.html
index 82399054..e7cba6c2 100644
--- a/client/src/app/admin/components/settings/option-list.component.html
+++ b/client/src/app/admin/components/settings/option-table.component.html
@@ -15,9 +15,15 @@
                 <td class="align-middle">{{ option.value }}</td>
                 <td class="align-middle">{{ option.display }}</td>
                 <td class="align-middle">
-                    <button title="Edit this option" (click)="openModal(templateForEdit, option); $event.stopPropagation()" class="btn btn-outline-primary">
-                        <span class="fas fa-edit"></span>
-                    </button>
+                    <app-edit-btn [type]="'option'" [type]="'option'" [label]="option.label" #editBtn>
+                        <app-form-option [selectOption]="option" (onSubmit)="editOption.emit($event)" #formAddOption>
+                            <button [disabled]="!formAddOption.selectOptionForm.valid || formAddOption.selectOptionForm.pristine" (click)="editBtn.modalRef.hide()" type="submit" class="btn btn-primary">
+                                <span class="fa fa-database"></span> Edit select option
+                            </button>
+                            &nbsp;
+                            <button (click)="editBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+                        </app-form-option>
+                    </app-edit-btn>
                 </td>
                 <td class="align-middle">
                     <app-delete-btn
@@ -30,18 +36,3 @@
         </tbody>
     </table>
 </div>
-
-<ng-template #templateForEdit>
-    <div class="modal-header">
-        <h4 class="modal-title pull-left"><strong>{{ optionSelected.label }}</strong></h4>
-    </div>
-    <div class="modal-body">
-        <app-form-option [model]="optionSelected" (submitted)="confirmEdit($event)" #formEditOption>
-            <button [disabled]="!formEditOption.ngForm.form.valid || formEditOption.ngForm.form.pristine" type="submit" class="btn btn-primary">
-                <span class="fa fa-database"></span> Update option information
-            </button>
-            &nbsp;
-            <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button>
-        </app-form-option>
-    </div>
-</ng-template>
diff --git a/client/src/app/admin/components/settings/option-table.component.ts b/client/src/app/admin/components/settings/option-table.component.ts
new file mode 100644
index 00000000..32417d3c
--- /dev/null
+++ b/client/src/app/admin/components/settings/option-table.component.ts
@@ -0,0 +1,14 @@
+import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter } from '@angular/core';
+
+import { SelectOption } from 'src/app/metamodel/store/models';
+
+@Component({
+    selector: 'app-option-table',
+    templateUrl: 'option-table.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class OptionTableComponent {
+    @Input() optionList: SelectOption[];
+    @Output() editOption: EventEmitter<SelectOption> = new EventEmitter();
+    @Output() deleteOption: EventEmitter<SelectOption> = new EventEmitter();
+}
diff --git a/client/src/app/admin/components/settings/select-buttons.component.html b/client/src/app/admin/components/settings/select-buttons.component.html
index 8a1f50bc..4da6a2ff 100644
--- a/client/src/app/admin/components/settings/select-buttons.component.html
+++ b/client/src/app/admin/components/settings/select-buttons.component.html
@@ -1,43 +1,25 @@
-<button title="Add option" class="btn btn-outline-success" (click)="openModal(templateForAddOption)">
-    <span class="fas fa-plus"></span>
-</button>
+<app-add-btn [type]="'option'" [label]="getFormAddOptionLabel()" #addBtn>
+    <app-form-option (onSubmit)="addNewSelectOption($event)" #formAddOption>
+        <button [disabled]="!formAddOption.selectOptionForm.valid || formAddOption.selectOptionForm.pristine" (click)="addBtn.modalRef.hide()" type="submit" class="btn btn-primary">
+            <span class="fa fa-database"></span> Add new option
+        </button>
+        &nbsp;
+        <button (click)="addBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+    </app-form-option>
+</app-add-btn>
 &nbsp;
-<button title="Edit select" class="btn btn-outline-primary" (click)="openModal(templateForEdit)">
-    <span class="fas fa-edit"></span>
-</button>
+<app-edit-btn [type]="'select'" [label]="select.label" #editBtn>
+    <app-form-select [select]="select" (onSubmit)="editSelect.emit($event)" #formEditSelect>
+        <button [disabled]="!formEditSelect.selectForm.valid || formEditSelect.selectForm.pristine" (click)="editBtn.modalRef.hide()" type="submit" class="btn btn-primary">
+            <span class="fa fa-database"></span> Update select information
+        </button>
+        &nbsp;
+        <button (click)="editBtn.modalRef.hide()" type="button" class="btn btn-danger">Cancel</button>
+    </app-form-select>
+</app-edit-btn>
 &nbsp;
 <app-delete-btn
     [type]="'select'"
     [label]="select.label"
     (confirm)="deleteSelect.emit(select)">
 </app-delete-btn>
-
-<ng-template #templateForEdit>
-    <div class="modal-header">
-        <h4 class="modal-title pull-left"><strong>{{ select.label }}</strong></h4>
-    </div>
-    <div class="modal-body">
-        <app-form-select [model]="select" (submitted)="confirmEdit($event)" #formEditSelect>
-            <button [disabled]="!formEditSelect.ngForm.form.valid || formEditSelect.ngForm.form.pristine" type="submit" class="btn btn-primary">
-                <span class="fa fa-database"></span> Update select information
-            </button>
-            &nbsp;
-            <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button>
-        </app-form-select>
-    </div>
-</ng-template>
-
-<ng-template #templateForAddOption>
-    <div class="modal-header">
-        <h4 class="modal-title pull-left"><strong>Add new option for : {{ select.label }}</strong></h4>
-    </div>
-    <div class="modal-body">
-        <app-form-option (submitted)="addNewOption($event)" #formAddOption>
-            <button [disabled]="!formAddOption.ngForm.form.valid || formAddOption.ngForm.form.pristine" type="submit" class="btn btn-primary">
-                <span class="fa fa-database"></span> Add new option
-            </button>
-            &nbsp;
-            <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button>
-        </app-form-option>
-    </div>
-</ng-template>
diff --git a/client/src/app/admin/components/settings/select-buttons.component.ts b/client/src/app/admin/components/settings/select-buttons.component.ts
index d1f495cd..640892b5 100644
--- a/client/src/app/admin/components/settings/select-buttons.component.ts
+++ b/client/src/app/admin/components/settings/select-buttons.component.ts
@@ -1,7 +1,4 @@
-import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core';
-
-import { BsModalService } from 'ngx-bootstrap/modal';
-import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter } from '@angular/core';
 
 import { Select, SelectOption } from 'src/app/metamodel/store/models';
 
@@ -16,21 +13,11 @@ export class SelectButtonsComponent {
     @Output() editSelect: EventEmitter<Select> = new EventEmitter();
     @Output() addOption: EventEmitter<SelectOption> = new EventEmitter();
 
-    modalRef: BsModalRef;
-
-    constructor(private modalService: BsModalService) { }
-
-    openModal(template: TemplateRef<any>) {
-        this.modalRef = this.modalService.show(template);
-    }
-
-    confirmEdit(select: Select) {
-        this.editSelect.emit(select);
-        this.modalRef.hide();
+    getFormAddOptionLabel() {
+        return `Add new option for : ${this.select.label}`;
     }
 
-    addNewOption(option: SelectOption) {
-        this.addOption.emit({...option, select_name: this.select.name});
-        this.modalRef.hide();
+    addNewSelectOption(selectOption: SelectOption) {
+        this.addOption.emit({...selectOption, select_name: this.select.name});
     }
 }
diff --git a/client/src/app/admin/components/settings/select-list.component.html b/client/src/app/admin/components/settings/select-list.component.html
index acd01265..ec36de71 100644
--- a/client/src/app/admin/components/settings/select-list.component.html
+++ b/client/src/app/admin/components/settings/select-list.component.html
@@ -3,7 +3,7 @@
         <a class="nav-link" routerLink="/admin/settings/{{select.name}}" routerLinkActive="active">{{ select.label }}</a>
     </li>
     <li class="nav-item">
-        <a class="nav-link add-select" (click)="openModal(templateForNew); $event.stopPropagation()"><span class="fas fa-plus"></span></a>
+        <a class="nav-link add-select pointer" (click)="openModal(templateForNew); $event.stopPropagation()"><span class="fas fa-plus"></span></a>
     </li>
 </ul>
 
@@ -12,12 +12,12 @@
         <h4 class="modal-title pull-left"><strong>Add new select</strong></h4>
     </div>
     <div class="modal-body">
-        <app-form-select (submitted)="confirmAdd($event)" #formNewSelect>
-            <button [disabled]="!formNewSelect.ngForm.form.valid || formNewSelect.ngForm.form.pristine" type="submit" class="btn btn-primary">
+        <app-form-select (onSubmit)="confirmAdd($event)" #formNewSelect>
+            <button [disabled]="!formNewSelect.selectForm.valid || formNewSelect.selectForm.pristine" type="submit" class="btn btn-primary">
                 <span class="fa fa-database"></span> Add select
             </button>
             &nbsp;
-            <button (click)="modalRef.hide()" class="btn btn-danger">Cancel</button>
+            <button (click)="modalRef.hide(); $event.stopPropagation()" type="button" class="btn btn-danger">Cancel</button>
         </app-form-select>
     </div>
 </ng-template>
diff --git a/client/src/app/admin/components/settings/select-list.component.scss b/client/src/app/admin/components/settings/select-list.component.scss
deleted file mode 100644
index c3b50f96..00000000
--- a/client/src/app/admin/components/settings/select-list.component.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-.add-select {
-    cursor: pointer;
-    color: #007bff;
-}
-
-.add-select:hover {
-    color: #007bff;
-}
diff --git a/client/src/app/admin/components/settings/select-list.component.ts b/client/src/app/admin/components/settings/select-list.component.ts
index 199392ff..5ad9a6e7 100644
--- a/client/src/app/admin/components/settings/select-list.component.ts
+++ b/client/src/app/admin/components/settings/select-list.component.ts
@@ -8,7 +8,6 @@ import { Select } from 'src/app/metamodel/store/models';
 @Component({
     selector: 'app-select-list',
     templateUrl: 'select-list.component.html',
-    styleUrls: ['select-list.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class SelectListComponent {
diff --git a/client/src/app/admin/components/shared/add-btn.component.html b/client/src/app/admin/components/shared/add-btn.component.html
new file mode 100644
index 00000000..bafcf26f
--- /dev/null
+++ b/client/src/app/admin/components/shared/add-btn.component.html
@@ -0,0 +1,12 @@
+<button title="Add {{ type }}" class="btn btn-outline-success" (click)="openModal(template)">
+    <span class="fas fa-plus"></span>
+</button>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>{{ label }}</strong></h4>
+    </div>
+    <div class="modal-body">
+        <ng-content></ng-content>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/components/shared/add-btn.component.ts b/client/src/app/admin/components/shared/add-btn.component.ts
new file mode 100644
index 00000000..fc9035a5
--- /dev/null
+++ b/client/src/app/admin/components/shared/add-btn.component.ts
@@ -0,0 +1,23 @@
+import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+@Component({
+    selector: 'app-add-btn',
+    templateUrl: 'add-btn.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class AddBtnComponent {
+    @Input() type: string;
+    @Input() label: string;
+    @Input() disabled: boolean = false;
+
+    modalRef: BsModalRef;
+
+    constructor(private modalService: BsModalService) { }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+}
diff --git a/client/src/app/admin/components/shared/edit-btn.component.html b/client/src/app/admin/components/shared/edit-btn.component.html
new file mode 100644
index 00000000..892b9382
--- /dev/null
+++ b/client/src/app/admin/components/shared/edit-btn.component.html
@@ -0,0 +1,12 @@
+<button [disabled]="disabled" title="Edit this {{ type }}" (click)="openModal(template)" class="btn btn-outline-primary">
+    <span class="fas fa-edit"></span>
+</button>
+
+<ng-template #template>
+    <div class="modal-header">
+        <h4 class="modal-title pull-left"><strong>{{ label }}</strong></h4>
+    </div>
+    <div class="modal-body">
+        <ng-content></ng-content>
+    </div>
+</ng-template>
diff --git a/client/src/app/admin/components/shared/edit-btn.component.ts b/client/src/app/admin/components/shared/edit-btn.component.ts
new file mode 100644
index 00000000..29717291
--- /dev/null
+++ b/client/src/app/admin/components/shared/edit-btn.component.ts
@@ -0,0 +1,23 @@
+import { Component, Input, ChangeDetectionStrategy, TemplateRef } from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
+
+@Component({
+    selector: 'app-edit-btn',
+    templateUrl: 'edit-btn.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class EditBtnComponent {
+    @Input() type: string;
+    @Input() label: string;
+    @Input() disabled: boolean = false;
+
+    modalRef: BsModalRef;
+
+    constructor(private modalService: BsModalService) { }
+
+    openModal(template: TemplateRef<any>) {
+        this.modalRef = this.modalService.show(template);
+    }
+}
diff --git a/client/src/app/admin/components/shared/index.ts b/client/src/app/admin/components/shared/index.ts
new file mode 100644
index 00000000..9fff83b3
--- /dev/null
+++ b/client/src/app/admin/components/shared/index.ts
@@ -0,0 +1,9 @@
+import { AddBtnComponent } from "./add-btn.component";
+import { EditBtnComponent } from "./edit-btn.component";
+import { DeleteBtnComponent } from "./delete-btn.component";
+
+export const sharedComponents = [
+    AddBtnComponent,
+    EditBtnComponent,
+    DeleteBtnComponent
+];
diff --git a/client/src/app/admin/containers/settings/settings.component.html b/client/src/app/admin/containers/settings/settings.component.html
index d82e1911..fffd6d48 100644
--- a/client/src/app/admin/containers/settings/settings.component.html
+++ b/client/src/app/admin/containers/settings/settings.component.html
@@ -3,7 +3,8 @@
         <ol class="breadcrumb">
             <li class="breadcrumb-item active">Settings</li>
             <li *ngIf="(currentSelect | async)" class="breadcrumb-item active" aria-current="page">
-                {{ (currentSelect | async).label }}</li>
+                {{ (currentSelect | async).label }}
+            </li>
         </ol>
     </nav>
 
@@ -12,22 +13,30 @@
     <div *ngIf="(selectListIsLoaded | async) && (optionListIsLoaded | async)">
         <div class="card text-center">
             <div class="card-header">
-                <app-select-list [selectList]="selectList | async" (addSelect)="addSelect($event)"></app-select-list>
+                <app-select-list
+                    [selectList]="selectList | async"
+                    (addSelect)="addSelect($event)">
+                </app-select-list>
             </div>
             <div *ngIf="(currentSelect | async)" class="card-body">
                 <div class="row">
                     <div class="col text-right">
-                        <app-select-buttons [select]="currentSelect | async" (deleteSelect)="deleteSelect($event)"
-                            (editSelect)="editSelect($event)" (addOption)="addOption($event)">
+                        <app-select-buttons 
+                            [select]="currentSelect | async"
+                            (deleteSelect)="deleteSelect($event)"
+                            (editSelect)="editSelect($event)"
+                            (addOption)="addOption($event)">
                         </app-select-buttons>
                     </div>
                 </div>
 
                 <div class="row mt-1">
                     <div class="col-12">
-                        <app-option-list [optionList]="optionList | async" (editOption)="editOption($event)"
+                        <app-option-table 
+                            [optionList]="optionList | async"
+                            (editOption)="editOption($event)"
                             (deleteOption)="deleteOption($event)">
-                        </app-option-list>
+                        </app-option-table>
                     </div>
                 </div>
             </div>
diff --git a/client/src/app/admin/containers/settings/settings.component.ts b/client/src/app/admin/containers/settings/settings.component.ts
index e7b49566..ef58d756 100644
--- a/client/src/app/admin/containers/settings/settings.component.ts
+++ b/client/src/app/admin/containers/settings/settings.component.ts
@@ -37,27 +37,27 @@ export class SettingsComponent implements OnInit {
         this.store.dispatch(optionActions.loadSelectOptionList());
     }
 
-    addSelect(settingsSelect: Select) {
-        // this.store.dispatch(new selectActions.AddNewSelectAction(settingsSelect));
+    addSelect(select: Select) {
+        this.store.dispatch(selectActions.addSelect({ select }));
     }
 
-    editSelect(settingsSelect: Select) {
-        // this.store.dispatch(new selectActions.EditSelectAction(settingsSelect));
+    editSelect(select: Select) {
+        this.store.dispatch(selectActions.editSelect({ select }));
     }
 
-    deleteSelect(settingsSelect: Select) {
-        // this.store.dispatch(new selectActions.DeleteSelectAction(settingsSelect));
+    deleteSelect(select: Select) {
+        this.store.dispatch(selectActions.deleteSelect({ select }));
     }
 
-    addOption(settingsSelectOption: SelectOption) {
-        // this.store.dispatch(new optionActions.AddNewSelectOptionAction(settingsSelectOption));
+    addOption(selectOption: SelectOption) {
+        this.store.dispatch(optionActions.addSelectOption({ selectOption }));
     }
 
-    editOption(settingsSelectOption: SelectOption) {
-        // this.store.dispatch(new optionActions.EditSelectOptionAction(settingsSelectOption));
+    editOption(selectOption: SelectOption) {
+        this.store.dispatch(optionActions.editSelectOption({ selectOption }));
     }
 
-    deleteOption(settingsSelectOption: SelectOption) {
-        // this.store.dispatch(new optionActions.DeleteSelectOptionAction(settingsSelectOption));
+    deleteOption(selectOption: SelectOption) {
+        this.store.dispatch(optionActions.deleteSelectOption({ selectOption }));
     }
 }
diff --git a/client/src/app/metamodel/store/actions/select-option.actions.ts b/client/src/app/metamodel/store/actions/select-option.actions.ts
index 0fe84a33..2397f7db 100644
--- a/client/src/app/metamodel/store/actions/select-option.actions.ts
+++ b/client/src/app/metamodel/store/actions/select-option.actions.ts
@@ -5,3 +5,12 @@ import { SelectOption } from '../models';
 export const loadSelectOptionList = createAction('[Metamodel] Load Select Option List');
 export const loadSelectOptionListSuccess = createAction('[Metamodel] Load Select Option List Success', props<{ selectOptions: SelectOption[] }>());
 export const loadSelectOptionListFail = createAction('[Metamodel] Load Select Option List Fail');
+export const addSelectOption = createAction('[Metamodel] Add Select Option', props<{ selectOption: SelectOption }>());
+export const addSelectOptionSuccess = createAction('[Metamodel] Add Select Option Success', props<{ selectOption: SelectOption }>());
+export const addSelectOptionFail = createAction('[Metamodel] Add Select Option Fail');
+export const editSelectOption = createAction('[Metamodel] Edit Select Option', props<{ selectOption: SelectOption }>());
+export const editSelectOptionSuccess = createAction('[Metamodel] Edit Select Option Success', props<{ selectOption: SelectOption }>());
+export const editSelectOptionFail = createAction('[Metamodel] Edit Select Option Fail');
+export const deleteSelectOption = createAction('[Metamodel] Delete Select Option', props<{ selectOption: SelectOption }>());
+export const deleteSelectOptionSuccess = createAction('[Metamodel] Delete Select Option Success', props<{ selectOption: SelectOption }>());
+export const deleteSelectOptionFail = createAction('[Metamodel] Delete Select Option Fail');
diff --git a/client/src/app/metamodel/store/actions/select.actions.ts b/client/src/app/metamodel/store/actions/select.actions.ts
index ca8585bd..2b6caea7 100644
--- a/client/src/app/metamodel/store/actions/select.actions.ts
+++ b/client/src/app/metamodel/store/actions/select.actions.ts
@@ -5,3 +5,12 @@ import { Select } from '../models';
 export const loadSelectList = createAction('[Metamodel] Load Select List');
 export const loadSelectListSuccess = createAction('[Metamodel] Load Select List Success', props<{ selects: Select[] }>());
 export const loadSelectListFail = createAction('[Metamodel] Load Select List Fail');
+export const addSelect = createAction('[Metamodel] Add Select', props<{ select: Select }>());
+export const addSelectSuccess = createAction('[Metamodel] Add Select Success', props<{ select: Select }>());
+export const addSelectFail = createAction('[Metamodel] Add Select Fail');
+export const editSelect = createAction('[Metamodel] Edit Select', props<{ select: Select }>());
+export const editSelectSuccess = createAction('[Metamodel] Edit Select Success', props<{ select: Select }>());
+export const editSelectFail = createAction('[Metamodel] Edit Select Fail');
+export const deleteSelect = createAction('[Metamodel] Delete Select', props<{ select: Select }>());
+export const deleteSelectSuccess = createAction('[Metamodel] Delete Select Success', props<{ select: Select }>());
+export const deleteSelectFail = createAction('[Metamodel] Delete Select Fail');
diff --git a/client/src/app/metamodel/store/effects/select-option.effects.ts b/client/src/app/metamodel/store/effects/select-option.effects.ts
index d94e21c0..b8fc3427 100644
--- a/client/src/app/metamodel/store/effects/select-option.effects.ts
+++ b/client/src/app/metamodel/store/effects/select-option.effects.ts
@@ -1,7 +1,9 @@
 import { Injectable } from '@angular/core';
 import { Actions, createEffect, ofType } from '@ngrx/effects';
 import { of } from 'rxjs';
-import { map, mergeMap, catchError } from 'rxjs/operators';
+import { map, tap, mergeMap, catchError } from 'rxjs/operators';
+
+import { ToastrService } from 'ngx-toastr';
 
 import * as selectOptionActions from '../actions/select-option.actions';
 import { SelectOptionService } from '../services/select-option.service';
@@ -21,8 +23,87 @@ export class SelectOptionEffects {
         )
     );
  
+    addSelectOption$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.addSelectOption),
+            mergeMap(action => this.selectOptionService.addSelectOption(action.selectOption)
+                .pipe(
+                    map(selectOption => selectOptionActions.addSelectOptionSuccess({ selectOption })),
+                    catchError(() => of(selectOptionActions.addSelectOptionFail()))
+                )
+            )
+        )
+    );
+
+    addSelectOptionSuccess$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.addSelectOptionSuccess),
+            tap(() => this.toastr.success('Select option successfully added', 'The new select option was added into the database'))
+        ), { dispatch: false}
+    );
+
+    addSelectOptionFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.addSelectOptionFail),
+            tap(() => this.toastr.error('Failure to add select option', 'The new select option could not be added into the database'))
+        ), { dispatch: false}
+    );
+
+    editSelectOption$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.editSelectOption),
+            mergeMap(action => this.selectOptionService.editSelectOption(action.selectOption)
+                .pipe(
+                    map(selectOption => selectOptionActions.editSelectOptionSuccess({ selectOption })),
+                    catchError(() => of(selectOptionActions.editSelectOptionFail()))
+                )
+            )
+        )
+    );
+
+    editSelectOptionSuccess$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.editSelectOptionSuccess),
+            tap(() => this.toastr.success('Select option successfully edited', 'The existing select option has been edited into the database'))
+        ), { dispatch: false}
+    );
+
+    editSelectOptionFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.editSelectOptionFail),
+            tap(() => this.toastr.error('Failure to edit select option', 'The existing select option could not be edited into the database'))
+        ), { dispatch: false}
+    );
+
+    deleteSelectOption$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.deleteSelectOption),
+            mergeMap(action => this.selectOptionService.deleteSelectOption(action.selectOption.id)
+                .pipe(
+                    map(() => selectOptionActions.deleteSelectOptionSuccess({ selectOption: action.selectOption })),
+                    catchError(() => of(selectOptionActions.deleteSelectOptionFail()))
+                )
+            )
+        )
+    );
+
+    deleteSelectOptionSuccess$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.deleteSelectOptionSuccess),
+            tap(() => this.toastr.success('Select option successfully deleted', 'The existing select option has been deleted'))
+        ), { dispatch: false}
+    );
+
+    deleteSelectOptionFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectOptionActions.deleteSelectOptionFail),
+            tap(() => this.toastr.error('Failure to delete select option', 'The existing select option could not be deleted from the database'))
+        ), { dispatch: false}
+    );
+
     constructor(
         private actions$: Actions,
-        private selectOptionService: SelectOptionService
+        private selectOptionService: SelectOptionService,
+        private toastr: ToastrService
     ) {}
 }
diff --git a/client/src/app/metamodel/store/effects/select.effects.ts b/client/src/app/metamodel/store/effects/select.effects.ts
index 6d88ca12..f735b4e9 100644
--- a/client/src/app/metamodel/store/effects/select.effects.ts
+++ b/client/src/app/metamodel/store/effects/select.effects.ts
@@ -1,14 +1,15 @@
 import { Injectable } from '@angular/core';
 import { Actions, createEffect, ofType } from '@ngrx/effects';
 import { of } from 'rxjs';
-import { map, mergeMap, catchError } from 'rxjs/operators';
+import { map, tap, mergeMap, catchError } from 'rxjs/operators';
+
+import { ToastrService } from 'ngx-toastr';
 
 import * as selectActions from '../actions/select.actions';
 import { SelectService } from '../services/select.service';
  
 @Injectable()
 export class SelectEffects {
- 
     loadSelects$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.loadSelectList),
@@ -20,9 +21,88 @@ export class SelectEffects {
             )
         )
     );
+
+    addSelect$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.addSelect),
+            mergeMap(action => this.selectService.addSelect(action.select)
+                .pipe(
+                    map(select => selectActions.addSelectSuccess({ select })),
+                    catchError(() => of(selectActions.addSelectFail()))
+                )
+            )
+        )
+    );
+
+    addSelectSuccess$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.addSelectSuccess),
+            tap(() => this.toastr.success('Select successfully added', 'The new select was added into the database'))
+        ), { dispatch: false}
+    );
+
+    addSelectFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.addSelectFail),
+            tap(() => this.toastr.error('Failure to add select', 'The new select could not be added into the database'))
+        ), { dispatch: false}
+    );
+
+    editSelect$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.editSelect),
+            mergeMap(action => this.selectService.editSelect(action.select)
+                .pipe(
+                    map(select => selectActions.editSelectSuccess({ select })),
+                    catchError(() => of(selectActions.editSelectFail()))
+                )
+            )
+        )
+    );
+
+    editSelectSuccess$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.editSelectSuccess),
+            tap(() => this.toastr.success('Select successfully edited', 'The existing select has been edited into the database'))
+        ), { dispatch: false}
+    );
+
+    editSelectFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.editSelectFail),
+            tap(() => this.toastr.error('Failure to edit select', 'The existing select could not be edited into the database'))
+        ), { dispatch: false}
+    );
+
+    deleteSelect$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.deleteSelect),
+            mergeMap(action => this.selectService.deleteSelect(action.select.name)
+                .pipe(
+                    map(() => selectActions.deleteSelectSuccess({ select: action.select })),
+                    catchError(() => of(selectActions.deleteSelectFail()))
+                )
+            )
+        )
+    );
+
+    deleteSelectSuccess$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.deleteSelectSuccess),
+            tap(() => this.toastr.success('Select successfully deleted', 'The existing select has been deleted'))
+        ), { dispatch: false}
+    );
+
+    deleteSelectFail$ = createEffect(() => 
+        this.actions$.pipe(
+            ofType(selectActions.deleteSelectFail),
+            tap(() => this.toastr.error('Failure to delete select', 'The existing select could not be deleted from the database'))
+        ), { dispatch: false}
+    );
  
     constructor(
         private actions$: Actions,
-        private selectService: SelectService
+        private selectService: SelectService,
+        private toastr: ToastrService
     ) {}
 }
diff --git a/client/src/app/metamodel/store/reducers/select-option.reducer.ts b/client/src/app/metamodel/store/reducers/select-option.reducer.ts
index 1c7cc6b2..55eddafa 100644
--- a/client/src/app/metamodel/store/reducers/select-option.reducer.ts
+++ b/client/src/app/metamodel/store/reducers/select-option.reducer.ts
@@ -39,6 +39,15 @@ export const selectOptionReducer = createReducer(
             ...state,
             selectOptionListIsLoaded: false
         }
+    }),
+    on(selectOptionActions.addSelectOptionSuccess, (state, { selectOption }) => {
+        return adapter.addOne(selectOption, state)
+    }),
+    on(selectOptionActions.editSelectOptionSuccess, (state, { selectOption }) => {
+        return adapter.setOne(selectOption, state)
+    }),
+    on(selectOptionActions.deleteSelectOptionSuccess, (state, { selectOption }) => {
+        return adapter.removeOne(selectOption.id, state)
     })
 );
 
diff --git a/client/src/app/metamodel/store/reducers/select.reducer.ts b/client/src/app/metamodel/store/reducers/select.reducer.ts
index 779b7915..bef99bfd 100644
--- a/client/src/app/metamodel/store/reducers/select.reducer.ts
+++ b/client/src/app/metamodel/store/reducers/select.reducer.ts
@@ -42,6 +42,15 @@ export const selectReducer = createReducer(
             ...state,
             selectListIsLoaded: false
         }
+    }),
+    on(selectActions.addSelectSuccess, (state, { select }) => {
+        return adapter.addOne(select, state)
+    }),
+    on(selectActions.editSelectSuccess, (state, { select }) => {
+        return adapter.setOne(select, state)
+    }),
+    on(selectActions.deleteSelectSuccess, (state, { select }) => {
+        return adapter.removeOne(select.name, state)
     })
 );
 
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index d367b071..8cf0cf93 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
 import { RouterModule } from '@angular/router';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 
+import { ToastrModule } from 'ngx-toastr';
 import { CollapseModule } from 'ngx-bootstrap/collapse';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ModalModule } from 'ngx-bootstrap/modal';
@@ -18,6 +19,7 @@ import { sharedComponents } from './components';
         RouterModule,
         FormsModule,
         ReactiveFormsModule,
+        ToastrModule.forRoot(),
         CollapseModule.forRoot(),
         BsDropdownModule.forRoot(),
         ModalModule.forRoot()
@@ -26,6 +28,7 @@ import { sharedComponents } from './components';
         CommonModule,
         FormsModule,
         ReactiveFormsModule,
+        ToastrModule,
         CollapseModule,
         BsDropdownModule,
         ModalModule,
diff --git a/client/src/styles.scss b/client/src/styles.scss
index 5e2753dc..74dcd7b6 100644
--- a/client/src/styles.scss
+++ b/client/src/styles.scss
@@ -18,14 +18,28 @@
 @import "~bootstrap/scss/dropdown";
 @import "~bootstrap/scss/modal";
 @import "~bootstrap/scss/tables";
+@import "~bootstrap/scss/forms";
 @import "~bootstrap/scss/utilities";
 
+/* Import ngx-toastr bootstrap 4 alert styled design */
+
+@import '~ngx-toastr/toastr-bs4-alert';
+
 /* Global styles */
 
 main {
     margin-top: 100px;
 }
 
+/* Angular forms */
+.ng-valid:not(form) {
+    border-left: 5px solid #42A948; /* green */
+}
+  
+.ng-invalid:not(form)  {
+    border-left: 5px solid #a94442; /* red */
+}
+
 /* Utilities */
 
 .pointer {
-- 
GitLab