Merge branch 'DEVELOP' of ssh://git.eclipse.org:29418/openk-usermodules/org.eclipse.openk-usermodules.gridFailureInformation.frontend into SI-541-Rolle-Gast-Lesezugriff

Signed-off-by: Ina Curdt <Ina.Curdt@pta.de>
diff --git a/i18n/grid-failure.de.json b/i18n/grid-failure.de.json
index 6c9a44f..5f7646a 100644
--- a/i18n/grid-failure.de.json
+++ b/i18n/grid-failure.de.json
@@ -44,7 +44,8 @@
     "StatusIntern": "Status (intern)",
     "Street": "Straße (betroffene Straße)",
     "VoltageLevel": "Spannungsebene",
-    "PublicationStatus": "Veröffentlichungsstatus"
+    "PublicationStatus": "Veröffentlichungsstatus",
+    "MapView": "Kartenansicht"
   },
   "GridFailureTemp": {
     "FailureTypeId": "(TypId)",
diff --git a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.html b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.html
index 2887813..ac0378a 100644
--- a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.html
+++ b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.html
@@ -487,7 +487,6 @@
                   type="text"
                   class="form-control"
                   id="housenumber"
-                  (change)="gridFailureDetailsSandbox.setLatLong()"
                   [ngrxFormControlState]="((gridFailureDetailsSandbox.gridFailureDetailsFormState$ | async)?.controls)['housenumber']"
                 >
                   <option [value]="null" selected disabled>{{ 'SelectOption' | translate }}</option>
@@ -507,13 +506,14 @@
                 <input
                   type="text"
                   maxlength="255"
+                  id="stationDescription"
                   class="form-control"
+                  autocomplete="off"
+                  (input)="resetCoords($event.target.value)"
+                  (selectItem)="gridFailureDetailsSandbox.latLonMapping($event.item); gridFailureDetailsSandbox.setStationId($event.item.stationId)"
                   [ngbTypeahead]="gridFailureDetailsSandbox.searchForStation"
                   [ngrxValueConverter]="gridFailureDetailsSandbox.stationValueConverter"
-                  (selectItem)="gridFailureDetailsSandbox.latLonMapping($event.item); gridFailureDetailsSandbox.setStationId($event.item.stationId)"
-                  id="stationDescription"
                   [ngrxFormControlState]="((gridFailureDetailsSandbox.gridFailureDetailsFormState$ | async)?.controls)['stationDescription']"
-                  autocomplete="off"
                   [resultFormatter]="gridFailureDetailsSandbox.formatter"
                   [inputFormatter]="gridFailureDetailsSandbox.formatter"
                 />
@@ -536,6 +536,17 @@
             </div>
           </div>
         </app-expandable>
+
+        <app-expandable [showBodyInitially]="true">
+          <label header>{{ 'GridFailure.MapView' | translate }}</label>
+          <div class="expandable-content map-container" body>
+            <div class="map-detail-view">
+              <openk-grid-failure-information-map
+                [mapDetailData]="gridFailureDetailsSandbox.currentGridFailureDetailsCoords"
+              ></openk-grid-failure-information-map>
+            </div>
+          </div>
+        </app-expandable>
       </div>
     </form>
   </div>
diff --git a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.scss b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.scss
index d2fa2b6..8ac0d62 100644
--- a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.scss
+++ b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.scss
@@ -62,7 +62,11 @@
   margin-left: 0px;
 }
 .expandable-content {
-  padding: 20px 0px 20px 35px;
+  padding: 20px 35px 20px 35px;
+}
+.map-container {
+  height: 600px;
+  width: 100%;
 }
 .header-container {
   display: flex;
@@ -76,3 +80,7 @@
 .card {
   height: calc(100vh - 180px);
 }
+.map-detail-view {
+  position: relative;
+  height: -webkit-fill-available;
+}
diff --git a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.spec.ts b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.spec.ts
index 29c5373..03e6c00 100644
--- a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.spec.ts
+++ b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.spec.ts
@@ -14,11 +14,9 @@
 import { of } from 'rxjs';
 import { Store } from '@ngrx/store';
 import { State } from '@grid-failure-information-app/shared/store';
-import { TestBed, ComponentFixture } from '@angular/core/testing';
 
 describe('GridFailureDetailsComponent', () => {
   let component: GridFailureDetailsComponent;
-  let fixture: ComponentFixture<GridFailureDetailsComponent>;
   let gridFailureSandbox: any;
   let gridFailureDetailsFormResponse: any = {
     value: {
@@ -36,6 +34,9 @@
       setGridFailureDateTime(dateISOString: string, actionType: string) {},
       registerEvents() {},
       endSubscriptions() {},
+      resetCoords() {},
+      resetStationId() {},
+      resetPostCode() {},
       gridFailureDetailsFormState$: of(gridFailureDetailsFormResponse),
     } as any;
     component = new GridFailureDetailsComponent(gridFailureSandbox, appState);
@@ -85,10 +86,16 @@
     expect(expectedResult.toISOString).toBe(result.toISOString);
   });
 
-  it('should call the "FailureDetailsSandbox.setState(..)" event 1 times', ()=>{
-    //fixture = TestBed.createComponent(GridFailureDetailsComponent);
-    // let comp = fixture.componentInstance;
-    // fixture.detectChanges();
-    // let onClickSpy = spyOn(comp, '')
+  it('checks if resetCoords(value: string) works fine', () => {
+    let spy1 = spyOn(gridFailureSandbox, 'resetCoords');
+    let spy2 = spyOn(gridFailureSandbox, 'resetStationId');
+
+    component.resetCoords('1');
+    expect(spy1).not.toHaveBeenCalled();
+    expect(spy2).not.toHaveBeenCalled();
+
+    component.resetCoords(null);
+    expect(spy1).toHaveBeenCalled();
+    expect(spy2).toHaveBeenCalled();
   });
 });
diff --git a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.ts b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.ts
index 7ea4328..a0e9cfd 100644
--- a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.ts
+++ b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.component.ts
@@ -29,6 +29,7 @@
   public RolesEnum = RolesEnum;
   public StateEnum = StateEnum;
   public gridFailureVersions$: Observable<GridFailure[]> = this.appState$.select(store.getGridFailureVersionsData);
+
   constructor(public gridFailureDetailsSandbox: GridFailureDetailsSandbox, protected appState$: Store<store.State>) {}
 
   ngOnInit() {
@@ -44,6 +45,13 @@
     this.gridFailureDetailsSandbox.clearGridFailureData(actionType);
   }
 
+  public resetCoords(value: string): void {
+    if (!value) {
+      this.gridFailureDetailsSandbox.resetCoords();
+      this.gridFailureDetailsSandbox.resetStationId();
+    }
+  }
+
   private _createDateTime(dateTime: any): Date {
     let date: Date = new Date();
     date.setDate(dateTime.day);
diff --git a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.spec.ts b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.spec.ts
index 402ff30..b1631cd 100644
--- a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.spec.ts
+++ b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.spec.ts
@@ -1,4 +1,3 @@
-import { FailureHousenumber } from './../../../shared/models/failure-housenumber.model';
 /********************************************************************************
  * Copyright (c) 2020 Contributors to the Eclipse Foundation
  *
@@ -22,6 +21,7 @@
 import { Router } from '@angular/router';
 import { StateEnum } from '@grid-failure-information-app/shared/constants/enums';
 import { FailureStation } from '@grid-failure-information-app/shared/models';
+import { FailureHousenumber } from '@grid-failure-information-app/shared/models';
 
 describe('GridFailureDetailsSandbox', () => {
   let service: GridFailureDetailsSandbox;
@@ -41,6 +41,8 @@
     dispatchSpy = spyOn(appState, 'dispatch').and.callFake(() => {});
 
     service = new GridFailureDetailsSandbox(appState, actionSubject, router, utilService, modalService);
+    const gridFailureDetailsFormState = { value: { lontitude: 1.24, latitude: 1.24 } };
+    service.gridFailureDetailsFormState$ = of(gridFailureDetailsFormState as any);
   });
 
   it('should create GridFailureDetailsSandbox service', () => {
@@ -211,7 +213,6 @@
   });
 
   it('should dispatch action in response to controlId = gridFailureDetailsForm', () => {
-    service.gridFailureInternalStates$ = { pipe: () => of({}), map: () => of({}) } as any;
     let formState: any = {
       id: 'gridFailureDetailsForm',
       value: {
@@ -229,7 +230,6 @@
   });
 
   it('should dispatch action in response to controlId = gridFailureDetailsForm.postcode', () => {
-    service.gridFailureInternalStates$ = { pipe: () => of({}), map: () => of({}) } as any;
     let formState: any = {
       value: {
         statusIntern: StateEnum.CREATED,
@@ -246,7 +246,6 @@
   });
 
   it('should dispatch action in response to controlId = gridFailureDetailsForm.city', () => {
-    service.gridFailureInternalStates$ = { pipe: () => of({}), map: () => of({}) } as any;
     let formState: any = {
       value: {
         statusIntern: StateEnum.CREATED,
@@ -265,7 +264,6 @@
   });
 
   it('should dispatch action in response to controlId = gridFailureDetailsForm.district', () => {
-    service.gridFailureInternalStates$ = { pipe: () => of({}), map: () => of({}) } as any;
     let formState: any = {
       value: {
         statusIntern: StateEnum.CREATED,
@@ -286,7 +284,6 @@
   });
 
   it('should dispatch action in response to controlId = gridFailureDetailsForm.street', () => {
-    service.gridFailureInternalStates$ = { pipe: () => of({}), map: () => of({}) } as any;
     let formState: any = {
       value: {
         statusIntern: StateEnum.CREATED,
@@ -403,6 +400,9 @@
       value: gfdetail,
     };
 
+    service.latLonMapping(null);
+    expect(dispatchSpy).not.toHaveBeenCalled();
+
     const event = new FailureStation();
     event.latitude = 1.24;
     event.longitude = 1.23;
@@ -412,39 +412,130 @@
     expect(dispatchSpy).toHaveBeenCalled();
   });
 
-  it('should format Object to String', () => {
-    const station: FailureStation = {
-      g3efid: 0,
-      id: '',
-      longitude: 0,
-      latitude: 0,
-      sdoxl: 0,
-      sdoyl: 0,
-      stationId: '',
-      stationName: 'test',
-    };
-
-    const result = service.formatter(station);
-
-    expect(result).toBe('test');
-  });
-
   it('should dispatch loadAddressPostalcodes Action via loadAddressPostalcodes()', () => {
     service.loadAddressPostalcodes();
     expect(appState.dispatch).toHaveBeenCalledWith(gridFailureActions.loadAddressPostalcodes());
   });
 
-  it('should dispatch loadGridFailureAddress action with uuid from housenumber', () => {
-    service.addressHouseNumbers = [new FailureHousenumber({ uuid: 'x', housenumber: '1' })];
-    let gridFailureDetailsFormResponse: any = {
-      controls: {
-        housenumber: {
-          value: '1',
-        } as any,
-      } as any,
+  it('should map stationId to gridFailure object', () => {
+    const gfdetail = new GridFailure();
+    gfdetail.id = 'id';
+    gfdetail.stationId = '1';
+
+    service.currentFormState = {
+      ...service.currentFormState,
+      isValid: true,
+      value: gfdetail,
     };
-    service.gridFailureDetailsFormState$ = of(gridFailureDetailsFormResponse);
-    service.setLatLong();
-    expect(appState.dispatch).toHaveBeenCalledWith(gridFailureActions.loadGridFailureAddress({ payload: 'x' }));
+
+    const stationId = '2';
+
+    service.setStationId(stationId);
+
+    expect(dispatchSpy).toHaveBeenCalled();
+  });
+
+  it('should reset Coords', () => {
+    const gfdetail = new GridFailure();
+    gfdetail.id = 'id';
+    gfdetail.longitude = 1.0;
+    gfdetail.latitude = 1.0;
+
+    service.currentFormState = {
+      ...service.currentFormState,
+      isValid: true,
+      value: gfdetail,
+    };
+
+    service.resetCoords();
+
+    expect(dispatchSpy).toHaveBeenCalled();
+  });
+
+  it('should reset stationId', () => {
+    const gfdetail = new GridFailure();
+    gfdetail.id = 'id';
+    gfdetail.stationId = 'test';
+
+    service.currentFormState = {
+      ...service.currentFormState,
+      isValid: true,
+      value: gfdetail,
+    };
+
+    service.resetStationId();
+
+    expect(dispatchSpy).toHaveBeenCalled();
+  });
+
+  it('should trigger formatter and check if the right value was returned', () => {
+    const failureStation: FailureStation = new FailureStation();
+    failureStation.stationName = 'test';
+    failureStation.stationId = 'test';
+    const result1 = service.formatter(failureStation);
+    const result2 = service.formatter('test');
+
+    expect(result1).toBe('test (test)');
+    expect(result2).toBe('test');
+  });
+
+  it('should dispatch Action after loadPostalCodes', () => {
+    service.loadAddressPostalcodes();
+    expect(appState.dispatch).toHaveBeenCalledWith(gridFailureActions.loadAddressPostalcodes());
+  });
+
+  it('should trigger searchForAddressPostalcodes and check if the right value was returned', () => {
+    (service as any)._addressPostalcodes = ['test', 'test1', 'test2', 'hello'];
+    const text$ = of('hello');
+    const result = service.searchForAddressPostalcodes(text$);
+
+    result.subscribe(item => {
+      expect(item).toEqual(['hello']);
+    });
+  });
+
+  it('should trigger searchForAddressPostalcodes and check if the right value was returned if length is under 2', () => {
+    (service as any)._addressPostalcodes = ['test', 'test1', 'test2', 'hello'];
+    const text$ = of('h');
+    const result = service.searchForAddressPostalcodes(text$);
+
+    result.subscribe(item => {
+      expect(item).toEqual([]);
+    });
+  });
+
+  it('should trigger searchForStation  and check if the right value was returned', () => {
+    const failureStation: FailureStation = new FailureStation();
+    failureStation.stationName = 'hello';
+    (service as any)._gridFailureStations = [new FailureStation(), new FailureStation(), failureStation];
+    const text$ = of('hello');
+    const result = service.searchForStation(text$);
+
+    result.subscribe(item => {
+      expect(item).toEqual([failureStation]);
+    });
+  });
+
+  it('should trigger searchForStation  and check if the right value was returned if length is under 2', () => {
+    const failureStation: FailureStation = new FailureStation();
+    failureStation.stationName = 'hello';
+    (service as any)._gridFailureStations = [new FailureStation(), new FailureStation(), failureStation];
+    const text$ = of('h');
+    const result = service.searchForStation(text$);
+
+    result.subscribe(item => {
+      expect(item).toEqual([]);
+    });
+  });
+
+  it('should call setLatLong and dispatch loadGridFailureAddress()', () => {
+    const failureHousenumber = new FailureHousenumber();
+    failureHousenumber.housenumber = 'test';
+    failureHousenumber.uuid = 'hello';
+    service.addressHouseNumbers = [failureHousenumber];
+    const hsnr = 'test';
+    service.setLatLong(hsnr);
+
+    expect(appState.dispatch).toHaveBeenCalledWith(gridFailureActions.loadGridFailureAddress({ payload: failureHousenumber.uuid }));
   });
 });
diff --git a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.ts b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.ts
index ed8179d..55c2217 100644
--- a/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.ts
+++ b/projects/grid-failure-information-app/src/app/pages/grid-failure/grid-failure-details/grid-failure-details.sandbox.ts
@@ -1,3 +1,4 @@
+import { switchMap, withLatestFrom, mergeMap, takeWhile } from 'rxjs/operators';
 /********************************************************************************
  * Copyright (c) 2020 Contributors to the Eclipse Foundation
  *
@@ -31,20 +32,25 @@
   FailureStation,
   FailureHousenumber,
   FailureAddress,
+  FailureCoords,
 } from '@grid-failure-information-app/shared/models';
 import { BaseFormSandbox } from '@grid-failure-information-app/shared/sandbox/base-form.sandbox';
 import * as store from '@grid-failure-information-app/shared/store';
 import * as gridFailureActions from '@grid-failure-information-app/shared/store/actions/grid-failures.action';
 import * as fromGridFailuresDetailFormReducer from '@grid-failure-information-app/shared/store/reducers/grid-failures/grid-failure-details-form.reducer';
 import * as gridFailuresDetailFormReducer from '@grid-failure-information-app/shared/store/reducers/grid-failures/grid-failure-details-form.reducer';
-import { dateTimeValueConverter, navigateHome, stationToStationNameConverter } from '@grid-failure-information-app/shared/utility';
+import {
+  dateTimeValueConverter,
+  navigateHome,
+  stationToStationNameConverter as stationToStationDescriptionConverter,
+} from '@grid-failure-information-app/shared/utility';
 import { UtilService } from '@grid-failure-information-app/shared/utility/utility.service';
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 import { ofType } from '@ngrx/effects';
 import { ActionsSubject, Store } from '@ngrx/store';
 import { Moment } from 'moment';
 import { DisableAction, EnableAction, FormGroupState, NgrxValueConverter, ResetAction, SetValueAction } from 'ngrx-forms';
-import { Observable } from 'rxjs';
+import { Observable, combineLatest, of } from 'rxjs';
 import { take, takeUntil, map, debounceTime, distinctUntilChanged, tap, skip } from 'rxjs/operators';
 import { Globals } from '@grid-failure-information-app/shared/constants/globals';
 import { StateEnum } from '@grid-failure-information-app/shared/constants/enums';
@@ -75,6 +81,7 @@
   public internExternEnum = InternExternEnum;
   public saveEnabled: boolean = true;
   public maxVersionNumber: number;
+  public currentGridFailureDetailsCoords: FailureCoords = new FailureCoords();
 
   public addressCommunities$: Observable<Array<string>> = this.actionsSubject.pipe(
     ofType(gridFailureActions.loadAddressCommunitiesSuccess),
@@ -97,6 +104,7 @@
     map(payload => payload.map(adrs => adrs.housenumber)),
     takeUntil(this._endSubscriptions$)
   );
+
   public addressHouseNumbers: Array<FailureHousenumber>;
   public showQualifyButton: boolean = false;
   public showStornoButton: boolean = false;
@@ -259,10 +267,24 @@
     }
   }
 
-  public setLatLong(): void {
-    this.gridFailureDetailsFormState$.pipe(take(1), takeUntil(this._endSubscriptions$)).subscribe((currentFormState: FormGroupState<GridFailure>) => {
+  public setLatLong(hsnr: string): void {
+    const test$ = this.actionsSubject.pipe(
+      ofType(gridFailureActions.loadAddressHouseNumbersSuccess),
+      map(action => action.payload),
+      takeUntil(this._endSubscriptions$)
+    );
+    if (this.addressHouseNumbers.length > 0) {
       const failureHousenumber: FailureHousenumber = this.addressHouseNumbers.find((housenumber: FailureHousenumber) => {
-        return housenumber.housenumber === currentFormState.controls.housenumber.value;
+        return housenumber.housenumber === hsnr;
+      });
+      if (!!failureHousenumber) {
+        this.appState$.dispatch(gridFailureActions.loadGridFailureAddress({ payload: failureHousenumber.uuid }));
+      }
+      return;
+    }
+    test$.pipe(take(1)).subscribe(addresses => {
+      const failureHousenumber: FailureHousenumber = addresses.find((housenumber: FailureHousenumber) => {
+        return housenumber.housenumber === hsnr;
       });
       if (!!failureHousenumber) {
         this.appState$.dispatch(gridFailureActions.loadGridFailureAddress({ payload: failureHousenumber.uuid }));
@@ -320,6 +342,9 @@
             })
           );
           break;
+        case formState.controls.housenumber.id:
+          this.setLatLong(formState.controls.housenumber.value);
+          break;
 
         default:
           break;
@@ -358,18 +383,30 @@
         const event = { longitude: address.longitude, latitude: address.latitude };
         this.latLonMapping(event);
       });
+
+    this.gridFailureDetailsFormState$.subscribe(gridFailureDetails => {
+      if (
+        this.currentGridFailureDetailsCoords.latitude !== gridFailureDetails.value.latitude ||
+        this.currentGridFailureDetailsCoords.longitude !== gridFailureDetails.value.longitude
+      ) {
+        this.currentGridFailureDetailsCoords = new FailureCoords(gridFailureDetails.value);
+      }
+    });
   }
 
   public searchForStation = (text$: Observable<string>) =>
     text$.pipe(
       debounceTime(200),
       distinctUntilChanged(),
-      map(term => (term.length < 2 ? [] : this._gridFailureStations.filter(s => s.stationName.toLowerCase().indexOf(term.toLowerCase()) > -1)))
+      map(term => (term.length < 2 ? [] : this._gridFailureStations.filter(s => s.failureStationSearchString.toLowerCase().indexOf(term.toLowerCase()) > -1)))
     );
 
-  public formatter = (s: FailureStation) => s.stationName;
+  public formatter = (s: FailureStation | string) => {
+    if (s instanceof FailureStation) return s.failureStationSearchString;
+    else return s;
+  };
 
-  public stationValueConverter: NgrxValueConverter<any | null, string | null> = stationToStationNameConverter;
+  public stationValueConverter: NgrxValueConverter<any | null, string | null> = stationToStationDescriptionConverter;
 
   public latLonMapping(data: { longitude: number; latitude: number }): void {
     !!data &&
@@ -392,6 +429,25 @@
       );
   }
 
+  public resetCoords(): void {
+    this.appState$.dispatch(
+      new SetValueAction(this.currentFormState.id, {
+        ...this.currentFormState.value,
+        longitude: null,
+        latitude: null,
+      })
+    );
+  }
+
+  public resetStationId() {
+    this.appState$.dispatch(
+      new SetValueAction(this.currentFormState.id, {
+        ...this.currentFormState.value,
+        stationId: null,
+      })
+    );
+  }
+
   public searchForAddressPostalcodes = (text$: Observable<string>) =>
     text$.pipe(
       debounceTime(300),
@@ -445,21 +501,15 @@
 
     switch (state) {
       case StateEnum.NEW:
+      case StateEnum.PLANNED:
         this.showCreatedButton = true;
+        break;
       case StateEnum.CREATED:
       case StateEnum.UPDATED:
         this.showQualifyButton = true;
         this.showStornoButton = true;
         break;
-      case StateEnum.PLANNED:
-        this.showQualifyButton = true;
-        this.showStornoButton = true;
-        this.showCreatedButton = true;
-        break;
       default:
-        this.showQualifyButton = false;
-        this.showStornoButton = false;
-        this.showCreatedButton = false;
         break;
     }
   }
diff --git a/projects/grid-failure-information-app/src/app/shared/directives/form-disable.directive.spec.ts b/projects/grid-failure-information-app/src/app/shared/directives/form-disable.directive.spec.ts
new file mode 100644
index 0000000..bb0771b
--- /dev/null
+++ b/projects/grid-failure-information-app/src/app/shared/directives/form-disable.directive.spec.ts
@@ -0,0 +1,49 @@
+/********************************************************************************
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+// import { FormDisableDirective } from '@shared/directives/form-disable.directive';
+import { FormDisableDirective } from '@grid-failure-information-app/shared/directives/form-disable.directive';
+import { async } from '@angular/core/testing';
+import { of } from 'rxjs/observable/of';
+import { PermissionsModel } from '../models/permissions.model';
+
+describe('FormDisableDirective', () => {
+  let viewContainerRef: any;
+  let appState: any;
+  beforeEach(async(() => {
+    viewContainerRef = {
+      createEmbeddedView: () => {},
+      clear: () => {},
+      element: { nativeElement: { elements: [{ classList: {}, disabled: undefined, childNodes: [] }] } },
+    };
+
+    appState = {
+      pipe: () => of(),
+      dispatch: () => {},
+      select: () => of({ roles: ['grid-failure-reader'] }),
+      map: () => of({ reader: true }),
+    };
+  }));
+
+  it('should create an instance', () => {
+    const directive = new FormDisableDirective(viewContainerRef as any, appState as any);
+    expect(directive).toBeTruthy();
+  });
+
+  it('should traverse a DOM', () => {
+    const directive = new FormDisableDirective(viewContainerRef as any, appState as any);
+    const spy = spyOn(directive, '_traverseDOM' as any);
+    directive.ngAfterViewInit();
+    expect(spy).toHaveBeenCalled();
+  });
+
+});
diff --git a/projects/grid-failure-information-app/src/app/shared/directives/form-disable.directive.ts b/projects/grid-failure-information-app/src/app/shared/directives/form-disable.directive.ts
new file mode 100644
index 0000000..9bfb851
--- /dev/null
+++ b/projects/grid-failure-information-app/src/app/shared/directives/form-disable.directive.ts
@@ -0,0 +1,56 @@
+/********************************************************************************
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+import { Directive, ViewContainerRef, AfterViewInit, OnDestroy } from '@angular/core';
+import { Store } from '@ngrx/store';
+// import * as store from '@shared/store';
+import * as store from '@grid-failure-information-app/shared/store';
+import { Observable, Subject } from 'rxjs';
+// import { User } from '@shared/models/user';
+import { User } from '@grid-failure-information-app/shared/models/user';
+// import { PermissionsModel } from '@shared/models/permissions.model';
+import { PermissionsModel } from '@grid-failure-information-app/shared/models/permissions.model';
+import { takeUntil } from 'rxjs/operators';
+
+@Directive({
+  selector: 'form',
+})
+export class FormDisableDirective implements AfterViewInit, OnDestroy {
+  private _endSubscriptions$: Subject<boolean> = new Subject();
+  private _permissions$: Observable<PermissionsModel> = this._appState$
+    .select(store.getUser)
+    .pipe(takeUntil(this._endSubscriptions$))
+    .map((user: User) => {
+      return new PermissionsModel(user.roles);
+    });
+
+  constructor(private _viewContainer: ViewContainerRef, private _appState$: Store<store.State>) {}
+
+  ngAfterViewInit() {
+    this._permissions$.subscribe(permissions => {
+      this._traverseDOM(this._viewContainer.element.nativeElement.elements, permissions);
+    });
+  }
+  ngOnDestroy() {
+    this._endSubscriptions$.next(true);
+  }
+  private _traverseDOM(elements: any[], permissions: PermissionsModel): void {
+    for (const element of elements) {
+      const isCancelButton = !!Object.keys(element.classList || {}).find(
+        item => element.classList[item] === 'cancel-button' || element.classList[item] === 'btn-sm'
+      );
+      element.disabled = element.disabled || (!isCancelButton && permissions.reader);
+
+      this._traverseDOM(element.childNodes, permissions);
+    }
+  }
+}
diff --git a/projects/grid-failure-information-app/src/app/shared/directives/visible-by-right.spec.ts b/projects/grid-failure-information-app/src/app/shared/directives/visible-by-right.spec.ts
index c7d6e12..dc83e66 100644
--- a/projects/grid-failure-information-app/src/app/shared/directives/visible-by-right.spec.ts
+++ b/projects/grid-failure-information-app/src/app/shared/directives/visible-by-right.spec.ts
@@ -74,6 +74,22 @@
     expect(spy).toHaveBeenCalled();
   });
 
+  it('should clear view for invalidRole', () => {
+    const directive = new VisibleByRightDirective(templateRef as any, viewContainerRef as any, appState as any);
+
+    directive.visibleByRight = ['_InvalidRole_'];
+    const spy = spyOn(directive['_viewContainer'], 'clear' as any);
+    directive.ngOnInit();
+    expect(spy).toHaveBeenCalled();
+  });
+  it('should create embedded view for role creator', () => {
+    const directive = new VisibleByRightDirective(templateRef as any, viewContainerRef as any, appState as any);
+    directive.visibleByRight = [RolesEnum.CREATOR];
+    const spy = spyOn(directive['_viewContainer'], 'createEmbeddedView' as any);
+    directive.ngOnInit();
+    expect(spy).toHaveBeenCalled();
+  });
+
   it('should clear view for another role than admin or qualifier', () => {
     appState = {
       pipe: () => of(),
@@ -82,6 +98,7 @@
       map: () => of({}),
     };
     const directive = new VisibleByRightDirective(templateRef as any, viewContainerRef as any, appState as any);
+
     directive.visibleByRight = [RolesEnum.QUALIFIER];
     const spy = spyOn(directive['_viewContainer'], 'clear' as any);
     directive.ngOnInit();
@@ -89,14 +106,10 @@
   });
 
   it('should create embedded view for role creator', () => {
-    // Arrange
-    //appState.map = () => of({creator: false, publisher: true, admin: false, qualifier: false});
     const directive = new VisibleByRightDirective(templateRef as any, viewContainerRef as any, appState as any);
     directive.visibleByRight = [RolesEnum.CREATOR];
     const spy = spyOn(directive['_viewContainer'], 'createEmbeddedView' as any);
-    // Act
     directive.ngOnInit();
-    // Assert
     expect(spy).toHaveBeenCalled();
   });
 
diff --git a/projects/grid-failure-information-app/src/app/shared/guards/admin.guard.ts b/projects/grid-failure-information-app/src/app/shared/guards/admin.guard.ts
index 8a5e441..2b6312b 100644
--- a/projects/grid-failure-information-app/src/app/shared/guards/admin.guard.ts
+++ b/projects/grid-failure-information-app/src/app/shared/guards/admin.guard.ts
@@ -33,7 +33,7 @@
     const isAdmin$: Observable<boolean> = this._appState$
       .select(store.getUser)
       .pipe(takeUntil(this._endSubscriptions$), take(1))
-      .map((user: User) => !!(new PermissionsModel(user.roles).admin));
+      .map((user: User) => !!new PermissionsModel(user.roles).admin);
     isAdmin$.subscribe(isAdmin => !isAdmin && this._router.navigate(['/grid-failures']));
     return isAdmin$.pipe(take(1));
   }
diff --git a/projects/grid-failure-information-app/src/app/shared/models/failure-coords.model.ts b/projects/grid-failure-information-app/src/app/shared/models/failure-coords.model.ts
new file mode 100644
index 0000000..de6290b
--- /dev/null
+++ b/projects/grid-failure-information-app/src/app/shared/models/failure-coords.model.ts
@@ -0,0 +1,31 @@
+/********************************************************************************
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+import { Boxed, box } from 'ngrx-forms';
+import { Globals } from '@grid-failure-information-app/shared/constants/globals';
+
+export class FailureCoords {
+  public longitude: number = null;
+  public latitude: number = null;
+
+  public constructor(data: any = null) {
+    Object.keys(data || {})
+      .filter(property => this.hasOwnProperty(property))
+      .forEach(property => {
+        if (Globals.PROPERTIES_TO_BOX.includes(property)) {
+          this[property] = box(data[property]);
+        } else {
+          this[property] = data[property];
+        }
+      });
+  }
+}
diff --git a/projects/grid-failure-information-app/src/app/shared/models/failure.station.model.ts b/projects/grid-failure-information-app/src/app/shared/models/failure.station.model.ts
index e46f42b..3c6a46d 100644
--- a/projects/grid-failure-information-app/src/app/shared/models/failure.station.model.ts
+++ b/projects/grid-failure-information-app/src/app/shared/models/failure.station.model.ts
@@ -25,4 +25,8 @@
       .filter(property => this.hasOwnProperty(property))
       .forEach(property => (this[property] = data[property]));
   }
+
+  get failureStationSearchString(): string {
+    return this.stationName + ' (' + this.stationId + ')';
+  }
 }
diff --git a/projects/grid-failure-information-app/src/app/shared/models/index.ts b/projects/grid-failure-information-app/src/app/shared/models/index.ts
index 01ce0d0..ed6d7fe 100644
--- a/projects/grid-failure-information-app/src/app/shared/models/index.ts
+++ b/projects/grid-failure-information-app/src/app/shared/models/index.ts
@@ -21,3 +21,4 @@
 export * from './failure.station.model';
 export * from './failure-housenumber.model';
 export * from './failure-address.model';
+export * from './failure-coords.model';
diff --git a/projects/grid-failure-information-app/src/app/shared/utility/utilityHelpers.ts b/projects/grid-failure-information-app/src/app/shared/utility/utilityHelpers.ts
index 987fd74..989edfa 100644
--- a/projects/grid-failure-information-app/src/app/shared/utility/utilityHelpers.ts
+++ b/projects/grid-failure-information-app/src/app/shared/utility/utilityHelpers.ts
@@ -71,7 +71,7 @@
 }
 
 /**
- * Convert a station object to station name
+ * Convert a station object to station description
  */
 export const stationToStationNameConverter: NgrxValueConverter<any | null, string | null> = {
   convertViewToStateValue(value) {
@@ -79,13 +79,11 @@
       return null;
     }
 
-    if (value.stationName) return value.stationName;
+    if (value.stationName) return value.stationName + ' (' + value.stationId + ')';
     else return value;
   },
   convertStateToViewValue(value) {
-    const failureStation: FailureStation = new FailureStation();
-    failureStation.stationName = value;
-    return failureStation;
+    return value;
   },
 };
 
diff --git a/projects/grid-failure-information-app/src/styles.scss b/projects/grid-failure-information-app/src/styles.scss
index 7148a44..131ecfb 100644
--- a/projects/grid-failure-information-app/src/styles.scss
+++ b/projects/grid-failure-information-app/src/styles.scss
@@ -2338,6 +2338,8 @@
 }
 .dropdown-menu.show {
   display: block;
+  max-height: 200px;
+  overflow: auto;
 }
 .dropdown-header {
   display: block;
diff --git a/projects/openk/grid-failure-information-map/src/constants/globals.ts b/projects/openk/grid-failure-information-map/src/constants/globals.ts
index 4839654..b012a93 100644
--- a/projects/openk/grid-failure-information-map/src/constants/globals.ts
+++ b/projects/openk/grid-failure-information-map/src/constants/globals.ts
@@ -11,9 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 export class Globals {
-  static INITIAL_LATITUDE = 52.640256;
-  static INITIAL_LONGITUDE = 11.495788;
-  static INITIAL_ZOOM = 6;
+  static OVERVIEW_MAP_INITIAL_LATITUDE = 51.640256;
+  static OVERVIEW_MAP_INITIAL_LONGITUDE = 10.495788;
+  static OVERVIEW_MAP_INITIAL_ZOOM = 6;
+  static DETAIL_MAP_INITIAL_ZOOM = 13;
   static MAX_ZOOM = 19;
   static ICON_SIZE_X_COORDINATE = 41;
   static ICON_SIZE_Y_COORDINATE = 51;
@@ -25,4 +26,6 @@
   static RADIUS_BORDER_COLOR = '#204d74';
   static RADIUS_FILL_COLOR = '#337ab7';
   static RADIUS_FILL_OPACITY = 0.3;
+
+  public static PROPERTIES_TO_BOX: string[] = ['addressPolygonPoints'];
 }
diff --git a/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.spec.ts b/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.spec.ts
index b4c3465..369e311 100644
--- a/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.spec.ts
+++ b/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.spec.ts
@@ -36,6 +36,8 @@
     let spyCircle: any = spyOn(L, 'circle').and.returnValue({ addTo() {} });
     component.ngAfterViewInit();
     component.mapData = [{ latitude: latitude, longitude: longitude, radius: radius }];
+    component.mapDetailData = { latitude: latitude, longitude: longitude };
+    component.mapDetailData = {};
     expect(spyMap).toHaveBeenCalled();
     expect(spyTileLayer).toHaveBeenCalled();
     expect(spyMarker).toHaveBeenCalledWith([latitude, longitude], { icon: (component as any)._icon });
diff --git a/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.ts b/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.ts
index 5bf2c59..995fa84 100644
--- a/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.ts
+++ b/projects/openk/grid-failure-information-map/src/lib/grid-failure-information-map.component.ts
@@ -14,6 +14,7 @@
 import * as L from 'leaflet';
 import { Globals } from '@openk-libs/grid-failure-information-map/constants/globals';
 import { GridFailureCoordinates } from '@openk-libs/grid-failure-information-map/shared/models/grid-failure-coordinates.model';
+import { unbox } from 'ngrx-forms';
 
 @Component({
   selector: 'openk-grid-failure-information-map',
@@ -22,6 +23,7 @@
 })
 export class GridFailureInformationMapComponent implements AfterViewInit {
   private _mapData: Array<GridFailureCoordinates> = [];
+  private _mapDetailData: GridFailureCoordinates;
 
   @Input()
   public set mapData(data: Array<any>) {
@@ -30,9 +32,23 @@
     });
     if (!!this._map) {
       this._map.remove();
-      this._initMap();
+      this._initMap(Globals.OVERVIEW_MAP_INITIAL_LATITUDE, Globals.OVERVIEW_MAP_INITIAL_LONGITUDE, Globals.OVERVIEW_MAP_INITIAL_ZOOM);
     }
-    this._setMarker();
+    this._setMultipleMarkers();
+  }
+
+  @Input()
+  public set mapDetailData(gridFailureDetail: any) {
+    this._mapDetailData = new GridFailureCoordinates(gridFailureDetail);
+    if (!!this._map) {
+      this._map.remove();
+      if (this._mapDetailData.latitude && this._mapDetailData.longitude) {
+        this._initMap(this._mapDetailData.latitude, this._mapDetailData.longitude, Globals.DETAIL_MAP_INITIAL_ZOOM);
+      } else {
+        this._initMap(Globals.OVERVIEW_MAP_INITIAL_LATITUDE, Globals.OVERVIEW_MAP_INITIAL_LONGITUDE, Globals.OVERVIEW_MAP_INITIAL_ZOOM);
+      }
+    }
+    this._setDetailMarker();
   }
 
   private _map: any;
@@ -45,14 +61,13 @@
   constructor() {}
 
   ngAfterViewInit(): void {
-    this._initMap();
-    this._setMarker();
+    this._initMap(Globals.OVERVIEW_MAP_INITIAL_LATITUDE, Globals.OVERVIEW_MAP_INITIAL_LONGITUDE, Globals.OVERVIEW_MAP_INITIAL_ZOOM);
   }
 
-  private _initMap(): void {
+  private _initMap(latitude: number, longitude: number, zoom: number): void {
     this._map = L.map(Globals.MAP_ID, {
-      center: [Globals.INITIAL_LATITUDE, Globals.INITIAL_LONGITUDE],
-      zoom: Globals.INITIAL_ZOOM,
+      center: [latitude, longitude],
+      zoom: zoom,
     });
     const tiles = L.tileLayer(Globals.TITLE_LAYER, {
       maxZoom: Globals.MAX_ZOOM,
@@ -62,15 +77,23 @@
     tiles.addTo(this._map);
   }
 
-  private _setMarker(): void {
-    !!this._map &&
-      !!this._mapData &&
+  private _setMultipleMarkers(): void {
+    if (!!this._map && !!this._mapData) {
       this._mapData.forEach(gridFailure => {
         if (gridFailure.latitude && gridFailure.longitude) {
           L.marker([gridFailure.latitude, gridFailure.longitude], { icon: this._icon }).addTo(this._map);
           this._drawPolygonOrCircle(gridFailure);
         }
       });
+    }
+  }
+
+  private _setDetailMarker(): void {
+    if (!!this._map && !!this._mapDetailData) {
+      if (this._mapDetailData.latitude && this._mapDetailData.longitude) {
+        L.marker([this._mapDetailData.latitude, this._mapDetailData.longitude], { icon: this._icon }).addTo(this._map);
+      }
+    }
   }
 
   // Draw whether polygon, circle or nothing; graded according to priority
diff --git a/projects/openk/grid-failure-information-map/src/shared/models/grid-failure-coordinates.model.ts b/projects/openk/grid-failure-information-map/src/shared/models/grid-failure-coordinates.model.ts
index 9578542..fb606bb 100644
--- a/projects/openk/grid-failure-information-map/src/shared/models/grid-failure-coordinates.model.ts
+++ b/projects/openk/grid-failure-information-map/src/shared/models/grid-failure-coordinates.model.ts
@@ -10,6 +10,9 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+import { unbox } from 'ngrx-forms';
+import { Globals } from '@openk-libs/grid-failure-information-map/constants/globals';
+
 export class GridFailureCoordinates {
   public id: string = null;
   public radius: number = null;
@@ -20,6 +23,12 @@
   public constructor(data: any = null) {
     Object.keys(data || {})
       .filter(property => this.hasOwnProperty(property))
-      .forEach(property => (this[property] = data[property]));
+      .forEach(property => {
+        if (Globals.PROPERTIES_TO_BOX.includes(property)) {
+          this[property] = unbox(data[property]);
+        } else {
+          this[property] = data[property];
+        }
+      });
   }
 }