[TOB-241,38,20,62,396,397,399,400,73,458,460,425] feat: v1.0.0

[TOB-241] feat: Add configuration page for attachment tags

* Add basic settings routing
* Add back end calls to add tags
* Add component for list of items
* Integrate component into settings page

[TOB-38] feat: Add configuration page for departments

* Add util functions to parse CSV data
* Add back end calls for departments settings
* Add store effect for departments settings
* Add pipes to convert departments data
* Add component to show department table
* Integrate components into settings page

[TOB-20] feat: Add configuration page for text blocks

* Add back end calls for text block config
* Add store effect for text block config
* Add pipe to create select options from array
* Resize text areas after rendering
* Add class for text block configuration form
* Add component to edit text blocks
* Add component to edit text block groups
* Add component to edit select lists
* Integrate components into settings page

[TOB-62] feat: Add configuration page for user

* Add back end calls for user configuration
* Add user table component
* Add user settings form component
* Add pipes to transform and filter user data
* Integrate components into settings page

[TOB-396] feat: Select position on leaflet map only via click

[TOB-397] feat: Add location search for leaflet map

* Add back end calls for Nominatim search
* Add search bar to leaflet map component
* Reorganize map module as feature module
* Integrate map store into leaflet map component

[TOB-399] feat: Show username of task that is currently claimed

[TOB-400] feat: Show confirm dialog before delete operations

[TOB-73] feat: Add configurable help url

* Add help url to app.config.json
* Use help url in header bar
* Redirect to help url on help route

[TOB-458] refactor: Auto activate search filters on value changes

* Auto activate search filter on value change for statement search
* Auto activate search filter on value change for user search

[TOB-460] feat: Open GIS merely via GET call

[TOB-425] fix: Minor bug fixes

* Fix minor styling issues
* Hide contribution status in task create negative response

Signed-off-by: Christopher Keim <keim@develop-group.de>
diff --git a/README.md b/README.md
index 82712e5..80d921e 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@
 * `routes.portal`: Route on which the main portal is served 
 * `routes.contactDataBase`: Route on which the contact data base  
 module is served
+* `routes.userDocu`: Route on which the user documentation is served
 * `leaflet.urlTemplate`: URL template to the map tile server 
 required by [Leaflet](https://leafletjs.com)
 * `leaflet.attribution`: Attribution which is added to all 
diff --git a/app.config.json b/app.config.json
index e1ecf44..8f73e86 100644
--- a/app.config.json
+++ b/app.config.json
@@ -18,6 +18,7 @@
   "routes": {
     "spaBackend": "/statementpaBE",
     "portal": "/portalFE",
-    "contactDataBase": "/contactdatabase"
+    "contactDataBase": "/contactdatabase",
+    "userDocu": "./assets/docu/userDocumentation.pdf"
   }
 }
diff --git a/package-lock.json b/package-lock.json
index c1b2c07..2e75d93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "openkonsequenz-statement-public-affairs",
-  "version": "0.9.0",
+  "version": "1.0.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -4649,9 +4649,9 @@
       }
     },
     "bl": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
-      "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
+      "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
       "dev": true,
       "requires": {
         "buffer": "^5.5.0",
@@ -4660,13 +4660,13 @@
       },
       "dependencies": {
         "buffer": {
-          "version": "5.6.0",
-          "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
-          "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+          "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
           "dev": true,
           "requires": {
-            "base64-js": "^1.0.2",
-            "ieee754": "^1.1.4"
+            "base64-js": "^1.3.1",
+            "ieee754": "^1.1.13"
           }
         },
         "readable-stream": {
@@ -11789,9 +11789,9 @@
       }
     },
     "node-fetch": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
-      "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+      "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
       "dev": true
     },
     "node-fetch-npm": {
@@ -11806,9 +11806,9 @@
       }
     },
     "node-forge": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
-      "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
+      "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
       "dev": true
     },
     "node-libs-browser": {
@@ -16121,12 +16121,12 @@
       }
     },
     "selfsigned": {
-      "version": "1.10.7",
-      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
-      "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
+      "version": "1.10.8",
+      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
+      "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
       "dev": true,
       "requires": {
-        "node-forge": "0.9.0"
+        "node-forge": "^0.10.0"
       }
     },
     "semver": {
diff --git a/package.json b/package.json
index 2d0c3be..35ed20a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "openkonsequenz-statement-public-affairs",
-  "version": "0.9.0",
+  "version": "1.0.0",
   "description": "Statement Public Affairs",
   "license": "Eclipse Public License - v 2.0",
   "repository": {
diff --git a/src/app/app-routing.module.spec.ts b/src/app/app-routing.module.spec.ts
index aab37b5..4c18b95 100644
--- a/src/app/app-routing.module.spec.ts
+++ b/src/app/app-routing.module.spec.ts
@@ -104,7 +104,7 @@
     it("should navigate to /settings", async () => {
         const isRoutingSuccessful = await callInZone(() => router.navigate(["settings"]));
         expect(isRoutingSuccessful).toBeTruthy();
-        expect(location.path()).toBe("/settings");
+        expect(location.path()).toBe("/settings/text-blocks");
     });
 
 });
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 7ebe298..455f6b6 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -13,6 +13,7 @@
 
 import {NgModule} from "@angular/core";
 import {PreloadAllModules, RouterModule, Routes} from "@angular/router";
+import {RedirectRouteGuard} from "./core";
 
 export const appRoutes: Routes = [
     {
@@ -50,6 +51,14 @@
         loadChildren: () => import("./features/settings/settings-routing.module")
             .then((m) => m.SettingsRoutingModule)
     },
+    {
+        path: "help",
+        canActivate: [RedirectRouteGuard],
+        component: RedirectRouteGuard,
+        data: {
+            externalUrl: "help"
+        }
+    },
     // The wildcard has to be placed as the last item (otherwise, its overriding routes)
     {
         path: "**",
diff --git a/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts b/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts
index e25c4b3..784fb61 100644
--- a/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts
+++ b/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts
@@ -11,16 +11,25 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Attachment tags that cannot be set manually. Those are automatically set on specified task steps.
+ * E.g. attachments tranferred from a mail are automatically assigned the "email" tag.
+ */
 export enum EAPIStaticAttachmentTagIds {
 
+    // Used for the attachment generated from the mail body.
     EMAIL_TEXT = "email-text",
 
+    // Used for the transferred mail attachments.
     EMAIL = "email",
 
+    // Used for all attachments that will be send with the generated statement pdf.
     OUTBOX = "outbox",
 
+    // Used for the attachments uploaded after the statement has been finished.
     CONSIDERATION = "consideration",
 
+    // Used for the generated statement pdf from text block arrangement.
     STATEMENT = "statement",
 
     COVER_LETTER = "cover-letter"
diff --git a/src/app/core/api/attachments/attachments-api.service.ts b/src/app/core/api/attachments/attachments-api.service.ts
index e6d4835..cd92f7e 100644
--- a/src/app/core/api/attachments/attachments-api.service.ts
+++ b/src/app/core/api/attachments/attachments-api.service.ts
@@ -85,8 +85,9 @@
      * Creates a new tag in the back end data base.
      */
     public addNewTag(label: string) {
+        const params = {label};
         const endPoint = `/tags`;
-        return this.httpClient.put(urlJoin(this.baseUrl, endPoint), {label});
+        return this.httpClient.put(urlJoin(this.baseUrl, endPoint), {}, {params});
     }
 
 }
diff --git a/src/app/core/api/contacts/IAPIContactPerson.ts b/src/app/core/api/contacts/IAPIContactPerson.ts
index d92f4c1..3dde31f 100644
--- a/src/app/core/api/contacts/IAPIContactPerson.ts
+++ b/src/app/core/api/contacts/IAPIContactPerson.ts
@@ -11,6 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface which represents the model of all basic information of a specific contact.
+ */
 export interface IAPIContactPerson {
     companyId: string;
     companyName: string;
diff --git a/src/app/core/api/contacts/IAPIContactPersonDetails.ts b/src/app/core/api/contacts/IAPIContactPersonDetails.ts
index 4fcda36..5f5acb0 100644
--- a/src/app/core/api/contacts/IAPIContactPersonDetails.ts
+++ b/src/app/core/api/contacts/IAPIContactPersonDetails.ts
@@ -11,6 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface which represents all data accessible for a specific contact.
+ * This contains additional values to IAPIContactPerson.
+ */
 export interface IAPIContactPersonDetails {
     community: string;
     communitySuffix: string;
diff --git a/src/app/core/api/core/EAPIUserRoles.ts b/src/app/core/api/core/EAPIUserRoles.ts
index 3e2e8d8..c78fe05 100644
--- a/src/app/core/api/core/EAPIUserRoles.ts
+++ b/src/app/core/api/core/EAPIUserRoles.ts
@@ -16,7 +16,8 @@
     ROLE_SPA_ACCESS = "ROLE_SPA_ACCESS",
     SPA_APPROVER = "ROLE_SPA_APPROVER",
     SPA_OFFICIAL_IN_CHARGE = "ROLE_SPA_OFFICIAL_IN_CHARGE",
-    SPA_ADMIN = "ROLE_SPA_ADMIN"
+    SPA_ADMIN = "ROLE_SPA_ADMIN",
+    SPA_CUSTOMER = "ROLE_SPA_CUSTOMER"
 }
 
 export const ALL_NON_TRIVIAL_USER_ROLES = [
diff --git a/src/app/core/api/core/IAPIUserInfo.ts b/src/app/core/api/core/IAPIUserInfo.ts
index 00eeedd..a1e3e86 100644
--- a/src/app/core/api/core/IAPIUserInfo.ts
+++ b/src/app/core/api/core/IAPIUserInfo.ts
@@ -13,6 +13,9 @@
 
 import {EAPIUserRoles} from "./EAPIUserRoles";
 
+/**
+ * Interface which represents basic information of a specific user.
+ */
 export interface IAPIUserInfo {
     firstName: string;
     lastName: string;
diff --git a/src/app/core/api/core/IAPIVersion.ts b/src/app/core/api/core/IAPIVersion.ts
index 81fed53..584ad0e 100644
--- a/src/app/core/api/core/IAPIVersion.ts
+++ b/src/app/core/api/core/IAPIVersion.ts
@@ -11,6 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface which represents version information.
+ */
 export interface IAPIVersion {
     buildVersion: string;
     applicationName: string;
diff --git a/src/app/core/api/geo/IAPIGeographicPositions.ts b/src/app/core/api/geo/IAPIGeographicPositions.ts
index 687b05a..817167c 100644
--- a/src/app/core/api/geo/IAPIGeographicPositions.ts
+++ b/src/app/core/api/geo/IAPIGeographicPositions.ts
@@ -11,6 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Multiple geo positions as object properties.
+ */
 export interface IAPIGeographicPositions {
     [key: string]: {
         x: number;
diff --git a/src/app/core/api/geo/IAPINominatimSearchResult.ts b/src/app/core/api/geo/IAPINominatimSearchResult.ts
new file mode 100644
index 0000000..abd3528
--- /dev/null
+++ b/src/app/core/api/geo/IAPINominatimSearchResult.ts
@@ -0,0 +1,39 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+/**
+ * Search result from nominatim server. Includes information about a specific place like coordinates and name/description.
+ */
+export interface IAPINominatimSearchResult {
+    place_id: number;
+    licence: string;
+    osm_type: string;
+    osm_id: number;
+    boundingbox: [string, string, string, string];
+    lat: string;
+    lon: string;
+    display_name: string;
+    place_rank: number;
+    category: string;
+    type: string;
+    importance: number;
+    geojson: {
+        type: string;
+        coordinates: [
+            [string, string],
+            [string, string],
+            [string, string],
+            [string, string]
+        ]
+    };
+}
diff --git a/src/app/core/api/geo/geo-api.service.ts b/src/app/core/api/geo/geo-api.service.ts
index 700163b..0e925f3 100644
--- a/src/app/core/api/geo/geo-api.service.ts
+++ b/src/app/core/api/geo/geo-api.service.ts
@@ -15,23 +15,38 @@
 import {Inject, Injectable} from "@angular/core";
 import {Observable} from "rxjs";
 import {objectToHttpParams, urlJoin} from "../../../util/http";
+import {APP_CONFIGURATION, IAppConfiguration} from "../../configuration";
 import {SPA_BACKEND_ROUTE} from "../../external-routes";
 import {IAPIGeographicPositions} from "./IAPIGeographicPositions";
+import {IAPINominatimSearchResult} from "./IAPINominatimSearchResult";
 
 @Injectable({providedIn: "root"})
 export class GeoApiService {
 
     public constructor(
         protected readonly httpClient: HttpClient,
-        @Inject(SPA_BACKEND_ROUTE) protected readonly baseUrl: string
+        @Inject(SPA_BACKEND_ROUTE) protected readonly baseUrl: string,
+        @Inject(APP_CONFIGURATION) protected readonly configuration: IAppConfiguration
     ) {
 
     }
 
+    /**
+     * Sends geo positions and input + output format and receives the transformed positions back.
+     */
     public transform(geographicPositions: IAPIGeographicPositions, from: string, to: string): Observable<IAPIGeographicPositions> {
         const params = objectToHttpParams({from, to});
         const endPoint = `/geo-coordinate-transform`;
         return this.httpClient.post<IAPIGeographicPositions>(urlJoin(this.baseUrl, endPoint), geographicPositions, {params});
     }
 
+    /**
+     * Searches for a specific place to retrieve coordinates to display.
+     */
+    public search(searchQuery: string) {
+        const endPoint = `search`;
+        const params = objectToHttpParams({q: this.configuration.nominatim.searchQueryPrefix + " " + searchQuery, format: "json"});
+        return this.httpClient.get<IAPINominatimSearchResult[]>(urlJoin(this.configuration.nominatim.url, endPoint), {params});
+    }
+
 }
diff --git a/src/app/core/api/geo/index.ts b/src/app/core/api/geo/index.ts
index 9f1b524..b40cc25 100644
--- a/src/app/core/api/geo/index.ts
+++ b/src/app/core/api/geo/index.ts
@@ -12,4 +12,5 @@
  ********************************************************************************/
 
 export * from "./IAPIGeographicPositions";
+export * from "./IAPINominatimSearchResult";
 export * from "./geo-api.service";
diff --git a/src/app/core/api/mail/IAPIEmailAttachmentModel.ts b/src/app/core/api/mail/IAPIEmailAttachmentModel.ts
index eeb188c..a6c8787 100644
--- a/src/app/core/api/mail/IAPIEmailAttachmentModel.ts
+++ b/src/app/core/api/mail/IAPIEmailAttachmentModel.ts
@@ -11,6 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface which represents the model of an email attachment.
+ */
 export interface IAPIEmailAttachmentModel {
     name: string;
     size: string;
diff --git a/src/app/core/api/mail/IAPIEmailModel.ts b/src/app/core/api/mail/IAPIEmailModel.ts
index a087714..1b03290 100644
--- a/src/app/core/api/mail/IAPIEmailModel.ts
+++ b/src/app/core/api/mail/IAPIEmailModel.ts
@@ -13,6 +13,9 @@
 
 import {IAPIEmailAttachmentModel} from "./IAPIEmailAttachmentModel";
 
+/**
+ * Interface which represents the model of an email. Contains meta data and mail content as text or html.
+ */
 export interface IAPIEmailModel {
     identifier: string;
     subject: string;
diff --git a/src/app/core/api/process/IAPIStatementHistory.ts b/src/app/core/api/process/IAPIStatementHistory.ts
index 8177eb2..d4cac72 100644
--- a/src/app/core/api/process/IAPIStatementHistory.ts
+++ b/src/app/core/api/process/IAPIStatementHistory.ts
@@ -11,6 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface which represents the model of the statement history as saved in the back end database.
+ */
 export interface IAPIStatementHistory {
     processName: string;
     processVersion: number;
@@ -18,6 +21,9 @@
     currentProcessActivities: IAPIProcessActivity[];
 }
 
+/**
+ * Interface which represents the model of a single process activity.
+ */
 export interface IAPIProcessActivity {
     id: string;
     activityId: string;
diff --git a/src/app/core/api/process/process-api.service.ts b/src/app/core/api/process/process-api.service.ts
index b57dced..b8260fe 100644
--- a/src/app/core/api/process/process-api.service.ts
+++ b/src/app/core/api/process/process-api.service.ts
@@ -70,11 +70,17 @@
         return this.httpClient.post<void>(urlJoin(this.baseUrl, endPoint), body);
     }
 
+    /**
+     * Fetches the statement history.
+     */
     public getStatementHistory(statementId: number) {
         const endPoint = `/process/statements/${statementId}/history`;
         return this.httpClient.get<IAPIStatementHistory>(urlJoin(this.baseUrl, endPoint));
     }
 
+    /**
+     * Fetches the statement process diagram. Response is a xml sent as text.
+     */
     public getStatementProcessDiagram(statementId: number) {
         const endPoint = `/process/statements/${statementId}/workflowmodel`;
         return this.httpClient.get(urlJoin(this.baseUrl, endPoint), {responseType: "text"});
diff --git a/src/app/core/api/settings/IAPIDepartmentsConfiguration.ts b/src/app/core/api/settings/IAPIDepartmentsConfiguration.ts
index 8a562ef..11cad75 100644
--- a/src/app/core/api/settings/IAPIDepartmentsConfiguration.ts
+++ b/src/app/core/api/settings/IAPIDepartmentsConfiguration.ts
@@ -39,3 +39,14 @@
     [groupName: string]: string[];
 
 }
+
+/**
+ * Interface which models the underlying table to assign city and district values to sectors and departments.
+ * Each key represents a pair of city and district concatenated with a #.
+ */
+export interface IAPIDepartmentTable {
+    [cityDistrict: string]: {
+        provides: string[];
+        departments: IAPIDepartmentGroups
+    };
+}
diff --git a/src/app/shared/leaflet/index.ts b/src/app/core/api/settings/IAPIUserInfoExtended.ts
similarity index 73%
copy from src/app/shared/leaflet/index.ts
copy to src/app/core/api/settings/IAPIUserInfoExtended.ts
index 849c149..31bfc5a 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/core/api/settings/IAPIUserInfoExtended.ts
@@ -10,10 +10,10 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+import {IAPIUserInfo} from "../core";
+import {IAPIUserSettings} from "./IAPIUserSettings";
 
-export * from "./directives";
-export * from "./pipes";
-export * from "./util";
-
-export * from "./leaflet.module";
-export * from "./leaflet-configuration.token";
+export interface IAPIUserInfoExtended extends IAPIUserInfo {
+    id: number;
+    settings: IAPIUserSettings;
+}
diff --git a/src/app/shared/controls/map-select/components/map-select.component.scss b/src/app/core/api/settings/IAPIUserSettings.ts
similarity index 81%
copy from src/app/shared/controls/map-select/components/map-select.component.scss
copy to src/app/core/api/settings/IAPIUserSettings.ts
index 7e59f1f..02edab5 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.scss
+++ b/src/app/core/api/settings/IAPIUserSettings.ts
@@ -11,10 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
-
-:host {
-  display: block;
-  width: 100%;
-  height: 100%;
+export interface IAPIUserSettings {
+    email?: string;
+    department?: {
+        group: string;
+        name: string;
+    };
 }
diff --git a/src/app/core/api/settings/index.ts b/src/app/core/api/settings/index.ts
index 4137763..9940ee4 100644
--- a/src/app/core/api/settings/index.ts
+++ b/src/app/core/api/settings/index.ts
@@ -13,5 +13,7 @@
 
 export * from "./IAPIDepartmentsConfiguration";
 export * from "./IAPIStatementType";
+export * from "./IAPIUserInfoExtended";
+export * from "./IAPIUserSettings";
 
 export * from "./settings-api.service";
diff --git a/src/app/core/api/settings/settings-api.service.ts b/src/app/core/api/settings/settings-api.service.ts
index e84168b..68c900c 100644
--- a/src/app/core/api/settings/settings-api.service.ts
+++ b/src/app/core/api/settings/settings-api.service.ts
@@ -15,9 +15,12 @@
 import {Inject, Injectable} from "@angular/core";
 import {urlJoin} from "../../../util";
 import {SPA_BACKEND_ROUTE} from "../../external-routes";
-import {IAPISectorsModel} from "../statements/IAPISectorsModel";
-import {IAPIDepartmentsConfiguration} from "./IAPIDepartmentsConfiguration";
+import {IAPISectorsModel} from "../statements";
+import {IAPITextBlockConfigurationModel} from "../text";
+import {IAPIDepartmentsConfiguration, IAPIDepartmentTable} from "./IAPIDepartmentsConfiguration";
 import {IAPIStatementType} from "./IAPIStatementType";
+import {IAPIUserInfoExtended} from "./IAPIUserInfoExtended";
+import {IAPIUserSettings} from "./IAPIUserSettings";
 
 @Injectable({
     providedIn: "root"
@@ -55,4 +58,61 @@
         return this.httpClient.get<IAPISectorsModel>(urlJoin(this.baseUrl, endPoint));
     }
 
+    /**
+     * Fetches the table with all departments and sectors from the back end.
+     */
+    public getDepartmentTable() {
+        const endPoint = `/admin/departments`;
+        return this.httpClient.get<IAPIDepartmentTable>(urlJoin(this.baseUrl, endPoint));
+    }
+
+    /**
+     * Sets the table of all department and sectors in the back end.
+     */
+    public putDepartmentTable(data: IAPIDepartmentTable) {
+        const endPoint = "/admin/departments";
+        return this.httpClient.put(urlJoin(this.baseUrl, endPoint), data);
+    }
+
+    /**
+     * Fetches the text block configuration for all newly created statements from the back end.
+     */
+    public getTextblockConfig() {
+        const endPoint = `/admin/textblockconfig`;
+        return this.httpClient.get<IAPITextBlockConfigurationModel>(urlJoin(this.baseUrl, endPoint));
+    }
+
+    /**
+     * Sets the text block configuration for all newly created statements in the back end.
+     */
+    public putTextblockConfig(data: IAPITextBlockConfigurationModel) {
+        const endPoint = "/admin/textblockconfig";
+        return this.httpClient.put(urlJoin(this.baseUrl, endPoint), data);
+    }
+
+    /**
+     * Triggers the backend to refresh user data with auth-n-auth module. User data is only refreshed after receiving this command, not
+     * automatically.
+     */
+    public syncUserData() {
+        const endPoint = "/admin/users-sync";
+        return this.httpClient.post(urlJoin(this.baseUrl, endPoint), {});
+    }
+
+    /**
+     * Fetches the list of users with assigned email addresses from the back end.
+     */
+    public fetchUsers() {
+        const endPoint = "/admin/users";
+        return this.httpClient.get<IAPIUserInfoExtended[]>(urlJoin(this.baseUrl, endPoint), {});
+    }
+
+    /**
+     * Sets settings like email address or department assignment for a specific user in the back end database.
+     */
+    public setUserData(userId: number, settings: IAPIUserSettings) {
+        const endPoint = `/admin/users/${userId}/settings`;
+        return this.httpClient.post(urlJoin(this.baseUrl, endPoint), {...settings});
+    }
+
 }
diff --git a/src/app/core/api/shared/IAPISearchOptions.ts b/src/app/core/api/shared/IAPISearchOptions.ts
index 93d07f8..f406ef5 100644
--- a/src/app/core/api/shared/IAPISearchOptions.ts
+++ b/src/app/core/api/shared/IAPISearchOptions.ts
@@ -19,7 +19,7 @@
     /**
      * Search strings to find statements.
      */
-    q: string;
+    q?: string;
 
     /**
      * Size of the loaded page.
diff --git a/src/app/core/api/statements/IAPIPositionSearchStatementModel.ts b/src/app/core/api/statements/IAPIPositionSearchStatementModel.ts
index 8de60b7..da6ab17 100644
--- a/src/app/core/api/statements/IAPIPositionSearchStatementModel.ts
+++ b/src/app/core/api/statements/IAPIPositionSearchStatementModel.ts
@@ -11,6 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface that represents the model of a statement from a position search call. Similiar to IAPIStatementModel but has information
+ * specifically for the position search such as position and dueDate.
+ */
 export interface IAPIPositionSearchStatementModel {
 
     id: number;
diff --git a/src/app/core/api/statements/IAPISectorsModel.ts b/src/app/core/api/statements/IAPISectorsModel.ts
index c6cffa9..b7ca879 100644
--- a/src/app/core/api/statements/IAPISectorsModel.ts
+++ b/src/app/core/api/statements/IAPISectorsModel.ts
@@ -11,6 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Interface that represents the model the sector information. Contains properties that describe which sectors are available at that
+ * location.
+ */
 export interface IAPISectorsModel {
 
     [sectorIdentifier: string]: string[];
diff --git a/src/app/core/api/statements/index.ts b/src/app/core/api/statements/index.ts
index 91527b0..ac56225 100644
--- a/src/app/core/api/statements/index.ts
+++ b/src/app/core/api/statements/index.ts
@@ -12,8 +12,10 @@
  ********************************************************************************/
 
 export * from "./IAPICommentModel";
+export * from "./IAPIDashboardStatementModel";
+export * from "./IAPIPositionSearchStatementModel";
+export * from "./IAPISectorsModel";
 export * from "./IAPIStatementModel";
 export * from "./IAPIWorkflowData";
-export * from "./IAPIPositionSearchStatementModel";
 
 export * from "./statements-api.service";
diff --git a/src/app/core/api/statements/statements-api.service.ts b/src/app/core/api/statements/statements-api.service.ts
index d611a35..c798a47 100644
--- a/src/app/core/api/statements/statements-api.service.ts
+++ b/src/app/core/api/statements/statements-api.service.ts
@@ -16,8 +16,7 @@
 import {objectToHttpParams, urlJoin} from "../../../util";
 import {SPA_BACKEND_ROUTE} from "../../external-routes";
 import {IAPIDepartmentGroups} from "../settings";
-import {IAPIPaginationResponse, IAPISearchOptions} from "../shared";
-import {IAPIPositionSearchOptions} from "../shared/IAPIPositionSearchOptions";
+import {IAPIPaginationResponse, IAPIPositionSearchOptions, IAPISearchOptions} from "../shared";
 import {IAPICommentModel} from "./IAPICommentModel";
 import {IAPIDashboardStatementModel} from "./IAPIDashboardStatementModel";
 import {IAPIPositionSearchStatementModel} from "./IAPIPositionSearchStatementModel";
@@ -57,7 +56,7 @@
     }
 
     /**
-     *
+     * Search for a list of statements in the back end data base.
      */
     public getStatementPositionsSearch(searchOptions: IAPIPositionSearchOptions) {
         const endPoint = `statementpositionsearch`;
diff --git a/src/app/core/api/text/IAPITextBlockModel.ts b/src/app/core/api/text/IAPITextBlockModel.ts
index 52b4f3b..79c301e 100644
--- a/src/app/core/api/text/IAPITextBlockModel.ts
+++ b/src/app/core/api/text/IAPITextBlockModel.ts
@@ -39,3 +39,5 @@
     requires: IAPIRequireRuleModel[];
 
 }
+
+export type TAPITextBlockRuleKey = keyof IAPITextBlockModel & ("requires" | "excludes");
diff --git a/src/app/core/auth/auth-interceptor.service.ts b/src/app/core/auth/auth-interceptor.service.ts
index 4e79d17..85686fb 100644
--- a/src/app/core/auth/auth-interceptor.service.ts
+++ b/src/app/core/auth/auth-interceptor.service.ts
@@ -19,6 +19,9 @@
 import {SPA_BACKEND_ROUTE} from "../external-routes";
 import {AuthService} from "./auth.service";
 
+/**
+ * This service adds the access token to the headers for all backend requests if needed.
+ */
 @Injectable({providedIn: "root"})
 export class AuthInterceptorService implements HttpInterceptor {
 
diff --git a/src/app/core/configuration/app-configuration.token.ts b/src/app/core/configuration/app-configuration.token.ts
index 5b5eeba..b36d46b 100644
--- a/src/app/core/configuration/app-configuration.token.ts
+++ b/src/app/core/configuration/app-configuration.token.ts
@@ -35,6 +35,7 @@
         spaBackend: string;
         portal: string;
         contactDataBase: string;
+        userDocu: string;
     };
 }
 
diff --git a/src/app/core/confirm/confirm.service.ts b/src/app/core/confirm/confirm.service.ts
new file mode 100644
index 0000000..8f5fd82
--- /dev/null
+++ b/src/app/core/confirm/confirm.service.ts
@@ -0,0 +1,33 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Inject, Injectable} from "@angular/core";
+import {WINDOW} from "../dom";
+
+@Injectable({providedIn: "root"})
+export class ConfirmService {
+
+    public constructor(
+        @Inject(WINDOW) private readonly window: Window
+    ) {
+
+    }
+
+    /**
+     * Opens a confirmation dialog on the browser window and returns result. OK = true, Cancel = false.
+     */
+    public askForConfirmation(message: string) {
+        return this.window.confirm(message);
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/core/confirm/index.ts
similarity index 93%
copy from src/app/features/settings/components/index.ts
copy to src/app/core/confirm/index.ts
index 990bb42..4a397bd 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/core/confirm/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./confirm.service";
diff --git a/src/app/core/download/download.service.ts b/src/app/core/download/download.service.ts
index d69dbdd..68c2b37 100644
--- a/src/app/core/download/download.service.ts
+++ b/src/app/core/download/download.service.ts
@@ -20,6 +20,10 @@
 
     }
 
+    /**
+     * To download an attachment the backend link to the attachment is put into an dom element, clicked and the value is removed again.
+     * This is done so the needed accessToken for the data access is not displayed on hovering the button used for the download.
+     */
     public startDownload(url: string, token?: string) {
         url += token == null ? "" : `?accessToken=${token}`;
         const anchor = this.document.createElement("a");
diff --git a/src/app/core/external-routes/index.ts b/src/app/core/external-routes/index.ts
index a4576e0..2455a42 100644
--- a/src/app/core/external-routes/index.ts
+++ b/src/app/core/external-routes/index.ts
@@ -12,5 +12,8 @@
  ********************************************************************************/
 
 export * from "./contact-data-base-route.token";
+export * from "./nominatim-route.token";
+export * from "./redirect-route-guard.service";
 export * from "./portal-route.token";
 export * from "./spa-backend-route.token";
+export * from "./user-docu-route.token";
diff --git a/src/app/core/external-routes/redirect-route-guard.service.ts b/src/app/core/external-routes/redirect-route-guard.service.ts
new file mode 100644
index 0000000..fbf5fb6
--- /dev/null
+++ b/src/app/core/external-routes/redirect-route-guard.service.ts
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Inject, Injectable} from "@angular/core";
+import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from "@angular/router";
+import {HELP_ROUTE} from "./user-docu-route.token";
+
+@Injectable({
+    providedIn: "root"
+})
+export class RedirectRouteGuard implements CanActivate {
+
+    public constructor(
+        @Inject(HELP_ROUTE) public helpRoute: string
+    ) {
+
+    }
+
+    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
+        if (route.data.externalUrl === "help") {
+            window.location.href = this.helpRoute;
+        }
+        return false;
+    }
+
+}
diff --git a/src/app/core/external-routes/user-docu-route.token.ts b/src/app/core/external-routes/user-docu-route.token.ts
new file mode 100644
index 0000000..43daa26
--- /dev/null
+++ b/src/app/core/external-routes/user-docu-route.token.ts
@@ -0,0 +1,24 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {InjectionToken} from "@angular/core";
+import * as config from "../../../../app.config.json";
+import {IAppConfiguration} from "../configuration";
+
+/**
+ * Injection token for the external route to the main portal.
+ */
+export const HELP_ROUTE = new InjectionToken<string>("External route user documentation", {
+    providedIn: "root",
+    factory: () => (config as IAppConfiguration).routes.userDocu
+});
diff --git a/src/app/core/i18n/i18n.service.ts b/src/app/core/i18n/i18n.service.ts
index f5b5ab6..5d91b55 100644
--- a/src/app/core/i18n/i18n.service.ts
+++ b/src/app/core/i18n/i18n.service.ts
@@ -16,6 +16,11 @@
 import * as moment from "moment";
 import {WINDOW} from "../dom";
 
+/**
+ * This component is used to set the language to use for the app. The language is set to the language of the current browser window the app
+ * was opened in. If there is no language file for the browser language, the default specified for the translation service is used.
+ * The default language for momentjs is set to the value specified in the translation file for the selected display language.
+ */
 @Injectable({providedIn: "root"})
 export class I18nService {
 
diff --git a/src/app/core/index.ts b/src/app/core/index.ts
index ecefda5..623ee96 100644
--- a/src/app/core/index.ts
+++ b/src/app/core/index.ts
@@ -14,6 +14,7 @@
 export * from "./api";
 export * from "./auth";
 export * from "./configuration";
+export * from "./confirm";
 export * from "./dom";
 export * from "./download";
 export * from "./external-routes";
diff --git a/src/app/features/dashboard/components/dashboard/dashboard.component.ts b/src/app/features/dashboard/components/dashboard/dashboard.component.ts
index a45fb13..79dcb14 100644
--- a/src/app/features/dashboard/components/dashboard/dashboard.component.ts
+++ b/src/app/features/dashboard/components/dashboard/dashboard.component.ts
@@ -39,6 +39,12 @@
     showSubCaption$?: Observable<boolean>;
 }
 
+/**
+ * This component displayed multiple lists of statements that might be relevant to the logged in users.
+ * Different list for the various user roles are displayed. The goal is to show the most relevant statements for the user.
+ * The lists are sorted by due date to show the statements that need attention.
+ */
+
 @Component({
     selector: "app-dashboard",
     templateUrl: "./dashboard.component.html",
diff --git a/src/app/features/dashboard/pipe/get-dashboard-entries.pipe.ts b/src/app/features/dashboard/pipe/get-dashboard-entries.pipe.ts
index 74ceb3d..46f6f76 100644
--- a/src/app/features/dashboard/pipe/get-dashboard-entries.pipe.ts
+++ b/src/app/features/dashboard/pipe/get-dashboard-entries.pipe.ts
@@ -16,6 +16,12 @@
 import {IStatementEntityWithTasks} from "../../../store/statements/model";
 import {arrayJoin} from "../../../util/store";
 
+
+/**
+ * This function converts a list of statement entities to a list of statement table entries.
+ * Which means the additional properties needed to display the statements in the dashboard lists are added to the list elements.
+ */
+
 @Pipe({name: "getDashboardEntries"})
 export class GetDashboardEntriesPipe implements PipeTransform {
 
diff --git a/src/app/features/details/components/attachments/statement-details-attachments.component.html b/src/app/features/details/components/attachments/statement-details-attachments.component.html
index 2b66156..0c0fef5 100644
--- a/src/app/features/details/components/attachments/statement-details-attachments.component.html
+++ b/src/app/features/details/components/attachments/statement-details-attachments.component.html
@@ -61,7 +61,7 @@
           (appDownload)="downloadAttachment($event)"
           [appAttachments]="statementAttachments"
           [appTagList]="tags$ | async"
-          [appTitle]="'Manuell hinzugefügte Anhänge'"
+          [appTitle]="'details.attachments.added' | translate"
           class="attachments--document--lists---half-size">
         </app-attachment-display-list>
       </div>
diff --git a/src/app/features/details/components/attachments/statement-details-attachments.component.ts b/src/app/features/details/components/attachments/statement-details-attachments.component.ts
index 026f1d9..da72d14 100644
--- a/src/app/features/details/components/attachments/statement-details-attachments.component.ts
+++ b/src/app/features/details/components/attachments/statement-details-attachments.component.ts
@@ -67,6 +67,10 @@
     public constructor(public store: Store) {
     }
 
+    /**
+     * Subscribing to the attachment observables to apply the filtering for tags when changes to the statement attachments happen.
+     */
+
     public ngOnInit() {
         combineLatest([this.allAttachments$, this.tags$]).pipe(
             takeUntil(this.destroy$)
@@ -121,6 +125,10 @@
         this.store.dispatch(startAttachmentDownloadAction({statementId, attachmentId}));
     }
 
+    /**
+     * Filters the input list of attachments to only return the ones that have the isSelected property set to true.
+     */
+
     public filterBySelectedTags(attachments: IAttachmentControlValue[]) {
         return attachments.filter((attachment) => {
             const selectedTags = this.availableTags?.filter((_) => _.isSelected);
diff --git a/src/app/features/details/components/considerations/statement-details-considerations.component.html b/src/app/features/details/components/considerations/statement-details-considerations.component.html
index 5ecd60c..270c495 100644
--- a/src/app/features/details/components/considerations/statement-details-considerations.component.html
+++ b/src/app/features/details/components/considerations/statement-details-considerations.component.html
@@ -33,7 +33,10 @@
       [appTitle]="'attachments.add' | translate"
       class="attachments--container">
       <button (click)="submit()" class="openk-button openk-success"
-              style="margin-left: 0.25em;">{{'details.considerations.upload' | translate}}</button>
+              style="margin-left: 0.25em;">
+        <mat-icon class="attachments--select-file-button--icon">publish</mat-icon>
+        {{'details.considerations.upload' | translate}}
+      </button>
     </app-attachment-file-drop-form>
   </div>
 
diff --git a/src/app/features/details/components/considerations/statement-details-considerations.component.scss b/src/app/features/details/components/considerations/statement-details-considerations.component.scss
index 6bc3cc8..caaac5e 100644
--- a/src/app/features/details/components/considerations/statement-details-considerations.component.scss
+++ b/src/app/features/details/components/considerations/statement-details-considerations.component.scss
@@ -42,3 +42,9 @@
   padding: 1em;
   font-style: italic;
 }
+
+.attachments--select-file-button--icon {
+  height: initial;
+  width: initial;
+  font-size: 1em;
+}
diff --git a/src/app/features/details/components/considerations/statement-details-considerations.component.ts b/src/app/features/details/components/considerations/statement-details-considerations.component.ts
index a79cd2e..9ddb07e 100644
--- a/src/app/features/details/components/considerations/statement-details-considerations.component.ts
+++ b/src/app/features/details/components/considerations/statement-details-considerations.component.ts
@@ -25,6 +25,11 @@
 import {createFormGroup} from "../../../../util/forms";
 import {AbstractReactiveFormComponent} from "../../../forms/abstract";
 
+/**
+ * This component displays the list of files saved as consideration result to the statement.
+ * For the role of official in charge files can be uploaded as consideration via the file drop component.
+ */
+
 @Component({
     selector: "app-statement-details-considerations",
     templateUrl: "./statement-details-considerations.component.html",
diff --git a/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.html b/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.html
index 928dde4..13b13bc 100644
--- a/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.html
+++ b/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.html
@@ -13,7 +13,7 @@
 
 <app-collapsible
   [(appCollapsed)]="appCollapsed"
-  [appTitle]="'Geographische Position'">
+  [appTitle]="'details.geoPositions.title' | translate">
 
   <div *ngIf="(geographicPosition$ | async) != null"
        class="map">
diff --git a/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.spec.ts b/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.spec.ts
index 9be77b7..6993752 100644
--- a/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.spec.ts
+++ b/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.spec.ts
@@ -14,9 +14,9 @@
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
 import {I18nModule} from "../../../../core/i18n";
-import {ILeafletBounds} from "../../../../shared/leaflet/directives/leaflet";
 import {openGisAction} from "../../../../store/geo/actions";
 import {userNameSelector} from "../../../../store/root/selectors";
+import {ILeafletBounds} from "../../../map/directives/leaflet";
 import {StatementDetailsModule} from "../../statement-details.module";
 import {StatementDetailsGeographicPositionComponent} from "./statement-details-geographic-position.component";
 
diff --git a/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.ts b/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.ts
index d249aaa..ccb1861 100644
--- a/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.ts
+++ b/src/app/features/details/components/geographic-position/statement-details-geographic-position.component.ts
@@ -14,8 +14,8 @@
 import {Component, Input} from "@angular/core";
 import {select, Store} from "@ngrx/store";
 import {take} from "rxjs/operators";
-import {ILeafletBounds} from "../../../../shared/leaflet";
 import {openGisAction, statementGeographicPositionSelector, userNameSelector} from "../../../../store";
+import {ILeafletBounds} from "../../../map";
 
 /**
  * This component displays the statements coordinates on a map using leaflet.
diff --git a/src/app/features/details/components/information/statement-details-information.component.html b/src/app/features/details/components/information/statement-details-information.component.html
index a94d7a7..57e27ac 100644
--- a/src/app/features/details/components/information/statement-details-information.component.html
+++ b/src/app/features/details/components/information/statement-details-information.component.html
@@ -13,7 +13,7 @@
 
 <app-collapsible
   [(appCollapsed)]="appCollapsed"
-  [appTitle]="'Allgemeine Informationen'">
+  [appTitle]="'details.information.title' | translate">
 
   <div class="information">
 
diff --git a/src/app/features/details/components/process-information/process-information/process-information.component.ts b/src/app/features/details/components/process-information/process-information/process-information.component.ts
index 15a449a..fd18582 100644
--- a/src/app/features/details/components/process-information/process-information/process-information.component.ts
+++ b/src/app/features/details/components/process-information/process-information/process-information.component.ts
@@ -14,6 +14,11 @@
 import {Component, Input} from "@angular/core";
 import {IProcessHistoryData} from "../process-history";
 
+/**
+ * This component displays the process diagram (bpmn) and marks the current state the statement is in.
+ * Also shows the process history as a table.
+ */
+
 @Component({
     selector: "app-process-information",
     templateUrl: "./process-information.component.html",
diff --git a/src/app/features/details/components/side-menu/statement-details-side-menu.component.spec.ts b/src/app/features/details/components/side-menu/statement-details-side-menu.component.spec.ts
index 1a5afd1..bde6e26 100644
--- a/src/app/features/details/components/side-menu/statement-details-side-menu.component.spec.ts
+++ b/src/app/features/details/components/side-menu/statement-details-side-menu.component.spec.ts
@@ -13,6 +13,8 @@
 
 import {SimpleChange} from "@angular/core";
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {TranslateService} from "@ngx-translate/core";
+import {take} from "rxjs/operators";
 import {EAPIProcessTaskDefinitionKey, EAPIUserRoles, I18nModule, IAPIProcessTask} from "../../../../core";
 import {EErrorCode} from "../../../../store/root/model";
 import {StatementDetailsModule} from "../../statement-details.module";
@@ -21,10 +23,13 @@
 describe("StatementDetailsSideMenuComponent", () => {
     let component: StatementDetailsSideMenuComponent;
     let fixture: ComponentFixture<StatementDetailsSideMenuComponent>;
+    let translateService: TranslateService;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [StatementDetailsModule, I18nModule]
+            imports: [
+                StatementDetailsModule, I18nModule
+            ]
         }).compileComponents();
     }));
 
@@ -32,6 +37,7 @@
         fixture = TestBed.createComponent(StatementDetailsSideMenuComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
+        translateService = TestBed.inject(TranslateService);
     });
 
     it("should create", () => {
@@ -80,8 +86,8 @@
         expect(component.buttonLayout.length).toEqual(0);
     });
 
-    it("should update info message based upon current user name and tasks", () => {
-        component.update();
+    it("should update info message based upon current user name and tasks", async () => {
+        await component.update();
         expect(component.buttonLayout).toEqual([]);
 
         component.appUserName = "hugo";
@@ -94,11 +100,13 @@
             authorized: true,
             assignee: "hugo"
         }];
-        component.update();
+        await component.update();
         expect(component.infoMessage).not.toBeDefined();
 
         component.appUserName = "noAssignee";
-        component.update();
-        expect(component.infoMessage).toEqual(EErrorCode.CLAIMED_BY_OTHER_USER);
+        await component.update();
+        const expectedInfoMessage = await translateService.get(EErrorCode.CLAIMED_BY_OTHER_USER, {user: "hugo"})
+            .pipe(take(1)).toPromise();
+        expect(component.infoMessage).toEqual(expectedInfoMessage);
     });
 });
diff --git a/src/app/features/details/components/side-menu/statement-details-side-menu.component.ts b/src/app/features/details/components/side-menu/statement-details-side-menu.component.ts
index 9988172..501197a 100644
--- a/src/app/features/details/components/side-menu/statement-details-side-menu.component.ts
+++ b/src/app/features/details/components/side-menu/statement-details-side-menu.component.ts
@@ -13,6 +13,7 @@
 
 import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core";
 import {Action} from "@ngrx/store";
+import {TranslateService} from "@ngx-translate/core";
 import {
     ALL_NON_TRIVIAL_USER_ROLES,
     EAPIProcessTaskDefinitionKey,
@@ -92,7 +93,7 @@
                     emit: this.emitClaimTaskFactory(),
                     label: "details.sideMenu.editInfoData",
                     icon: "subject",
-                    cssClass: "openk-success"
+                    cssClass: "openk-info"
                 }
             ],
             [EAPIProcessTaskDefinitionKey.CREATE_NEGATIVE_RESPONSE]: [
@@ -103,7 +104,7 @@
                     ),
                     label: "details.sideMenu.backToInfoData",
                     icon: "subject",
-                    cssClass: "openk-info"
+                    cssClass: "openk-danger"
                 },
                 {
                     emit: this.emitClaimTaskFactory(),
@@ -148,7 +149,8 @@
                 {
                     emit: (task) => this.appDispatch.emit(sendStatementViaMailAction({
                         statementId: task?.statementId,
-                        taskId: task?.taskId
+                        taskId: task?.taskId,
+                        assignee: task?.assignee
                     })),
                     label: "details.sideMenu.sendEmail",
                     icon: "send",
@@ -190,6 +192,11 @@
         }
     };
 
+    public constructor(
+        private translateService: TranslateService
+    ) {
+    }
+
     public ngOnChanges(changes: SimpleChanges) {
         const keys: Array<keyof StatementDetailsSideMenuComponent> = ["appUserRoles", "appTasks", "appUserName"];
         if (keys.some((_) => changes[_] != null)) {
@@ -197,7 +204,7 @@
         }
     }
 
-    public update() {
+    public async update() {
         let roles = filterDistinctValues(this.appUserRoles);
         roles = ALL_NON_TRIVIAL_USER_ROLES.filter((_) => roles.indexOf(_) > -1);
         const tasks = filterDistinctValues(this.appTasks);
@@ -207,13 +214,14 @@
                 .map((task) => this.getLayoutForRoleAndTask(role, task));
         });
         this.buttonLayout = arrayJoin(...arrayJoin(...actionsForRoles));
-        this.infoMessage = this.getInfoMessage();
+        this.infoMessage = await this.getInfoMessage();
     }
 
     private emitClaimAndCompleteFactory(variables: TCompleteTaskVariable, claimNext?: EAPIProcessTaskDefinitionKey) {
         return (task: IAPIProcessTask) => this.appDispatch.emit(claimAndCompleteTask({
             statementId: task?.statementId,
             taskId: task?.taskId,
+            assignee: task?.assignee,
             variables,
             claimNext
         }));
@@ -231,13 +239,12 @@
         return userActions == null ? [] : arrayJoin(userActions[task.taskDefinitionKey]).map((_) => ({..._, task}));
     }
 
-    private getInfoMessage(): string {
-        const isTaskClaimedByOtherUser = arrayJoin(this.appTasks)
-            .map((task) => task.assignee)
-            .some((assignee) => assignee != null && assignee !== this.appUserName);
+    private async getInfoMessage(): Promise<string> {
+        const assignees = arrayJoin(this.appTasks).map((task) => task.assignee);
+        const userTaskIsClaimedBy = assignees.some((assignee) => assignee != null && assignee !== this.appUserName) ? assignees[0] : null;
 
-        if (isTaskClaimedByOtherUser) {
-            return EErrorCode.CLAIMED_BY_OTHER_USER;
+        if (userTaskIsClaimedBy) {
+            return this.translateService.get(EErrorCode.CLAIMED_BY_OTHER_USER, {user: userTaskIsClaimedBy}).toPromise();
         }
 
         return;
diff --git a/src/app/features/details/components/statement-details/statement-details.component.html b/src/app/features/details/components/statement-details/statement-details.component.html
index 00a2a34..64f57aa 100644
--- a/src/app/features/details/components/statement-details/statement-details.component.html
+++ b/src/app/features/details/components/statement-details/statement-details.component.html
@@ -64,7 +64,7 @@
 
   <app-collapsible
     [appCollapsed]="false"
-    [appTitle]="'Prozessinformationen'"
+    [appTitle]="'details.processInformation.title' | translate"
     class="statement-details">
     <app-process-information
       [appCurrentActivities]="processCurrentActivityIds$ | async"
diff --git a/src/app/features/details/components/statement-details/statement-details.component.ts b/src/app/features/details/components/statement-details/statement-details.component.ts
index b43fe9c..693c8e6 100644
--- a/src/app/features/details/components/statement-details/statement-details.component.ts
+++ b/src/app/features/details/components/statement-details/statement-details.component.ts
@@ -38,6 +38,12 @@
     userRolesSelector
 } from "../../../../store";
 
+
+/**
+ * This component displays all information saved for the selected statement.
+ * Data cannot be edited. Only comments can be added to the statement.
+ */
+
 @Component({
     selector: "app-statement-details",
     templateUrl: "./statement-details.component.html",
diff --git a/src/app/features/details/directives/bpmn-directive.ts b/src/app/features/details/directives/bpmn-directive.ts
index 7c11c98..e3be829 100644
--- a/src/app/features/details/directives/bpmn-directive.ts
+++ b/src/app/features/details/directives/bpmn-directive.ts
@@ -14,6 +14,11 @@
 import {Directive, ElementRef, Input, NgZone, OnDestroy} from "@angular/core";
 import * as BpmnJS from "bpmn-js/dist/bpmn-navigated-viewer.production.min.js";
 
+
+/**
+ * This directive takes a bpmn diagram as xml input and attaches it to the dom node.
+ */
+
 @Directive({
     selector: "[appBpmn]"
 })
diff --git a/src/app/features/details/pipes/get-process-history-entries.pipe.spec.ts b/src/app/features/details/pipes/get-process-history-entries.pipe.spec.ts
new file mode 100644
index 0000000..9ae280b
--- /dev/null
+++ b/src/app/features/details/pipes/get-process-history-entries.pipe.spec.ts
@@ -0,0 +1,75 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+
+import {IAPIProcessActivity, IAPIStatementHistory} from "../../../core/api/process";
+import {GetProcessHistoryEntriesPipe} from "./get-process-history-entries.pipe";
+
+describe("GetProcessHistoryEntriesPipe", () => {
+
+    const pipe = new GetProcessHistoryEntriesPipe();
+
+    describe("transform", () => {
+
+        const history: IAPIStatementHistory = {
+            processName: "processName",
+            processVersion: 0,
+            finishedProcessActivities: [
+                {
+                    activityName: "activityName",
+                    activityType: "userTask",
+                    assignee: "assignee",
+                    endTime: "endTime",
+                    canceled: false
+                } as IAPIProcessActivity
+            ],
+            currentProcessActivities: [
+                {
+                    activityName: "activityName1",
+                    activityType: "serviceTask",
+                    assignee: "assignee1",
+                    endTime: "endTime1",
+                    canceled: true
+                } as IAPIProcessActivity
+            ]
+        };
+
+        it("should return empty array for invalid input", () => {
+            let result = pipe.transform(null);
+            expect(result).toEqual([]);
+            result = pipe.transform(undefined);
+            expect(result).toEqual([]);
+        });
+
+        it("should combine active and finished activities and returns a list of all user and service task as process history data", () => {
+            const result = pipe.transform(history);
+            expect(result).toEqual([
+                {
+                    icon: "account_circle",
+                    activityName: "activityName",
+                    assignee: "assignee",
+                    endTime: "endTime",
+                    cancelled: false
+                },
+                {
+                    icon: "group_work",
+                    activityName: "activityName1",
+                    assignee: "assignee1",
+                    endTime: "endTime1",
+                    cancelled: true
+                }
+            ]);
+        });
+    });
+});
+
diff --git a/src/app/features/details/pipes/get-process-history-entries.pipe.ts b/src/app/features/details/pipes/get-process-history-entries.pipe.ts
index c35f0cc..6a9ff64 100644
--- a/src/app/features/details/pipes/get-process-history-entries.pipe.ts
+++ b/src/app/features/details/pipes/get-process-history-entries.pipe.ts
@@ -16,6 +16,11 @@
 import {arrayJoin} from "../../../util/store";
 import {IProcessHistoryData} from "../components/process-information/process-history";
 
+
+/**
+ * From the process history data combines finished and ongoing tasks, then filters for only user and service tasks.
+ */
+
 @Pipe({name: "getProcessHistoryEntries"})
 export class GetProcessHistoryEntriesPipe implements PipeTransform {
 
diff --git a/src/app/features/details/statement-details.module.ts b/src/app/features/details/statement-details.module.ts
index f370e1b..1b726a7 100644
--- a/src/app/features/details/statement-details.module.ts
+++ b/src/app/features/details/statement-details.module.ts
@@ -25,11 +25,11 @@
 import {CollapsibleModule} from "../../shared/layout/collapsible";
 import {SideMenuModule} from "../../shared/layout/side-menu";
 import {StatementTableModule} from "../../shared/layout/statement-table";
-import {LeafletModule} from "../../shared/leaflet";
 import {SharedPipesModule} from "../../shared/pipes";
 import {AttachmentsFormModule} from "../forms/attachments";
 import {CommentsFormModule} from "../forms/comments";
 import {StatementInformationFormModule} from "../forms/statement-information";
+import {MapModule} from "../map";
 import {
     ProcessDiagramComponent,
     ProcessHistoryComponent,
@@ -64,7 +64,7 @@
         CommentsFormModule,
         SideMenuModule,
         ActionButtonModule,
-        LeafletModule,
+        MapModule,
         AttachmentsFormModule,
         StatementInformationFormModule,
         SelectModule,
diff --git a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.ts b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.ts
index 64d4f56..95f9969 100644
--- a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.ts
+++ b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.ts
@@ -29,6 +29,11 @@
 import {EStatementEditSites} from "../../model";
 import {statementEditSiteSelector} from "../../selectors";
 
+/**
+ * This component displays the corresponding edit page to the selected task. The task is selected via task id in the url parameters.
+ * The task definition key determines the edit page to display.
+ */
+
 @Component({
     selector: "app-statement-edit-portal",
     templateUrl: "./statement-edit-portal.component.html",
diff --git a/src/app/features/forms/attachments/components/attachments-form-group.component.ts b/src/app/features/forms/attachments/components/attachments-form-group.component.ts
index 5508298..f534592 100644
--- a/src/app/features/forms/attachments/components/attachments-form-group.component.ts
+++ b/src/app/features/forms/attachments/components/attachments-form-group.component.ts
@@ -36,6 +36,12 @@
 import {AbstractReactiveFormComponent} from "../../abstract";
 import {getMailAttachment, getMailAttachments} from "../util/mail-attachments.util";
 
+/**
+ * This form component shows the list of statement attachments. The attachments are split into two categories.
+ * Mail attachments are displayed separately from the normal attachments.
+ * New attachments can be uploaded via the file drop component. Displayed attachments can be selected and downloaded.
+ */
+
 @Component({
     selector: "app-attachments-form-group",
     templateUrl: "./attachments-form-group.component.html",
@@ -102,6 +108,9 @@
         this.attachments$.pipe(delay(0), takeUntil(this.destroy$))
             .subscribe((values) => this.setValueForArray(values, "edit"));
 
+        /**
+         * Reacts to changes on email and attachment data and sets the list of email attachments accordingly.
+         */
         combineLatest([this.selectedMail$, this.statementMail$, this.allAttachments$]).pipe(
             filter(([_, m, a]) => (_ != null || m != null) && a != null),
             delay(0),
@@ -112,7 +121,6 @@
             this.setMailAttachmentValues(attachments, arrayJoin(this.mail?.attachments));
         });
 
-
         this.fileCache$.pipe(delay(0), takeUntil(this.destroy$))
             .subscribe((values) => this.setValueForArray(values, "add"));
         this.store.dispatch(fetchAttachmentTagsAction());
diff --git a/src/app/features/forms/attachments/pipes/get-email-text-attachment.pipe.ts b/src/app/features/forms/attachments/pipes/get-email-text-attachment.pipe.ts
index 28beec6..50f6505 100644
--- a/src/app/features/forms/attachments/pipes/get-email-text-attachment.pipe.ts
+++ b/src/app/features/forms/attachments/pipes/get-email-text-attachment.pipe.ts
@@ -21,6 +21,11 @@
 })
 export class GetEmailTextAttachmentPipe implements PipeTransform {
 
+
+    /**
+     * Given the mail data and attachments list, gets the mail text attachment and replaces the name with the mail subject.
+     */
+
     public transform(attachments: IAPIAttachmentModel[], mail: IAPIEmailModel): IAPIAttachmentModel {
         const mailTextAttachment = getMailAttachment(attachments);
 
diff --git a/src/app/features/forms/comments/components/comments-form/comments-form.component.spec.ts b/src/app/features/forms/comments/components/comments-form/comments-form.component.spec.ts
index 07b491b..e74c432 100644
--- a/src/app/features/forms/comments/components/comments-form/comments-form.component.spec.ts
+++ b/src/app/features/forms/comments/components/comments-form/comments-form.component.spec.ts
@@ -15,10 +15,8 @@
 import {Store} from "@ngrx/store";
 import {provideMockStore} from "@ngrx/store/testing";
 import {take} from "rxjs/operators";
-import {I18nModule} from "../../../../../core/i18n";
-import {queryParamsIdSelector} from "../../../../../store/root/selectors";
-import {addCommentAction, deleteCommentAction} from "../../../../../store/statements/actions";
-import {statementCommentsSelector} from "../../../../../store/statements/selectors";
+import {ConfirmService, I18nModule} from "../../../../../core";
+import {addCommentAction, deleteCommentAction, queryParamsIdSelector, statementCommentsSelector} from "../../../../../store";
 import {CommentsFormModule} from "../../comments-form.module";
 import {CommentsFormComponent} from "./comments-form.component";
 
@@ -46,7 +44,8 @@
                             value: [{id: 1919} as any]
                         }
                     ]
-                })
+                }),
+                {provide: ConfirmService, useValue: {askForConfirmation: (message: string) => true}}
             ]
         }).compileComponents();
     }));
diff --git a/src/app/features/forms/comments/components/comments-form/comments-form.component.ts b/src/app/features/forms/comments/components/comments-form/comments-form.component.ts
index af647cd..6449e42 100644
--- a/src/app/features/forms/comments/components/comments-form/comments-form.component.ts
+++ b/src/app/features/forms/comments/components/comments-form/comments-form.component.ts
@@ -13,12 +13,19 @@
 
 import {Component, Input} from "@angular/core";
 import {select, Store} from "@ngrx/store";
+import {TranslateService} from "@ngx-translate/core";
 import {defer} from "rxjs";
 import {map, take} from "rxjs/operators";
-import {queryParamsIdSelector} from "../../../../../store/root/selectors";
-import {addCommentAction, deleteCommentAction} from "../../../../../store/statements/actions";
-import {statementCommentsSelector} from "../../../../../store/statements/selectors";
-import {arrayJoin} from "../../../../../util/store";
+import {ConfirmService} from "../../../../../core";
+import {addCommentAction, deleteCommentAction, queryParamsIdSelector, statementCommentsSelector} from "../../../../../store";
+import {arrayJoin} from "../../../../../util";
+
+
+/**
+ * This component displays all saved comments to the statement.
+ * Also makes it possible to input text for a new comment in a text field. New comment can be added to the statement.
+ * Comments can be deleted by the user that created the comment.
+ */
 
 @Component({
     selector: "app-comments-form",
@@ -41,7 +48,10 @@
         map((comments) => arrayJoin(comments).length)
     );
 
-    public constructor(public store: Store) {
+    public constructor(
+        public store: Store,
+        public confirmService: ConfirmService,
+        private translationService: TranslateService) {
 
     }
 
@@ -51,8 +61,11 @@
     }
 
     public async deleteComment(commentId: number) {
-        const statementId = await this.statementId$.pipe(take(1)).toPromise();
-        this.store.dispatch(deleteCommentAction({statementId, commentId}));
+        const confirmationText = await this.translationService.get("comments.confirmDelete").toPromise();
+        if (this.confirmService.askForConfirmation(confirmationText)) {
+            const statementId = await this.statementId$.pipe(take(1)).toPromise();
+            this.store.dispatch(deleteCommentAction({statementId, commentId}));
+        }
     }
 
 }
diff --git a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.html b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.html
index fa2ac29..a19e25c 100644
--- a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.html
+++ b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.html
@@ -44,25 +44,34 @@
   class="statement-details">
 </app-statement-details-attachments>
 
-<app-collapsible
-  [appTitle]="('statementEditorForm.container.contributionStatus' | translate)
+<ng-container *ngIf="(showContributions$ | async) === true">
+
+  <app-collapsible
+    *ngIf="(showContributionsControl$ | async) === true"
+    [appTitle]="('statementEditorForm.container.contributionStatus' | translate)
      + ' (' + (selectedContributionsCount$ | async) + '/' + (requiredContributionOptions$ | async)?.length + ')'"
-  [formGroup]="appFormGroup">
+    [formGroup]="appFormGroup">
 
-  <app-select-group
-    *ngIf="(showContributions$ | async) && (requiredContributionOptions$ | async)?.length > 0"
-    [appGroups]="requiredContributionGroups$ | async"
-    [appOptions]="requiredContributionOptions$ | async"
-    [formControlName]="'contributions'"
-    style="padding: 1em;">
-  </app-select-group>
+    <app-select-group
+      *ngIf="(requiredContributionOptions$ | async)?.length > 0"
+      [appGroups]="requiredContributionGroups$ | async"
+      [appOptions]="requiredContributionOptions$ | async"
+      [formControlName]="'contributions'"
+      style="padding: 1em;">
+    </app-select-group>
 
-  <div *ngIf="!((showContributions$ | async) && (requiredContributionOptions$ | async)?.length > 0)"
-       class="placeholder">
-    {{"statementEditorForm.contributions.placeholder" | translate}}
-  </div>
+    <div *ngIf="!((requiredContributionOptions$ | async)?.length > 0)"
+         class="placeholder">
+      {{"details.contributions.placeholder" | translate}}
+    </div>
+  </app-collapsible>
 
-</app-collapsible>
+  <app-statement-details-contributions
+    *ngIf="(showContributionsControl$ | async) === false"
+    class="statement-details">
+  </app-statement-details-contributions>
+
+</ng-container>
 
 <app-collapsible
   [appHeaderTemplateRef]="arrangementHeaderRef"
diff --git a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.scss b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.scss
index 29e28d3..dc6bcf8 100644
--- a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.scss
+++ b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.scss
@@ -17,6 +17,7 @@
   display: flex;
   flex-flow: column;
   max-width: 70em;
+  width: 100%;
   margin: 0 auto;
 
   & > * {
@@ -64,3 +65,8 @@
   height: initial;
   font-size: 1em;
 }
+
+.statement-details {
+  width: 100%;
+  max-width: 70em;
+}
diff --git a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.ts b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.ts
index 683b3b7..8701177 100644
--- a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.ts
+++ b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.ts
@@ -52,6 +52,14 @@
 import {arrayJoin, filterDistinctValues} from "../../../../../util";
 import {AbstractReactiveFormComponent} from "../../../abstract";
 
+/**
+ * This component displays information about the statement.
+ * Also gives the possibility to upload attachments to send as response. (outbox attachments)
+ * The component is shown for different task states.
+ * For the official in charge, when creating or checking the response, the current state of contributions is also shown and can be edited.
+ * The text content for the response can be edited by placing and filling the text blocks.
+ */
+
 @Component({
     selector: "app-statement-editor-form",
     templateUrl: "./statement-editor-form.component.html",
@@ -78,6 +86,13 @@
     public showContributions$ = defer(() => this.task$).pipe(
         map((task) => task?.taskDefinitionKey),
         map((taskDefinitionKey) => {
+            return taskDefinitionKey !== EAPIProcessTaskDefinitionKey.CREATE_NEGATIVE_RESPONSE;
+        })
+    );
+
+    public showContributionsControl$ = defer(() => this.task$).pipe(
+        map((task) => task?.taskDefinitionKey),
+        map((taskDefinitionKey) => {
             return taskDefinitionKey === EAPIProcessTaskDefinitionKey.CREATE_DRAFT
                 || taskDefinitionKey === EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE;
         })
@@ -181,7 +196,7 @@
             taskId: task.taskId,
             value: {
                 ...value,
-                contributions: await this.showContributions$.pipe(take(1)).toPromise() ? value.contributions : null
+                contributions: await this.showContributionsControl$.pipe(take(1)).toPromise() ? value.contributions : null
             },
             options
         }));
diff --git a/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.html b/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.html
index c10e5c2..72d726e 100644
--- a/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.html
+++ b/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.html
@@ -10,10 +10,11 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
+
 <app-text-block
-  (appButtonPress)="appRemove.emit(appValue.textblockId)"
+  (appDelete)="appRemove.emit(appValue.textblockId)"
   (appNewLine)="addNewLine()"
-  (appChangeText)="setReplacement($event)"
+  (appTextChange)="setReplacement($event)"
   (appTextInput)="convertToFreeText()"
   (appValueChange)="addPlaceholder($event)"
   [appTextBlockData]="appValue | getBlockDataFromArrangement: appTextBlockModel: appValue?.placeholderValues: appReplacements: appSelects"
@@ -21,6 +22,7 @@
   [appShowClose]="true"
   [appTitle]="appValue | getTitle"
   [appBlockText]="appValue?.replacement"
+  [appWithoutTitlePrefix]="appValue?.type !== 'block'"
   [appDisabled]="appDisabled"
   [appType]="appValue?.type"
   [class.disable]="appDisabled">
diff --git a/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.ts b/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.ts
index ad230af..a93ef9c 100644
--- a/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.ts
+++ b/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.ts
@@ -17,6 +17,11 @@
 import {IStatementEditorControlConfiguration} from "../../../../store/statements/model";
 import {arrayJoin} from "../../../../util/store";
 
+/**
+ * Converts arrangement configuration to text. Integrates the current values set for the select/input/date replacements into the textblocks
+ * texts to then be able to display them as text.
+ */
+
 @Pipe({
     name: "arrangementToPreview"
 })
diff --git a/src/app/features/forms/statement-editor/pipes/error-to-messages.pipe.ts b/src/app/features/forms/statement-editor/pipes/error-to-messages.pipe.ts
index 42bddf8..77db6cc 100644
--- a/src/app/features/forms/statement-editor/pipes/error-to-messages.pipe.ts
+++ b/src/app/features/forms/statement-editor/pipes/error-to-messages.pipe.ts
@@ -15,6 +15,10 @@
 import {IAPITextArrangementErrorModel} from "../../../../core/api/text";
 import {arrayJoin} from "../../../../util/store";
 
+/**
+ * For the given error model returns the error message for the most important error.
+ */
+
 @Pipe({
     name: "errorToMessages"
 })
diff --git a/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.ts b/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.ts
index cb7e40d..1dba1cc 100644
--- a/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.ts
+++ b/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.ts
@@ -47,6 +47,14 @@
 import {ExtractMailAddressPipe} from "../../../../mail/pipes/extract-mail-address.pipe";
 import {AbstractReactiveFormComponent} from "../../../abstract";
 
+/**
+ * This component shows the basic statement information. The data can be edited. A contact can be selected
+ * and previous statements can be linked to the current one.
+ * This page is also used for the creation of a new statement. If this page is opened with a valid mailid in the url parameters,
+ * values for title, dates and also contact will be prefilled.
+ * All mandatory values have to be set else an error will be displayed on submitting.
+ */
+
 @Component({
     selector: "app-statement-information-form",
     templateUrl: "./statement-information-form.component.html",
@@ -132,6 +140,10 @@
             this.clearErrors(true);
             await this.setInitialValue();
             this.store.dispatch(fetchSettingsAction());
+
+            /**
+             * If a new statement is to be created and a mailid is set, prefill values to values from the email. (subject, receipt date)
+             */
             if (this.mailId) {
                 await this.setEmailValues(mailId);
                 this.appFormGroup.markAllAsTouched();
@@ -229,6 +241,9 @@
         const task = await this.task$.pipe(take(1)).toPromise();
         this.store.dispatch(fetchEmailAction({mailId, statementId: this.appForNewStatement ? "new" : task?.statementId}));
 
+        /**
+         * As soon as the mail data has been fetched, set the form values.
+         */
         combineLatest([this.selectedMail$, this.statementMail$]).pipe(
             filter(([mail, statementMail]) => mail != null || statementMail != null),
             take(1),
diff --git a/src/app/features/forms/statement-information/pipes/sector.pipe.ts b/src/app/features/forms/statement-information/pipes/sector.pipe.ts
index 4ba7481..9a10f52 100644
--- a/src/app/features/forms/statement-information/pipes/sector.pipe.ts
+++ b/src/app/features/forms/statement-information/pipes/sector.pipe.ts
@@ -14,6 +14,10 @@
 import {Pipe, PipeTransform} from "@angular/core";
 import {IAPISectorsModel} from "../../../../core/api/statements/IAPISectorsModel";
 
+/**
+ * For given city and district, returns the available sectors.
+ */
+
 @Pipe({
     name: "sector"
 })
diff --git a/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.html b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.html
index 4d70c87..c79314f 100644
--- a/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.html
+++ b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.html
@@ -34,8 +34,8 @@
 
 <app-side-menu-status
   *appSideMenu="'center'"
-  [appLoadingMessage]="'core.submitting' | translate"
   [appErrorMessage]="appErrorMessage"
+  [appLoadingMessage]="'core.submitting' | translate"
   [appLoading]="appLoading">
 </app-side-menu-status>
 
diff --git a/src/app/features/forms/workflow-data/components/workflow-data-form.component.html b/src/app/features/forms/workflow-data/components/workflow-data-form.component.html
index d7c7f3b..cef9f29 100644
--- a/src/app/features/forms/workflow-data/components/workflow-data-form.component.html
+++ b/src/app/features/forms/workflow-data/components/workflow-data-form.component.html
@@ -14,8 +14,8 @@
 <app-workflow-data-side-menu
   (appSubmit)="submit($event)"
   [appDisabled]="appFormGroup.disabled"
-  [appLoading]="isStatementLoading$ | async"
   [appErrorMessage]="(appErrorMessage$ | async)?.errorMessage"
+  [appLoading]="isStatementLoading$ | async"
   [appStatementId]="(task$ | async)?.statementId">
 </app-workflow-data-side-menu>
 
@@ -39,7 +39,7 @@
 
     <app-map-select
       (appOpenGis)="openGis($event)"
-      [appCenter]="'leaflet.defaultView' | translate"
+      [appCenter]="geographicPosition$ | async"
       [formControlName]="'geographicPosition'"
       class="geographic-position">
     </app-map-select>
@@ -51,9 +51,9 @@
 
     <app-select-group
       [appGroups]="departmentGroups$ | async"
+      [appIndeterminate]="true"
       [appOptions]="departmentOptions$ | async"
       [formControlName]="'departments'"
-      [appIndeterminate]="true"
       class="departments">
     </app-select-group>
 
diff --git a/src/app/features/forms/workflow-data/components/workflow-data-form.component.spec.ts b/src/app/features/forms/workflow-data/components/workflow-data-form.component.spec.ts
index cedcdd3..0c70992 100644
--- a/src/app/features/forms/workflow-data/components/workflow-data-form.component.spec.ts
+++ b/src/app/features/forms/workflow-data/components/workflow-data-form.component.spec.ts
@@ -16,7 +16,6 @@
 import {RouterTestingModule} from "@angular/router/testing";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
 import {I18nModule, IAPISearchOptions} from "../../../../core";
-import {ILeafletBounds} from "../../../../shared/leaflet";
 import {
     IWorkflowFormValue,
     openGisAction,
@@ -25,6 +24,7 @@
     taskSelector,
     userNameSelector
 } from "../../../../store";
+import {ILeafletBounds} from "../../../map";
 import {WorkflowDataFormModule} from "../workflow-data-form.module";
 import {WorkflowDataFormComponent} from "./workflow-data-form.component";
 
diff --git a/src/app/features/forms/workflow-data/components/workflow-data-form.component.ts b/src/app/features/forms/workflow-data/components/workflow-data-form.component.ts
index e0b4122..9523676 100644
--- a/src/app/features/forms/workflow-data/components/workflow-data-form.component.ts
+++ b/src/app/features/forms/workflow-data/components/workflow-data-form.component.ts
@@ -16,7 +16,6 @@
 import {combineLatest, Observable, Subscription} from "rxjs";
 import {distinctUntilChanged, filter, take, takeUntil} from "rxjs/operators";
 import {APP_CONFIGURATION, IAPISearchOptions, IAppConfiguration} from "../../../../core";
-import {ILeafletBounds, latLngZoomToString} from "../../../../shared/leaflet";
 import {
     createWorkflowForm,
     departmentGroupsSelector,
@@ -31,6 +30,7 @@
     queryParamsIdSelector,
     setErrorAction,
     startStatementSearchAction,
+    statementGeographicPositionSelector,
     statementLoadingSelector,
     statementSelector,
     statementTypesSelector,
@@ -39,8 +39,14 @@
     userNameSelector,
     workflowFormValueSelector
 } from "../../../../store";
+import {ILeafletBounds, latLngZoomToString} from "../../../map";
 import {AbstractReactiveFormComponent} from "../../abstract";
 
+/**
+ * This component displays all the workflow information for the statement. (departments/geo-coordinates/linked statements)
+ * The coordinates can be set by selecting a point on a integrated map. Previous statements can be selected from a list to link them
+ * together. Departments from whom input is needed, can be made mandatory or optional.
+ */
 @Component({
     selector: "app-workflow-data-form",
     templateUrl: "./workflow-data-form.component.html",
@@ -66,15 +72,17 @@
 
     public appErrorMessage$: Observable<IStatementErrorEntity> = this.store.pipe(select(getStatementErrorSelector));
 
-    private form$ = this.store.pipe(select(workflowFormValueSelector));
-
     public statement$: Observable<IStatementEntity> = this.store.pipe(select(statementSelector));
 
     public userName$ = this.store.pipe(select(userNameSelector));
 
     public subscription: Subscription;
 
-    private defaultGeographicPosition = latLngZoomToString(this.configuration.leaflet, this.configuration.leaflet.zoom);
+    public defaultGeographicPosition = latLngZoomToString(this.configuration.leaflet, this.configuration.leaflet.zoom);
+
+    public geographicPosition$ = this.store.pipe(select(statementGeographicPositionSelector));
+
+    private form$ = this.store.pipe(select(workflowFormValueSelector));
 
     public constructor(public store: Store, @Inject(APP_CONFIGURATION) public configuration: IAppConfiguration) {
         super();
@@ -134,12 +142,9 @@
         this.isStatementLoading$.pipe(takeUntil(this.destroy$), distinctUntilChanged())
             .subscribe((loading) => loading ? this.appFormGroup.disable() : this.appFormGroup.enable());
         this.form$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
-            this.patchValue({
-                ...value,
-                geographicPosition: value.geographicPosition == null ? this.defaultGeographicPosition : value.geographicPosition,
-            });
+            const geographicPosition = value.geographicPosition == null ? this.defaultGeographicPosition : value.geographicPosition;
+            this.patchValue({...value, geographicPosition});
         });
     }
 
-
 }
diff --git a/src/app/features/forms/workflow-data/workflow-data-form.module.ts b/src/app/features/forms/workflow-data/workflow-data-form.module.ts
index b03fc0e..bb805db 100644
--- a/src/app/features/forms/workflow-data/workflow-data-form.module.ts
+++ b/src/app/features/forms/workflow-data/workflow-data-form.module.ts
@@ -16,13 +16,13 @@
 import {ReactiveFormsModule} from "@angular/forms";
 import {MatIconModule} from "@angular/material/icon";
 import {TranslateModule} from "@ngx-translate/core";
-import {MapSelectModule} from "../../../shared/controls/map-select/map-select.module";
 import {SelectModule} from "../../../shared/controls/select";
 import {StatementSelectModule} from "../../../shared/controls/statement-select";
 import {ActionButtonModule} from "../../../shared/layout/action-button";
 import {CollapsibleModule} from "../../../shared/layout/collapsible";
 import {SideMenuModule} from "../../../shared/layout/side-menu";
 import {StatementDetailsModule} from "../../details";
+import {MapModule} from "../../map";
 import {WorkflowDataFormComponent, WorkflowDataSideMenuComponent} from "./components";
 
 @NgModule({
@@ -37,7 +37,7 @@
         StatementSelectModule,
         SideMenuModule,
         ActionButtonModule,
-        MapSelectModule,
+        MapModule,
         StatementDetailsModule
     ],
     declarations: [
diff --git a/src/app/features/mail/components/mail-inbox/mail-inbox.component.html b/src/app/features/mail/components/mail-inbox/mail-inbox.component.html
index 54fad24..a413595 100644
--- a/src/app/features/mail/components/mail-inbox/mail-inbox.component.html
+++ b/src/app/features/mail/components/mail-inbox/mail-inbox.component.html
@@ -46,7 +46,7 @@
         {{"mails.from" | translate}}
       </span>
       <div class="email-inbox--list--element--sender--column">
-        <span *ngFor="let contact of (item.from | appSenderSplitNameMail)"
+        <span *ngFor="let contact of (item.from | appDivideSenderAndMail)"
               class="email-inbox--list--element--sender--text">
           {{contact}}
         </span>
diff --git a/src/app/features/mail/components/mail/mail.component.spec.ts b/src/app/features/mail/components/mail/mail.component.spec.ts
index 377a3cb..61f9d6c 100644
--- a/src/app/features/mail/components/mail/mail.component.spec.ts
+++ b/src/app/features/mail/components/mail/mail.component.spec.ts
@@ -14,6 +14,7 @@
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {RouterTestingModule} from "@angular/router/testing";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
+import {I18nModule} from "../../../../core";
 import {MailModule} from "../../mail.module";
 import {MailComponent} from "./mail.component";
 
@@ -26,7 +27,8 @@
         TestBed.configureTestingModule({
             imports: [
                 MailModule,
-                RouterTestingModule
+                RouterTestingModule,
+                I18nModule
             ],
             providers: [
                 provideMockStore()
diff --git a/src/app/features/mail/components/mail/mail.component.ts b/src/app/features/mail/components/mail/mail.component.ts
index 714793f..075da21 100644
--- a/src/app/features/mail/components/mail/mail.component.ts
+++ b/src/app/features/mail/components/mail/mail.component.ts
@@ -14,16 +14,27 @@
 import {Component, OnDestroy, OnInit} from "@angular/core";
 import {Router} from "@angular/router";
 import {select, Store} from "@ngrx/store";
+import {TranslateService} from "@ngx-translate/core";
 import {combineLatest, Subject} from "rxjs";
 import {distinctUntilChanged, filter, map, takeUntil, withLatestFrom} from "rxjs/operators";
+import {ConfirmService} from "../../../../core";
 import {
     deleteEmailFromInboxAction,
     downloadEmailAttachmentAction,
     fetchEmailAction,
-    fetchEmailInboxAction
-} from "../../../../store/mail/actions";
-import {getEmailInboxSelector, getEmailLoadingSelector, getSelectedEmailSelector} from "../../../../store/mail/selectors";
-import {queryParamsMailIdSelector} from "../../../../store/root/selectors";
+    fetchEmailInboxAction,
+    getEmailInboxSelector,
+    getEmailLoadingSelector,
+    getSelectedEmailSelector,
+    queryParamsMailIdSelector
+} from "../../../../store";
+
+/**
+ * This component displays the list of mails in the inbox.
+ * Upon loading the inbox the first time, the first mail is automatically selected.
+ * For the selected mail the mail details are displayed. That includes mail text, mail attachments, subject and sender information.
+ * The mails can be deleted. Attachments can be downloaded.
+ */
 
 @Component({
     selector: "app-mail",
@@ -44,7 +55,9 @@
 
     public constructor(
         public store: Store,
-        public readonly router: Router
+        public readonly router: Router,
+        private confirmService: ConfirmService,
+        private translationService: TranslateService
     ) {
 
     }
@@ -86,8 +99,11 @@
         this.store.dispatch(downloadEmailAttachmentAction({mailId, name}));
     }
 
-    public remove(mailId: string) {
-        this.store.dispatch(deleteEmailFromInboxAction({mailId}));
+    public async remove(mailId: string) {
+        const confirmationText = await this.translationService.get("mails.confirmDelete").toPromise();
+        if (this.confirmService.askForConfirmation(confirmationText)) {
+            this.store.dispatch(deleteEmailFromInboxAction({mailId}));
+        }
     }
 
     public selectMail(mailId: string) {
diff --git a/src/app/features/mail/mail.module.ts b/src/app/features/mail/mail.module.ts
index d03b4bc..56a65da 100644
--- a/src/app/features/mail/mail.module.ts
+++ b/src/app/features/mail/mail.module.ts
@@ -23,9 +23,9 @@
 import {SharedPipesModule} from "../../shared/pipes";
 import {ProgressSpinnerModule} from "../../shared/progress-spinner";
 import {MailComponent, MailDetailsComponent, MailInboxComponent} from "./components";
+import {DivideSenderAndMailPipe} from "./pipes/divide-sender-and-mail.pipe";
 import {EmailTextToArrayPipe} from "./pipes/email-text.pipe";
 import {ExtractMailAddressPipe} from "./pipes/extract-mail-address.pipe";
-import {SenderSplitNameMailPipe} from "./pipes/sender-split-name-mail.pipe";
 
 @NgModule({
     imports: [
@@ -45,7 +45,7 @@
         MailInboxComponent,
         MailDetailsComponent,
         EmailTextToArrayPipe,
-        SenderSplitNameMailPipe,
+        DivideSenderAndMailPipe,
         ExtractMailAddressPipe
     ],
     exports: [
diff --git a/src/app/features/mail/pipes/sender-split-name-mail.pipe.spec.ts b/src/app/features/mail/pipes/divide-sender-and-mail.pipe.spec.ts
similarity index 91%
rename from src/app/features/mail/pipes/sender-split-name-mail.pipe.spec.ts
rename to src/app/features/mail/pipes/divide-sender-and-mail.pipe.spec.ts
index e40668b..cd9eb95 100644
--- a/src/app/features/mail/pipes/sender-split-name-mail.pipe.spec.ts
+++ b/src/app/features/mail/pipes/divide-sender-and-mail.pipe.spec.ts
@@ -11,12 +11,11 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {DivideSenderAndMailPipe} from "./divide-sender-and-mail.pipe";
 
-import {SenderSplitNameMailPipe} from "./sender-split-name-mail.pipe";
+describe("DivideSenderAndMailPipe", () => {
 
-describe("SenderSplitNameMailPipe", () => {
-
-    const pipe = new SenderSplitNameMailPipe();
+    const pipe = new DivideSenderAndMailPipe();
 
     describe("transform", () => {
 
diff --git a/src/app/features/mail/pipes/sender-split-name-mail.pipe.ts b/src/app/features/mail/pipes/divide-sender-and-mail.pipe.ts
similarity index 76%
rename from src/app/features/mail/pipes/sender-split-name-mail.pipe.ts
rename to src/app/features/mail/pipes/divide-sender-and-mail.pipe.ts
index 3cfb33d..97b67b7 100644
--- a/src/app/features/mail/pipes/sender-split-name-mail.pipe.ts
+++ b/src/app/features/mail/pipes/divide-sender-and-mail.pipe.ts
@@ -14,14 +14,19 @@
 import {Pipe, PipeTransform} from "@angular/core";
 import {arrayJoin} from "../../../util/store";
 
+/**
+ * From given string with format: <name e@mail.com>
+ * removes the brackets and returns name and email as separate strings in an array:
+ * ["name", "e@mail.com"]
+ */
+
 @Pipe({
-    name: "appSenderSplitNameMail"
+    name: "appDivideSenderAndMail"
 })
-export class SenderSplitNameMailPipe implements PipeTransform {
+export class DivideSenderAndMailPipe implements PipeTransform {
 
     public transform(text: string): Array<string> {
         const textAsArray = arrayJoin(text?.split(/(?=<)/g));
         return [textAsArray[0], textAsArray.slice(1).join("")].filter(x => x);
-        // return text.split(/(?=<)(.+)/);
     }
 }
diff --git a/src/app/features/mail/pipes/email-text.pipe.ts b/src/app/features/mail/pipes/email-text.pipe.ts
index f96f557..718dc80 100644
--- a/src/app/features/mail/pipes/email-text.pipe.ts
+++ b/src/app/features/mail/pipes/email-text.pipe.ts
@@ -13,6 +13,10 @@
 
 import {Pipe, PipeTransform} from "@angular/core";
 
+/**
+ * Removes all new lines (\n( from the input string and returns an array split by new lines.
+ */
+
 @Pipe({
     name: "appEmailTextToArray"
 })
diff --git a/src/app/features/mail/pipes/extract-mail-address.pipe.ts b/src/app/features/mail/pipes/extract-mail-address.pipe.ts
index 07031e0..af5ebe4 100644
--- a/src/app/features/mail/pipes/extract-mail-address.pipe.ts
+++ b/src/app/features/mail/pipes/extract-mail-address.pipe.ts
@@ -14,6 +14,11 @@
 import {Pipe, PipeTransform} from "@angular/core";
 import {arrayJoin} from "../../../util/store";
 
+/**
+ * From given string with format: <name e@mail.com>
+ * returns only the email: "e@mail.com"
+ */
+
 @Pipe({
     name: "appExtractMailAddress"
 })
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/map/components/index.ts
similarity index 90%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/map/components/index.ts
index 990bb42..7407c30 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/map/components/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./map-select";
+export * from "./leaflet";
diff --git a/src/app/shared/leaflet/components/index.ts b/src/app/features/map/components/leaflet/index.ts
similarity index 100%
rename from src/app/shared/leaflet/components/index.ts
rename to src/app/features/map/components/leaflet/index.ts
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.html b/src/app/features/map/components/leaflet/leaflet-map.component.html
similarity index 65%
rename from src/app/shared/leaflet/components/leaflet-map.component.html
rename to src/app/features/map/components/leaflet/leaflet-map.component.html
index d71960e..c06e19e 100644
--- a/src/app/shared/leaflet/components/leaflet-map.component.html
+++ b/src/app/features/map/components/leaflet/leaflet-map.component.html
@@ -13,7 +13,7 @@
 
 <div class="map">
   <div #appLeaflet="appLeaflet"
-       (appClick)="appClick.emit($event)"
+       (appClick)="appClick.emit($event )"
        (appPopupClose)="appPopupClose.emit($event)"
        (appLatLngZoomChange)="appCenterChange.emit($event)"
        [appCenter]="appCenter | stringToLatLngZoom"
@@ -25,6 +25,22 @@
 
   </div>
 
+  <div class="map--search">
+    <input #inputElement
+           (keydown.enter)="search(inputElement.value);"
+           [placeholder]="'search.placeHolder' | translate"
+           class="openk-input map--search--input"/>
+
+    <button (click)="search(inputElement.value);"
+            *ngIf="(appIsLoading$ | async) !== true"
+            class="openk-button openk-info map--search--btn">
+      <mat-icon class="map--search--icon">search</mat-icon>
+    </button>
+
+    <app-progress-spinner *ngIf="(appIsLoading$ | async) === true" class="progress-spinner">
+    </app-progress-spinner>
+  </div>
+
   <app-action-button
     (appClick)="appOpenGis.emit(appLeaflet.getBounds())"
     [appIcon]="'my_location'"
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.scss b/src/app/features/map/components/leaflet/leaflet-map.component.scss
similarity index 72%
rename from src/app/shared/leaflet/components/leaflet-map.component.scss
rename to src/app/features/map/components/leaflet/leaflet-map.component.scss
index e8e1a36..4a86137 100644
--- a/src/app/shared/leaflet/components/leaflet-map.component.scss
+++ b/src/app/features/map/components/leaflet/leaflet-map.component.scss
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
+@import "src/styles/openk.styles";
 
 :host {
   width: 100%;
@@ -45,9 +45,37 @@
   z-index: 1000;
 }
 
+.map--search {
+  display: flex;
+  width: fit-content;
+  height: fit-content;
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  z-index: 1000;
+  align-items: center;
+}
+
 .sub-caption {
   color: $openk-form-border;
   margin-left: auto;
   font-size: smaller;
   font-style: italic;
 }
+
+.progress-spinner {
+  padding: 0 0.0275em;
+  transition: opacity 75ms ease-out;
+}
+
+.map--search--input {
+  margin-right: 0.25em;
+}
+
+.map--search--btn {
+  padding: 0.175em;
+}
+
+.map--search--icon {
+  padding: 0;
+}
diff --git a/src/app/features/map/components/leaflet/leaflet-map.component.spec.ts b/src/app/features/map/components/leaflet/leaflet-map.component.spec.ts
new file mode 100644
index 0000000..7a87ff9
--- /dev/null
+++ b/src/app/features/map/components/leaflet/leaflet-map.component.spec.ts
@@ -0,0 +1,70 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {MockStore, provideMockStore} from "@ngrx/store/testing";
+import {I18nModule, IAPINominatimSearchResult} from "../../../../core";
+import {getNominatimResponseContentSelector, searchMapAction} from "../../../../store";
+import {MapModule} from "../../map.module";
+import {latLngZoomToString} from "../../util";
+import {LeafletMapComponent} from "./leaflet-map.component";
+
+describe("LeafletMapComponent", () => {
+    let component: LeafletMapComponent;
+    let fixture: ComponentFixture<LeafletMapComponent>;
+    let store: MockStore;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [MapModule, I18nModule],
+            providers: [provideMockStore({initialState: {geo: {loading: false, responseContent: null}}})]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(LeafletMapComponent);
+        component = fixture.componentInstance;
+        store = TestBed.inject(MockStore);
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should dispatch searchMapAction on search", async () => {
+        const dispatchSpy = spyOn(store, "dispatch");
+        await component.search("");
+        expect(dispatchSpy).not.toHaveBeenCalled();
+        await component.search(null);
+        expect(dispatchSpy).not.toHaveBeenCalled();
+        await component.search("test");
+        expect(dispatchSpy).toHaveBeenCalledWith(searchMapAction({q: "test"}));
+    });
+
+    it("should emit appViewChanged when search response is changed", () => {
+        const lat = 52.520008;
+        const lng = 13.404954;
+        const searchResultEntry: IAPINominatimSearchResult = {
+            ...{} as IAPINominatimSearchResult,
+            lat: "" + lat,
+            lon: "" + lng
+        };
+        store.overrideSelector(getNominatimResponseContentSelector, [searchResultEntry]);
+        const searchSpy = spyOn(component.appSearch, "emit");
+        store.refreshState();
+        const zoom = component.getZoom();
+        expect(searchSpy).toHaveBeenCalledWith(latLngZoomToString({lat, lng}, zoom));
+    });
+
+});
diff --git a/src/app/features/map/components/leaflet/leaflet-map.component.ts b/src/app/features/map/components/leaflet/leaflet-map.component.ts
new file mode 100644
index 0000000..58c5c5f
--- /dev/null
+++ b/src/app/features/map/components/leaflet/leaflet-map.component.ts
@@ -0,0 +1,107 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, EventEmitter, forwardRef, Input, NgZone, OnDestroy, OnInit, Output, ViewChild} from "@angular/core";
+import {select, Store} from "@ngrx/store";
+import {LeafletMouseEvent, PopupEvent} from "leaflet";
+import {Subject} from "rxjs";
+import {filter, map, skip, takeUntil} from "rxjs/operators";
+import {getNominatimLoadingSelector, getNominatimSearchResultSelector, searchMapAction} from "../../../../store";
+import {ILeafletBounds, LeafletDirective, LeafletHandler} from "../../directives";
+import {latLngZoomToString, stringToLatLngZoom} from "../../util";
+
+@Component({
+    selector: "app-leaflet-map",
+    templateUrl: "./leaflet-map.component.html",
+    styleUrls: ["./leaflet-map.component.scss"],
+    providers: [
+        {
+            provide: LeafletHandler,
+            useExisting: forwardRef(() => LeafletMapComponent)
+        }
+    ]
+})
+export class LeafletMapComponent extends LeafletHandler implements OnInit, OnDestroy {
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appCenter: string;
+
+    @Output()
+    public appClick = new EventEmitter<LeafletMouseEvent>();
+
+    @Output()
+    public appPopupClose = new EventEmitter<PopupEvent>();
+
+    @Output()
+    public appCenterChange = new EventEmitter<string>();
+
+    @Input()
+    public appSubCaption: string;
+
+    @Output()
+    public appOpenGis = new EventEmitter<ILeafletBounds>();
+
+    @Output()
+    public appSearch = new EventEmitter<string>();
+
+    public appIsLoading$ = this.store.pipe(select(getNominatimLoadingSelector));
+
+    public searchResult$ = this.store.pipe(select(getNominatimSearchResultSelector));
+
+    private destroy$ = new Subject();
+
+    @ViewChild(LeafletDirective, {static: true})
+    public leafletDirective: LeafletDirective;
+
+    public constructor(
+        public readonly ngZone: NgZone,
+        private readonly store: Store
+    ) {
+        super();
+    }
+
+    public get instance() {
+        return this.leafletDirective.instance;
+    }
+
+    public getZoom() {
+        return this.leafletDirective.getZoom();
+    }
+
+    public ngOnInit() {
+        this.searchResult$.pipe(
+            skip(1),
+            filter((center) => center != null),
+            map((center) => latLngZoomToString(center, this.leafletDirective.getZoom())),
+            takeUntil(this.destroy$)
+        ).subscribe((result) => {
+            this.appCenter = result;
+            this.appSearch.emit(result);
+            this.leafletDirective.appCenter = stringToLatLngZoom(result);
+        });
+    }
+
+    public async ngOnDestroy() {
+        this.destroy$.next();
+        this.destroy$.complete();
+    }
+
+    public search(search: string) {
+        if (search) {
+            this.store.dispatch(searchMapAction({q: search}));
+        }
+    }
+}
diff --git a/src/app/shared/controls/map-select/components/index.ts b/src/app/features/map/components/map-select/index.ts
similarity index 100%
rename from src/app/shared/controls/map-select/components/index.ts
rename to src/app/features/map/components/map-select/index.ts
diff --git a/src/app/shared/controls/map-select/components/map-select.component.html b/src/app/features/map/components/map-select/map-select.component.html
similarity index 81%
rename from src/app/shared/controls/map-select/components/map-select.component.html
rename to src/app/features/map/components/map-select/map-select.component.html
index 21b71a7..64a610c 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.html
+++ b/src/app/features/map/components/map-select/map-select.component.html
@@ -12,14 +12,16 @@
  -------------------------------------------------------------------------------->
 
 <app-leaflet-map
-  (appCenterChange)="select($event)"
+  #map
+  (appClick)="select($event, map.getZoom())"
   (appOpenGis)="appOpenGis.emit($event)"
-  [appCenter]="appValue"
+  (appSearch)="writeValue($event, true)"
+  [appCenter]="appCenter"
   [appDisabled]="appDisabled"
   [appSubCaption]="appSubCaption"
   class="map">
 
-  <ng-container appLeafletCenterMarker>
+  <ng-container [appLeafletMarker]="appValue">
   </ng-container>
 
 </app-leaflet-map>
diff --git a/src/app/shared/controls/map-select/components/map-select.component.scss b/src/app/features/map/components/map-select/map-select.component.scss
similarity index 94%
rename from src/app/shared/controls/map-select/components/map-select.component.scss
rename to src/app/features/map/components/map-select/map-select.component.scss
index 7e59f1f..edc4cc5 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.scss
+++ b/src/app/features/map/components/map-select/map-select.component.scss
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
+@import "src/styles/openk.styles";
 
 :host {
   display: block;
diff --git a/src/app/shared/controls/map-select/components/map-select.component.spec.ts b/src/app/features/map/components/map-select/map-select.component.spec.ts
similarity index 62%
rename from src/app/shared/controls/map-select/components/map-select.component.spec.ts
rename to src/app/features/map/components/map-select/map-select.component.spec.ts
index dd5e766..91bcbf2 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.spec.ts
+++ b/src/app/features/map/components/map-select/map-select.component.spec.ts
@@ -12,8 +12,10 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {provideMockStore} from "@ngrx/store/testing";
+import {LatLng, LeafletMouseEvent} from "leaflet";
 import {I18nModule} from "../../../../core";
-import {MapSelectModule} from "../map-select.module";
+import {MapModule} from "../../map.module";
 import {MapSelectComponent} from "./map-select.component";
 
 describe("MapSelectComponent", () => {
@@ -22,7 +24,8 @@
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [MapSelectModule, I18nModule]
+            imports: [MapModule, I18nModule],
+            providers: [provideMockStore()]
         }).compileComponents();
     }));
 
@@ -33,16 +36,19 @@
     });
 
     it("should select a new value", () => {
-        const value = "52.520008,13.404954,11";
-        const valueEmitSpy = spyOn(component.appValueChange, "emit");
-        const onChangeSpy = spyOn(component, "onChange");
-        const onTouchSpy = spyOn(component, "onTouch");
+        const changeSpy = spyOn(component.appValueChange, "emit");
+        const mouseEvent: LeafletMouseEvent = {
+            latlng: {
+                lat: 52.520008,
+                lng: 13.404954
+            } as LatLng
+        } as unknown as LeafletMouseEvent;
 
-        component.select(value);
+        component.select(mouseEvent, 11);
 
-        expect(valueEmitSpy).toHaveBeenCalledWith(value);
-        expect(onChangeSpy).toHaveBeenCalledWith(value);
-        expect(onTouchSpy).toHaveBeenCalledWith();
+        const expectedValue = "52.520008,13.404954,11";
+        expect(component.appValue).toEqual(expectedValue);
+        expect(changeSpy).toHaveBeenCalledWith(expectedValue);
     });
 
 });
diff --git a/src/app/shared/controls/map-select/components/map-select.component.ts b/src/app/features/map/components/map-select/map-select.component.ts
similarity index 73%
rename from src/app/shared/controls/map-select/components/map-select.component.ts
rename to src/app/features/map/components/map-select/map-select.component.ts
index 9c8759d..1f81674 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.ts
+++ b/src/app/features/map/components/map-select/map-select.component.ts
@@ -13,8 +13,10 @@
 
 import {Component, EventEmitter, forwardRef, Input, Output} from "@angular/core";
 import {NG_VALUE_ACCESSOR} from "@angular/forms";
-import {ILeafletBounds} from "../../../leaflet";
-import {AbstractControlValueAccessorComponent} from "../../common";
+import {LeafletMouseEvent} from "leaflet";
+import {AbstractControlValueAccessorComponent} from "../../../../shared/controls/common";
+import {ILeafletBounds} from "../../directives/leaflet";
+import {latLngZoomToString} from "../../util";
 
 @Component({
     selector: "app-map-select",
@@ -39,12 +41,11 @@
     @Output()
     public appOpenGis = new EventEmitter<ILeafletBounds>();
 
-    public select(value: string) {
-        // Note that this.appValue should not be changed here:
-        // Changing it here can create an infinite loop in which the map position changes without any user input.
-        this.onChange(value);
-        this.onTouch();
-        this.appValueChange.emit(value);
+    public select(clickEvent: LeafletMouseEvent, zoom: number) {
+        const position = latLngZoomToString(clickEvent.latlng, zoom);
+        if (position) {
+            this.writeValue(position, true);
+        }
     }
 
 }
diff --git a/src/app/shared/leaflet/directives/index.ts b/src/app/features/map/directives/index.ts
similarity index 94%
rename from src/app/shared/leaflet/directives/index.ts
rename to src/app/features/map/directives/index.ts
index d2be228..770e43d 100644
--- a/src/app/shared/leaflet/directives/index.ts
+++ b/src/app/features/map/directives/index.ts
@@ -11,7 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./center-marker";
 export * from "./leaflet";
 export * from "./marker";
 export * from "./popup";
diff --git a/src/app/shared/leaflet/directives/leaflet/LeafletHandler.ts b/src/app/features/map/directives/leaflet/LeafletHandler.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/leaflet/LeafletHandler.ts
rename to src/app/features/map/directives/leaflet/LeafletHandler.ts
diff --git a/src/app/shared/leaflet/directives/leaflet/index.ts b/src/app/features/map/directives/leaflet/index.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/leaflet/index.ts
rename to src/app/features/map/directives/leaflet/index.ts
diff --git a/src/app/shared/leaflet/directives/leaflet/leaflet.directive.spec.ts b/src/app/features/map/directives/leaflet/leaflet.directive.spec.ts
similarity index 97%
rename from src/app/shared/leaflet/directives/leaflet/leaflet.directive.spec.ts
rename to src/app/features/map/directives/leaflet/leaflet.directive.spec.ts
index ddf71fc..ba27e40 100644
--- a/src/app/shared/leaflet/directives/leaflet/leaflet.directive.spec.ts
+++ b/src/app/features/map/directives/leaflet/leaflet.directive.spec.ts
@@ -16,7 +16,7 @@
 import {LatLngLiteral} from "leaflet";
 import {Subject, Subscription} from "rxjs";
 import {LEAFLET_RESIZE_TOKEN} from "../../leaflet-configuration.token";
-import {LeafletModule} from "../../leaflet.module";
+import {MapModule} from "../../map.module";
 import {LeafletDirective} from "./leaflet.directive";
 
 describe("LeafletDirective", () => {
@@ -27,7 +27,7 @@
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [LeafletModule],
+            imports: [MapModule],
             declarations: [LeafletSpecComponent],
             providers: [
                 {
diff --git a/src/app/shared/leaflet/directives/leaflet/leaflet.directive.ts b/src/app/features/map/directives/leaflet/leaflet.directive.ts
similarity index 96%
rename from src/app/shared/leaflet/directives/leaflet/leaflet.directive.ts
rename to src/app/features/map/directives/leaflet/leaflet.directive.ts
index bbcc73d..8e30601 100644
--- a/src/app/shared/leaflet/directives/leaflet/leaflet.directive.ts
+++ b/src/app/features/map/directives/leaflet/leaflet.directive.ts
@@ -112,10 +112,14 @@
         this.ngZone.runOutsideAngular(() => this.instance.remove());
     }
 
+    public getZoom(): number {
+        return this.ngZone.runOutsideAngular(() => this.instance.getZoom());
+    }
+
     public getBounds(): ILeafletBounds {
         return this.ngZone.runOutsideAngular(() => {
             const bounds = this.instance.getBounds();
-            const zoom = this.instance.getZoom();
+            const zoom = this.getZoom();
             const result: ILeafletBounds = {
                 northWest: bounds.getNorthWest(),
                 northEast: bounds.getNorthEast(),
diff --git a/src/app/shared/leaflet/directives/marker/abstract-leaflet-marker.directive.ts b/src/app/features/map/directives/marker/abstract-leaflet-marker.directive.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/marker/abstract-leaflet-marker.directive.ts
rename to src/app/features/map/directives/marker/abstract-leaflet-marker.directive.ts
diff --git a/src/app/shared/leaflet/directives/marker/index.ts b/src/app/features/map/directives/marker/index.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/marker/index.ts
rename to src/app/features/map/directives/marker/index.ts
diff --git a/src/app/shared/leaflet/directives/marker/leaflet-marker.directive.spec.ts b/src/app/features/map/directives/marker/leaflet-marker.directive.spec.ts
similarity index 97%
rename from src/app/shared/leaflet/directives/marker/leaflet-marker.directive.spec.ts
rename to src/app/features/map/directives/marker/leaflet-marker.directive.spec.ts
index 24f654f..9faa2ea 100644
--- a/src/app/shared/leaflet/directives/marker/leaflet-marker.directive.spec.ts
+++ b/src/app/features/map/directives/marker/leaflet-marker.directive.spec.ts
@@ -15,7 +15,7 @@
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {LatLngLiteral} from "leaflet";
 import {Subject, Subscription} from "rxjs";
-import {LeafletModule} from "../../leaflet.module";
+import {MapModule} from "../../map.module";
 import {LeafletMarkerDirective} from "./leaflet-marker.directive";
 
 describe("LeafletMarkerDirective", () => {
@@ -31,7 +31,7 @@
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [LeafletModule],
+            imports: [MapModule],
             declarations: [LeafletMarkerSpecComponent]
         }).compileComponents();
     }));
diff --git a/src/app/shared/leaflet/directives/marker/leaflet-marker.directive.ts b/src/app/features/map/directives/marker/leaflet-marker.directive.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/marker/leaflet-marker.directive.ts
rename to src/app/features/map/directives/marker/leaflet-marker.directive.ts
diff --git a/src/app/shared/leaflet/directives/popup/index.ts b/src/app/features/map/directives/popup/index.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/popup/index.ts
rename to src/app/features/map/directives/popup/index.ts
diff --git a/src/app/shared/leaflet/directives/popup/leaflet-popup.directive.spec.ts b/src/app/features/map/directives/popup/leaflet-popup.directive.spec.ts
similarity index 82%
rename from src/app/shared/leaflet/directives/popup/leaflet-popup.directive.spec.ts
rename to src/app/features/map/directives/popup/leaflet-popup.directive.spec.ts
index fc8d984..e5343a8 100644
--- a/src/app/shared/leaflet/directives/popup/leaflet-popup.directive.spec.ts
+++ b/src/app/features/map/directives/popup/leaflet-popup.directive.spec.ts
@@ -11,22 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
 import {Component, ViewChild} from "@angular/core";
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {LeafletModule} from "../../leaflet.module";
+import {MapModule} from "../../map.module";
 import {LeafletPopupDirective} from "./leaflet-popup.directive";
 
 describe("LeafletPopupDirective", () => {
@@ -39,7 +26,7 @@
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [LeafletModule],
+            imports: [MapModule],
             declarations: [LeafletPopupSpecComponent]
         }).compileComponents();
     }));
diff --git a/src/app/shared/leaflet/directives/popup/leaflet-popup.directive.ts b/src/app/features/map/directives/popup/leaflet-popup.directive.ts
similarity index 100%
rename from src/app/shared/leaflet/directives/popup/leaflet-popup.directive.ts
rename to src/app/features/map/directives/popup/leaflet-popup.directive.ts
diff --git a/src/app/shared/leaflet/index.ts b/src/app/features/map/index.ts
similarity index 91%
rename from src/app/shared/leaflet/index.ts
rename to src/app/features/map/index.ts
index 849c149..e122506 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/features/map/index.ts
@@ -11,9 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./components";
 export * from "./directives";
 export * from "./pipes";
 export * from "./util";
 
-export * from "./leaflet.module";
+export * from "./map.module";
 export * from "./leaflet-configuration.token";
diff --git a/src/app/shared/leaflet/leaflet-configuration.token.ts b/src/app/features/map/leaflet-configuration.token.ts
similarity index 100%
rename from src/app/shared/leaflet/leaflet-configuration.token.ts
rename to src/app/features/map/leaflet-configuration.token.ts
diff --git a/src/app/shared/leaflet/leaflet.module.ts b/src/app/features/map/map.module.ts
similarity index 65%
rename from src/app/shared/leaflet/leaflet.module.ts
rename to src/app/features/map/map.module.ts
index 5625400..4f3e261 100644
--- a/src/app/shared/leaflet/leaflet.module.ts
+++ b/src/app/features/map/map.module.ts
@@ -13,12 +13,17 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
+import {MatButtonModule} from "@angular/material/button";
+import {MatIconModule} from "@angular/material/icon";
 import {TranslateModule} from "@ngx-translate/core";
 import {APP_CONFIGURATION, IAppConfiguration} from "../../core/configuration";
-import {ActionButtonModule} from "../layout/action-button";
-import {SideMenuRegistrationService} from "../layout/side-menu/services";
-import {LeafletMapComponent} from "./components";
-import {LeafletCenterMarkerDirective, LeafletDirective, LeafletMarkerDirective, LeafletPopupDirective} from "./directives";
+import {ActionButtonModule} from "../../shared/layout/action-button";
+import {SearchbarModule} from "../../shared/layout/searchbar";
+import {SideMenuRegistrationService} from "../../shared/layout/side-menu/services";
+import {ProgressSpinnerModule} from "../../shared/progress-spinner";
+import {LeafletMapComponent} from "./components/leaflet";
+import {MapSelectComponent} from "./components/map-select";
+import {LeafletDirective, LeafletMarkerDirective, LeafletPopupDirective} from "./directives";
 import {LEAFLET_CONFIGURATION_TOKEN, LEAFLET_RESIZE_TOKEN} from "./leaflet-configuration.token";
 import {StringToLatLngZoomPipe} from "./pipes";
 
@@ -26,23 +31,27 @@
     imports: [
         CommonModule,
         ActionButtonModule,
-        TranslateModule
+        TranslateModule,
+        SearchbarModule,
+        ProgressSpinnerModule,
+        MatButtonModule,
+        MatIconModule
     ],
     declarations: [
-        LeafletCenterMarkerDirective,
         LeafletDirective,
         LeafletMarkerDirective,
         LeafletPopupDirective,
         LeafletMapComponent,
-        StringToLatLngZoomPipe
+        StringToLatLngZoomPipe,
+        MapSelectComponent
     ],
     exports: [
-        LeafletCenterMarkerDirective,
         LeafletDirective,
         LeafletMarkerDirective,
         LeafletPopupDirective,
         LeafletMapComponent,
-        StringToLatLngZoomPipe
+        StringToLatLngZoomPipe,
+        MapSelectComponent
     ],
     providers: [
         {
@@ -57,6 +66,6 @@
         }
     ]
 })
-export class LeafletModule {
+export class MapModule {
 
 }
diff --git a/src/app/shared/leaflet/pipes/index.ts b/src/app/features/map/pipes/index.ts
similarity index 100%
rename from src/app/shared/leaflet/pipes/index.ts
rename to src/app/features/map/pipes/index.ts
diff --git a/src/app/shared/leaflet/pipes/string-to-lat-lng-zoom.pipe.spec.ts b/src/app/features/map/pipes/string-to-lat-lng-zoom.pipe.spec.ts
similarity index 100%
rename from src/app/shared/leaflet/pipes/string-to-lat-lng-zoom.pipe.spec.ts
rename to src/app/features/map/pipes/string-to-lat-lng-zoom.pipe.spec.ts
diff --git a/src/app/shared/leaflet/pipes/string-to-lat-lng-zoom.pipe.ts b/src/app/features/map/pipes/string-to-lat-lng-zoom.pipe.ts
similarity index 100%
rename from src/app/shared/leaflet/pipes/string-to-lat-lng-zoom.pipe.ts
rename to src/app/features/map/pipes/string-to-lat-lng-zoom.pipe.ts
diff --git a/src/app/shared/leaflet/util/index.ts b/src/app/features/map/util/index.ts
similarity index 100%
rename from src/app/shared/leaflet/util/index.ts
rename to src/app/features/map/util/index.ts
diff --git a/src/app/shared/leaflet/util/leaflet.util.spec.ts b/src/app/features/map/util/leaflet.util.spec.ts
similarity index 100%
rename from src/app/shared/leaflet/util/leaflet.util.spec.ts
rename to src/app/features/map/util/leaflet.util.spec.ts
diff --git a/src/app/shared/leaflet/util/leaflet.util.ts b/src/app/features/map/util/leaflet.util.ts
similarity index 100%
rename from src/app/shared/leaflet/util/leaflet.util.ts
rename to src/app/features/map/util/leaflet.util.ts
diff --git a/src/app/features/navigation/components/nav-header/nav-header.component.html b/src/app/features/navigation/components/nav-header/nav-header.component.html
index 00c9237..238dca5 100644
--- a/src/app/features/navigation/components/nav-header/nav-header.component.html
+++ b/src/app/features/navigation/components/nav-header/nav-header.component.html
@@ -45,6 +45,13 @@
         </a>
       </ng-container>
 
+      <a [href]="helpRoute"
+         [title]="'core.header.help' | translate"
+         class="openk-button openk-info openk-button-rounded nav-header-menu-anchor nav-header-menu-anchor---large-icon"
+         target="_blank">
+        <mat-icon>help_outline</mat-icon>
+      </a>
+
       <app-nav-drop-down
         (appLogOut)="logOut()">
         {{appUserName}}
diff --git a/src/app/features/navigation/components/nav-header/nav-header.component.ts b/src/app/features/navigation/components/nav-header/nav-header.component.ts
index 9a927cd..7bcadc0 100644
--- a/src/app/features/navigation/components/nav-header/nav-header.component.ts
+++ b/src/app/features/navigation/components/nav-header/nav-header.component.ts
@@ -11,8 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from "@angular/core";
-import {EAPIUserRoles} from "../../../../core";
+import {ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, Output} from "@angular/core";
+import {EAPIUserRoles, HELP_ROUTE} from "../../../../core";
 import {arrayJoin} from "../../../../util/store";
 
 export interface INavHeaderRoute {
@@ -82,16 +82,13 @@
             link: "/settings",
             tooltip: "core.header.settings",
             roles: [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE, EAPIUserRoles.SPA_ADMIN]
-        },
-        {
-            icon: "help_outline",
-            link: "/help",
-            tooltip: "core.header.help",
-            target: "_blank",
-            largeIcon: true
         }
     ];
 
+    public constructor(@Inject(HELP_ROUTE) public helpRoute: string) {
+
+    }
+
     public logOut() {
         this.appLogout.emit();
     }
diff --git a/src/app/features/search/components/date-filter/date-filter.component.html b/src/app/features/search/components/date-filter/date-filter.component.html
index 76fb14e..bc74556 100644
--- a/src/app/features/search/components/date-filter/date-filter.component.html
+++ b/src/app/features/search/components/date-filter/date-filter.component.html
@@ -21,7 +21,7 @@
   <div>
     <app-date-control
       #dueDateFromSelect
-      (appValueChange)="emitNewValue(dueDateFromSelect.value)"
+      (appValueChange)="appActive = true; emitNewValue(dueDateFromSelect.value)"
       class="openk-info">
     </app-date-control>
   </div>
diff --git a/src/app/features/search/components/position-search/position-search.component.spec.ts b/src/app/features/search/components/position-search/position-search.component.spec.ts
index eeec9c8..e3eabcb 100644
--- a/src/app/features/search/components/position-search/position-search.component.spec.ts
+++ b/src/app/features/search/components/position-search/position-search.component.spec.ts
@@ -17,8 +17,8 @@
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
 import {IAPIPositionSearchStatementModel} from "../../../../core";
 import {I18nModule} from "../../../../core/i18n";
-import {ILeafletBounds} from "../../../../shared/leaflet";
 import {openGisAction, startStatementPositionSearchAction, userNameSelector} from "../../../../store";
+import {ILeafletBounds} from "../../../map";
 import {SearchModule} from "../../search.module";
 import {PositionSearchComponent} from "./position-search.component";
 
diff --git a/src/app/features/search/components/position-search/position-search.component.ts b/src/app/features/search/components/position-search/position-search.component.ts
index 9a4d1f1..e5d909c 100644
--- a/src/app/features/search/components/position-search/position-search.component.ts
+++ b/src/app/features/search/components/position-search/position-search.component.ts
@@ -16,7 +16,6 @@
 import {select, Store} from "@ngrx/store";
 import {take} from "rxjs/operators";
 import {IAPIPositionSearchOptions, IAPIPositionSearchStatementModel} from "../../../../core";
-import {ILeafletBounds} from "../../../../shared/leaflet";
 import {
     getStatementPositionSearchSelector,
     openGisAction,
@@ -26,8 +25,14 @@
     statementTypesSelector,
     userNameSelector
 } from "../../../../store";
-import {IFilterToDisplay} from "../search-filter";
+import {ILeafletBounds} from "../../../map";
+import {ISearchFilterToDisplay, MAP_SEARCH_FILTER} from "../search-filter";
 
+/**
+ * This component display a list of statements on a map. A keyword search and filters can be applied to the list of statements.
+ * The statements are displayed as markers on the map and by clicking on a marker the statement details page of that statement can be
+ * opened.
+ */
 @Component({
     selector: "app-position-search",
     templateUrl: "./position-search.component.html",
@@ -45,12 +50,7 @@
 
     public userName$ = this.store.pipe(select(userNameSelector));
 
-
-    public filtersToShow: IFilterToDisplay = {
-        filterForType: false,
-        dueDateFrom: false,
-        dueDateTo: false
-    };
+    public filtersToShow: ISearchFilterToDisplay = MAP_SEARCH_FILTER;
 
     public selected: IAPIPositionSearchStatementModel;
 
diff --git a/src/app/features/search/components/search-filter/IFilterToDisplay.ts b/src/app/features/search/components/search-filter/ISearchFilterToDisplay.ts
similarity index 64%
rename from src/app/features/search/components/search-filter/IFilterToDisplay.ts
rename to src/app/features/search/components/search-filter/ISearchFilterToDisplay.ts
index 203b033..cdde65f 100644
--- a/src/app/features/search/components/search-filter/IFilterToDisplay.ts
+++ b/src/app/features/search/components/search-filter/ISearchFilterToDisplay.ts
@@ -11,14 +11,32 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export interface IFilterToDisplay {
-    filterForType?: boolean;
-    status?: boolean;
+export interface ISearchFilterToDisplay {
+    typeId?: boolean;
+    finished?: boolean;
     editedByMe?: boolean;
-    dueDateFrom?: boolean;
-    dueDateTo?: boolean;
     creationDateFrom?: boolean;
     creationDateTo?: boolean;
+    dueDateFrom?: boolean;
+    dueDateTo?: boolean;
     receiptDateFrom?: boolean;
     receiptDateTo?: boolean;
 }
+
+export const ALL_SEARCH_FILTER = {
+    typeId: true,
+    finished: true,
+    editedByMe: true,
+    dueDateFrom: true,
+    dueDateTo: true,
+    creationDateFrom: true,
+    creationDateTo: true,
+    receiptDateFrom: true,
+    receiptDateTo: true
+};
+
+export const MAP_SEARCH_FILTER = {
+    typeId: true,
+    dueDateFrom: true,
+    dueDateTo: true
+};
diff --git a/src/app/features/search/components/search-filter/index.ts b/src/app/features/search/components/search-filter/index.ts
index da57faa..1aa65bb 100644
--- a/src/app/features/search/components/search-filter/index.ts
+++ b/src/app/features/search/components/search-filter/index.ts
@@ -12,4 +12,4 @@
  ********************************************************************************/
 
 export * from "./search-filter.component";
-export * from "./IFilterToDisplay";
+export * from "./ISearchFilterToDisplay";
diff --git a/src/app/features/search/components/search-filter/search-filter.component.html b/src/app/features/search/components/search-filter/search-filter.component.html
index 37c2735..b52b23a 100644
--- a/src/app/features/search/components/search-filter/search-filter.component.html
+++ b/src/app/features/search/components/search-filter/search-filter.component.html
@@ -14,7 +14,7 @@
 <div *ngIf="appShowSearch" class="filters--searchbar">
   <span class="filters--searchbar--text">{{"search.title" | translate}}</span>
   <app-searchbar
-    (appSearch)="searchByString($event)"
+    (appSearch)="setSearchParameter('q', $event)"
     [appIsLoading]="appLoading"
     [appPlaceholder]="'search.executeSearch' | translate"
     class="filters--searchbar--input">
@@ -32,19 +32,18 @@
   </button>
 
   <div class="all-filters">
-    <div *ngIf="appFilters.filterForType != null || appFilters.editedByMe != null || appFilters.status != null"
+    <div *ngIf="appFilters.typeId != null || appFilters.editedByMe != null || appFilters.finished != null"
          class="filters--row">
       <div class="filter-group filter-group---stacked">
         <button
-          (click)="appFilters.filterForType = !appFilters.filterForType; setSearchParams('typeId', typeSelect.appValue, appFilters.filterForType); emitSearch()"
-          [class.openk-info]="appFilters.filterForType"
+          (click)="toggleSearchParameter('typeId', typeSelect.appValue)"
+          [class.openk-info]="appValue.typeId != null"
           class="openk-button openk-chip filters--btn filters--btn---margin">
           {{"search.type" | translate}}
         </button>
         <div class="filters--type-select-width">
           <app-select #typeSelect
-                      (appValueChange)="setSearchParams('typeId', typeSelect.appValue, appFilters.filterForType);
-                      appFilters.filterForType ? emitSearch() : null"
+                      (appValueChange)="setSearchParameter('typeId', $event)"
                       [appDisabled]="appStatementTypeOptions?.length == null || appStatementTypeOptions.length === 0"
                       [appOptions]="appStatementTypeOptions"
                       [appPlaceholder]="typeSelect.appDisabled ? ('search.noData' | translate) : ''"
@@ -53,19 +52,17 @@
           </app-select>
         </div>
       </div>
-      <div *ngIf="appFilters.status != null"
+      <div *ngIf="appFilters.finished != null"
            class="filter-group filter-group---stacked">
         <button
-          (click)="appFilters.status = !appFilters.status;
-          setSearchParams('finished', statusSelect.appValue, appFilters.status); emitSearch()"
-          [class.openk-info]="appFilters.status"
+          (click)="toggleSearchParameter('finished', statusSelect.appValue)"
+          [class.openk-info]="appValue.finished != null"
           class="openk-button openk-chip filters--btn filters--btn---margin">
           Status
         </button>
         <div class="filters--finished-select-width">
           <app-select #statusSelect
-                      (appValueChange)="setSearchParams('finished', statusSelect.appValue, appFilters.status);
-                      appFilters.status ? emitSearch() : null"
+                      (appValueChange)="setSearchParameter('finished', statusSelect.appValue)"
                       [appOptions]="finishedOptions$ | async"
                       [appValue]="(finishedOptions$ | async)?.length > 0 ? (finishedOptions$ | async)[0].value : statusSelect.appValue"
                       class="openk-info filters--select--input-width">
@@ -75,9 +72,8 @@
       <div *ngIf="appFilters.editedByMe != null"
            class="filter-group filter-group---stacked">
         <button
-          (click)="appFilters.editedByMe = !appFilters.editedByMe; setParamsBoolean('editedByMe', appFilters.editedByMe ? appFilters.editedByMe : undefined)"
-          *ngIf="appFilters.editedByMe != null"
-          [class.openk-info]="appFilters.editedByMe"
+          (click)="toggleSearchParameter('editedByMe', true)"
+          [class.openk-info]="appValue.editedByMe != null"
           class="openk-button openk-chip filters--btn">
           {{"search.editedByMe" | translate}}
         </button>
@@ -89,16 +85,16 @@
         <div class="filters--date-selects--block">
           <app-date-filter
             #creationDateFrom
-            (appValueChange)="setSearchParamsDate('creationDateFrom', $event, creationDateFrom.appActive)"
+            (appValueChange)="setSearchParameter('creationDateFrom', creationDateFrom.appActive ? $event : null)"
             *ngIf="appFilters.creationDateFrom != null"
-            [appActive]="appFilters.creationDateFrom"
+            [appActive]="appValue.creationDateFrom != null"
             [appTitle]="'search.creationDateFrom' | translate">
           </app-date-filter>
           <app-date-filter
             #creationDateTo
-            (appValueChange)="setSearchParamsDate('creationDateTo', $event, creationDateTo.appActive)"
+            (appValueChange)="setSearchParameter('creationDateTo', creationDateTo.appActive ? $event : null)"
             *ngIf="appFilters.creationDateTo != null"
-            [appActive]="appFilters.creationDateTo"
+            [appActive]="appValue.creationDateTo != null"
             [appTitle]="'search.creationDateTo' | translate">
           </app-date-filter>
         </div>
@@ -108,16 +104,16 @@
         <div class="filters--date-selects--block">
           <app-date-filter
             #dueDateFrom
-            (appValueChange)="setSearchParamsDate('dueDateFrom', $event, dueDateFrom.appActive)"
+            (appValueChange)="setSearchParameter('dueDateFrom', dueDateFrom.appActive ? $event : null)"
             *ngIf="appFilters.dueDateFrom != null"
-            [appActive]="appFilters.dueDateFrom"
+            [appActive]="appValue.dueDateFrom != null"
             [appTitle]="'search.dueDateFrom' | translate">
           </app-date-filter>
           <app-date-filter
             #dueDateTo
-            (appValueChange)="setSearchParamsDate('dueDateTo', $event, dueDateTo.appActive)"
+            (appValueChange)="setSearchParameter('dueDateTo', dueDateTo.appActive  ? $event : null)"
             *ngIf="appFilters.dueDateTo != null"
-            [appActive]="appFilters.dueDateTo"
+            [appActive]="appValue.dueDateTo != null"
             [appTitle]="'search.dueDateTo' | translate">
           </app-date-filter>
         </div>
@@ -127,16 +123,16 @@
         <div class="filters--date-selects--block">
           <app-date-filter
             #receiptDateFrom
-            (appValueChange)="setSearchParamsDate('receiptDateFrom', $event, receiptDateFrom.appActive)"
+            (appValueChange)="setSearchParameter('receiptDateFrom', receiptDateFrom.appActive ? $event : null)"
             *ngIf="appFilters.receiptDateFrom != null"
-            [appActive]="appFilters.receiptDateFrom"
+            [appActive]="appValue.receiptDateFrom != null"
             [appTitle]="'search.receiptDateFrom' | translate">
           </app-date-filter>
           <app-date-filter
             #receiptDateTo
-            (appValueChange)="setSearchParamsDate('receiptDateTo', $event, receiptDateTo.appActive)"
+            (appValueChange)="setSearchParameter('receiptDateTo', receiptDateTo.appActive ? $event : null)"
             *ngIf="appFilters.receiptDateTo != null"
-            [appActive]="appFilters.receiptDateTo"
+            [appActive]="appValue.receiptDateTo != null"
             [appTitle]="'search.receiptDateTo' | translate">
           </app-date-filter>
         </div>
diff --git a/src/app/features/search/components/search-filter/search-filter.component.scss b/src/app/features/search/components/search-filter/search-filter.component.scss
index 6dae64d..1be7426 100644
--- a/src/app/features/search/components/search-filter/search-filter.component.scss
+++ b/src/app/features/search/components/search-filter/search-filter.component.scss
@@ -61,7 +61,7 @@
 }
 
 .filters--type-select-width {
-  width: 12em;
+  width: 12.5em;
 }
 
 .filters--finished-select-width {
diff --git a/src/app/features/search/components/search-filter/search-filter.component.spec.ts b/src/app/features/search/components/search-filter/search-filter.component.spec.ts
index ac06435..ee48854 100644
--- a/src/app/features/search/components/search-filter/search-filter.component.spec.ts
+++ b/src/app/features/search/components/search-filter/search-filter.component.spec.ts
@@ -14,8 +14,10 @@
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {Store} from "@ngrx/store";
 import {provideMockStore} from "@ngrx/store/testing";
+import {IAPISearchOptions} from "../../../../core/api/shared";
 import {I18nModule} from "../../../../core/i18n";
 import {queryParamsIdSelector} from "../../../../store/root/selectors";
+import {momentFormatInternal, parseMomentToString} from "../../../../util/moment";
 import {SearchModule} from "../../search.module";
 import {SearchFilterComponent} from "./search-filter.component";
 
@@ -55,40 +57,49 @@
         expect(component).toBeTruthy();
     });
 
-    it("should set all filter active status to false on disableAllFilters", () => {
-        component.appFilters = {
-            status: false,
-            editedByMe: true,
-            dueDateFrom: false,
-            dueDateTo: false
-        };
+    it("should toggle a search parameter", () => {
+        const emitSpy = spyOn(component.appValueChange, "emit");
+        const today = new Date();
+        const searchParams: IAPISearchOptions = {};
+
+        searchParams.q = "test";
+        component.setSearchParameter("q", "test");
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
+
+        searchParams.dueDateTo = parseMomentToString(today, momentFormatInternal, momentFormatInternal);
+        component.toggleSearchParameter("dueDateTo", today);
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
+
+        searchParams.dueDateTo = parseMomentToString(today, momentFormatInternal, momentFormatInternal);
+        component.toggleSearchParameter("dueDateTo", today);
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
+
+        delete searchParams.dueDateTo;
+        component.toggleSearchParameter("dueDateTo", today);
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
+    });
+
+    it("should disable all search filters", () => {
+        const emitSpy = spyOn(component.appValueChange, "emit");
+        const today = new Date();
+        const searchParams: IAPISearchOptions = {};
+
+        searchParams.q = "test";
+        component.setSearchParameter("q", "test");
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
+
+        searchParams.dueDateTo = parseMomentToString(today, momentFormatInternal, momentFormatInternal);
+        component.setSearchParameter("dueDateTo", today);
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
+
+        delete searchParams.dueDateTo;
         component.disableAllFilters();
-        expect(component.appFilters).toEqual({
-            status: false,
-            editedByMe: false,
-            dueDateFrom: false,
-            dueDateTo: false
-        });
+        expect(emitSpy).toHaveBeenCalledWith(searchParams);
     });
 
-    it("should set the search parameter q and emit onValueChange", () => {
-        spyOn(component.appValueChange, "emit").and.callThrough();
-        component.searchByString("test");
-        expect(component.appValueChange.emit).toHaveBeenCalledWith({q: "test"});
+    it("should only write values which are set", () => {
+        component.writeValue(({q: "test", dueDateTo: ""}));
+        expect(component.appValue).toEqual({q: "test"});
     });
 
-    it("should set search parameter and not emit", () => {
-        component.appFilters = {
-            filterForType: false
-        };
-        spyOn(component.appValueChange, "emit").and.callThrough();
-        component.setSearchParams("typeId", 2, true);
-        expect(component.appValueChange.emit).not.toHaveBeenCalled();
-        expect(component.appValue).toEqual({
-            q: "",
-            typeId: 2
-        });
-        component.setSearchParams("typeId", 2, false);
-        expect(component.appValue).toEqual({q: "", typeId: undefined});
-    });
 });
diff --git a/src/app/features/search/components/search-filter/search-filter.component.ts b/src/app/features/search/components/search-filter/search-filter.component.ts
index d407dcc..e71023c 100644
--- a/src/app/features/search/components/search-filter/search-filter.component.ts
+++ b/src/app/features/search/components/search-filter/search-filter.component.ts
@@ -13,28 +13,24 @@
 
 import {Component, EventEmitter, Input, OnInit, Output} from "@angular/core";
 import {TranslateService} from "@ngx-translate/core";
-import {Observable} from "rxjs";
+import {defer, Observable} from "rxjs";
 import {map} from "rxjs/operators";
 import {IAPISearchOptions} from "../../../../core/api/shared";
 import {AbstractControlValueAccessorComponent} from "../../../../shared/controls/common";
 import {ISelectOption} from "../../../../shared/controls/select/model";
 import {momentFormatInternal, parseMomentToString} from "../../../../util/moment";
-import {IFilterToDisplay} from "./IFilterToDisplay";
+import {ALL_SEARCH_FILTER, ISearchFilterToDisplay} from "./ISearchFilterToDisplay";
 
 /**
  * This component displays a selection of filters and generates search parameters.
  * Those search parameters can be used in the backend calls to filter the list of all statements for e.g. specific keywords or date ranges.
- * The displayed filters and their state are set by the input property appFilters.
- *  E.g.
+ * The displayed filters are set by the input property appFilters, e.g.
  *  {
  *      status: true,
  *      editedByMe: false
  *  }
  *  only displays the filter dropdown for status (finished/not finished) and the toggle for editedByMe state.
- *  The initial value for the filter button for status is true.
- *  That means the filter is active with the default value (first value in drop down)
  */
-
 @Component({
     selector: "app-search-filter",
     templateUrl: "./search-filter.component.html",
@@ -54,76 +50,52 @@
     @Output()
     public appFilterParams = new EventEmitter<IAPISearchOptions>();
 
+    /**
+     * For each set property key, the specific filter is displayed in the component.
+     */
     @Input()
-    public appFilters: IFilterToDisplay = {
-        filterForType: false,
-        status: false,
-        editedByMe: false,
-        dueDateFrom: false,
-        dueDateTo: false,
-        creationDateFrom: false,
-        creationDateTo: false,
-        receiptDateFrom: false,
-        receiptDateTo: false
-    };
+    public appFilters: ISearchFilterToDisplay = ALL_SEARCH_FILTER;
 
-    public finishedOptions$: Observable<ISelectOption[]> = this.translateService.get(["search.open", "search.finished"]).pipe(
-        map((value) => {
-            return [
-                {
-                    label: value["search.open"],
-                    value: false
-                },
-                {
-                    label: value["search.finished"],
-                    value: true
-                }
-            ];
-        })
-    );
+    public finishedOptions$: Observable<ISelectOption[]> = defer(() => this.getFinishedOptions());
 
     public constructor(public readonly translateService: TranslateService) {
         super();
     }
 
     public ngOnInit() {
-        this.writeValue({
-            q: this.appShowSearch ? "" : undefined
-        }, true);
+        this.writeValue({}, true);
     }
 
-    public searchByString(q: string) {
-        this.writeValue({...this.appValue, q}, true);
+    public toggleSearchParameter(label: keyof IAPISearchOptions, value?: boolean | string | number | Date) {
+        this.setSearchParameter(label, this.appValue[label] != null ? undefined : value);
     }
 
-    public setSearchParams(label: string, value: string | number, filterActive: boolean) {
-        const newValue = {...this.appValue};
-        newValue[label] = filterActive ? value : undefined;
-        this.writeValue(newValue);
-    }
-
-    public setSearchParamsDate(label: string, value: Date, filterActive: boolean) {
-        const newValue = {...this.appValue};
-        newValue[label] = filterActive ? parseMomentToString(value, momentFormatInternal, momentFormatInternal) : undefined;
-        this.appFilters[label] = filterActive;
+    public setSearchParameter(label: keyof IAPISearchOptions, value?: boolean | string | number | Date) {
+        const newValue: IAPISearchOptions = {
+            ...this.appValue,
+            [label]: typeof value === "object" ? parseMomentToString(value, momentFormatInternal, momentFormatInternal) : value
+        };
         this.writeValue(newValue, true);
     }
 
-    public setParamsBoolean(label: string, value: boolean) {
-        const newValue = {...this.appValue};
-        newValue[label] = value;
-        this.writeValue(newValue, true);
-    }
-
-    public emitSearch() {
-        this.appValueChange.emit(this.appValue);
-    }
-
     public disableAllFilters() {
-        for (const key of Object.keys(this.appFilters)) {
-            this.appFilters[key] = false;
-        }
-        this.writeValue({q: undefined}, true);
+        this.writeValue({q: this.appValue.q}, true);
+    }
+
+    public writeValue(obj: IAPISearchOptions, emit?: boolean) {
+        obj = {...obj};
+        Object.keys(obj)
+            .filter((key) => obj[key] == null || obj[key] === "")
+            .forEach((key) => delete obj[key]);
+        super.writeValue(obj, emit);
+    }
+
+    private getFinishedOptions() {
+        const options: ISelectOption[] = ["search.open", "search.finished"]
+            .map((label, index) => ({label, value: index > 0}));
+        return this.translateService.get(options.map((option) => option.label)).pipe(
+            map((translation) => options.map((option) => ({...option, label: translation[option.label]})))
+        );
     }
 
 }
diff --git a/src/app/features/search/components/search-statements/search-statements.component.html b/src/app/features/search/components/search-statements/search-statements.component.html
index 518a4a0..fa53b57 100644
--- a/src/app/features/search/components/search-statements/search-statements.component.html
+++ b/src/app/features/search/components/search-statements/search-statements.component.html
@@ -25,7 +25,7 @@
   [appEntries]="searchContent$ | async"
   [appIsSortable]="true"
   [appStatementTypeOptions]="statementTypeOptions$ | async"
-  class="openk-table---last-row-without-border search-list">
+  class="search-list">
 </app-statement-table>
 
 <app-pagination-counter
diff --git a/src/app/features/search/components/search-statements/search-statements.component.scss b/src/app/features/search/components/search-statements/search-statements.component.scss
index 2d673ee..9cdebde 100644
--- a/src/app/features/search/components/search-statements/search-statements.component.scss
+++ b/src/app/features/search/components/search-statements/search-statements.component.scss
@@ -13,10 +13,11 @@
 
 @import "openk.styles";
 
-
 :host {
   position: relative;
-  display: block;
+  display: flex;
+  flex-flow: column;
+  min-height: 100%;
   width: 100%;
   box-sizing: border-box;
   padding: 1em;
@@ -24,6 +25,7 @@
 
 .search-list {
   min-height: 5.3125em;
+  flex: 1 1 24em;
   background: get-color($openk-default-palette);
 }
 
diff --git a/src/app/features/search/components/search-statements/search-statements.component.ts b/src/app/features/search/components/search-statements/search-statements.component.ts
index 90579db..c936234 100644
--- a/src/app/features/search/components/search-statements/search-statements.component.ts
+++ b/src/app/features/search/components/search-statements/search-statements.component.ts
@@ -21,7 +21,7 @@
 import {getSearchContentInfoSelector, getSearchContentStatementsSelector} from "../../../../store/statements/selectors/search";
 
 /**
- * This component display a list of statements. A keyword search and filters can be applied to the list of statements.
+ * This component displays a list of statements. A keyword search and filters can be applied to the list of statements.
  * The list can also be sorted by clicking on specific header column to sort for.
  * Pagination and sorting will be added to the query parameters for the search calls to the backend.
  */
diff --git a/src/app/features/search/search.module.ts b/src/app/features/search/search.module.ts
index 937ca93..9478926 100644
--- a/src/app/features/search/search.module.ts
+++ b/src/app/features/search/search.module.ts
@@ -21,7 +21,7 @@
 import {PaginationCounterModule} from "../../shared/layout/pagination-counter";
 import {SearchbarModule} from "../../shared/layout/searchbar";
 import {StatementTableModule} from "../../shared/layout/statement-table";
-import {LeafletModule} from "../../shared/leaflet";
+import {MapModule} from "../map";
 import {DateFilterComponent} from "./components/date-filter";
 import {PositionSearchComponent} from "./components/position-search";
 import {SearchFilterComponent} from "./components/search-filter";
@@ -38,7 +38,7 @@
         TranslateModule,
         MatIconModule,
         RouterModule,
-        LeafletModule
+        MapModule
     ],
     declarations: [
         SearchFilterComponent,
diff --git a/src/app/features/settings/components/search/index.ts b/src/app/features/settings/components/search/index.ts
deleted file mode 100644
index 154cd69..0000000
--- a/src/app/features/settings/components/search/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
-export * from "./settings.component";
diff --git a/src/app/features/settings/components/search/settings.component.scss b/src/app/features/settings/components/search/settings.component.scss
deleted file mode 100644
index 06db89a..0000000
--- a/src/app/features/settings/components/search/settings.component.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
diff --git a/src/app/features/settings/departments/components/department-table/departments-settings-table.component.html b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.html
new file mode 100644
index 0000000..83c3723
--- /dev/null
+++ b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.html
@@ -0,0 +1,85 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<table [dataSource]="appDepartments" cdk-table
+       class="openk-table openk-table---without-last-border">
+
+  <caption hidden>{{ "settings.departments.title" | translate}}</caption>
+
+  <tr *cdkHeaderRowDef="appColumns; sticky: true" cdk-header-row></tr>
+
+  <tr *cdkRowDef="let myRowData; columns: appColumns" cdk-row></tr>
+
+  <ng-container cdkColumnDef="city">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.departments.table.city" | translate}}
+    </th>
+    <td *cdkCellDef="let department"
+        cdk-cell
+        class="table-column">
+      <span>{{department?.key?.split("#").length > 1 ? department.key.split("#")[0] : ""}}</span>
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="district">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.departments.table.district" | translate}}
+    </th>
+    <td *cdkCellDef="let department"
+        cdk-cell
+        class="table-column">
+      <span>{{department?.key?.split("#").length > 1 ? department.key.split("#")[1] : ""}}</span>
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="sectors">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.departments.table.sectors" | translate}}
+    </th>
+    <td *cdkCellDef="let department"
+        cdk-cell
+        class="table-column">
+      <span>{{department?.value?.provides.join(', ')}}</span>
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="departments">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.departments.table.departments" | translate}}
+    </th>
+    <td *cdkCellDef="let department"
+        cdk-cell
+        class="table-column">
+      <ng-container *ngFor="let group of (department?.value?.departments | objToArray)">
+        <div *ngFor="let dep of group.value">
+          <mat-icon class="list--element--icon">fiber_manual_record</mat-icon>
+          <span class="list--element---bold">{{group.key}}</span>
+          <span>{{dep}}</span>
+        </div>
+      </ng-container>
+    </td>
+  </ng-container>
+
+</table>
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.scss b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.scss
similarity index 61%
copy from src/app/shared/leaflet/components/leaflet-map.component.scss
copy to src/app/features/settings/departments/components/department-table/departments-settings-table.component.scss
index e8e1a36..c11a6b5 100644
--- a/src/app/shared/leaflet/components/leaflet-map.component.scss
+++ b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.scss
@@ -11,43 +11,38 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
+@import "../../../../../../styles/openk.styles";
 
 :host {
-  width: 100%;
-  height: 100%;
-
   display: flex;
   flex-flow: column;
-}
-
-.map {
-  height: 100%;
   width: 100%;
   position: relative;
-  overflow: hidden;
+  overflow: auto;
   box-sizing: border-box;
   border: 1px solid $openk-form-border;
+  border-radius: 4px;
+  background: $openk-background-card;
+  padding-bottom: 2px;
 }
 
-.map--leaflet {
-  width: 100%;
-  height: 100%;
+.openk-table---last-row-without-border:host {
+  padding-bottom: 0;
 }
 
-.map--button {
-  display: block;
-  width: fit-content;
-  height: fit-content;
-  position: absolute;
-  bottom: 10px;
-  left: 10px;
-  z-index: 1000;
+.table-column {
+  vertical-align: baseline;
+  text-align: start;
 }
 
-.sub-caption {
-  color: $openk-form-border;
-  margin-left: auto;
-  font-size: smaller;
-  font-style: italic;
+.list--element--icon {
+  width: initial;
+  height: initial;
+  font-size: 0.5em;
+  margin-right: 1em;
+}
+
+.list--element---bold {
+  font-weight: 600;
+  margin-right: 0.25em;
 }
diff --git a/src/app/features/settings/departments/components/department-table/departments-settings-table.component.spec.ts b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.spec.ts
new file mode 100644
index 0000000..eda42fd
--- /dev/null
+++ b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.spec.ts
@@ -0,0 +1,47 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {provideMockStore} from "@ngrx/store/testing";
+import {I18nModule} from "../../../../../core/i18n";
+import {DepartmentsSettingsModule} from "../../departments-settings.module";
+import {DepartmentsSettingsTableComponent} from "./departments-settings-table.component";
+
+describe("DepartmentsSettingsTableComponent", () => {
+    let component: DepartmentsSettingsTableComponent;
+    let fixture: ComponentFixture<DepartmentsSettingsTableComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                DepartmentsSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(DepartmentsSettingsTableComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/src/app/features/settings/departments/components/department-table/departments-settings-table.component.ts b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.ts
new file mode 100644
index 0000000..0429153
--- /dev/null
+++ b/src/app/features/settings/departments/components/department-table/departments-settings-table.component.ts
@@ -0,0 +1,30 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, Input} from "@angular/core";
+import {IAPIDepartmentGroups} from "../../../../../core";
+
+@Component({
+    selector: "app-departments-settings-table",
+    templateUrl: "./departments-settings-table.component.html",
+    styleUrls: ["./departments-settings-table.component.scss"]
+})
+export class DepartmentsSettingsTableComponent {
+
+    @Input()
+    public appDepartments: { key: string, value: { provides: string[], departments: IAPIDepartmentGroups }, searchString?: string }[] = [];
+
+    @Input()
+    public appColumns = ["city", "district", "sectors", "departments"];
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/departments/components/department-table/index.ts
similarity index 90%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/departments/components/department-table/index.ts
index 990bb42..085e0fa 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/departments/components/department-table/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./departments-settings-table.component";
diff --git a/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.html b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.html
new file mode 100644
index 0000000..539b17e
--- /dev/null
+++ b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.html
@@ -0,0 +1,34 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<div class="search">
+  <span class="search--bar">{{"settings.departments.search" | translate}}</span>
+  <app-searchbar
+    (appSearch)="filterDepartmentList($event)"
+    [appPlaceholder]="'settings.departments.placeholderSearch' | translate"
+    [appSearchText]="searchText"
+    class="search--input">
+  </app-searchbar>
+</div>
+
+<app-departments-settings-table
+  [appDepartments]="filteredDepartmentList?.slice(appPage * appPageSize, (appPage + 1) * appPageSize)">
+</app-departments-settings-table>
+
+<app-pagination-counter
+  (appPageChange)="changePage($event.page, $event.size)"
+  [appPageSizeOptions]="appPageSizeOptions"
+  [appPageSize]="appPageSize"
+  [appPage]="appPage"
+  [appTotalPages]="totalPages">
+</app-pagination-counter>
diff --git a/src/app/shared/controls/map-select/components/map-select.component.scss b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.scss
similarity index 73%
copy from src/app/shared/controls/map-select/components/map-select.component.scss
copy to src/app/features/settings/departments/components/departments-search/departments-settings-search.component.scss
index 7e59f1f..5701f34 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.scss
+++ b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.scss
@@ -11,10 +11,23 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
+@import "../../../../../../styles/openk.styles";
 
 :host {
-  display: block;
   width: 100%;
-  height: 100%;
+}
+
+.search {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  margin-bottom: 1em;
+}
+
+.search--bar {
+  margin-right: 0.5em;
+}
+
+.search--input {
+  flex: 1;
 }
diff --git a/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.spec.ts b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.spec.ts
new file mode 100644
index 0000000..fa805d1
--- /dev/null
+++ b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.spec.ts
@@ -0,0 +1,106 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {SimpleChange} from "@angular/core";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {provideMockStore} from "@ngrx/store/testing";
+import {I18nModule, IAPIDepartmentGroups} from "../../../../../core";
+import {DepartmentsSettingsModule} from "../../departments-settings.module";
+import {DepartmentsSettingsSearchComponent} from "./departments-settings-search.component";
+
+describe("DepartmentsSettingsSearchComponent", () => {
+
+    const departments: { key: string, value: { provides: string[], departments: IAPIDepartmentGroups }, searchString?: string }[] = [
+        {
+            key: "Ort#Ortsteil",
+            value: {
+                provides: [
+                    "Gas",
+                    "Strom"
+                ],
+                departments: {
+                    Allgemein: [
+                        "Medianet",
+                        "Planung"
+                    ]
+                }
+            }
+        },
+        {
+            key: "Ort#Süd",
+            value: {
+                provides: [
+                    "Wasser",
+                    "Strom"
+                ],
+                departments: {
+                    Allgemein: [
+                        "Planung"
+                    ]
+                }
+            }
+        }
+    ];
+
+    let component: DepartmentsSettingsSearchComponent;
+    let fixture: ComponentFixture<DepartmentsSettingsSearchComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                DepartmentsSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(DepartmentsSettingsSearchComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should filter the given department table", () => {
+        component.appDepartmentList = departments;
+        component.ngOnChanges({appDepartmentList: new SimpleChange([], departments, false)});
+        expect(component.filteredDepartmentList).toEqual(departments);
+
+        component.filterDepartmentList("Allgemein, Media");
+        expect(component.filteredDepartmentList).toEqual([departments[0]]);
+    });
+
+    it("should set page info", () => {
+        component.appPage = 1;
+        component.appPageSize = 1;
+        component.appDepartmentList = departments;
+        component.filterDepartmentList("");
+        expect(component.appPage).toBe(0);
+
+        component.appPage = 1;
+        component.appPageSize = 1;
+        component.ngOnChanges({appPage: new SimpleChange(0, 1, false)});
+        expect(component.appPageSize).toBe(1);
+        expect(component.appPage).toBe(1);
+        expect(component.totalPages).toBe(2);
+    });
+
+});
diff --git a/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.ts b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.ts
new file mode 100644
index 0000000..80ed8fb
--- /dev/null
+++ b/src/app/features/settings/departments/components/departments-search/departments-settings-search.component.ts
@@ -0,0 +1,91 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core";
+import {IAPIDepartmentGroups} from "../../../../../core";
+import {ISelectOption} from "../../../../../shared/controls/select";
+import {arrayJoin} from "../../../../../util";
+
+@Component({
+    selector: "app-departments-settings-search",
+    templateUrl: "./departments-settings-search.component.html",
+    styleUrls: ["./departments-settings-search.component.scss"]
+})
+export class DepartmentsSettingsSearchComponent implements OnChanges {
+
+    @Input()
+    public appDepartmentList: { key: string, value: { provides: string[], departments: IAPIDepartmentGroups } }[];
+
+    @Input()
+    public appPage = 0;
+
+    @Input()
+    public appPageSize = 10;
+
+    @Input()
+    public appPageSizeOptions: ISelectOption[] = [
+        {label: "5", value: 5},
+        {label: "10", value: 10},
+        {label: "25", value: 25},
+        {label: "50", value: 50}
+    ];
+
+    @Output()
+    public appIsSearching = new EventEmitter<boolean>();
+
+    public filteredDepartmentList: { key: string, value: { provides: string[], departments: IAPIDepartmentGroups } }[] = [];
+
+    public totalPages: number;
+
+    public searchText = "";
+
+    public ngOnChanges(changes: SimpleChanges) {
+        const onChange = (keys: Array<keyof DepartmentsSettingsSearchComponent>, fn: () => any) => {
+            if (keys.some((key) => changes[key] != null)) {
+                fn();
+            }
+        };
+        onChange(["appDepartmentList"], () => {
+            this.filterDepartmentList("");
+        });
+        onChange(["appPage", "appPageSize"], () => {
+            this.changePage(this.appPage, this.appPageSize);
+        });
+    }
+
+    public filterDepartmentList(searchText: string) {
+        this.searchText = searchText;
+        this.filteredDepartmentList = arrayJoin(this.appDepartmentList)
+            .filter((_) => {
+                const searchValues = arrayJoin(
+                    _.key.split("#"), // City and district
+                    _.value.provides, // Sectors
+                    Object.keys(_.value.departments), // Department group names
+                    ...Object.values(_.value.departments) // Department names
+                ).join("").toLowerCase();
+                const searchTokens = this.searchText
+                    .toLowerCase()
+                    .replace(/[;,.]/g, " ")
+                    .split(" ");
+                return !searchTokens.some((token) => !searchValues.includes(token));
+            });
+        this.changePage(0);
+    }
+
+    public changePage(page: number = this.appPage, pageSize: number = this.appPageSize) {
+        this.appPageSize = Math.max(1, pageSize);
+        this.appPage = Math.max(0, page);
+        this.totalPages = Math.ceil(this.filteredDepartmentList.length / this.appPageSize);
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/departments/components/departments-search/index.ts
similarity index 90%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/departments/components/departments-search/index.ts
index 990bb42..a94a8cf 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/departments/components/departments-search/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./departments-settings-search.component";
diff --git a/src/app/features/settings/departments/components/departments-settings.component.html b/src/app/features/settings/departments/components/departments-settings.component.html
new file mode 100644
index 0000000..d90909e
--- /dev/null
+++ b/src/app/features/settings/departments/components/departments-settings.component.html
@@ -0,0 +1,105 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<app-settings-side-menu
+  [appForAdmin]="true">
+</app-settings-side-menu>
+
+<app-side-menu-status *appSideMenu="'center'"
+                      [appLoadingMessage]="'core.loading' | translate"
+                      [appLoading]="(loading$ | async) || searching">
+  <ng-container *ngIf="selectedFile != null">
+    {{'settings.departments.selectedFile' | translate}}: <br> {{selectedFile.name}}
+  </ng-container>
+
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+  <app-action-button (appClick)="anchorElement.click()"
+                     [appDisabled]="currentFileUrl == null || (loading$ | async)"
+                     [appIcon]="'get_app'"
+                     class="openk-info side-menu-button">
+    {{'settings.departments.downloadCurrent' | translate}}
+  </app-action-button>
+  <app-action-button (appClick)="inputElement.click()"
+                     [appDisabled]="loading$ | async"
+                     [appIcon]="'attach_file'"
+                     class="openk-info side-menu-button">
+    {{'settings.departments.selectNew' | translate}}
+  </app-action-button>
+  <app-action-button (appClick)="submit(selectedFileData)"
+                     [appDisabled]="selectedFileData == null || (loading$ | async)"
+                     [appIcon]="'publish'"
+                     class="openk-success side-menu-button">
+    {{'settings.departments.submit' | translate}}
+  </app-action-button>
+
+  <input #inputElement
+         (change)="selectFile(inputElement.files?.item(0)); inputElement.value = null;"
+         style="display: none;"
+         type="file">
+  <a #anchorElement [download]="currentConfigFileName" [href]="currentFileUrl" style="display: none;"></a>
+</ng-container>
+
+<div class="title">
+  <span class="title--label">
+    {{"settings.title" | translate}} - {{"settings.departments.title" | translate}}
+  </span>
+</div>
+
+<app-collapsible
+  [appTitle]="'settings.departments.departments'| translate"
+  class="departments-info">
+  <div class="departments-info--container">
+    <app-list
+      *ngFor="let val of (selectedFileData ? selectedFileData : (currentData$ | async)) | getDepartmentGroupsFromTablePipe | objToArray"
+      [appListItems]="val.value | appStringArrayToLabelList"
+      [appTitle]="val.key">
+    </app-list>
+    <span
+      *ngIf="((selectedFileData ? selectedFileData : (currentData$ | async)) | getDepartmentGroupsFromTablePipe | objToArray).length <= 0">
+      {{'settings.departments.placeholderNoDepartments'| translate}}
+    </span>
+  </div>
+</app-collapsible>
+
+<app-collapsible
+  [appTitle]="'settings.departments.sectors'| translate"
+  class="departments-info">
+  <div class="departments-info--container">
+    <span>
+      <ng-container
+        *ngFor="let sector of (selectedFileData ? selectedFileData : (currentData$ | async)) | appSectorsFromDepartment; let last = last;">
+        {{sector + (last ? "" : ",")}}
+      </ng-container>
+
+      <ng-container
+        *ngIf="((selectedFileData ? selectedFileData : (currentData$ | async)) | appSectorsFromDepartment).length === 0">
+        {{'settings.departments.placeholderNoSectors'| translate}}
+      </ng-container>
+    </span>
+  </div>
+</app-collapsible>
+
+<app-collapsible
+  [appTitle]="'settings.departments.organisation'| translate">
+  <div class="departments-info--container">
+    <app-departments-settings-search
+      (appIsSearching)="searching = $event;"
+      [appDepartmentList]="(selectedFileData ? selectedFileData : (currentData$ | async)) | objToArray">
+    </app-departments-settings-search>
+  </div>
+</app-collapsible>
+
+
+
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.scss b/src/app/features/settings/departments/components/departments-settings.component.scss
similarity index 60%
copy from src/app/shared/leaflet/components/leaflet-map.component.scss
copy to src/app/features/settings/departments/components/departments-settings.component.scss
index e8e1a36..a3793d7 100644
--- a/src/app/shared/leaflet/components/leaflet-map.component.scss
+++ b/src/app/features/settings/departments/components/departments-settings.component.scss
@@ -11,43 +11,37 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
-
 :host {
   width: 100%;
-  height: 100%;
-
+  padding: 1em;
+  box-sizing: border-box;
   display: flex;
   flex-flow: column;
+  max-width: 70em;
+  margin: 0 auto 0 auto;
 }
 
-.map {
-  height: 100%;
+.title {
+  margin-bottom: 1em;
+}
+
+.title--label {
+  font-size: x-large;
+  font-weight: 600;
+}
+
+.side-menu-button {
   width: 100%;
-  position: relative;
-  overflow: hidden;
-  box-sizing: border-box;
-  border: 1px solid $openk-form-border;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
 
-.map--leaflet {
-  width: 100%;
-  height: 100%;
+.departments-info {
+  margin-bottom: 1em;
 }
 
-.map--button {
-  display: block;
-  width: fit-content;
-  height: fit-content;
-  position: absolute;
-  bottom: 10px;
-  left: 10px;
-  z-index: 1000;
-}
-
-.sub-caption {
-  color: $openk-form-border;
-  margin-left: auto;
-  font-size: smaller;
-  font-style: italic;
+.departments-info--container {
+  padding: 1em;
 }
diff --git a/src/app/features/settings/departments/components/departments-settings.component.spec.ts b/src/app/features/settings/departments/components/departments-settings.component.spec.ts
new file mode 100644
index 0000000..ac4cbd1
--- /dev/null
+++ b/src/app/features/settings/departments/components/departments-settings.component.spec.ts
@@ -0,0 +1,47 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {provideMockStore} from "@ngrx/store/testing";
+import {I18nModule} from "../../../../core";
+import {DepartmentsSettingsModule} from "../departments-settings.module";
+import {DepartmentsSettingsComponent} from "./departments-settings.component";
+
+describe("DepartmentsSettingsComponent", () => {
+    let component: DepartmentsSettingsComponent;
+    let fixture: ComponentFixture<DepartmentsSettingsComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                DepartmentsSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(DepartmentsSettingsComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/src/app/features/settings/departments/components/departments-settings.component.ts b/src/app/features/settings/departments/components/departments-settings.component.ts
new file mode 100644
index 0000000..aeed52b
--- /dev/null
+++ b/src/app/features/settings/departments/components/departments-settings.component.ts
@@ -0,0 +1,116 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, Inject, OnDestroy, OnInit} from "@angular/core";
+import {DomSanitizer, SafeUrl} from "@angular/platform-browser";
+import {select, Store} from "@ngrx/store";
+import {Observable, of, Subject} from "rxjs";
+import {map, switchMap, takeUntil} from "rxjs/operators";
+import {IAPIDepartmentTable, URL_TOKEN} from "../../../../core";
+import {
+    EErrorCode,
+    fetchDepartmentsSettingsAction,
+    getDepartmentsSettingsSelector,
+    getSettingsLoadingSelector,
+    setErrorAction,
+    submitDepartmentsSettingsAction
+} from "../../../../store";
+import {catchErrorTo} from "../../../../util";
+import {parseDepartmentTableFromCsv, reduceDepartmentTableToCsv} from "../util";
+
+@Component({
+    selector: "app-departments-settings",
+    templateUrl: "./departments-settings.component.html",
+    styleUrls: ["./departments-settings.component.scss"]
+})
+export class DepartmentsSettingsComponent implements OnInit, OnDestroy {
+
+    public currentConfigFileName = "departments.config.csv";
+
+    public loading$ = this.store.pipe(select(getSettingsLoadingSelector));
+
+    public searching = false;
+
+    public currentData$ = this.store.pipe(select(getDepartmentsSettingsSelector));
+
+    public currentConfigFile$: Observable<File> = this.currentData$.pipe(
+        switchMap((data) => {
+            return of(data).pipe(
+                map(() => new File([reduceDepartmentTableToCsv(data)], this.currentConfigFileName)),
+                catchErrorTo(null)
+            );
+        })
+    );
+
+    public currentFileUrl: SafeUrl;
+
+    public selectedFile: File;
+
+    public selectedFileData: IAPIDepartmentTable;
+
+    private currentFileObjectUrl: string;
+
+    private destroy$ = new Subject();
+
+    public constructor(public store: Store, @Inject(URL_TOKEN) public url: typeof URL, public sanitizer: DomSanitizer) {
+
+    }
+
+    public ngOnInit() {
+        this.currentConfigFile$.pipe(takeUntil(this.destroy$)).subscribe((file) => this.createObjectURLForCurrentFile(file));
+        this.currentData$.pipe(takeUntil(this.destroy$)).subscribe(() => this.clearFileSelection());
+        this.store.dispatch(fetchDepartmentsSettingsAction());
+    }
+
+    public ngOnDestroy() {
+        this.destroy$.next();
+        this.destroy$.complete();
+        this.revokeObjectUrl();
+    }
+
+    public submit(data: IAPIDepartmentTable) {
+        this.store.dispatch(submitDepartmentsSettingsAction({data}));
+    }
+
+    public async selectFile(file: File) {
+        try {
+            const text = await file.text();
+            this.selectedFileData = parseDepartmentTableFromCsv(text);
+            this.selectedFile = file;
+        } catch (e) {
+            this.store.dispatch(setErrorAction({error: EErrorCode.INVALID_FILE_FORMAT}));
+        }
+    }
+
+    public clearFileSelection() {
+        this.selectedFile = null;
+        this.selectedFileData = null;
+    }
+
+    private createObjectURLForCurrentFile(file: File) {
+        this.revokeObjectUrl();
+        if (file != null) {
+            this.currentFileObjectUrl = this.url.createObjectURL(file);
+            this.currentFileUrl = this.sanitizer.bypassSecurityTrustUrl(this.currentFileObjectUrl);
+        }
+    }
+
+    private revokeObjectUrl() {
+        if (this.currentFileObjectUrl != null) {
+            this.url.revokeObjectURL(this.currentFileObjectUrl);
+            this.currentFileObjectUrl = null;
+            this.currentFileUrl = null;
+        }
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/departments/components/index.ts
similarity index 81%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/departments/components/index.ts
index 990bb42..1e625fb 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/departments/components/index.ts
@@ -11,4 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./department-table";
+export * from "./departments-search";
+
+export * from "./departments-settings.component";
diff --git a/src/app/features/settings/departments/departments-settings.module.ts b/src/app/features/settings/departments/departments-settings.module.ts
new file mode 100644
index 0000000..8567c49
--- /dev/null
+++ b/src/app/features/settings/departments/departments-settings.module.ts
@@ -0,0 +1,63 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CdkTableModule} from "@angular/cdk/table";
+import {CommonModule} from "@angular/common";
+import {NgModule} from "@angular/core";
+import {MatIconModule} from "@angular/material/icon";
+import {TranslateModule} from "@ngx-translate/core";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
+import {CollapsibleModule} from "../../../shared/layout/collapsible";
+import {ListModule} from "../../../shared/layout/list";
+import {PaginationCounterModule} from "../../../shared/layout/pagination-counter";
+import {SearchbarModule} from "../../../shared/layout/searchbar";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
+import {SharedPipesModule} from "../../../shared/pipes";
+import {SharedSettingsModule} from "../shared";
+import {DepartmentsSettingsComponent, DepartmentsSettingsSearchComponent, DepartmentsSettingsTableComponent} from "./components";
+import {SectorsFromDepartmentPipe} from "./pipes";
+
+@NgModule({
+    imports: [
+        CommonModule,
+        TranslateModule,
+        CdkTableModule,
+        MatIconModule,
+
+        ActionButtonModule,
+        CollapsibleModule,
+        ListModule,
+        PaginationCounterModule,
+        SearchbarModule,
+        SharedPipesModule,
+        SharedSettingsModule,
+        SideMenuModule
+    ],
+    declarations: [
+        DepartmentsSettingsTableComponent,
+        DepartmentsSettingsSearchComponent,
+        DepartmentsSettingsComponent,
+
+        SectorsFromDepartmentPipe
+    ],
+    exports: [
+        DepartmentsSettingsTableComponent,
+        DepartmentsSettingsSearchComponent,
+        DepartmentsSettingsComponent,
+
+        SectorsFromDepartmentPipe
+    ]
+})
+export class DepartmentsSettingsModule {
+
+}
diff --git a/src/app/shared/leaflet/index.ts b/src/app/features/settings/departments/index.ts
similarity index 84%
copy from src/app/shared/leaflet/index.ts
copy to src/app/features/settings/departments/index.ts
index 849c149..cc40d2e 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/features/settings/departments/index.ts
@@ -11,9 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./directives";
+export * from "./components";
 export * from "./pipes";
 export * from "./util";
 
-export * from "./leaflet.module";
-export * from "./leaflet-configuration.token";
+export * from "./departments-settings.module";
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/departments/pipes/index.ts
similarity index 90%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/departments/pipes/index.ts
index 990bb42..c88fbb8 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/departments/pipes/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./sectors-from-department-object.pipe";
diff --git a/src/app/features/settings/departments/pipes/sectors-from-department-object.pipe.spec.ts b/src/app/features/settings/departments/pipes/sectors-from-department-object.pipe.spec.ts
new file mode 100644
index 0000000..c3479d2
--- /dev/null
+++ b/src/app/features/settings/departments/pipes/sectors-from-department-object.pipe.spec.ts
@@ -0,0 +1,64 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPIDepartmentTable} from "../../../../core/api/settings";
+import {SectorsFromDepartmentPipe} from "./sectors-from-department-object.pipe";
+
+describe("SectorsFromDepartmentPipe", () => {
+
+    const pipe = new SectorsFromDepartmentPipe();
+
+    const departmentTable: IAPIDepartmentTable = {
+        "Ort#Ortsteil": {
+            provides: [
+                "Gas",
+                "Strom",
+                "Wasser"
+            ],
+            departments: {
+                Allgemein: [
+                    "Medianet",
+                    "Planung"
+                ],
+                Weiteres: [
+                    "Planung",
+                    "Strom Beratung"
+                ]
+            }
+        },
+        "Zweiter#Ort": {
+            provides: [
+                "Beleuchtung"
+            ],
+            departments: {}
+        }
+    };
+
+    describe("transform", () => {
+
+        it("should return sectors from IAPIDepartment table object as a single string sorted alphabetically", () => {
+            let result = pipe.transform(departmentTable);
+            expect(result).toEqual(["Beleuchtung", "Gas", "Strom", "Wasser"]);
+
+            result = pipe.transform(null);
+            expect(result).toEqual([]);
+
+            result = pipe.transform(undefined);
+            expect(result).toEqual([]);
+
+            result = pipe.transform({});
+            expect(result).toEqual([]);
+        });
+    });
+});
+
diff --git a/src/app/features/settings/departments/pipes/sectors-from-department-object.pipe.ts b/src/app/features/settings/departments/pipes/sectors-from-department-object.pipe.ts
new file mode 100644
index 0000000..7d7d60d
--- /dev/null
+++ b/src/app/features/settings/departments/pipes/sectors-from-department-object.pipe.ts
@@ -0,0 +1,34 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {IAPIDepartmentTable} from "../../../../core/api/settings";
+import {arrayJoin, filterDistinctValues, objectToArray} from "../../../../util/store";
+
+/**
+ * From a IAPIDepartmentTable object returns a string with all distinct sectors in all department groups.
+ */
+
+@Pipe({
+    name: "appSectorsFromDepartment"
+})
+export class SectorsFromDepartmentPipe implements PipeTransform {
+
+    public transform(table: IAPIDepartmentTable): string[] {
+        const sectors: string[] = arrayJoin(
+            ...objectToArray(table).map((entry) => entry.value.provides)
+        );
+        return filterDistinctValues(sectors).sort();
+    }
+
+}
diff --git a/src/app/features/settings/departments/util/departments.util.spec.ts b/src/app/features/settings/departments/util/departments.util.spec.ts
new file mode 100644
index 0000000..c80691b
--- /dev/null
+++ b/src/app/features/settings/departments/util/departments.util.spec.ts
@@ -0,0 +1,55 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPIDepartmentTable} from "../../../../core/api/settings";
+import {parseDepartmentTableFromCsv, reduceDepartmentTableToCsv} from "./departments.util";
+
+describe("DepartmentsUtil", () => {
+    const text = "City;District;Sectors;Departments;;;;;;;;;;" +
+        "\r\n" +
+        "Darmstadt;Darmstadt;Strom,Gas,Wasser,Straßenbeleuchtung;" +
+        "Allgemein;Gas;Allgemein;Wasser;Regionalstelle Darmstadt;Gas (Betrieb);Regionalstelle Darmstadt;Gas (Planung/Bau);" +
+        "Regionalstelle Darmstadt;Strom (Planung/Bau);Regionalstelle Darmstadt;Strom (Betrieb)" +
+        "\r\n" +
+        "Heppenheim;Heppenheim;Strom,Gas,Straßenbeleuchtung;" +
+        "Allgemein;Gas;Allgemein;Wasser;Regionalstelle Heppenheim;Gas (Betrieb);Regionalstelle Heppenheim;Gas (Planung/Bau);" +
+        "Regionalstelle Heppenheim;Strom (Planung/Bau);;";
+
+    const data: IAPIDepartmentTable = {
+        "Darmstadt#Darmstadt": {
+            provides: ["Strom", "Gas", "Wasser", "Straßenbeleuchtung"],
+            departments: {
+                Allgemein: ["Gas", "Wasser"],
+                "Regionalstelle Darmstadt": ["Gas (Betrieb)", "Gas (Planung/Bau)", "Strom (Planung/Bau)", "Strom (Betrieb)"]
+            }
+        },
+        "Heppenheim#Heppenheim": {
+            provides: ["Strom", "Gas", "Straßenbeleuchtung"],
+            departments: {
+                Allgemein: ["Gas", "Wasser"],
+                "Regionalstelle Heppenheim": ["Gas (Betrieb)", "Gas (Planung/Bau)", "Strom (Planung/Bau)"]
+            }
+        }
+    };
+
+    it("parseDepartmentTableFromCsv", () => {
+        expect(parseDepartmentTableFromCsv(text + "\r\n;;;;;;" + "\r\n")).toEqual(data);
+        expect(() => parseDepartmentTableFromCsv("\r\na;b;c")).not.toThrow();
+        expect(() => parseDepartmentTableFromCsv("\r\na;b")).toThrow();
+    });
+
+    it("reduceDepartmentTableToCsv", () => {
+        expect(reduceDepartmentTableToCsv(data)).toBe(text);
+    });
+
+});
diff --git a/src/app/features/settings/departments/util/departments.util.ts b/src/app/features/settings/departments/util/departments.util.ts
new file mode 100644
index 0000000..a76d7ec
--- /dev/null
+++ b/src/app/features/settings/departments/util/departments.util.ts
@@ -0,0 +1,59 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPIDepartmentTable} from "../../../../core";
+import {arrayJoin, parseCsv, reduceRowToCsv, reduceToCsv} from "../../../../util";
+
+export function parseDepartmentTableFromCsv(text: string): IAPIDepartmentTable {
+    return parseCsv(text, ";").slice(1)
+        .map((row) => row.map((entry) => entry.trim()))
+        .filter((row) => row.length > 1)
+        .reduce<IAPIDepartmentTable>((result, value) => {
+            if (value.length < 3) {
+                throw new Error("Invalid format");
+            }
+            if (value.slice(0, 2).some((_) => _.length === 0)) {
+                return result;
+            }
+            const key: string = value[0] + "#" + value[1];
+            const provides: IAPIDepartmentTable[""]["provides"] = value[2].split(",").filter((sector) => sector.length > 0);
+            const departments: IAPIDepartmentTable[""]["departments"] = value.slice(3)
+                .reduce((dept, groupName, index, array) => {
+                    const name: string = array[index + 1];
+                    return index % 2 !== 0 || [groupName, name].some((_) => _.length === 0) ? dept : {
+                        ...dept,
+                        [groupName]: arrayJoin(dept[groupName], [name])
+                    };
+                }, {});
+            return {
+                ...result,
+                [key]: {provides, departments}
+            };
+        }, {});
+}
+
+
+export function reduceDepartmentTableToCsv(table: IAPIDepartmentTable) {
+    const headers = ["City", "District", "Sectors", "Departments"];
+    const data: string[][] = Object.entries(table).map(([cityDistrict, entry]) => {
+        return arrayJoin(
+            [cityDistrict.replace("#", ";")],
+            [reduceRowToCsv(entry.provides, ",")],
+            ...Object.entries(entry.departments)
+                .map<string[]>(([groupName, names]) => {
+                    return arrayJoin(...names.map((name) => [groupName, name]));
+                })
+        );
+    });
+    return reduceToCsv([headers, ...data], ";");
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/departments/util/index.ts
similarity index 93%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/departments/util/index.ts
index 990bb42..b04e96f 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/departments/util/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./departments.util";
diff --git a/src/app/features/settings/documents/components/documents-settings.component.html b/src/app/features/settings/documents/components/documents-settings.component.html
new file mode 100644
index 0000000..d2c1def
--- /dev/null
+++ b/src/app/features/settings/documents/components/documents-settings.component.html
@@ -0,0 +1,62 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<app-settings-side-menu
+  [appForAdmin]="true">
+</app-settings-side-menu>
+
+<app-side-menu-status *appSideMenu="'center'"
+                      [appLoadingMessage]="'core.loading' | translate"
+                      [appLoading]="loading$ | async">
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+  <app-action-button (appClick)="save()" [appIcon]="'publish'" class="openk-info openk-success side-menu-button">
+    {{"settings.documents.save" | translate}}
+  </app-action-button>
+</ng-container>
+
+<div class="title">
+  <span class="title--label">
+    {{"settings.title" | translate}} - {{"settings.documents.title" | translate}}
+  </span>
+</div>
+
+<app-collapsible
+  [appTitle]="'settings.documents.tags' | translate">
+
+  <div style="padding: 1em;">
+
+    <div class="add-tag">
+      <span class="add-tag--title">{{"settings.documents.tag" | translate}}</span>
+      <input #inputElement
+             (keydown.enter)="addTag(inputElement.value); inputElement.value = '';"
+             [placeholder]="'settings.documents.placeholder' | translate"
+             [disabled]="loading$| async"
+             class="openk-input add-tag--input"/>
+
+      <button (click)="addTag(inputElement.value); inputElement.value = '';"
+              [disabled]="loading$| async"
+              class="openk-button openk-button-icon list-select--button add-tag--btn">
+        <mat-icon>add</mat-icon>
+      </button>
+
+    </div>
+
+    <app-list (appDelete)="deleteTag($event)"
+              [appListItems]="tagList"
+              [appTitle]="'settings.documents.listTitle' | translate">
+    </app-list>
+  </div>
+</app-collapsible>
+
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.scss b/src/app/features/settings/documents/components/documents-settings.component.scss
similarity index 61%
copy from src/app/shared/leaflet/components/leaflet-map.component.scss
copy to src/app/features/settings/documents/components/documents-settings.component.scss
index e8e1a36..6587388 100644
--- a/src/app/shared/leaflet/components/leaflet-map.component.scss
+++ b/src/app/features/settings/documents/components/documents-settings.component.scss
@@ -11,43 +11,49 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
-
 :host {
   width: 100%;
-  height: 100%;
-
+  padding: 1em;
+  box-sizing: border-box;
   display: flex;
   flex-flow: column;
+  max-width: 70em;
+  margin: 0 auto 0 auto;
 }
 
-.map {
-  height: 100%;
+.title {
+  margin-bottom: 1em;
+}
+
+.title--label {
+  font-size: x-large;
+  font-weight: 600;
+}
+
+.side-menu-button {
   width: 100%;
-  position: relative;
-  overflow: hidden;
-  box-sizing: border-box;
-  border: 1px solid $openk-form-border;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
 
-.map--leaflet {
+.add-tag {
+  display: inline-flex;
+  align-items: center;
+  margin-bottom: 1em;
   width: 100%;
-  height: 100%;
 }
 
-.map--button {
-  display: block;
-  width: fit-content;
-  height: fit-content;
-  position: absolute;
-  bottom: 10px;
-  left: 10px;
-  z-index: 1000;
+.add-tag--title {
+  margin-right: 0.5em;
 }
 
-.sub-caption {
-  color: $openk-form-border;
-  margin-left: auto;
-  font-size: smaller;
-  font-style: italic;
+.add-tag--input {
+  margin-right: 0.3em;
+  flex: 1;
+}
+
+.add-tag--btn {
+  font-size: 0.8em;
 }
diff --git a/src/app/features/settings/documents/components/documents-settings.component.spec.ts b/src/app/features/settings/documents/components/documents-settings.component.spec.ts
new file mode 100644
index 0000000..aca9aa0
--- /dev/null
+++ b/src/app/features/settings/documents/components/documents-settings.component.spec.ts
@@ -0,0 +1,88 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {Store} from "@ngrx/store";
+import {provideMockStore} from "@ngrx/store/testing";
+import {I18nModule} from "../../../../core";
+import {submitTagsAction} from "../../../../store/statements/actions";
+import {DocumentsSettingsModule} from "../documents.settings.module";
+import {DocumentsSettingsComponent} from "./documents-settings.component";
+
+describe("TagSettingsComponent", () => {
+    let component: DocumentsSettingsComponent;
+    let fixture: ComponentFixture<DocumentsSettingsComponent>;
+    let store: Store;
+
+    const tags = [
+        {label: "label", add: true},
+        {label: "label1"},
+        {label: "label2"},
+        {label: "label3", add: true}
+    ];
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                DocumentsSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(DocumentsSettingsComponent);
+        component = fixture.componentInstance;
+        store = fixture.componentRef.injector.get(Store);
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should add string to taglist if set", () => {
+        expect(component.tagList).toEqual([]);
+        component.addTag(null);
+        expect(component.tagList).toEqual([]);
+        component.addTag("");
+        expect(component.tagList).toEqual([]);
+        component.addTag("test");
+        expect(component.tagList.length).toEqual(1);
+        expect(component.tagList).toEqual([{label: "test", add: true}]);
+    });
+
+    it("should delete the specified entry from taglist", () => {
+        component.tagList = tags;
+        component.deleteTag(14);
+        expect(component.tagList).toEqual(tags);
+        component.deleteTag(2);
+        expect(component.tagList).toEqual([
+            {label: "label", add: true},
+            {label: "label1"},
+            {label: "label3", add: true}
+        ]);
+    });
+
+    it("should dispatch submitTagsAction with labels of tags to be added", async () => {
+        const dispatchSpy = spyOn(store, "dispatch");
+        component.tagList = tags;
+        await component.save();
+        expect(dispatchSpy).toHaveBeenCalledWith(submitTagsAction({labels: ["label", "label3"]}));
+    });
+});
diff --git a/src/app/features/settings/documents/components/documents-settings.component.ts b/src/app/features/settings/documents/components/documents-settings.component.ts
new file mode 100644
index 0000000..410dd78
--- /dev/null
+++ b/src/app/features/settings/documents/components/documents-settings.component.ts
@@ -0,0 +1,68 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, OnDestroy, OnInit} from "@angular/core";
+import {select, Store} from "@ngrx/store";
+import {Subject} from "rxjs";
+import {map, takeUntil} from "rxjs/operators";
+import {getAttachmentTagsSelector, getSettingsLoadingSelector, submitTagsAction} from "../../../../store";
+
+@Component({
+    selector: "app-tag-settings",
+    templateUrl: "./documents-settings.component.html",
+    styleUrls: ["./documents-settings.component.scss"]
+})
+export class DocumentsSettingsComponent implements OnInit, OnDestroy {
+
+    public tags$ = this.store.pipe(select(getAttachmentTagsSelector)).pipe(
+        map((tags) => tags.map((_) => _.label))
+    );
+
+    public loading$ = this.store.pipe(select(getSettingsLoadingSelector));
+
+    public tagList: { label: string, add?: boolean }[] = [];
+
+    private destroy$ = new Subject();
+
+    public constructor(private readonly store: Store) {
+    }
+
+    public ngOnInit() {
+        this.tags$.pipe(
+            takeUntil(this.destroy$)
+        ).subscribe((tags) => {
+            this.tagList = tags.map((_) => ({label: _}));
+        });
+    }
+
+    public async ngOnDestroy() {
+        this.destroy$.next();
+        this.destroy$.complete();
+    }
+
+    public addTag(tagLabel: string) {
+        if (tagLabel) {
+            this.tagList.push({label: tagLabel, add: true});
+        }
+    }
+
+    public deleteTag(index: number) {
+        this.tagList.splice(index, 1);
+    }
+
+    public save() {
+        const labels = this.tagList.filter((tag) => tag.add != null).map((_) => _.label);
+        this.store.dispatch(submitTagsAction({labels}));
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/documents/components/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/documents/components/index.ts
index 990bb42..980f050 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/documents/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./documents-settings.component";
diff --git a/src/app/features/settings/documents/documents.settings.module.ts b/src/app/features/settings/documents/documents.settings.module.ts
new file mode 100644
index 0000000..fc71a7c
--- /dev/null
+++ b/src/app/features/settings/documents/documents.settings.module.ts
@@ -0,0 +1,46 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CommonModule} from "@angular/common";
+import {NgModule} from "@angular/core";
+import {MatIconModule} from "@angular/material/icon";
+import {TranslateModule} from "@ngx-translate/core";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
+import {CollapsibleModule} from "../../../shared/layout/collapsible";
+import {ListModule} from "../../../shared/layout/list";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
+import {SharedSettingsModule} from "../shared";
+import {DocumentsSettingsComponent} from "./components";
+
+@NgModule({
+    imports: [
+        CommonModule,
+        TranslateModule,
+        MatIconModule,
+
+        ActionButtonModule,
+        CollapsibleModule,
+        SideMenuModule,
+        SharedSettingsModule,
+        ListModule
+    ],
+    declarations: [
+        DocumentsSettingsComponent
+    ],
+    exports: [
+        DocumentsSettingsComponent
+    ]
+})
+export class DocumentsSettingsModule {
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/documents/index.ts
similarity index 88%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/documents/index.ts
index 990bb42..58635e5 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/documents/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./components";
+export * from "./documents.settings.module";
diff --git a/src/app/features/settings/index.ts b/src/app/features/settings/index.ts
index d659e68..dd97a22 100644
--- a/src/app/features/settings/index.ts
+++ b/src/app/features/settings/index.ts
@@ -11,4 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./settings.module";
+
+export * from "./departments/util";
+
diff --git a/src/app/features/settings/settings-routing.module.ts b/src/app/features/settings/settings-routing.module.ts
index 62871ef..aa21f87 100644
--- a/src/app/features/settings/settings-routing.module.ts
+++ b/src/app/features/settings/settings-routing.module.ts
@@ -13,22 +13,50 @@
 
 import {NgModule} from "@angular/core";
 import {RouterModule, Routes} from "@angular/router";
-import {OfficialInChargeOrAdminRouteGuardService} from "../../store/root/services";
-import {SettingsComponent} from "./components";
-import {SettingsModule} from "./settings.module";
+import {AdminRouteGuardService, OfficialInChargeOrAdminRouteGuardService} from "../../store";
+import {DepartmentsSettingsComponent, DepartmentsSettingsModule} from "./departments";
+import {DocumentsSettingsComponent, DocumentsSettingsModule} from "./documents";
+import {TextBlockSettingsModule, TextBlocksSettingsComponent} from "./text-blocks";
+import {UsersSettingsComponent, UsersSettingsModule} from "./users";
 
 const routes: Routes = [
     {
         path: "",
         pathMatch: "full",
-        component: SettingsComponent,
+        redirectTo: "text-blocks"
+    },
+    {
+        path: "departments",
+        pathMatch: "full",
+        component: DepartmentsSettingsComponent,
+        canActivate: [AdminRouteGuardService]
+    },
+    {
+        path: "documents",
+        pathMatch: "full",
+        component: DocumentsSettingsComponent,
+        canActivate: [AdminRouteGuardService]
+    },
+    {
+        path: "text-blocks",
+        pathMatch: "full",
+        component: TextBlocksSettingsComponent,
         canActivate: [OfficialInChargeOrAdminRouteGuardService]
+    },
+    {
+        path: "users",
+        pathMatch: "full",
+        component: UsersSettingsComponent,
+        canActivate: [AdminRouteGuardService]
     }
 ];
 
 @NgModule({
     imports: [
-        SettingsModule,
+        DepartmentsSettingsModule,
+        DocumentsSettingsModule,
+        TextBlockSettingsModule,
+        UsersSettingsModule,
         RouterModule.forChild(routes)
     ],
     exports: [
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/shared/components/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/shared/components/index.ts
index 990bb42..d98e4bc 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/shared/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./settings-side-menu.component";
diff --git a/src/app/features/settings/components/search/settings.component.html b/src/app/features/settings/shared/components/settings-side-menu.component.html
similarity index 60%
rename from src/app/features/settings/components/search/settings.component.html
rename to src/app/features/settings/shared/components/settings-side-menu.component.html
index 0bc2dce..68fabde 100644
--- a/src/app/features/settings/components/search/settings.component.html
+++ b/src/app/features/settings/shared/components/settings-side-menu.component.html
@@ -11,6 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<div style="padding: 1em;">
-  Not yet implemented.
-</div>
+<ng-container *appSideMenu="'top'">
+  <app-action-button
+    *ngFor="let item of appForAdmin ? adminEntries : defaultEntries"
+    [appIcon]="item.icon"
+    [appRouterLink]="item.routerLink"
+    [class.openk-info]="!router.url?.startsWith(item.routerLink)"
+    class="side-menu-button openk-primary">
+    {{item.label | translate}}
+  </app-action-button>
+</ng-container>
diff --git a/src/app/shared/controls/map-select/components/map-select.component.scss b/src/app/features/settings/shared/components/settings-side-menu.component.scss
similarity index 83%
copy from src/app/shared/controls/map-select/components/map-select.component.scss
copy to src/app/features/settings/shared/components/settings-side-menu.component.scss
index 7e59f1f..d765276 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.scss
+++ b/src/app/features/settings/shared/components/settings-side-menu.component.scss
@@ -11,10 +11,18 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
-
 :host {
-  display: block;
+  display: none;
+}
+
+.side-menu-button {
   width: 100%;
-  height: 100%;
+
+  & + & {
+    margin-top: 1em;
+  }
+}
+
+.info-message {
+  font-style: italic;
 }
diff --git a/src/app/features/settings/components/search/settings.component.spec.ts b/src/app/features/settings/shared/components/settings-side-menu.component.spec.ts
similarity index 60%
copy from src/app/features/settings/components/search/settings.component.spec.ts
copy to src/app/features/settings/shared/components/settings-side-menu.component.spec.ts
index 2bc2d9f..e868868 100644
--- a/src/app/features/settings/components/search/settings.component.spec.ts
+++ b/src/app/features/settings/shared/components/settings-side-menu.component.spec.ts
@@ -12,20 +12,27 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {SettingsComponent} from "./settings.component";
+import {RouterTestingModule} from "@angular/router/testing";
+import {I18nModule} from "../../../../core";
+import {SharedSettingsModule} from "../shared-settings.module";
+import {SettingsSideMenuComponent} from "./settings-side-menu.component";
 
-describe("SettingsComponent", () => {
-    let component: SettingsComponent;
-    let fixture: ComponentFixture<SettingsComponent>;
+describe("SettingsSideMenuComponent", () => {
+    let component: SettingsSideMenuComponent;
+    let fixture: ComponentFixture<SettingsSideMenuComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            declarations: [SettingsComponent]
+            imports: [
+                SharedSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(SettingsComponent);
+        fixture = TestBed.createComponent(SettingsSideMenuComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/features/settings/shared/components/settings-side-menu.component.ts b/src/app/features/settings/shared/components/settings-side-menu.component.ts
new file mode 100644
index 0000000..f5beedb
--- /dev/null
+++ b/src/app/features/settings/shared/components/settings-side-menu.component.ts
@@ -0,0 +1,68 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, Input} from "@angular/core";
+import {Router} from "@angular/router";
+
+interface ISettingsSideMenuEntry {
+    label: string;
+    icon: string;
+    routerLink: string;
+}
+
+@Component({
+    selector: "app-settings-side-menu",
+    templateUrl: "./settings-side-menu.component.html",
+    styleUrls: ["./settings-side-menu.component.scss"]
+})
+export class SettingsSideMenuComponent {
+
+    @Input()
+    public appForAdmin: boolean;
+
+    public defaultEntries: ISettingsSideMenuEntry[] = [
+        {
+            label: "settings.textBlocks.title",
+            icon: "subject",
+            routerLink: "/settings/text-blocks"
+        }
+    ];
+
+    public adminEntries: ISettingsSideMenuEntry[] = [
+        {
+            label: "settings.textBlocks.title",
+            icon: "subject",
+            routerLink: "/settings/text-blocks"
+        },
+        {
+            label: "settings.documents.title",
+            icon: "description",
+            routerLink: "/settings/documents"
+        },
+        {
+            label: "settings.departments.title",
+            icon: "work",
+            routerLink: "/settings/departments"
+        },
+        {
+            label: "settings.users.title",
+            icon: "supervisor_account",
+            routerLink: "/settings/users"
+        }
+    ];
+
+    constructor(public router: Router) {
+
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/shared/index.ts
similarity index 89%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/shared/index.ts
index 990bb42..b5cf4ef 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/shared/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./shared-settings.module";
+export * from "./pipes";
diff --git a/src/app/features/settings/shared/pipes/get-department-groups-from-table.pipe.ts b/src/app/features/settings/shared/pipes/get-department-groups-from-table.pipe.ts
new file mode 100644
index 0000000..b41784f
--- /dev/null
+++ b/src/app/features/settings/shared/pipes/get-department-groups-from-table.pipe.ts
@@ -0,0 +1,32 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+import {Pipe, PipeTransform} from "@angular/core";
+import {IAPIDepartmentGroups, IAPIDepartmentTable} from "../../../../core/api/settings";
+import {arrayJoin, filterDistinctValues} from "../../../../util/store";
+
+@Pipe({name: "getDepartmentGroupsFromTablePipe"})
+export class GetDepartmentGroupsFromTablePipe implements PipeTransform {
+
+    public transform(table: IAPIDepartmentTable): IAPIDepartmentGroups {
+        return Object.values({...table})
+            .map((entry) => entry.departments)
+            .reduce((current, departments) => {
+                current = {...departments, ...current};
+                Object.entries(current).forEach(([groupName, groups]) => {
+                    current[groupName] = filterDistinctValues(arrayJoin(groups, departments[groupName])).sort();
+                });
+                return current;
+            }, {});
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/shared/pipes/index.ts
similarity index 90%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/shared/pipes/index.ts
index 990bb42..22a3b41 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/shared/pipes/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./get-department-groups-from-table.pipe";
diff --git a/src/app/features/settings/shared/shared-settings.module.ts b/src/app/features/settings/shared/shared-settings.module.ts
new file mode 100644
index 0000000..0458146
--- /dev/null
+++ b/src/app/features/settings/shared/shared-settings.module.ts
@@ -0,0 +1,44 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CommonModule} from "@angular/common";
+import {NgModule} from "@angular/core";
+import {TranslateModule} from "@ngx-translate/core";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
+import {SettingsSideMenuComponent} from "./components";
+import {GetDepartmentGroupsFromTablePipe} from "./pipes";
+
+@NgModule({
+    imports: [
+        CommonModule,
+        TranslateModule,
+
+        ActionButtonModule,
+        SideMenuModule
+    ],
+    declarations: [
+        SettingsSideMenuComponent,
+
+        GetDepartmentGroupsFromTablePipe
+    ],
+    exports: [
+        SettingsSideMenuComponent,
+
+        GetDepartmentGroupsFromTablePipe
+    ]
+})
+export class SharedSettingsModule {
+
+}
+
diff --git a/src/app/features/settings/text-blocks/TextBlockSettingsForm.ts b/src/app/features/settings/text-blocks/TextBlockSettingsForm.ts
new file mode 100644
index 0000000..b606fdd
--- /dev/null
+++ b/src/app/features/settings/text-blocks/TextBlockSettingsForm.ts
@@ -0,0 +1,294 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {AbstractControl, FormArray, FormControl, FormGroup} from "@angular/forms";
+import {IAPITextBlockConfigurationModel, IAPITextBlockGroupModel, IAPITextBlockModel} from "../../../core/api/text";
+import {createFormGroup} from "../../../util/forms";
+import {arrayJoin, filterDistinctValues} from "../../../util/store";
+
+export class TextBlockSettingsForm {
+
+    public selects = new FormGroup({});
+
+    public groups = new FormArray([]);
+
+    public negativeGroups = new FormArray([]);
+
+    public formGroup = createFormGroup<IAPITextBlockConfigurationModel>({
+        selects: this.selects,
+        groups: this.groups,
+        negativeGroups: this.negativeGroups
+    });
+
+    private defaultSelectEntry = "";
+
+    private defaultTextBlockModel: IAPITextBlockModel = {id: "", text: "", excludes: [], requires: []};
+
+    private defaultTextBlockGroupModel: IAPITextBlockGroupModel = {groupName: "", textBlocks: []};
+
+    public disable(disabled: boolean) {
+        if (disabled) {
+            this.formGroup.disable();
+        } else {
+            this.formGroup.enable();
+        }
+    }
+
+    public getValue(): IAPITextBlockConfigurationModel {
+        return this.formGroup.value;
+    }
+
+    public setForm(value: IAPITextBlockConfigurationModel) {
+        const disabled = this.formGroup.disabled;
+
+        Object.keys(this.selects.controls).forEach((key) => this.removeSelect(key));
+        Object.entries(value.selects).forEach(([key, options]) => this.addSelect(key, options));
+
+        this.groups.clear();
+        value.groups.forEach((group) => {
+            if (group.textBlocks?.length > 0) {
+                this.groups.push(this.createTextBlockGroupControl(group));
+            } else {
+                this.groups.push(this.createTextBlockGroupControl({...group, textBlocks: [this.defaultTextBlockModel]}));
+            }
+        });
+
+        this.negativeGroups.clear();
+        value.negativeGroups.forEach((group) => {
+            this.negativeGroups.push(this.createTextBlockGroupControl(group));
+        });
+
+        this.changeTextBlockReferences();
+        this.insertObjectsForEmptyGroups(false);
+        this.insertObjectsForEmptyGroups(true);
+        this.disable(disabled);
+    }
+
+
+    public getSelectKeys(): string[] {
+        return Object.keys(this.selects.value);
+    }
+
+    public getSelectEntries(key: string): string[] {
+        return this.getValue().selects[key];
+    }
+
+    public getSelectControl(key: string): FormArray {
+        const control = this.selects.get([key]);
+        return control instanceof FormArray ? control : undefined;
+    }
+
+    public addSelect(key: string, entries?: string[]) {
+        const disabled = this.formGroup.disabled;
+        this.selects.addControl(key, new FormArray([]));
+        arrayJoin(entries).forEach((entry) => this.addSelectEntry(key, entry));
+        this.disable(disabled);
+    }
+
+    public changeSelectKey(key: string, newKey: string) {
+        const disabled = this.formGroup.disabled;
+        const selectControl = this.getSelectControl(key);
+        if (key === newKey || selectControl == null) {
+            return;
+        }
+
+        this.patchTextBlockValues((value) => ({
+            text: value.text.replace(new RegExp(`<s:${key}>`, "g"), `<s:${newKey}>`)
+        }));
+
+        this.selects.removeControl(key);
+        this.selects.addControl(newKey, selectControl);
+        this.disable(disabled);
+    }
+
+    public removeSelect(key: string) {
+        this.selects.removeControl(key);
+        this.patchTextBlockValues((value) => ({
+            text: value.text.replace(new RegExp(`<s:${key}>`, "g"), "")
+        }));
+    }
+
+    public addSelectEntry(key: string, entry: string) {
+        const disabled = this.formGroup.disabled;
+        this.getSelectControl(key)?.push(this.createSelectEntryControl(entry));
+        this.disable(disabled);
+    }
+
+    public removeSelectEntry(key: string, index: number) {
+        this.getSelectControl(key)?.removeAt(index);
+    }
+
+
+    public getTextBlockGroups(negative?: boolean): FormArray {
+        return negative ? this.negativeGroups : this.groups;
+    }
+
+    public insertGroup(groupIndex: number, negative?: boolean) {
+        const disabled = this.formGroup.disabled;
+        const groups = this.getTextBlockGroups(negative);
+        const groupControl = this.createTextBlockGroupControl();
+        groups.insert(groupIndex, groupControl);
+        this.disable(disabled);
+    }
+
+    public removeGroup(groupIndex: number, negative?: boolean) {
+        const groups = this.getTextBlockGroups(negative);
+        groups.removeAt(groupIndex);
+        this.changeTextBlockReferences();
+        return groups.length;
+    }
+
+
+    public getTextBlockValue(groupIndex: number, textBlockIndex: number, negative?: boolean): IAPITextBlockModel {
+        return this.getTextBlockFormArrayForGroup(groupIndex, negative)?.at(textBlockIndex)?.value;
+    }
+
+    public getTextBlockFormArrayForGroup(groupIndex: number, negative?: boolean): FormArray {
+        const result = this.getTextBlockGroups(negative).at(groupIndex)?.get("textBlocks");
+        return result instanceof FormArray ? result : undefined;
+    }
+
+    public insertTextBlock(groupIndex: number, index: number, negative?: boolean) {
+        const textBlockGroup = this.getTextBlockFormArrayForGroup(groupIndex, negative);
+        if (textBlockGroup != null) {
+            const disabled = this.formGroup.disabled;
+            const control = this.createTextBlockModelControl();
+            textBlockGroup.insert(index, control);
+            this.changeTextBlockReferences();
+            this.disable(disabled);
+            return control;
+        }
+    }
+
+    public moveTextBlockInGroup(groupIndex: number, from: number, to: number, negative?: boolean) {
+        const textBlockGroup = this.getTextBlockFormArrayForGroup(groupIndex, negative);
+        const control = textBlockGroup.get([from]);
+        if (to != null && from !== to && control != null) {
+            textBlockGroup.removeAt(from);
+            textBlockGroup.insert(to, control);
+            this.changeTextBlockReferences();
+            return control;
+        }
+    }
+
+    public removeTextBlock(groupIndex: number, textBlockIndex: number, negative?: boolean) {
+        const group = this.getTextBlockFormArrayForGroup(groupIndex, negative);
+        if (group != null) {
+            group.removeAt(textBlockIndex);
+            this.changeTextBlockReferences(); // This method removes all references to the now deleted text block.
+            return group.length;
+        }
+    }
+
+
+    public compareIds(a: string, b: string): number {
+        const allIds = this.getTextBlockControls().map((control) => control.value?.id);
+        return allIds.indexOf(a) - allIds.indexOf(b);
+    }
+
+    public getTextBlockControls(): AbstractControl[] {
+        return arrayJoin(
+            ...this.groups.controls.map((_, index) => this.getTextBlockFormArrayForGroup(index, false)?.controls),
+            ...this.negativeGroups.controls.map((_, index) => this.getTextBlockFormArrayForGroup(index, true)?.controls)
+        );
+    }
+
+    private changeTextBlockReferences() {
+        const lookup: { [id: string]: string } = {};
+        let offset = 0;
+
+        this.groups.controls.forEach((_, groupIndex) => {
+            offset++;
+            this.getTextBlockFormArrayForGroup(groupIndex).controls.forEach((control, textBlockIndex) => {
+                const id = this.getTextBlockValue(groupIndex, textBlockIndex).id;
+                lookup[id] = `${groupIndex + 1}.${textBlockIndex + 1}`;
+            });
+        });
+
+        this.negativeGroups.controls.forEach((_, groupIndex) => {
+            this.getTextBlockFormArrayForGroup(groupIndex, true).controls.forEach((control, textBlockIndex) => {
+                const id = this.getTextBlockValue(groupIndex, textBlockIndex, true).id;
+                lookup[id] = `${offset + groupIndex + 1}.${textBlockIndex + 1}`;
+            });
+        });
+
+        this.mapAllTextBlockIds((id) => lookup[id]);
+    }
+
+    private createSelectEntryControl(value: string = this.defaultSelectEntry) {
+        return new FormControl(value);
+    }
+
+    private createTextBlockGroupControl(group?: IAPITextBlockGroupModel): FormGroup {
+        group = {
+            ...this.defaultTextBlockGroupModel,
+            ...group
+        };
+
+        return createFormGroup<IAPITextBlockGroupModel>({
+            groupName: new FormControl(group.groupName),
+            textBlocks: group.textBlocks.reduce((formArray, textBlock) => {
+                formArray.push(this.createTextBlockModelControl(textBlock));
+                return formArray;
+            }, new FormArray([]))
+        });
+    }
+
+    private createTextBlockModelControl(textBlock?: IAPITextBlockModel): FormGroup {
+        textBlock = {
+            ...this.defaultTextBlockModel,
+            ...textBlock
+        };
+        return createFormGroup<IAPITextBlockModel>({
+            id: new FormControl(textBlock.id),
+            requires: new FormControl(textBlock.requires),
+            excludes: new FormControl(textBlock.excludes),
+            text: new FormControl(textBlock.text)
+        });
+    }
+
+    private patchTextBlockValues(fn: (value: IAPITextBlockModel, index: number) => Partial<IAPITextBlockModel>) {
+        this.getTextBlockControls().forEach((control, index) => {
+            control.patchValue(fn(control.value, index));
+        });
+    }
+
+    private mapAllTextBlockIds(fn: (id: string) => string | null) {
+        this.patchTextBlockValues((value) => ({
+            id: fn(value.id),
+            excludes: filterDistinctValues(arrayJoin(value.excludes).map(fn)),
+            requires: arrayJoin(value.requires)
+                .map((rule) => ({
+                    ...rule,
+                    ids: filterDistinctValues(arrayJoin(rule.ids).map(fn))
+                }))
+                .filter((rule) => rule.ids.length > 0)
+        }));
+    }
+
+    private insertObjectsForEmptyGroups(negative: boolean) {
+        const disabled = this.formGroup.disabled;
+        const value = this.getValue()[negative ? "negativeGroups" : "groups"];
+        if (value.length === 0) {
+            this.insertGroup(0, negative);
+            this.insertObjectsForEmptyGroups(negative);
+        }
+        value.forEach((group, groupIndex) => {
+            if (arrayJoin(group.textBlocks).length === 0) {
+                this.insertTextBlock(groupIndex, 0, negative);
+            }
+        });
+        this.disable(disabled);
+    }
+
+}
diff --git a/src/app/shared/leaflet/index.ts b/src/app/features/settings/text-blocks/components/index.ts
similarity index 72%
copy from src/app/shared/leaflet/index.ts
copy to src/app/features/settings/text-blocks/components/index.ts
index 849c149..09cfb15 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/features/settings/text-blocks/components/index.ts
@@ -11,9 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./directives";
-export * from "./pipes";
-export * from "./util";
+export * from "./text-block-form";
+export * from "./text-block-list-form";
+export * from "./text-block-select-form";
 
-export * from "./leaflet.module";
-export * from "./leaflet-configuration.token";
+export * from "./text-blocks-settings.component";
+export * from "../TextBlockSettingsForm";
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/text-blocks/components/text-block-form/index.ts
similarity index 92%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/text-blocks/components/text-block-form/index.ts
index 990bb42..a3bedcd 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/text-blocks/components/text-block-form/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./text-block-form.component";
diff --git a/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.html b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.html
new file mode 100644
index 0000000..eb562d1
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.html
@@ -0,0 +1,82 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<ng-template #templateRef>
+  {{ "settings.textBlocks.pickTextBlock" | translate }}
+</ng-template>
+
+<ng-container *ngIf="appFormGroup; else templateRef;">
+
+  <div class="text-block--title">
+    <label [for]="id">{{'textBlocks.textBlock' | translate}} {{appFormGroup?.value?.id}}</label>
+  </div>
+
+  <textarea #autoInsertTextField="appAutoInsertTextFieldToken"
+            #textAreaElement
+            (input)="appFormGroup?.get('text')?.patchValue(textAreaElement.value)"
+            [disabled]="appFormGroup?.get('text')?.disabled"
+            [id]="id"
+            [value]="appFormGroup?.get('text')?.value"
+            appAutoInsertTextFieldToken
+            class="openk-textarea text-block--textarea">
+  </textarea>
+
+  <div
+    *ngFor="let buttons of [appTextControlDefaultButtons, appTextControlReplacementButtons, appTextControlSelectButtons]"
+    class="text-block--format">
+    <button (click)="autoInsertTextField.insert(button.token, button.startToken, button?.requireLineBreak)"
+            *ngFor="let button of buttons"
+            [disabled]="appFormGroup.disabled"
+            class="openk-button openk-chip text-block--format--chip">
+      <span *ngIf="button.label" class="text-block--format--chip--label">
+        {{button.label}}
+      </span>
+      <mat-icon *ngIf="button.icon"
+                [class.text-block--format--chip--icon---rotate]="button.rotateIcon"
+                class="text-block--format--chip--icon">
+        {{button.icon}}
+      </mat-icon>
+    </button>
+  </div>
+
+  <div (cdkDropListDropped)="onDrop($event?.item?.data?.id, area)"
+       (cdkDropListEntered)="onEnter($event?.item?.data?.id, area)"
+       (cdkDropListExited)="onExit()"
+       *ngFor="let area of dropAreaConfig; let i = index;"
+       [cdkDropListEnterPredicate]="enterPredicate"
+       [id]="area.elementId"
+       cdkDropList class="drop-area openk-drag-list-with-hidden-placeholder">
+
+    <div class="drop-area--title">
+      {{area?.label | translate}}
+    </div>
+
+    <div class="drop--area--text-blocks">
+
+      <button (click)="removeIdFromArea(id, area)"
+              *ngFor="let id of appFormGroup.value | getRuleIdsOfTextBlock : area.key : area.type"
+              [disabled]="appFormGroup?.disabled"
+              class="openk-button openk-chip drop-area--button">
+        <mat-icon class="drop-area--button--icon">clear</mat-icon>
+        {{"textBlocks.textBlock" | translate }} {{id}}
+      </button>
+
+      <button *ngIf="area === selectedRule?.area" class="openk-button openk-chip drop-area--button"
+              disabled>
+        <mat-icon class="drop-area--button--icon">clear</mat-icon>
+        {{"textBlocks.textBlock" | translate }} {{selectedRule?.id}}
+      </button>
+    </div>
+  </div>
+
+</ng-container>
diff --git a/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.scss b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.scss
new file mode 100644
index 0000000..9f5f845
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.scss
@@ -0,0 +1,112 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "src/styles/openk.styles";
+
+:host {
+  display: flex;
+  flex-flow: column;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  padding: 1em;
+}
+
+.text-block--title {
+  font-weight: 600;
+  margin-bottom: 0.25em;
+}
+
+.text-block--textarea {
+  resize: none;
+  width: 100%;
+  box-sizing: border-box;
+  min-height: 8.4em;
+  flex: 1 1 100%;
+  height: 10em;
+  overflow-y: auto;
+}
+
+.text-block--format {
+  display: flex;
+  flex-flow: row wrap;
+  margin-top: 2px;
+
+  &:empty {
+    display: none;
+  }
+}
+
+.text-block--format--chip {
+  margin-top: 0.25em;
+  margin-right: 0.25em;
+}
+
+.text-block--format--chip--label {
+  padding-right: 0.2em;
+
+  &:last-child {
+    padding-right: 0;
+  }
+}
+
+.text-block--format--chip--icon {
+  font-size: 1em;
+  height: initial;
+  width: initial;
+  padding: 0;
+  margin: 0;
+}
+
+.text-block--format--chip--icon---rotate {
+  transform: rotate(90deg);
+}
+
+
+.drop-area {
+  @include rounded-border-mixin();
+
+  width: 100%;
+  flex: 1 1 3em;
+  border: 1px dashed $openk-form-border;
+  margin-top: 0.5em;
+  padding: 0.25em;
+  box-sizing: border-box;
+
+  display: flex;
+  flex-flow: column;
+}
+
+.drop-area--title {
+  line-height: 1.25;
+  font-size: x-small;
+  width: 100%;
+}
+
+.drop--area--text-blocks {
+  display: flex;
+  flex-flow: row wrap;
+  width: 100%;
+}
+
+.drop-area--button {
+  margin: 0.25em 0.5em 0.25em 0;
+}
+
+.drop-area--button--icon {
+  font-size: inherit;
+  height: initial;
+  width: initial;
+  line-height: 1;
+  padding-right: 0.25em;
+}
diff --git a/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.spec.ts b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.spec.ts
new file mode 100644
index 0000000..40cdd43
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.spec.ts
@@ -0,0 +1,98 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CdkDrag} from "@angular/cdk/drag-drop";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {FormGroup} from "@angular/forms";
+import {I18nModule, IAPITextBlockModel} from "../../../../../core";
+import {createTextBlockModelMock} from "../../../../../test";
+import {TextBlockSettingsModule} from "../../text-block-settings.module";
+import {TextBlockSettingsForm} from "../../TextBlockSettingsForm";
+import {TextBlockFormComponent} from "./text-block-form.component";
+
+describe("TextBlockFormComponent", () => {
+    let component: TextBlockFormComponent;
+    let fixture: ComponentFixture<TextBlockFormComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [TextBlockSettingsModule, I18nModule],
+            providers: [{
+                provide: TextBlockSettingsForm,
+                useValue: new TextBlockSettingsForm()
+            }]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(TextBlockFormComponent);
+        component = fixture.componentInstance;
+        component.form.setForm({
+            selects: {},
+            groups: [{groupName: "Group", textBlocks: [createTextBlockModelMock("", "")]}],
+            negativeGroups: []
+        });
+        component.appFormGroup = component.form.getTextBlockControls()[0] as FormGroup;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should check the text block ID as enter predicate", () => {
+        const drag = {data: {id: "1.1"}} as CdkDrag<IAPITextBlockModel>;
+        expect(component.enterPredicate(drag)).toBe(false);
+        drag.data.id = "1.19";
+        expect(component.enterPredicate(drag)).toBe(true);
+        drag.data = null;
+        expect(component.enterPredicate(drag)).toBe(true);
+        expect(component.enterPredicate(null)).toBe(true);
+        component.appFormGroup = null;
+    });
+
+    it("should focus text area element", () => {
+        component.focus();
+        expect(document.activeElement).toBe(component.queryList.first.nativeElement);
+    });
+
+    it("should add id to rule on drag and drop", () => {
+        const id = "1.19";
+        component.dropAreaConfig.forEach((area) => {
+            component.onEnter(id, area);
+            expect(component.selectedRule).toEqual({id, area});
+            component.onExit();
+            expect(component.selectedRule).not.toBeDefined();
+            component.onEnter(id, area);
+            expect(component.selectedRule).toEqual({id, area});
+            component.onDrop(id, area);
+            expect(component.selectedRule).not.toBeDefined();
+            expect(component.getRuleIds(area)).toEqual([id]);
+        });
+    });
+
+    it("should remove id from rule", () => {
+        const id = "1.19";
+        component.dropAreaConfig.forEach((area) => {
+            component.onDrop(id, area);
+            expect(component.getRuleIds(area)).toEqual([id]);
+        });
+        component.dropAreaConfig.forEach((area) => {
+            component.removeIdFromArea(id, area);
+            component.removeIdFromArea(id, area);
+            expect(component.getRuleIds(area)).toEqual([]);
+        });
+        expect(component.getValue().requires.length).toBe(0);
+    });
+
+});
diff --git a/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.ts b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.ts
new file mode 100644
index 0000000..a8f617b
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-form/text-block-form.component.ts
@@ -0,0 +1,182 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CdkDrag} from "@angular/cdk/drag-drop";
+import {Component, ElementRef, Input, OnDestroy, OnInit, QueryList, ViewChildren} from "@angular/core";
+import {FormGroup} from "@angular/forms";
+import {merge, of, Subject} from "rxjs";
+import {filter, map, switchMap, take, takeUntil, tap} from "rxjs/operators";
+import {IAPITextBlockModel, TAPIRequireRuleType, TAPITextBlockRuleKey} from "../../../../../core";
+import {arrayJoin, filterDistinctValues} from "../../../../../util/store";
+import {GetRuleIdsOfTextBlockPipe} from "../../pipes";
+import {TextBlockSettingsForm} from "../../TextBlockSettingsForm";
+
+export interface ITextBlockFormTextControlButton {
+    label?: string;
+    icon?: string;
+    token: string;
+    startToken?: string;
+    rotateIcon?: boolean;
+    requireLineBreak?: boolean;
+}
+
+export interface ITextBlockFormDropAreaConfig {
+    label: string;
+    key: TAPITextBlockRuleKey;
+    type?: TAPIRequireRuleType;
+    elementId: string;
+}
+
+@Component({
+    selector: "app-text-block-form",
+    templateUrl: "./text-block-form.component.html",
+    styleUrls: ["./text-block-form.component.scss"]
+})
+export class TextBlockFormComponent implements OnInit, OnDestroy {
+
+    private static id = 0;
+
+    public readonly id = `TextBlockFormComponent-${TextBlockFormComponent.id++}`;
+
+    @Input()
+    public appTextControlDefaultButtons: ITextBlockFormTextControlButton[] = [];
+
+    @Input()
+    public appTextControlReplacementButtons: ITextBlockFormTextControlButton[] = [];
+
+    @Input()
+    public appTextControlSelectButtons: ITextBlockFormTextControlButton[] = [];
+
+    public readonly dropAreaConfig: ITextBlockFormDropAreaConfig[] = [
+        {
+            label: "settings.textBlocks.rules.excludes",
+            key: "excludes",
+            elementId: this.id + "-DropArea-Excludes",
+        },
+        {
+            label: "settings.textBlocks.rules.requiresAnd",
+            key: "requires",
+            type: "and",
+            elementId: this.id + "-DropArea-Required-And"
+        },
+        {
+            label: "settings.textBlocks.rules.requiresOr",
+            key: "requires",
+            type: "or",
+            elementId: this.id + "-DropArea-Required-Or"
+        },
+        {
+            label: "settings.textBlocks.rules.requiresXOR",
+            key: "requires",
+            type: "xor",
+            elementId: this.id + "-DropArea-Required-Xor"
+        }
+    ];
+
+    public readonly dropListElementIds = this.dropAreaConfig.map((area) => area.elementId);
+
+    public selectedRule: { id: string, area: ITextBlockFormDropAreaConfig };
+
+    @Input()
+    public appFormGroup: FormGroup = new FormGroup({});
+
+    @ViewChildren("textAreaElement")
+    public queryList: QueryList<ElementRef<HTMLElement>>;
+
+    private focus$ = new Subject<FocusOptions>();
+
+    private destroy$ = new Subject();
+
+    public constructor(public form: TextBlockSettingsForm) {
+
+    }
+
+    public enterPredicate = (drag: CdkDrag<IAPITextBlockModel>) => drag?.data?.id !== this.getValue()?.id;
+
+    public ngOnInit() {
+        this.focus$.pipe(
+            switchMap((options) => {
+                return merge(of(0), this.queryList.changes).pipe(
+                    map(() => this.queryList.first),
+                    filter((element) => element != null),
+                    take(1),
+                    tap((element) => element.nativeElement.focus(options))
+                );
+            }),
+            takeUntil(this.destroy$),
+        ).subscribe();
+    }
+
+    public ngOnDestroy() {
+        this.destroy$.next();
+        this.destroy$.complete();
+    }
+
+    public focus(options?: FocusOptions) {
+        this.focus$.next(options);
+    }
+
+    public getValue(): IAPITextBlockModel {
+        return {...this.appFormGroup?.value};
+    }
+
+    public getRuleIds(area: ITextBlockFormDropAreaConfig): string[] {
+        return new GetRuleIdsOfTextBlockPipe().transform(this.getValue(), area.key, area.type);
+    }
+
+    public isDropAllowed(id: string, area: ITextBlockFormDropAreaConfig): boolean {
+        return id != null && id !== this.getValue().id && !this.getRuleIds(area).includes(id);
+    }
+
+    public onEnter(id: string, area: ITextBlockFormDropAreaConfig) {
+        if (this.isDropAllowed(id, area)) {
+            this.selectedRule = {id, area};
+        }
+    }
+
+    public onExit() {
+        this.selectedRule = undefined;
+    }
+
+    public onDrop(id: string, area: ITextBlockFormDropAreaConfig) {
+        if (this.isDropAllowed(id, area)) {
+            const ids = arrayJoin(this.getRuleIds(area), [id]);
+            this.patchRule(ids, area);
+        }
+        this.selectedRule = undefined;
+    }
+
+    public removeIdFromArea(id: string, area: ITextBlockFormDropAreaConfig) {
+        const ids = this.getRuleIds(area).filter((_) => _ !== id);
+        this.patchRule(ids, area);
+    }
+
+    private patchRule(ids: string[], area: ITextBlockFormDropAreaConfig) {
+        ids = filterDistinctValues(ids).sort((a, b) => this.form.compareIds(a, b));
+        area = {...area};
+        const value = this.getValue();
+        switch (area.key) {
+            case "excludes":
+                value.excludes = ids;
+                break;
+            case "requires":
+                value.requires = arrayJoin(value.requires)
+                    .filter((rule) => rule.type !== area.type);
+                value.requires.push({type: area.type, ids});
+                value.requires = value.requires.filter((rule) => arrayJoin(rule.ids).length > 0);
+                break;
+        }
+        this.appFormGroup.patchValue(value);
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/text-blocks/components/text-block-list-form/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/text-blocks/components/text-block-list-form/index.ts
index 990bb42..dfbb380 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/text-blocks/components/text-block-list-form/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./text-block-list-form.component";
diff --git a/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.html b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.html
new file mode 100644
index 0000000..bf7fcb3
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.html
@@ -0,0 +1,85 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<ng-container *ngFor="let group of appTextBlockGroups; trackBy: trackBy; let groupIndex = index;">
+  <ng-template #headerTemplateRef>
+    <div class="group-header">
+
+      <input #groupNameInputElement
+             (focusout)="appSelectedGroupIndex = undefined"
+             (input)="appGroupNameChange.emit({groupIndex: groupIndex, groupName: groupNameInputElement.value})"
+             (keydown.enter)="groupNameInputElement.blur()"
+             (keydown.escape)="groupNameInputElement.blur()"
+             *ngIf="groupIndex === appSelectedGroupIndex"
+             [disabled]="appDisabled"
+             [value]="group.groupName"
+             appAutoFocusAfterInit
+             autocomplete="off"
+             class="openk-input group-header--input">
+
+      <button (click)="appDeleteTextBlockGroup.emit({ groupIndex: groupIndex })"
+              *ngIf="appSelectedGroupIndex !== groupIndex"
+              [disabled]="appDisabled" class="openk-button openk-button-icon group-header--button"
+              style="margin-right: auto;"
+              type="button">
+        <mat-icon>delete_forever</mat-icon>
+      </button>
+
+      <button (click)="appSelectedGroupIndex = groupIndex"
+              *ngIf="appSelectedGroupIndex !== groupIndex"
+              [disabled]="appDisabled"
+              class="openk-button openk-button-icon group-header--button"
+              type="button">
+        <mat-icon>edit</mat-icon>
+      </button>
+
+      <button *ngIf="appSelectedGroupIndex === groupIndex"
+              [disabled]="appDisabled"
+              class="openk-button openk-button-icon group-header--button"
+              type="button">
+        <mat-icon>edit</mat-icon>
+      </button>
+
+      <button (click)="appAddTextBlockGroup.emit({ groupIndex: groupIndex }); appSelectedGroupIndex = groupIndex + 1;"
+              [disabled]="appDisabled"
+              class="openk-button openk-button-icon group-header--button"
+              type="button">
+        <mat-icon>add</mat-icon>
+      </button>
+    </div>
+  </ng-template>
+
+  <app-collapsible
+    [appHeaderTemplateRef]="headerTemplateRef"
+    [appSimpleCollapsible]="true"
+    [appTitle]="appSelectedGroupIndex === groupIndex ? '' : group.groupName"
+    class="text-block-group">
+    <app-text-block-list
+      (appAdd)="appCreateTextBlock.emit({ textBlock: $event.textBlock, index: $event.index, groupIndex: groupIndex})"
+      (appDelete)="appDeleteTextBlock.emit({ textBlock: $event.textBlock, index: $event.index, groupIndex: groupIndex})"
+      (appDown)="appMoveTextBlock.emit({ groupIndex: groupIndex, from: $event.index, to: $event.index + 1})"
+      (appEdit)="appEditTextBlock.emit({ textBlock: $event.textBlock, index: $event.index, groupIndex: groupIndex})"
+      (appUp)="appMoveTextBlock.emit({ groupIndex: groupIndex, from: $event.index, to: $event.index - 1})"
+      [appConnectedTo]="appConnectedTo"
+      [appDisabled]="appDisabled"
+      [appDuplicateOnDrag]="true"
+      [appForAdmin]="true"
+      [appListData]="group.textBlocks"
+      [appReplacements]="appReplacements"
+      [appSelectedIds]="[appSelectedId]"
+      [appShowPreview]="true"
+      class="openk-drag-list-with-hidden-placeholder">
+    </app-text-block-list>
+  </app-collapsible>
+</ng-container>
+
diff --git a/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.scss b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.scss
new file mode 100644
index 0000000..44dd8b7
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.scss
@@ -0,0 +1,55 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "src/styles/openk.styles";
+
+:host {
+  display: block;
+  width: 100%;
+  box-sizing: border-box;
+  padding: 0 0.5em 0.5em 0.5em;
+}
+
+.text-block-group {
+  border: 0;
+}
+
+.group-header {
+  display: flex;
+  flex: 1 1 auto;
+  width: auto;
+  align-items: center;
+  justify-content: flex-end;
+
+  color: get-color($openk-default-palette, 500, contrast);
+}
+
+.group-header--input {
+  padding: 0.1em;
+  width: auto;
+  flex: 1 1 auto;
+  box-sizing: border-box;
+  overflow-x: hidden;
+
+  &:focus {
+    display: block;
+    max-width: initial;
+    opacity: 1;
+  }
+}
+
+.group-header--button {
+  margin-left: 0.25em;
+}
+
+
diff --git a/src/app/features/settings/components/search/settings.component.spec.ts b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.spec.ts
similarity index 65%
copy from src/app/features/settings/components/search/settings.component.spec.ts
copy to src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.spec.ts
index 2bc2d9f..a8c0625 100644
--- a/src/app/features/settings/components/search/settings.component.spec.ts
+++ b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.spec.ts
@@ -12,20 +12,22 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {SettingsComponent} from "./settings.component";
+import {I18nModule} from "../../../../../core/i18n";
+import {TextBlockSettingsModule} from "../../text-block-settings.module";
+import {TextBlockListFormComponent} from "./text-block-list-form.component";
 
-describe("SettingsComponent", () => {
-    let component: SettingsComponent;
-    let fixture: ComponentFixture<SettingsComponent>;
+describe("TextBlockListFormComponent", () => {
+    let component: TextBlockListFormComponent;
+    let fixture: ComponentFixture<TextBlockListFormComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            declarations: [SettingsComponent]
+            imports: [TextBlockSettingsModule, I18nModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(SettingsComponent);
+        fixture = TestBed.createComponent(TextBlockListFormComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.ts b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.ts
new file mode 100644
index 0000000..ea50f06
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-list-form/text-block-list-form.component.ts
@@ -0,0 +1,67 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CdkDropList} from "@angular/cdk/drag-drop";
+import {Component, EventEmitter, Input, Output} from "@angular/core";
+import {IAPITextBlockGroupModel, IAPITextBlockModel} from "../../../../../core/api/text";
+
+@Component({
+    selector: "app-text-block-list-form",
+    templateUrl: "./text-block-list-form.component.html",
+    styleUrls: ["./text-block-list-form.component.scss"]
+})
+export class TextBlockListFormComponent {
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appConnectedTo: CdkDropList | string | Array<CdkDropList | string>;
+
+    @Input()
+    public appTextBlockGroups: IAPITextBlockGroupModel[];
+
+    @Input()
+    public appSelectedId: string;
+
+    @Output()
+    public appEditTextBlock = new EventEmitter<{ textBlock: IAPITextBlockModel, groupIndex: number, index: number }>();
+
+    @Output()
+    public appDeleteTextBlock = new EventEmitter<{ textBlock: IAPITextBlockModel, groupIndex: number, index: number }>();
+
+    @Output()
+    public appCreateTextBlock = new EventEmitter<{ textBlock: IAPITextBlockModel, groupIndex: number, index: number }>();
+
+    @Output()
+    public appAddTextBlockGroup = new EventEmitter<{ groupIndex: number }>();
+
+    @Output()
+    public appDeleteTextBlockGroup = new EventEmitter<{ groupIndex: number }>();
+
+    @Output()
+    public appGroupNameChange = new EventEmitter<{ groupIndex: number, groupName: string }>();
+
+    @Output()
+    public appMoveTextBlock = new EventEmitter<{ groupIndex: number, from: number, to: number }>();
+
+    public appSelectedGroupIndex: number;
+
+    @Input()
+    public appReplacements: { [key: string]: string } = {};
+
+    public trackBy(index: number) {
+        return index;
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/text-blocks/components/text-block-select-form/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/text-blocks/components/text-block-select-form/index.ts
index 990bb42..c38a89a 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/text-blocks/components/text-block-select-form/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./text-block-select-form.component";
diff --git a/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.html b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.html
new file mode 100644
index 0000000..a4031bd
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.html
@@ -0,0 +1,110 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<div class="list-select">
+
+  <label [for]="id + '-select'" class="list-select--label">
+    {{"settings.textBlocks.select" | translate}}
+  </label>
+
+  <div class="list-select--control">
+    <app-select (appValueChange)="setSelectedKey($event)"
+                [appDisabled]="form.formGroup.disabled || (selectKeys$ | async)?.length == 0"
+                [appId]="id + '-select'"
+                [appOptions]="selectKeys$ | async | arrayToSelectOptions"
+                [appValue]="selectedKey">
+    </app-select>
+  </div>
+
+
+  <button (click)="createNewSelectKey(); keyInputElement.focus()"
+          [disabled]="form.formGroup.disabled"
+          class="openk-button openk-button-icon list-select--button">
+    <mat-icon>add</mat-icon>
+  </button>
+
+
+  <label [for]="id + '-name'" class="list-select--label">
+    {{"settings.textBlocks.name" | translate}}
+  </label>
+
+  <div class="list-select--control">
+    <input #keyInputElement
+           (input)="changeSelectKey(selectedKey, keyInputElement.value)"
+           [disabled]="form.formGroup.disabled || selectedKey == null"
+           [id]="id + '-name'"
+           [value]="editedKey == null ? '' : editedKey"
+           autocomplete="off"
+           class="openk-input" type="text">
+  </div>
+
+
+  <button (click)="removeSelectKey(selectedKey); keyInputElement.focus();"
+          [disabled]="form.formGroup.disabled"
+          class="openk-button openk-button-icon list-select--button">
+    <mat-icon>delete_forever</mat-icon>
+  </button>
+
+  <ng-container *ngIf="!isEditedKeyValid">
+    <div></div>
+    <div class="list-select--error">
+      <span class="list-select--error--label">
+        {{"settings.textBlocks.invalidSelectKey" | translate}}
+      </span>
+    </div>
+    <div></div>
+  </ng-container>
+
+
+  <div class="list-select--options--title">
+    {{"settings.textBlocks.entries" | translate}}
+  </div>
+
+  <ng-container *ngIf="(form.selects | getFormArray : [selectedKey]) != null">
+
+    <div class="list-select--options">
+
+      <div *ngFor="let control of (form.selects | getFormArray : [selectedKey]).controls; let index = index;"
+           class="list--select--options--control">
+
+        <input #selectOptionInput
+               (input)="control.patchValue(selectOptionInput.value)"
+               [disabled]="form.formGroup.disabled"
+               [value]="control.value"
+               appAutoFocusAfterInit
+               autocomplete="off"
+               class="openk-input list--select--options--control--input"
+               type="text">
+
+        <div class="list--select--options--control--button">
+          <button (click)="removeSelectEntry(selectedKey, index)"
+                  [disabled]="form.formGroup.disabled" class="openk-button openk-button-icon">
+            <mat-icon>delete_forever</mat-icon>
+          </button>
+        </div>
+
+      </div>
+
+    </div>
+
+    <div class="list--select--options--control list-select--options--control--add">
+      <button (click)="addSelectEntry(selectedKey)"
+              [disabled]="form.formGroup.disabled"
+              class="openk-button openk-button-icon list-select--button">
+        <mat-icon>add</mat-icon>
+      </button>
+    </div>
+
+  </ng-container>
+
+</div>
diff --git a/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.scss b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.scss
new file mode 100644
index 0000000..06e0a27
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.scss
@@ -0,0 +1,98 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "src/styles/openk.styles";
+
+:host {
+  display: flex;
+  flex-flow: column;
+  padding: 1em;
+  box-sizing: border-box;
+  width: 100%;
+}
+
+
+.list-select {
+  display: grid;
+  width: 100%;
+  grid-template-columns: max-content auto max-content;
+  grid-gap: 0.5em;
+  margin-bottom: auto;
+  align-items: center;
+}
+
+.list-select--control {
+  display: flex;
+  width: 100%;
+  margin-left: 0.2em;
+
+  & > * {
+    flex: 1 1 100%;
+    width: 100%;
+  }
+}
+
+.list-select--button {
+  font-size: 0.8em;
+}
+
+.list-select--error {
+  display: flex;
+  margin-top: -0.4em;
+  margin-left: 0.85em;
+}
+
+.list-select--error--label {
+  color: $openk-error-color;
+  font-size: x-small;
+  font-style: italic;
+}
+
+
+.list-select--options--title {
+  align-self: start;
+  margin-top: 0.25em;
+}
+
+.list-select--options {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
+  grid-gap: 0.5em;
+}
+
+.list--select--options--control {
+  display: flex;
+  flex-flow: row nowrap;
+  align-items: center;
+  position: relative;
+}
+
+.list--select--options--control--input {
+  margin-left: 0.5em;
+  padding-right: 2em;
+  width: 100%;
+  flex: 1 1 100%;
+}
+
+.list--select--options--control--button {
+  position: absolute;
+  right: 0.4em;
+}
+
+.list-select--options--control--add {
+  align-self: end;
+  min-height: 2.137em;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
diff --git a/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.spec.ts b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.spec.ts
new file mode 100644
index 0000000..1b2e4ed
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.spec.ts
@@ -0,0 +1,115 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {IAPITextBlockModel} from "../../../../../core/api/text";
+import {I18nModule} from "../../../../../core/i18n";
+import {createTextBlockModelMock} from "../../../../../test";
+import {TextBlockSettingsModule} from "../../text-block-settings.module";
+import {TextBlockSettingsForm} from "../../TextBlockSettingsForm";
+import {TextBlockSelectFormComponent} from "./text-block-select-form.component";
+
+describe("TextBlockSelectFormComponent", () => {
+    const keyName = "test";
+    let component: TextBlockSelectFormComponent;
+    let fixture: ComponentFixture<TextBlockSelectFormComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [TextBlockSettingsModule, I18nModule],
+            providers: [{provide: TextBlockSettingsForm, useValue: new TextBlockSettingsForm()}]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(TextBlockSelectFormComponent);
+        component = fixture.componentInstance;
+        component.form.setForm({
+            selects: {
+                test: []
+            },
+            groups: [{
+                groupName: "Group",
+                textBlocks: [{...createTextBlockModelMock("19", createToken(keyName))}]
+            }],
+            negativeGroups: []
+        });
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should automatically select first key", () => {
+        expect(component.selectedKey).toBe(keyName);
+    });
+
+    it("should add/remove select entries", () => {
+        component.appDefaultSelectEntry = "Test";
+        component.setSelectedKey(keyName);
+        component.addSelectEntry(keyName);
+        expect(component.form.getSelectEntries(keyName)).toEqual(["Test"]);
+        component.removeSelectEntry(keyName, 0);
+        expect(component.form.getSelectEntries(keyName)).toEqual([]);
+    });
+
+    it("should generate/remove select keys", () => {
+        component.appDefaultSelectKey = keyName;
+        component.createNewSelectKey();
+        expect(component.form.getSelectKeys()).toEqual([keyName, keyName + "1"]);
+        component.removeSelectKey(keyName + "1");
+        component.removeSelectKey(keyName);
+        expect(component.form.getSelectKeys()).toEqual([]);
+        forEachTextBlock((textBlock) => {
+            expect(textBlock.text).toEqual("");
+        });
+    });
+
+    it("should change select keys", () => {
+        const newKeyName = "newKey";
+        component.changeSelectKey(keyName, newKeyName);
+        expect(component.form.getSelectKeys()).toEqual([newKeyName]);
+        forEachTextBlock((textBlock) => expect(textBlock.text).toEqual(createToken(newKeyName)));
+    });
+
+    it("should verify key names", () => {
+        component.appDefaultSelectKey = keyName;
+        component.createNewSelectKey();
+        const newKeyName = component.form.getSelectKeys().reverse()[0];
+
+        expect(component.editedKey).toBe(newKeyName);
+        expect(component.selectedKey).toBe(newKeyName);
+        expect(component.isEditedKeyValid).toBe(true);
+
+        component.changeSelectKey(newKeyName, newKeyName);
+        expect(component.editedKey).toBe(newKeyName);
+        expect(component.selectedKey).toBe(newKeyName);
+        expect(component.isEditedKeyValid).toBe(true);
+
+        [keyName, "Another Invalid Keyname"].forEach((invalidKeyName) => {
+            component.changeSelectKey(newKeyName, invalidKeyName);
+            expect(component.selectedKey).toBe(newKeyName);
+            expect(component.editedKey).toBe(invalidKeyName);
+            expect(component.isEditedKeyValid).toBe(false);
+        });
+    });
+
+    function forEachTextBlock(fn: (textBlock: IAPITextBlockModel) => any) {
+        component.form.getValue().groups.forEach((group) => group.textBlocks.forEach(fn));
+    }
+
+    function createToken(key: string) {
+        return `<s:${key}>`;
+    }
+});
diff --git a/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.ts b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.ts
new file mode 100644
index 0000000..7c53968
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-block-select-form/text-block-select-form.component.ts
@@ -0,0 +1,114 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, Input} from "@angular/core";
+import {defer, merge, of} from "rxjs";
+import {map, tap} from "rxjs/operators";
+import {arrayJoin} from "../../../../../util/store";
+import {TextBlockSettingsForm} from "../../TextBlockSettingsForm";
+
+@Component({
+    selector: "app-text-block-select-form",
+    templateUrl: "./text-block-select-form.component.html",
+    styleUrls: ["./text-block-select-form.component.scss"]
+})
+export class TextBlockSelectFormComponent {
+
+    private static id = 0;
+
+    @Input()
+    public appDefaultSelectEntry = "";
+
+    @Input()
+    public appDefaultSelectKey = "";
+
+    public readonly id = `TextBlockSelectFormComponent-${TextBlockSelectFormComponent.id++}`;
+
+    public selectKeys$ = defer(() => merge(of(0), this.form.selects.valueChanges)).pipe(
+        map(() => this.form.getSelectKeys()),
+        tap(() => this.reselectKey())
+    );
+
+    public selectedKey: string;
+
+    public editedKey: string;
+
+    private keyRegExp = /^[0-9a-zA-Z_\-]+$/;
+
+    public constructor(public form: TextBlockSettingsForm) {
+
+    }
+
+    public get isEditedKeyValid() {
+        return this.keyRegExp.test(this.editedKey)
+            && (this.editedKey === this.selectedKey || !this.isKeyInOptions(this.editedKey));
+    }
+
+    public setSelectedKey(key: string) {
+        this.editedKey = this.selectedKey = key;
+    }
+
+    public createNewSelectKey() {
+        const key = this.generateNewSelectKey();
+        this.form.addSelect(key);
+        this.editedKey = this.selectedKey = key;
+    }
+
+    public changeSelectKey(key: string, newKey: string) {
+        this.selectedKey = key;
+        this.editedKey = newKey;
+        if (key !== newKey && this.isEditedKeyValid) {
+            this.form.changeSelectKey(this.selectedKey, this.editedKey);
+            this.selectedKey = this.editedKey = newKey;
+        }
+    }
+
+    public removeSelectKey(key: string) {
+        const options = arrayJoin(this.getSelectKeys());
+        const indexOfKey = options.indexOf(key);
+        this.form.removeSelect(key);
+        this.editedKey = this.selectedKey = options.filter((_) => _ !== key)[Math.max(indexOfKey - 1, 0)];
+    }
+
+    public addSelectEntry(key: string) {
+        this.form.addSelectEntry(key, this.appDefaultSelectEntry);
+    }
+
+    public removeSelectEntry(key: string, index: number) {
+        this.form.removeSelectEntry(key, index);
+    }
+
+
+    private getSelectKeys() {
+        return this.form.getSelectKeys();
+    }
+
+    private reselectKey() {
+        if (arrayJoin(this.getSelectKeys()).length > 0 && !this.isKeyInOptions(this.selectedKey)) {
+            this.editedKey = this.selectedKey = this.getSelectKeys()[0];
+        }
+    }
+
+    private isKeyInOptions(key: string): boolean {
+        return arrayJoin(this.getSelectKeys()).includes(key);
+    }
+
+    private generateNewSelectKey() {
+        let key = this.appDefaultSelectKey;
+        for (let i = 1; this.isKeyInOptions(key); i++) {
+            key = this.appDefaultSelectKey + i;
+        }
+        return key;
+    }
+
+}
diff --git a/src/app/features/settings/text-blocks/components/text-blocks-settings.component.html b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.html
new file mode 100644
index 0000000..e921fed
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.html
@@ -0,0 +1,81 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<app-settings-side-menu
+  [appForAdmin]="isAdmin$ | async">
+</app-settings-side-menu>
+
+<app-side-menu-status
+  *appSideMenu="'center'"
+  [appLoadingMessage]="'core.loading' | translate"
+  [appLoading]="(loading$ | async)">
+
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+  <app-action-button (appClick)="submit()"
+                     [appDisabled]="loading$ | async"
+                     [appIcon]="'publish'"
+                     class="openk-success side-menu-button">
+    {{ "settings.save" | translate }}
+  </app-action-button>
+</ng-container>
+
+
+<div class="title">
+  <span class="title--label">
+    {{"settings.title" | translate}} - {{"settings.textBlocks.title" | translate}}
+  </span>
+</div>
+
+<app-collapsible [appTitle]="'settings.textBlocks.selects' | translate">
+
+  <app-text-block-select-form
+    [appDefaultSelectEntry]="'settings.textBlocks.default.selectEntry' | translate"
+    [appDefaultSelectKey]="'settings.textBlocks.default.selectKey' | translate">
+  </app-text-block-select-form>
+
+</app-collapsible>
+
+<app-collapsible
+  *ngFor="let item of config"
+  [appTitle]="item.negative ? 'Textbausteine (Negativantwort)' : 'Textbausteine'">
+
+  <div class="text-block-container">
+    <app-text-block-list-form
+      (appAddTextBlockGroup)="insertTextBlockGroup($event.groupIndex + 1, item.negative)"
+      (appCreateTextBlock)="insertTextBlock($event.groupIndex, $event.index + 1, item.negative); textBlockForm.focus();"
+      (appDeleteTextBlock)="deleteTextBlock($event?.textBlock?.id, $event?.groupIndex, $event?.index, item.negative)"
+      (appDeleteTextBlockGroup)="deleteTextBlockGroup($event.groupIndex, item.negative)"
+      (appEditTextBlock)="selectTextBlock($event?.groupIndex, $event?.index, item.negative); textBlockForm.focus();"
+      (appGroupNameChange)="changeGroupName($event.groupIndex, $event.groupName, item.negative)"
+      (appMoveTextBlock)="moveTextBlock($event.groupIndex, $event.from, $event.to, item.negative)"
+      [appConnectedTo]="textBlockForm.dropListElementIds"
+      [appDisabled]="form.formGroup.disabled"
+      [appReplacements]="replacements$ | async"
+      [appSelectedId]="item?.selection?.control?.value?.id"
+      [appTextBlockGroups]="item.negative ? form.negativeGroups.value : form.groups.value"
+      class="pane">
+    </app-text-block-list-form>
+
+    <div class="pane pane---with-border">
+      <app-text-block-form #textBlockForm
+                           [appFormGroup]="item.selection?.control"
+                           [appTextControlDefaultButtons]="defaultButtons$ | async "
+                           [appTextControlReplacementButtons]="replacementButtons$ | async"
+                           [appTextControlSelectButtons]="selectButtons$ | async">
+      </app-text-block-form>
+    </div>
+  </div>
+
+</app-collapsible>
diff --git a/src/app/features/settings/text-blocks/components/text-blocks-settings.component.scss b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.scss
new file mode 100644
index 0000000..ee9f6cb
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.scss
@@ -0,0 +1,65 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "src/styles/openk.styles";
+
+:host {
+  width: 100%;
+  padding: 1em;
+  box-sizing: border-box;
+  display: flex;
+  flex-flow: column;
+  max-width: 70em;
+  margin: 0 auto 0 auto;
+
+  & > * {
+    margin-bottom: 1em;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.side-menu-button {
+  width: 100%;
+
+  & + & {
+    margin-top: 1em;
+  }
+}
+
+.title {
+  margin-bottom: 1em;
+}
+
+.title--label {
+  font-size: x-large;
+  font-weight: 600;
+}
+
+
+.text-block-container {
+  display: flex;
+  width: 100%;
+}
+
+.pane {
+  width: 50%;
+  height: max(30em, calc(100vh - 20em));
+  overflow: auto;
+}
+
+.pane---with-border {
+  border-left: 1px solid $openk-form-border;
+}
diff --git a/src/app/features/settings/text-blocks/components/text-blocks-settings.component.spec.ts b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.spec.ts
new file mode 100644
index 0000000..c86751f
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.spec.ts
@@ -0,0 +1,61 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {MockStore, provideMockStore} from "@ngrx/store/testing";
+import {I18nModule} from "../../../../core";
+import {submitTextblockSettingsAction} from "../../../../store";
+import {TextBlockSettingsModule} from "../text-block-settings.module";
+import {TextBlocksSettingsComponent} from "./text-blocks-settings.component";
+
+describe("TextblockSettingsComponent", () => {
+    let component: TextBlocksSettingsComponent;
+    let fixture: ComponentFixture<TextBlocksSettingsComponent>;
+    let store: MockStore;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                TextBlockSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(TextBlocksSettingsComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+        store = TestBed.inject(MockStore);
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should track by index", () => {
+        expect(component.trackByIndex(19)).toBe(19);
+    });
+
+    it("should submit data", () => {
+        spyOn(store, "dispatch");
+        component.submit();
+        expect(store.dispatch).toHaveBeenCalledWith(submitTextblockSettingsAction({data: component.form.getValue()}));
+    });
+
+});
diff --git a/src/app/features/settings/text-blocks/components/text-blocks-settings.component.ts b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.ts
new file mode 100644
index 0000000..e5e1682
--- /dev/null
+++ b/src/app/features/settings/text-blocks/components/text-blocks-settings.component.ts
@@ -0,0 +1,212 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, OnDestroy, OnInit} from "@angular/core";
+import {FormGroup} from "@angular/forms";
+import {select, Store} from "@ngrx/store";
+import {TranslateService} from "@ngx-translate/core";
+import {defer, merge, Observable, of, Subject} from "rxjs";
+import {map, switchMap, takeUntil} from "rxjs/operators";
+import {
+    fetchTextblockSettingsAction,
+    getSettingsLoadingSelector,
+    getTextblockSettingsSelector,
+    isAdminSelector,
+    submitTextblockSettingsAction
+} from "../../../../store";
+import {TextBlockSettingsForm} from "../TextBlockSettingsForm";
+import {ITextBlockFormTextControlButton} from "./text-block-form";
+
+
+export interface ITextBlockSettingConfig {
+    selection?: {
+        groupIndex: number;
+        index: number;
+        control: FormGroup;
+    };
+    negative?: boolean;
+}
+
+@Component({
+    selector: "app-text-block-settings",
+    templateUrl: "./text-blocks-settings.component.html",
+    styleUrls: ["./text-blocks-settings.component.scss"],
+    providers: [
+        {
+            provide: TextBlockSettingsForm,
+            useValue: new TextBlockSettingsForm()
+        }
+    ]
+})
+export class TextBlocksSettingsComponent implements OnInit, OnDestroy {
+
+    public loading$ = this.store.pipe(select(getSettingsLoadingSelector));
+
+    public textBlockConfig$ = this.store.pipe(select(getTextblockSettingsSelector));
+
+    public isAdmin$ = this.store.pipe(select(isAdminSelector));
+
+    public selectKeys$ = defer(() => merge(of(0), this.form.selects.valueChanges)).pipe(
+        map(() => this.form.getSelectKeys())
+    );
+
+    public replacementKeys = ["id", "title", "city", "district", "sectors", "creationDate", "receiptDate", "dueDate", "customerReference"];
+
+    public additionalReplacementKeys = ["c-community", "c-communitySuffix", "c-company", "c-email", "c-firstName", "c-lastName",
+        "c-houseNumber", "c-postCode", "c-salutation", "c-street", "c-title"];
+
+    public replacements$: Observable<{ [key: string]: string }> = of([...this.replacementKeys, ...this.additionalReplacementKeys]).pipe(
+        switchMap((keys) => this.translateKeys(keys, "settings.textBlocks.replacements."))
+    );
+
+    public defaultButtons$: Observable<ITextBlockFormTextControlButton[]> = of(["freeText", "date"]).pipe(
+        switchMap((keys) => this.translateKeys(keys, "settings.textBlocks.replacements.")),
+        map((obj: { freeText: string, date: string }) => {
+            return [
+                {
+                    icon: "format_bold",
+                    token: "**",
+                    startToken: "**"
+                },
+                {
+                    icon: "format_italic",
+                    token: "__",
+                    startToken: "__"
+                },
+                {
+                    icon: "format_list_bulleted",
+                    startToken: "* ",
+                    token: "",
+                    requireLineBreak: true
+                },
+                {
+                    label: obj.freeText,
+                    icon: "edit",
+                    token: `<f:${obj.freeText}>`
+                },
+                {
+                    label: obj.date,
+                    icon: "today",
+                    token: `<d:${obj.date}>`
+                }
+            ];
+        })
+    );
+
+    public replacementButtons$: Observable<ITextBlockFormTextControlButton[]> = of(this.replacementKeys).pipe(
+        switchMap((keys) => this.translateKeys(keys, "settings.textBlocks.replacements.")),
+        map((obj) => {
+            return this.replacementKeys.map((key) => ({label: obj[key], token: `<t:${key}>`}));
+        })
+    );
+
+    public selectButtons$: Observable<ITextBlockFormTextControlButton[]> = defer(() => this.selectKeys$).pipe(
+        map((keys) => keys.map((key) => ({label: key, token: `<s:${key}>`, icon: "play_arrow", rotateIcon: true})))
+    );
+
+    public config: ITextBlockSettingConfig[] = [{}, {negative: true}];
+
+    private destroy$ = new Subject();
+
+    constructor(public store: Store, public translateService: TranslateService, public form: TextBlockSettingsForm) {
+
+    }
+
+    public ngOnInit() {
+        this.store.dispatch(fetchTextblockSettingsAction());
+        this.textBlockConfig$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
+            this.form.setForm(value);
+            this.config
+                .filter((_) => _.selection != null)
+                .forEach((_) => this.selectTextBlock(_.selection.groupIndex, _.selection.index, _.negative));
+        });
+        this.loading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => this.form.disable(loading));
+    }
+
+    public ngOnDestroy() {
+        this.destroy$.next();
+        this.destroy$.complete();
+    }
+
+    public submit() {
+        this.store.dispatch(submitTextblockSettingsAction({data: this.form.getValue()}));
+    }
+
+    public trackByIndex(index: number) {
+        return index;
+    }
+
+    public selectTextBlock(groupIndex: number, index: number, negative?: boolean) {
+        const control = this.form.getTextBlockFormArrayForGroup(groupIndex, negative)?.at(index);
+        this.config.find((_) => _.negative === negative).selection
+            = control instanceof FormGroup ? {groupIndex, index, control} : undefined;
+    }
+
+
+    public changeGroupName(groupIndex: number, groupName: string, negative?: boolean) {
+        this.form.getTextBlockGroups(negative).at(groupIndex)?.patchValue({groupName});
+    }
+
+    public insertTextBlockGroup(groupIndex: number, negative?: boolean) {
+        this.form.insertGroup(groupIndex, negative);
+        this.insertTextBlock(groupIndex, 0, negative);
+    }
+
+    public deleteTextBlockGroup(groupIndex: number, negative?: boolean) {
+        const length = this.form.removeGroup(groupIndex, negative);
+        if (length === 0) {
+            this.insertTextBlockGroup(0, negative);
+        }
+        this.deselect();
+    }
+
+
+    public insertTextBlock(groupIndex: number, index: number, negative?: boolean) {
+        this.form.insertTextBlock(groupIndex, index, negative);
+        this.selectTextBlock(groupIndex, index, negative);
+    }
+
+    public moveTextBlock(groupIndex: number, from: number, to: number, negative: boolean) {
+        this.form.moveTextBlockInGroup(groupIndex, from, to, negative);
+    }
+
+    public deleteTextBlock(id: string, groupIndex: number, index: number, negative?: boolean) {
+        const length = this.form.removeTextBlock(groupIndex, index, negative);
+        if (length === 0) {
+            this.insertTextBlock(groupIndex, 0, negative);
+        }
+        this.selectTextBlock(groupIndex, index, negative);
+    }
+
+    /**
+     * Returns an object, where each key from a given list has its translation as value.
+     * @param keys List of keys which are translated.
+     * @param translationKeyPrefix Prefix which is added to all keys prior translation (but is not part of any key in the result).
+     */
+    private translateKeys(keys: string[], translationKeyPrefix: string): Observable<{ [key: string]: string }> {
+        const translationKeys = keys.map((key) => translationKeyPrefix + key);
+        return this.translateService.get(translationKeys).pipe(
+            map((translation) => {
+                return keys.reduce((replacement, key, index) => ({
+                    ...replacement,
+                    [key]: translation[translationKeys[index]]
+                }), {});
+            })
+        );
+    }
+
+    private deselect(negative?: boolean) {
+        this.config.find((_) => _.negative === negative).selection = undefined;
+    }
+
+}
diff --git a/src/app/shared/leaflet/index.ts b/src/app/features/settings/text-blocks/index.ts
similarity index 81%
copy from src/app/shared/leaflet/index.ts
copy to src/app/features/settings/text-blocks/index.ts
index 849c149..5493fee 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/features/settings/text-blocks/index.ts
@@ -11,9 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./directives";
+export * from "./components";
 export * from "./pipes";
-export * from "./util";
 
-export * from "./leaflet.module";
-export * from "./leaflet-configuration.token";
+export * from "./text-block-settings.module";
+export * from "./TextBlockSettingsForm";
diff --git a/src/app/features/settings/text-blocks/pipes/get-rule-ids-of-text-block.pipe.spec.ts b/src/app/features/settings/text-blocks/pipes/get-rule-ids-of-text-block.pipe.spec.ts
new file mode 100644
index 0000000..7eca5dc
--- /dev/null
+++ b/src/app/features/settings/text-blocks/pipes/get-rule-ids-of-text-block.pipe.spec.ts
@@ -0,0 +1,47 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPITextBlockModel} from "../../../../core/api/text";
+import {GetRuleIdsOfTextBlockPipe} from "./get-rule-ids-of-text-block.pipe";
+
+describe("GetRuleIdsOfTextBlockPipe", () => {
+
+    let pipe: GetRuleIdsOfTextBlockPipe;
+
+    beforeEach(() => {
+        pipe = new GetRuleIdsOfTextBlockPipe();
+    });
+
+    it("should extract list of IDs for a given rule", () => {
+        const textBlock: IAPITextBlockModel = {
+            id: "1.9",
+            text: "",
+            requires: [
+                {
+                    type: "and",
+                    ids: ["1.119"]
+                }
+            ],
+            excludes: ["1.7", "1.19"]
+        };
+
+        expect(pipe.transform(null, "excludes")).toEqual([]);
+        expect(pipe.transform(textBlock, null)).toEqual([]);
+        expect(pipe.transform(textBlock, "requires")).toEqual([]);
+        expect(pipe.transform(textBlock, "requires", "xor")).toEqual([]);
+        expect(pipe.transform(textBlock, "excludes")).toEqual(textBlock.excludes);
+        expect(pipe.transform(textBlock, "requires", "and")).toEqual(textBlock.requires[0].ids);
+    });
+
+
+});
diff --git a/src/app/features/settings/text-blocks/pipes/get-rule-ids-of-text-block.pipe.ts b/src/app/features/settings/text-blocks/pipes/get-rule-ids-of-text-block.pipe.ts
new file mode 100644
index 0000000..5690593
--- /dev/null
+++ b/src/app/features/settings/text-blocks/pipes/get-rule-ids-of-text-block.pipe.ts
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {IAPITextBlockModel, TAPIRequireRuleType, TAPITextBlockRuleKey} from "../../../../core";
+import {arrayJoin} from "../../../../util";
+
+@Pipe({name: "getRuleIdsOfTextBlock"})
+export class GetRuleIdsOfTextBlockPipe implements PipeTransform {
+
+    public transform(
+        value: IAPITextBlockModel,
+        key: TAPITextBlockRuleKey,
+        type?: TAPIRequireRuleType
+    ): string[] {
+        if (value == null) {
+            return [];
+        }
+
+        switch (key) {
+            case "excludes":
+                return arrayJoin(value.excludes);
+            case "requires":
+                return arrayJoin(arrayJoin(value.requires).find((rule) => rule.type === type)?.ids);
+        }
+
+        return [];
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/text-blocks/pipes/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/text-blocks/pipes/index.ts
index 990bb42..379ba06 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/text-blocks/pipes/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./get-rule-ids-of-text-block.pipe";
diff --git a/src/app/features/settings/text-blocks/text-block-settings.module.ts b/src/app/features/settings/text-blocks/text-block-settings.module.ts
new file mode 100644
index 0000000..40fa396
--- /dev/null
+++ b/src/app/features/settings/text-blocks/text-block-settings.module.ts
@@ -0,0 +1,70 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {DragDropModule} from "@angular/cdk/drag-drop";
+import {CommonModule} from "@angular/common";
+import {NgModule} from "@angular/core";
+import {MatIconModule} from "@angular/material/icon";
+import {TranslateModule} from "@ngx-translate/core";
+import {CommonControlsModule} from "../../../shared/controls/common";
+import {SelectModule} from "../../../shared/controls/select";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
+import {AutoFocusAfterInitModule} from "../../../shared/layout/auto-focus-after-init";
+import {CollapsibleModule} from "../../../shared/layout/collapsible";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
+import {SharedPipesModule} from "../../../shared/pipes";
+import {TextBlockModule} from "../../../shared/text-block";
+import {SharedSettingsModule} from "../shared";
+import {TextBlocksSettingsComponent} from "./components";
+import {TextBlockFormComponent} from "./components/text-block-form";
+import {TextBlockListFormComponent} from "./components/text-block-list-form";
+import {TextBlockSelectFormComponent} from "./components/text-block-select-form";
+import {GetRuleIdsOfTextBlockPipe} from "./pipes";
+
+@NgModule({
+    imports: [
+        CommonModule,
+        TranslateModule,
+        MatIconModule,
+        DragDropModule,
+
+        ActionButtonModule,
+        AutoFocusAfterInitModule,
+        CollapsibleModule,
+        CommonControlsModule,
+        SelectModule,
+        SharedPipesModule,
+        SharedSettingsModule,
+        SideMenuModule,
+        TextBlockModule
+    ],
+    declarations: [
+        TextBlockFormComponent,
+        TextBlockListFormComponent,
+        TextBlockSelectFormComponent,
+        TextBlocksSettingsComponent,
+
+        GetRuleIdsOfTextBlockPipe
+    ],
+    exports: [
+        TextBlockFormComponent,
+        TextBlockListFormComponent,
+        TextBlockSelectFormComponent,
+        TextBlocksSettingsComponent,
+
+        GetRuleIdsOfTextBlockPipe
+    ]
+})
+export class TextBlockSettingsModule {
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/users/components/index.ts
similarity index 79%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/users/components/index.ts
index 990bb42..2d31e8c 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/users/components/index.ts
@@ -11,4 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./users-search";
+export * from "./users-settings-edit";
+export * from "./users-table";
+
+export * from "./users-settings.component";
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/users/components/users-search/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/users/components/users-search/index.ts
index 990bb42..ff482e5 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/users/components/users-search/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./users-settings-search.component";
diff --git a/src/app/features/settings/users/components/users-search/users-settings-search.component.html b/src/app/features/settings/users/components/users-search/users-settings-search.component.html
new file mode 100644
index 0000000..bf16ac1
--- /dev/null
+++ b/src/app/features/settings/users/components/users-search/users-settings-search.component.html
@@ -0,0 +1,96 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<div class="search">
+  <div class="search--bar">
+    <span class="search--bar--text">{{"settings.departments.search" | translate}}</span>
+    <app-searchbar
+      (appSearch)="setFilterParameter('q', $event)"
+      [appPlaceholder]="'settings.departments.placeholderSearch' | translate"
+      [appSearchText]="appValue?.q"
+      class="search--input">
+    </app-searchbar>
+  </div>
+
+  <div class="filters">
+
+    <button (click)="null"
+            class="openk-button openk-button-rounded"
+            type="button">
+      <mat-icon>filter_list</mat-icon>
+    </button>
+
+    <div class="filters--row">
+      <div *ngIf="(userRoleOptions$ | async) != null" class="filter-group filter-group---stacked">
+        <button
+          (click)="toggleFilterParameter('role', roleSelect.appValue)"
+          [class.openk-info]="appValue?.role != null"
+          class="openk-button openk-chip filters--btn filters--btn---margin">
+          {{"settings.users.table.role" | translate}}
+        </button>
+        <div class="filters--row-select-width">
+          <app-select #roleSelect
+                      (appValueChange)="setFilterParameter('role', $event)"
+                      [appOptions]="userRoleOptions$ | async"
+                      [appValue]="(userRoleOptions$ | async)[0].value"
+                      class="openk-info filters--select--input-width">
+          </app-select>
+        </div>
+
+      </div>
+
+      <div *ngIf="(appDepartmentGroups | objKeysToArray).length > 0" class="filter-group filter-group---stacked">
+        <div class="filters--block">
+          <div class="filter--block--entry">
+
+            <button
+              (click)="toggleFilterParameter('departmentGroupName', departmentGroupSelect.appValue)"
+              [class.openk-info]="appValue?.departmentGroupName != null"
+              class="openk-button openk-chip filters--btn filters--btn---margin">
+              {{"settings.users.departmentGroup" | translate}}
+            </button>
+            <div class="filters--row-select-width">
+              <app-select
+                #departmentGroupSelect
+                (appValueChange)="setFilterParameter('departmentGroupName', $event, true)"
+                [appOptions]="appDepartmentGroups | objKeysToArray | arrayToSelectOptions"
+                [appValue]="(appDepartmentGroups | objKeysToArray)[0]"
+                class="openk-info filters--select--input-width">
+              </app-select>
+            </div>
+
+          </div>
+          <div class="filter--block--entry">
+            <button
+              (click)="toggleFilterParameter('departmentName', departmentNameSelect.appValue)"
+              [class.openk-info]="appValue?.departmentName != null"
+              class="openk-button openk-chip filters--btn filters--btn---margin">
+              {{"settings.users.department" | translate}}
+            </button>
+            <div class="filters--row-select-width">
+              <app-select #departmentNameSelect
+                          (appValueChange)="setFilterParameter('departmentName', $event)"
+                          [appDisabled]="!(appDepartmentGroups[departmentGroupSelect.appValue]?.length > 0)"
+                          [appOptions]="appDepartmentGroups[departmentGroupSelect.appValue] | arrayToSelectOptions"
+                          [appValue]="appDepartmentGroups[departmentGroupSelect.appValue] == null ? null : appDepartmentGroups[departmentGroupSelect.appValue][0]"
+                          class="openk-info filters--select--input-width">
+              </app-select>
+            </div>
+
+          </div>
+        </div>
+      </div>
+    </div>
+
+  </div>
+</div>
diff --git a/src/app/features/settings/users/components/users-search/users-settings-search.component.scss b/src/app/features/settings/users/components/users-search/users-settings-search.component.scss
new file mode 100644
index 0000000..94f7248
--- /dev/null
+++ b/src/app/features/settings/users/components/users-search/users-settings-search.component.scss
@@ -0,0 +1,125 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "../../../../../../styles/openk.styles";
+
+:host {
+  width: 100%;
+  display: flex;
+  flex-flow: column;
+  overflow: auto;
+}
+
+.search {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 0.5em;
+}
+
+.search--bar {
+  display: flex;
+  align-items: center;
+  margin-bottom: 1em;
+}
+
+.search--bar--text {
+  margin-right: 0.5em;
+  min-width: 2.9em;
+}
+
+.search--input {
+  flex: 1;
+}
+
+.filters {
+  display: flex;
+  flex-direction: row;
+}
+
+.filters--row {
+  display: inline-flex;
+  align-items: baseline;
+  box-sizing: border-box;
+  flex-wrap: wrap;
+}
+
+.filters--btn {
+  min-width: 6em;
+  font-size: small;
+  border-color: get-color($openk-info-palette, 500);
+  margin-top: 0.5em;
+  margin-bottom: 0.5em;
+}
+
+.filters--btn---margin {
+  margin-right: 0.5em;
+}
+
+.filters--row-select-width {
+  width: 15em;
+}
+
+.filters--select--input-width {
+  width: 100%;
+}
+
+.filters--block {
+  display: flex;
+  flex-direction: column;
+}
+
+.openk-button-rounded {
+  font-size: 1.5em;
+  border: 0;
+  color: get-color($openk-info-palette);
+  margin-right: 0.25em;
+  min-width: 2em;
+
+  &:not(.openk-info) {
+    background-color: transparent;
+  }
+
+  &:not(.openk-info):active,
+  &:not(.openk-info):focus,
+  &:not(.openk-info):hover {
+    background-color: $openk-background-highlight;
+  }
+}
+
+.openk-chip {
+  height: 2em;
+}
+
+.filter-group {
+  background-color: $openk-background-highlight;
+  padding: 0.3em;
+  border-radius: 6px;
+  border: 1px solid $openk-form-border;
+  display: inline-flex;
+  align-items: center;
+  box-sizing: border-box;
+  height: 100%;
+  margin-right: 0.5em;
+}
+
+.filter-group---stacked {
+  height: initial;
+  margin-bottom: 0.5em;
+}
+
+.filter--block--entry {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
diff --git a/src/app/features/settings/users/components/users-search/users-settings-search.component.spec.ts b/src/app/features/settings/users/components/users-search/users-settings-search.component.spec.ts
new file mode 100644
index 0000000..769ccea
--- /dev/null
+++ b/src/app/features/settings/users/components/users-search/users-settings-search.component.spec.ts
@@ -0,0 +1,68 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {I18nModule} from "../../../../../core/i18n";
+import {UsersSettingsModule} from "../../users-settings.module";
+import {UsersSettingsSearchComponent} from "./users-settings-search.component";
+
+describe("UsersSettingsSearchComponent", () => {
+    let component: UsersSettingsSearchComponent;
+    let fixture: ComponentFixture<UsersSettingsSearchComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                UsersSettingsModule,
+                I18nModule
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(UsersSettingsSearchComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should set filter parameter", () => {
+        const departmentName = "test";
+        const departmentGroupName = "testGroup";
+        component.setFilterParameter("departmentName", departmentName);
+        expect(component.appValue).toEqual({departmentName});
+
+        component.setFilterParameter("departmentGroupName", departmentGroupName);
+        expect(component.appValue).toEqual({departmentName, departmentGroupName});
+
+        component.setFilterParameter("departmentGroupName", departmentGroupName, true);
+        expect(component.appValue).toEqual({departmentGroupName});
+    });
+
+    it("should toggle filter parameter", () => {
+        const departmentName = "test";
+        const departmentGroupName = "testGroup";
+        component.toggleFilterParameter("departmentName", departmentName);
+        expect(component.appValue).toEqual({departmentName});
+
+        component.toggleFilterParameter("departmentGroupName", departmentGroupName);
+        expect(component.appValue).toEqual({departmentName, departmentGroupName});
+
+        component.toggleFilterParameter("departmentGroupName", departmentGroupName);
+        expect(component.appValue).toEqual({departmentName, departmentGroupName: undefined});
+    });
+
+});
diff --git a/src/app/features/settings/users/components/users-search/users-settings-search.component.ts b/src/app/features/settings/users/components/users-search/users-settings-search.component.ts
new file mode 100644
index 0000000..ea02ebe
--- /dev/null
+++ b/src/app/features/settings/users/components/users-search/users-settings-search.component.ts
@@ -0,0 +1,70 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, Input} from "@angular/core";
+import {TranslateService} from "@ngx-translate/core";
+import {Observable, of} from "rxjs";
+import {map, switchMap} from "rxjs/operators";
+import {ALL_NON_TRIVIAL_USER_ROLES, EAPIUserRoles, IAPIDepartmentGroups, IAPIUserInfoExtended} from "../../../../../core";
+import {AbstractControlValueAccessorComponent} from "../../../../../shared/controls/common";
+import {ISelectOption} from "../../../../../shared/controls/select";
+import {IUserListFilter, roleToText} from "../../pipes";
+
+export interface IUserPlusSearch extends IAPIUserInfoExtended {
+    searchString?: string;
+}
+
+@Component({
+    selector: "app-users-settings-search",
+    templateUrl: "./users-settings-search.component.html",
+    styleUrls: ["./users-settings-search.component.scss"]
+})
+export class UsersSettingsSearchComponent extends AbstractControlValueAccessorComponent<IUserListFilter> {
+
+    @Input()
+    public appLoading: boolean;
+
+    @Input()
+    public appDepartmentGroups: IAPIDepartmentGroups;
+
+    public userRoleOptions$: Observable<ISelectOption<EAPIUserRoles>[]> = of(ALL_NON_TRIVIAL_USER_ROLES).pipe(
+        switchMap((roles) => this.translationService.get(roles.map(roleToText)).pipe(
+            map((translatedRoles) => roles.map((role) => {
+                return {label: translatedRoles[roleToText(role)], value: role};
+            }))
+        ))
+    );
+
+    public constructor(
+        private translationService: TranslateService
+    ) {
+        super();
+    }
+
+    public toggleFilterParameter(key: keyof IUserListFilter, value: string) {
+        const oldValue = {...this.appValue}[key];
+        this.setFilterParameter(key, oldValue != null ? undefined : value);
+    }
+
+    public setFilterParameter(key: keyof IUserListFilter, value: string, removeDepartmentNameFilter?: boolean) {
+        const newValue: IUserListFilter = {
+            ...this.appValue,
+            [key]: value
+        };
+        if (removeDepartmentNameFilter) {
+            delete newValue.departmentName;
+        }
+        this.writeValue(newValue, true);
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/users/components/users-settings-edit/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/users/components/users-settings-edit/index.ts
index 990bb42..a880da1 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/users/components/users-settings-edit/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./users-settings-edit.component";
diff --git a/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.html b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.html
new file mode 100644
index 0000000..14ad5c1
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.html
@@ -0,0 +1,58 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<app-collapsible
+  [appCollapsed]="false"
+  [appTitle]="('settings.users.editTitle' | translate) + appSelectedUser?.firstName + ' ' + appSelectedUser?.lastName + ' (' + appSelectedUser?.userName + ')'">
+
+  <div [formGroup]="formGroup" style="padding: 1em;">
+    <div class="email">
+      <span class="email--identifier">{{"settings.users.email" | translate}}</span>
+      <input [formControlName]="'email'"
+             [placeholder]="'settings.users.emailPlaceholder' | translate"
+             [type]="'email'"
+             appFormControlStatus
+             class="openk-input openk-info email--input"/>
+    </div>
+
+    <div *ngIf="appSelectedUser | appIsUserDivisionMember" class="department">
+      <span>{{"settings.users.departmentGroup" | translate}}</span>
+      <app-select #groupSelect
+                  (appValueChange)="patchValue({departmentName: null})"
+                  [appDisabled]="appDisabled"
+                  [appOptions]="appDepartments | appOptionsFromDepartmentStructure | async"
+                  [appPlaceholder]="'settings.users.departmentGroupPlaceholder' | translate"
+                  [formControlName]="'departmentGroupName'"
+                  class="openk-info department--select">
+      </app-select>
+      <span>{{"settings.users.department" | translate}}</span>
+      <app-select
+        [appDisabled]="appDisabled || ((appDepartments | appOptionsForDepartmentGroup: groupSelect.appValue).length === 0)"
+        [appOptions]="appDepartments | appOptionsForDepartmentGroup: groupSelect.appValue"
+        [appPlaceholder]="'settings.users.departmentPlaceholder' | translate"
+        [formControlName]="'departmentName'"
+        class="openk-info department--select"
+      ></app-select>
+
+    </div>
+
+    <div class="buttons">
+      <button (click)="submit(appSelectedUser.id)" [disabled]="appDisabled"
+              class="openk-button openk-success">
+        {{"settings.users.save" | translate}}
+      </button>
+    </div>
+  </div>
+
+</app-collapsible>
+
diff --git a/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.scss b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.scss
new file mode 100644
index 0000000..f10fc7c
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.scss
@@ -0,0 +1,54 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "../../../../../../styles/openk.styles";
+
+:host {
+  width: 100%;
+}
+
+.email {
+  display: flex;
+  width: 100%;
+  align-items: center;
+  margin-bottom: 1em;
+}
+
+.email--identifier {
+  margin-right: 0.5em;
+}
+
+.email--input {
+  flex: 1;
+}
+
+.department {
+  display: flex;
+  width: 100%;
+  align-items: center;
+  margin-bottom: 1em;
+
+  > * {
+    margin-right: 0.5em;
+  }
+}
+
+.department--select {
+  flex: 1 1 15em;
+  width: 15em;
+}
+
+.buttons {
+  display: flex;
+  justify-content: flex-end;
+}
diff --git a/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.spec.ts b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.spec.ts
new file mode 100644
index 0000000..824e3b9
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.spec.ts
@@ -0,0 +1,97 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {SimpleChange} from "@angular/core";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {I18nModule, IAPIUserInfoExtended, IAPIUserSettings} from "../../../../../core";
+import {EErrorCode} from "../../../../../store";
+import {UsersSettingsModule} from "../../users-settings.module";
+import {IUserSettingsEditFormValue, UsersSettingsEditComponent} from "./users-settings-edit.component";
+
+describe("UsersSettingsEditComponent", () => {
+    let component: UsersSettingsEditComponent;
+    let fixture: ComponentFixture<UsersSettingsEditComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                UsersSettingsModule,
+                I18nModule
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(UsersSettingsEditComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should disable form", () => {
+        expect(component.formGroup.disabled).toBe(false);
+        component.appDisabled = true;
+        component.ngOnChanges({
+            appDisabled: new SimpleChange(false, component.appDisabled, false)
+        });
+        expect(component.formGroup.disabled).toBe(true);
+    });
+
+    it("should patch form", () => {
+        const email = "A";
+        const departmentName = "B";
+        const departmentGroupName = "C";
+        const value: IUserSettingsEditFormValue = {email, departmentName, departmentGroupName};
+        component.appSelectedUser = {
+            ...{} as IAPIUserInfoExtended,
+            settings: {
+                email,
+                department: {
+                    group: departmentGroupName,
+                    name: departmentName
+                }
+            }
+        };
+        component.ngOnChanges({
+            appSelectedUser: new SimpleChange(null, component.appSelectedUser, false)
+        });
+        expect(component.formGroup.value).toEqual(value);
+    });
+
+    it("should submit form", () => {
+        const id = 19;
+        const email = "abc@ef.gh";
+        const departmentName = "B";
+        const departmentGroupName = "C";
+        const formValue: IUserSettingsEditFormValue = {email, departmentName, departmentGroupName};
+        const userSettings: IAPIUserSettings = {email, department: {name: departmentName, group: departmentGroupName}};
+        spyOn(component.appError, "emit");
+        spyOn(component.appSubmit, "emit");
+
+        component.patchValue({...formValue, email: "abc"});
+        component.submit(id);
+        expect(component.appError.emit).toHaveBeenCalledWith(EErrorCode.BAD_USER_DATA);
+
+        component.patchValue({...formValue, departmentName: null});
+        component.submit(id);
+        expect(component.appError.emit).toHaveBeenCalledWith(EErrorCode.MISSING_FORM_DATA);
+
+        component.patchValue(formValue);
+        component.submit(id);
+        expect(component.appSubmit.emit).toHaveBeenCalledWith({id, value: userSettings});
+    });
+
+});
diff --git a/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.ts b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.ts
new file mode 100644
index 0000000..99c453f
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings-edit/users-settings-edit.component.ts
@@ -0,0 +1,95 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core";
+import {FormControl, Validators} from "@angular/forms";
+import {IAPIUserInfoExtended, IAPIUserSettings} from "../../../../../core";
+import {EErrorCode} from "../../../../../store";
+import {createFormGroup} from "../../../../../util/forms";
+
+export interface IUserSettingsEditFormValue {
+    email: string;
+    departmentGroupName?: string;
+    departmentName?: string;
+}
+
+@Component({
+    selector: "app-users-settings-edit",
+    templateUrl: "./users-settings-edit.component.html",
+    styleUrls: ["./users-settings-edit.component.scss"]
+})
+export class UsersSettingsEditComponent implements OnChanges {
+
+    @Input()
+    public appSelectedUser: IAPIUserInfoExtended;
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appDepartments: { key: string, value: string[] }[];
+
+    @Output()
+    public appSubmit = new EventEmitter<{ id: number, value: IAPIUserSettings }>();
+
+    @Output()
+    public appError = new EventEmitter<EErrorCode>();
+
+    public formGroup = createFormGroup<IUserSettingsEditFormValue>({
+        email: new FormControl("", [Validators.email, Validators.required]),
+        departmentGroupName: new FormControl(null),
+        departmentName: new FormControl(null)
+    });
+
+    public patchValue(value: Partial<IUserSettingsEditFormValue>) {
+        this.formGroup.patchValue({...value});
+    }
+
+    public getValue(): IUserSettingsEditFormValue {
+        return this.formGroup.value;
+    }
+
+    public ngOnChanges(changes: SimpleChanges) {
+        const onChange = (keys: Array<keyof UsersSettingsEditComponent>, fn: () => any) => {
+            if (keys.some((key) => changes[key])) {
+                fn();
+            }
+        };
+        onChange(["appSelectedUser"], () => this.patchValue({
+            email: this.appSelectedUser.settings?.email,
+            departmentGroupName: this.appSelectedUser.settings?.department?.group,
+            departmentName: this.appSelectedUser.settings?.department?.name
+        }));
+        onChange(["appDisabled"], () => this.appDisabled ? this.formGroup.disable() : this.formGroup.enable());
+    }
+
+    public submit(id: number) {
+        const {email, departmentName, departmentGroupName} = {...this.getValue()};
+        this.formGroup.markAsTouched();
+
+        if (this.formGroup.invalid) {
+            this.appError.emit(EErrorCode.BAD_USER_DATA);
+            return;
+        }
+
+        if (departmentGroupName != null && departmentName == null) {
+            this.appError.emit(EErrorCode.MISSING_FORM_DATA);
+            return;
+        }
+
+        this.appSubmit.emit({
+            id,
+            value: {email, department: {name: departmentName, group: departmentGroupName}}
+        });
+    }
+}
diff --git a/src/app/features/settings/users/components/users-settings.component.html b/src/app/features/settings/users/components/users-settings.component.html
new file mode 100644
index 0000000..92338c5
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings.component.html
@@ -0,0 +1,57 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<app-settings-side-menu
+  [appForAdmin]="true">
+</app-settings-side-menu>
+
+<app-side-menu-status *appSideMenu="'center'"
+                      [appLoadingMessage]="'core.loading' | translate"
+                      [appLoading]="loading$ | async">
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+  <app-action-button (appClick)="sync()"
+                     [appDisabled]="false"
+                     [appIcon]="'refresh'"
+                     class="openk-success side-menu-button">
+    {{"settings.users.sync" | translate}}
+  </app-action-button>
+</ng-container>
+
+<div class="title">
+  <span class="title--label">
+    {{"settings.title" | translate}} - {{"settings.users.title" | translate}}
+  </span>
+</div>
+
+<app-users-settings-search
+  [appLoading]="loading$ | async"
+  [(appValue)]="filter"
+  [appDepartmentGroups]="departmentSettings$ | async | getDepartmentGroupsFromTablePipe">
+</app-users-settings-search>
+
+<app-users-settings-table
+  (appSelectedUserChange)="selectedUserId = $event;"
+  [appUsers]="users$ | async | filterUserList : filter"
+  class="user-table">
+</app-users-settings-table>
+
+<app-users-settings-edit (appError)="showErrorMessage($event)"
+                         (appSubmit)="submitUserSettings($event?.id, $event.value)"
+                         *ngIf="selectedUserId != null"
+                         [appDepartments]="departmentSettings$ | async | getDepartmentGroupsFromTablePipe | objToArray"
+                         [appDisabled]="(loading$ | async)"
+                         [appSelectedUser]="users$ | async | appGetUserForId : selectedUserId"
+                         class="user-edit">
+</app-users-settings-edit>
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.scss b/src/app/features/settings/users/components/users-settings.component.scss
similarity index 60%
copy from src/app/shared/leaflet/components/leaflet-map.component.scss
copy to src/app/features/settings/users/components/users-settings.component.scss
index e8e1a36..065a5e4 100644
--- a/src/app/shared/leaflet/components/leaflet-map.component.scss
+++ b/src/app/features/settings/users/components/users-settings.component.scss
@@ -11,43 +11,41 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
-
 :host {
   width: 100%;
-  height: 100%;
-
+  padding: 1em;
+  box-sizing: border-box;
   display: flex;
   flex-flow: column;
+  min-height: 100%;
+
+  max-width: 70em;
+  margin: 0 auto 0 auto;
 }
 
-.map {
-  height: 100%;
+.title {
+  margin-bottom: 1em;
+}
+
+.title--label {
+  font-size: x-large;
+  font-weight: 600;
+}
+
+.side-menu-button {
   width: 100%;
-  position: relative;
-  overflow: hidden;
-  box-sizing: border-box;
-  border: 1px solid $openk-form-border;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
 
-.map--leaflet {
-  width: 100%;
-  height: 100%;
+.user-table {
+  flex: 1 1 12em;
+  min-height: 12em;
+  margin-bottom: auto;
 }
 
-.map--button {
-  display: block;
-  width: fit-content;
-  height: fit-content;
-  position: absolute;
-  bottom: 10px;
-  left: 10px;
-  z-index: 1000;
-}
-
-.sub-caption {
-  color: $openk-form-border;
-  margin-left: auto;
-  font-size: smaller;
-  font-style: italic;
+.user-edit {
+  margin-top: 1em;
 }
diff --git a/src/app/features/settings/users/components/users-settings.component.spec.ts b/src/app/features/settings/users/components/users-settings.component.spec.ts
new file mode 100644
index 0000000..349a716
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings.component.spec.ts
@@ -0,0 +1,88 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {Store} from "@ngrx/store";
+import {provideMockStore} from "@ngrx/store/testing";
+import {I18nModule, IAPIUserSettings} from "../../../../core";
+import {
+    EErrorCode,
+    fetchDepartmentsSettingsAction,
+    fetchUsersAction,
+    setErrorAction,
+    submitUserSettingsAction,
+    syncUserDataAction
+} from "../../../../store";
+import {UsersSettingsModule} from "../users-settings.module";
+import {UsersSettingsComponent} from "./users-settings.component";
+
+describe("UsersSettingsComponent", () => {
+    let component: UsersSettingsComponent;
+    let fixture: ComponentFixture<UsersSettingsComponent>;
+    let store: Store;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                UsersSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(UsersSettingsComponent);
+        component = fixture.componentInstance;
+        store = fixture.componentRef.injector.get(Store);
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should fetch users data and department settings", () => {
+        const dispatchSpy = spyOn(store, "dispatch");
+        component.ngOnInit();
+        expect(dispatchSpy).toHaveBeenCalledWith(fetchUsersAction());
+        expect(dispatchSpy).toHaveBeenCalledWith(fetchDepartmentsSettingsAction());
+    });
+
+    it("should dispatch syncUserDataAction", () => {
+        const dispatchSpy = spyOn(store, "dispatch");
+        component.sync();
+        expect(dispatchSpy).toHaveBeenCalledWith(syncUserDataAction());
+    });
+
+    it("should submit user settings", () => {
+        const dispatchSpy = spyOn(store, "dispatch");
+        const userId = 19;
+        const data: IAPIUserSettings = {
+            email: "abc@ef.gh"
+        };
+        component.submitUserSettings(userId, data);
+        expect(dispatchSpy).toHaveBeenCalledWith(submitUserSettingsAction({userId, data}));
+    });
+
+    it("show error message", () => {
+        const dispatchSpy = spyOn(store, "dispatch");
+        component.showErrorMessage(EErrorCode.MISSING_FORM_DATA);
+        expect(dispatchSpy).toHaveBeenCalledWith(setErrorAction({error: EErrorCode.MISSING_FORM_DATA}));
+    });
+
+});
diff --git a/src/app/features/settings/users/components/users-settings.component.ts b/src/app/features/settings/users/components/users-settings.component.ts
new file mode 100644
index 0000000..c4918fb
--- /dev/null
+++ b/src/app/features/settings/users/components/users-settings.component.ts
@@ -0,0 +1,69 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, OnInit} from "@angular/core";
+import {select, Store} from "@ngrx/store";
+import {IAPIUserSettings} from "../../../../core/api/settings";
+import {
+    EErrorCode,
+    fetchDepartmentsSettingsAction,
+    fetchUsersAction,
+    getDepartmentsSettingsSelector,
+    getSettingsLoadingSelector,
+    getUsersSettingsSelector,
+    setErrorAction,
+    submitUserSettingsAction,
+    syncUserDataAction
+} from "../../../../store";
+import {IUserListFilter} from "../pipes";
+
+@Component({
+    selector: "app-users-settings",
+    templateUrl: "./users-settings.component.html",
+    styleUrls: ["./users-settings.component.scss"]
+})
+export class UsersSettingsComponent implements OnInit {
+
+    public users$ = this.store.pipe(select(getUsersSettingsSelector));
+
+    public loading$ = this.store.pipe(select(getSettingsLoadingSelector));
+
+    public departmentSettings$ = this.store.pipe(select(getDepartmentsSettingsSelector));
+
+    public selectedUserId: number;
+
+    public filter: IUserListFilter = {};
+
+    public constructor(
+        private readonly store: Store
+    ) {
+    }
+
+    public ngOnInit() {
+        this.store.dispatch(fetchUsersAction());
+        this.store.dispatch(fetchDepartmentsSettingsAction());
+    }
+
+    public sync() {
+        this.store.dispatch(syncUserDataAction());
+    }
+
+    public submitUserSettings(userId: number, data: IAPIUserSettings) {
+        this.store.dispatch(submitUserSettingsAction({userId, data}));
+    }
+
+    public showErrorMessage(error: EErrorCode) {
+        this.store.dispatch(setErrorAction({error}));
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/users/components/users-table/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/users/components/users-table/index.ts
index 990bb42..b6bc5b4 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/users/components/users-table/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./users-settings-table.component";
diff --git a/src/app/features/settings/users/components/users-table/users-settings-table.component.html b/src/app/features/settings/users/components/users-table/users-settings-table.component.html
new file mode 100644
index 0000000..4aa72e5
--- /dev/null
+++ b/src/app/features/settings/users/components/users-table/users-settings-table.component.html
@@ -0,0 +1,107 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<table [dataSource]="appUsers" cdk-table
+       class="openk-table openk-table---without-last-border">
+
+  <caption hidden>{{ "settings.users.title" | translate}}</caption>
+
+  <tr *cdkHeaderRowDef="appColumns; sticky: true" cdk-header-row></tr>
+
+  <tr *cdkRowDef="let myRowData; columns: appColumns" cdk-row class="table-row"></tr>
+
+  <ng-container cdkColumnDef="user-name">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.users.table.userName" | translate}}
+    </th>
+    <td (click)="onSelect(user)"
+        *cdkCellDef="let user"
+        [class.contact-list--table--cell---highlight]="user?.id === appSelectedUser"
+        cdk-cell
+        class="table-column">
+      {{user?.userName}}
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="first-name">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.users.table.firstName" | translate}}
+    </th>
+    <td (click)="onSelect(user)"
+        *cdkCellDef="let user"
+        [class.contact-list--table--cell---highlight]="user?.id === appSelectedUser"
+        cdk-cell
+        class="table-column">
+      {{user?.firstName}}
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="last-name">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.users.table.lastName" | translate}}
+    </th>
+    <td (click)="onSelect(user)"
+        *cdkCellDef="let user"
+        [class.contact-list--table--cell---highlight]="user?.id === appSelectedUser"
+        cdk-cell
+        class="table-column">
+      {{user?.lastName}}
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="email">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.users.table.email" | translate}}
+    </th>
+    <td (click)="onSelect(user)"
+        *cdkCellDef="let user"
+        [class.contact-list--table--cell---highlight]="user?.id === appSelectedUser"
+        cdk-cell
+        class="table-column">
+      {{user?.settings?.email}}
+    </td>
+  </ng-container>
+
+  <ng-container cdkColumnDef="roles">
+    <th *cdkHeaderCellDef="let header"
+        cdk-header-cell
+        class="table-column"
+        scope="col">
+      {{"settings.users.table.role" | translate}}
+    </th>
+    <td (click)="onSelect(user)"
+        *cdkCellDef="let user"
+        [class.contact-list--table--cell---highlight]="user?.id === appSelectedUser"
+        cdk-cell
+        class="table-column">
+      <div class="roles">
+        <span *ngFor="let role of (user?.roles | appRolesToDisplayText); let last = last;" class="role">
+          {{(role | translate) + (last ? '' : ', ')}}
+        </span>
+      </div>
+    </td>
+  </ng-container>
+
+</table>
diff --git a/src/app/features/settings/users/components/users-table/users-settings-table.component.scss b/src/app/features/settings/users/components/users-table/users-settings-table.component.scss
new file mode 100644
index 0000000..423b369
--- /dev/null
+++ b/src/app/features/settings/users/components/users-table/users-settings-table.component.scss
@@ -0,0 +1,66 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "../../../../../../styles/openk.styles";
+
+:host {
+  display: flex;
+  flex-flow: column;
+  width: 100%;
+  position: relative;
+  overflow: auto;
+  box-sizing: border-box;
+  border: 1px solid $openk-form-border;
+  border-radius: 4px;
+  background: $openk-background-card;
+  padding-bottom: 2px;
+}
+
+.openk-table---last-row-without-border:host {
+  padding-bottom: 0;
+}
+
+.table-column {
+  vertical-align: baseline;
+  text-align: start;
+}
+
+.table-row {
+  cursor: pointer;
+}
+
+.list--element--icon {
+  width: initial;
+  height: initial;
+  font-size: 0.5em;
+  margin-right: 1em;
+}
+
+.list--element---bold {
+  font-weight: 600;
+  margin-right: 0.25em;
+}
+
+.contact-list--table--cell---highlight {
+  background-color: get-color($openk-info-palette, 500);
+  color: get-color($openk-info-palette, 500, contrast);
+}
+
+.roles {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.role {
+  margin-right: 0.25em;
+}
diff --git a/src/app/features/settings/users/components/users-table/users-settings-table.component.spec.ts b/src/app/features/settings/users/components/users-table/users-settings-table.component.spec.ts
new file mode 100644
index 0000000..f622c39
--- /dev/null
+++ b/src/app/features/settings/users/components/users-table/users-settings-table.component.spec.ts
@@ -0,0 +1,58 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {Store} from "@ngrx/store";
+import {provideMockStore} from "@ngrx/store/testing";
+import {I18nModule, IAPIUserInfoExtended} from "../../../../../core";
+import {UsersSettingsModule} from "../../users-settings.module";
+import {UsersSettingsTableComponent} from "./users-settings-table.component";
+
+describe("UsersSettingsTableComponent", () => {
+    let component: UsersSettingsTableComponent;
+    let fixture: ComponentFixture<UsersSettingsTableComponent>;
+    let store: Store;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                UsersSettingsModule,
+                RouterTestingModule,
+                I18nModule
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(UsersSettingsTableComponent);
+        component = fixture.componentInstance;
+        store = fixture.componentRef.injector.get(Store);
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should emit user id when user is selected", () => {
+        const userChangeSpy = spyOn(component.appSelectedUserChange, "emit");
+        component.onSelect({id: 1} as IAPIUserInfoExtended);
+        expect(userChangeSpy).toHaveBeenCalledWith(1);
+    });
+
+});
diff --git a/src/app/features/settings/users/components/users-table/users-settings-table.component.ts b/src/app/features/settings/users/components/users-table/users-settings-table.component.ts
new file mode 100644
index 0000000..c20bc36
--- /dev/null
+++ b/src/app/features/settings/users/components/users-table/users-settings-table.component.ts
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, EventEmitter, Input, Output} from "@angular/core";
+import {IAPIUserInfoExtended} from "../../../../../core";
+
+@Component({
+    selector: "app-users-settings-table",
+    templateUrl: "./users-settings-table.component.html",
+    styleUrls: ["./users-settings-table.component.scss"]
+})
+export class UsersSettingsTableComponent {
+
+    @Input()
+    public appUsers: IAPIUserInfoExtended[];
+
+    @Input()
+    public appColumns = ["user-name", "first-name", "last-name", "email", "roles"];
+
+    @Output()
+    public appSelectedUserChange = new EventEmitter<number>();
+
+    public appSelectedUser: number = null;
+
+    public onSelect(user: IAPIUserInfoExtended) {
+        this.appSelectedUser = user.id;
+        this.appSelectedUserChange.emit(this.appSelectedUser);
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/features/settings/users/index.ts
similarity index 85%
copy from src/app/features/settings/components/index.ts
copy to src/app/features/settings/users/index.ts
index 990bb42..1e9e094 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/features/settings/users/index.ts
@@ -11,4 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./components";
+export * from "./pipes";
+
+export * from "./users-settings.module";
diff --git a/src/app/features/settings/users/pipes/filter-user-list.pipe.spec.ts b/src/app/features/settings/users/pipes/filter-user-list.pipe.spec.ts
new file mode 100644
index 0000000..22d50b9
--- /dev/null
+++ b/src/app/features/settings/users/pipes/filter-user-list.pipe.spec.ts
@@ -0,0 +1,50 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {EAPIUserRoles, IAPIUserInfoExtended} from "../../../../core";
+import {FilterUserListPipe} from "./filter-user-list.pipe";
+
+describe("FilterUserListPipe", () => {
+    const pipe = new FilterUserListPipe();
+    const departmentName = "departmentName";
+    const departmentGroupName = "departmentGroupName";
+    const userList: IAPIUserInfoExtended[] = [
+        createUserObject(17, EAPIUserRoles.SPA_ADMIN),
+        createUserObject(18, EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE),
+        createUserObject(19, EAPIUserRoles.DIVISION_MEMBER, departmentGroupName, departmentName)
+    ];
+
+    it("should filter the user list", () => {
+        expect(pipe.transform(userList)).toEqual(userList);
+        expect(pipe.transform(userList, {q: "19"})).toEqual([userList[2]]);
+        expect(pipe.transform(userList, {role: EAPIUserRoles.DIVISION_MEMBER})).toEqual([userList[2]]);
+        expect(pipe.transform(userList, {departmentName})).toEqual([userList[2]]);
+        expect(pipe.transform(userList, {departmentGroupName})).toEqual([userList[2]]);
+    });
+
+});
+
+function createUserObject(id: number, role: EAPIUserRoles, departmentGroupName?: string, departmenName?: string): IAPIUserInfoExtended {
+    return {
+        ...{} as IAPIUserInfoExtended,
+        id,
+        roles: [role],
+        settings: {
+            email: "test@email" + id + ".org",
+            department: departmenName == null ? undefined : {
+                group: departmentGroupName,
+                name: departmenName
+            }
+        }
+    };
+}
diff --git a/src/app/features/settings/users/pipes/filter-user-list.pipe.ts b/src/app/features/settings/users/pipes/filter-user-list.pipe.ts
new file mode 100644
index 0000000..58749c1
--- /dev/null
+++ b/src/app/features/settings/users/pipes/filter-user-list.pipe.ts
@@ -0,0 +1,52 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {EAPIUserRoles, IAPIUserInfoExtended} from "../../../../core";
+import {arrayJoin, filterDistinctValues} from "../../../../util";
+
+export interface IUserListFilter {
+    q?: string;
+    role?: EAPIUserRoles;
+    departmentGroupName?: string;
+    departmentName?: string;
+}
+
+@Pipe({name: "filterUserList"})
+export class FilterUserListPipe implements PipeTransform {
+
+    public transform(value: IAPIUserInfoExtended[], filter?: IUserListFilter): IAPIUserInfoExtended[] {
+        filter = {...filter};
+        return arrayJoin(value)
+            .filter((user) => {
+                const department = {...user?.settings?.department};
+                return user != null
+                    && (filter.departmentGroupName == null || department.group === filter.departmentGroupName)
+                    && (filter.departmentName == null || department.name === filter.departmentName)
+                    && (filter.role == null || user.roles.includes(filter.role));
+            })
+            .filter((user) => {
+                const searchString = filterDistinctValues(arrayJoin([
+                    user.firstName,
+                    user.lastName,
+                    user.userName,
+                    user.settings?.email
+                ])).join(" ").toLowerCase().trim();
+                const searchTokens = filter?.q?.toLowerCase()
+                    .replace(/[;,.]/g, " ")
+                    .split(" ");
+                return !arrayJoin(searchTokens).some((_) => !searchString.includes(_));
+            });
+    }
+
+}
diff --git a/src/app/features/settings/users/pipes/get-user-for-id.pipe.spec.ts b/src/app/features/settings/users/pipes/get-user-for-id.pipe.spec.ts
new file mode 100644
index 0000000..ed3c0f7
--- /dev/null
+++ b/src/app/features/settings/users/pipes/get-user-for-id.pipe.spec.ts
@@ -0,0 +1,37 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPIUserInfoExtended} from "../../../../core";
+import {GetUserForIdPipe} from "./get-user-for-id.pipe";
+
+describe("GetUserForIdPipe", () => {
+
+    const pipe = new GetUserForIdPipe();
+    const user = {id: 2} as IAPIUserInfoExtended;
+    const users: IAPIUserInfoExtended[] = [
+        {id: 1},
+        user,
+        {id: 3},
+        {id: 4}
+    ] as IAPIUserInfoExtended[];
+
+    describe("transform", () => {
+
+        it("should return the user with the given id", () => {
+            const result = pipe.transform(users, 2);
+            expect(result).toBeTruthy();
+            expect(result).toEqual(user);
+        });
+    });
+});
+
diff --git a/src/app/features/settings/components/search/settings.component.ts b/src/app/features/settings/users/pipes/get-user-for-id.pipe.ts
similarity index 62%
rename from src/app/features/settings/components/search/settings.component.ts
rename to src/app/features/settings/users/pipes/get-user-for-id.pipe.ts
index edced66..4412627 100644
--- a/src/app/features/settings/components/search/settings.component.ts
+++ b/src/app/features/settings/users/pipes/get-user-for-id.pipe.ts
@@ -11,13 +11,17 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {Component} from "@angular/core";
+import {Pipe, PipeTransform} from "@angular/core";
+import {IAPIUserInfoExtended} from "../../../../core";
 
-@Component({
-    selector: "app-settings",
-    templateUrl: "./settings.component.html",
-    styleUrls: ["./settings.component.scss"]
+@Pipe({
+    name: "appGetUserForId"
 })
-export class SettingsComponent {
+export class GetUserForIdPipe implements PipeTransform {
+
+    public transform(users: IAPIUserInfoExtended[], id: number): IAPIUserInfoExtended {
+        return users.find((_) => _.id === id);
+    }
 
 }
+
diff --git a/src/app/shared/leaflet/index.ts b/src/app/features/settings/users/pipes/index.ts
similarity index 66%
copy from src/app/shared/leaflet/index.ts
copy to src/app/features/settings/users/pipes/index.ts
index 849c149..a8205fe 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/features/settings/users/pipes/index.ts
@@ -11,9 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./directives";
-export * from "./pipes";
-export * from "./util";
-
-export * from "./leaflet.module";
-export * from "./leaflet-configuration.token";
+export * from "./filter-user-list.pipe";
+export * from "./get-user-for-id.pipe";
+export * from "./is-user-division-member.pipe";
+export * from "./options-for-department-group.pipe";
+export * from "./options-from-department-structure.pipe";
+export * from "./roles-to-display-text.pipe";
diff --git a/src/app/features/settings/users/pipes/is-user-division-member.pipe.spec.ts b/src/app/features/settings/users/pipes/is-user-division-member.pipe.spec.ts
new file mode 100644
index 0000000..40da5f4
--- /dev/null
+++ b/src/app/features/settings/users/pipes/is-user-division-member.pipe.spec.ts
@@ -0,0 +1,42 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {EAPIUserRoles, IAPIUserInfoExtended} from "../../../../core";
+import {IsUserDivisionMemberPipe} from "./is-user-division-member.pipe";
+
+describe("IsUserDivisionMemberPipe", () => {
+
+    const pipe = new IsUserDivisionMemberPipe();
+    const divisionMember: IAPIUserInfoExtended = {
+        roles: [
+            EAPIUserRoles.DIVISION_MEMBER,
+            EAPIUserRoles.SPA_CUSTOMER
+        ]
+    } as IAPIUserInfoExtended;
+    const officialInCharge: IAPIUserInfoExtended = {
+        roles: [
+            EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE
+        ]
+    } as IAPIUserInfoExtended;
+
+    describe("transform", () => {
+
+        it("should return if the given user has role division member", () => {
+            let result = pipe.transform(divisionMember);
+            expect(result).toBeTrue();
+            result = pipe.transform(officialInCharge);
+            expect(result).toBeFalse();
+        });
+    });
+});
+
diff --git a/src/app/features/mail/pipes/sender-split-name-mail.pipe.ts b/src/app/features/settings/users/pipes/is-user-division-member.pipe.ts
similarity index 62%
copy from src/app/features/mail/pipes/sender-split-name-mail.pipe.ts
copy to src/app/features/settings/users/pipes/is-user-division-member.pipe.ts
index 3cfb33d..cab16d6 100644
--- a/src/app/features/mail/pipes/sender-split-name-mail.pipe.ts
+++ b/src/app/features/settings/users/pipes/is-user-division-member.pipe.ts
@@ -12,16 +12,16 @@
  ********************************************************************************/
 
 import {Pipe, PipeTransform} from "@angular/core";
-import {arrayJoin} from "../../../util/store";
+import {EAPIUserRoles, IAPIUserInfoExtended} from "../../../../core";
 
 @Pipe({
-    name: "appSenderSplitNameMail"
+    name: "appIsUserDivisionMember"
 })
-export class SenderSplitNameMailPipe implements PipeTransform {
+export class IsUserDivisionMemberPipe implements PipeTransform {
 
-    public transform(text: string): Array<string> {
-        const textAsArray = arrayJoin(text?.split(/(?=<)/g));
-        return [textAsArray[0], textAsArray.slice(1).join("")].filter(x => x);
-        // return text.split(/(?=<)(.+)/);
+    public transform(user: IAPIUserInfoExtended): boolean {
+        return user?.roles?.find((_) => _ === EAPIUserRoles.DIVISION_MEMBER) != null;
     }
+
 }
+
diff --git a/src/app/features/settings/users/pipes/options-for-department-group.pipe.spec.ts b/src/app/features/settings/users/pipes/options-for-department-group.pipe.spec.ts
new file mode 100644
index 0000000..7083144
--- /dev/null
+++ b/src/app/features/settings/users/pipes/options-for-department-group.pipe.spec.ts
@@ -0,0 +1,46 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {OptionsForDepartmentGroupPipe} from "./options-for-department-group.pipe";
+
+describe("OptionsForDepartmentGroupPipe", () => {
+
+    const pipe = new OptionsForDepartmentGroupPipe();
+
+    const departments: { key: string, value: string[] }[] = [
+        {key: "group1", value: ["group1-value1", "group1-value2"]},
+        {key: "group2", value: ["group2-value1", "group2-value2"]},
+        {key: "group3", value: ["group3-value1", "group3-value2"]}
+    ];
+
+    describe("transform", () => {
+
+        it("should return an empty array for missing inputs", () => {
+            let result = pipe.transform(null, null);
+            expect(result).toEqual([]);
+            result = pipe.transform(undefined, undefined);
+            expect(result).toEqual([]);
+        });
+
+        it("should return the options for the given group from departments", () => {
+            let result = pipe.transform(departments, "anotherValue");
+            expect(result).toEqual([]);
+            result = pipe.transform(departments, "group2");
+            expect(result).toEqual([
+                {label: "group2-value1", value: "group2-value1"},
+                {label: "group2-value2", value: "group2-value2"}
+            ]);
+        });
+    });
+});
+
diff --git a/src/app/features/settings/users/pipes/options-for-department-group.pipe.ts b/src/app/features/settings/users/pipes/options-for-department-group.pipe.ts
new file mode 100644
index 0000000..f873fc9
--- /dev/null
+++ b/src/app/features/settings/users/pipes/options-for-department-group.pipe.ts
@@ -0,0 +1,28 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {arrayJoin} from "../../../../util";
+
+@Pipe({
+    name: "appOptionsForDepartmentGroup"
+})
+export class OptionsForDepartmentGroupPipe implements PipeTransform {
+
+    public transform(departments: { key: string, value: string[] }[], group: string): { label: string, value: string }[] {
+        return arrayJoin(arrayJoin(departments)
+            .find((_) => _.key === group)?.value)
+            .map((_) => ({label: _, value: _}));
+    }
+
+}
diff --git a/src/app/features/settings/users/pipes/options-from-department-structure.pipe.spec.ts b/src/app/features/settings/users/pipes/options-from-department-structure.pipe.spec.ts
new file mode 100644
index 0000000..5294a1d
--- /dev/null
+++ b/src/app/features/settings/users/pipes/options-from-department-structure.pipe.spec.ts
@@ -0,0 +1,52 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {async, TestBed} from "@angular/core/testing";
+import {TranslateService} from "@ngx-translate/core";
+import {of} from "rxjs";
+import {I18nModule} from "../../../../core";
+import {OptionsFromDepartmentStructurePipe} from "./options-from-department-structure.pipe";
+
+describe("OptionsFromDepartmentStructurePipe", () => {
+
+    const translatedLabel = "translatedLabel";
+    let pipe: OptionsFromDepartmentStructurePipe;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [I18nModule]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        const translateService = TestBed.inject(TranslateService);
+        pipe = new OptionsFromDepartmentStructurePipe(TestBed.inject(TranslateService));
+        spyOn(translateService, "get").and.returnValue(of(translatedLabel));
+    });
+
+    it("should get option array for departments", async () => {
+        const groupName = "groupName";
+        await expectAsync(pipe.transform([{key: groupName, value: []}])).toBeResolvedTo([
+            {
+                label: translatedLabel,
+                value: null
+            },
+            {
+                label: groupName,
+                value: groupName
+            }
+        ]);
+    });
+
+});
+
diff --git a/src/app/features/settings/users/pipes/options-from-department-structure.pipe.ts b/src/app/features/settings/users/pipes/options-from-department-structure.pipe.ts
new file mode 100644
index 0000000..aec9c2b
--- /dev/null
+++ b/src/app/features/settings/users/pipes/options-from-department-structure.pipe.ts
@@ -0,0 +1,38 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {TranslateService} from "@ngx-translate/core";
+import {arrayJoin} from "../../../../util";
+
+@Pipe({
+    name: "appOptionsFromDepartmentStructure"
+})
+export class OptionsFromDepartmentStructurePipe implements PipeTransform {
+
+    public constructor(private translationService: TranslateService) {
+    }
+
+    public async transform(departments: { key: string, value: string[] }[]): Promise<{ label: string, value: string }[]> {
+        const translation = await this.translationService.get("settings.users.noDepartment").toPromise();
+        return arrayJoin(
+            [{label: translation, value: null}],
+            departmentsToGroupOptions(departments)
+        );
+    }
+
+}
+
+export function departmentsToGroupOptions(departments: { key: string, value: string[] }[]) {
+    return arrayJoin(departments).map((_) => ({label: _.key, value: _.key}));
+}
diff --git a/src/app/features/settings/users/pipes/roles-to-display-text.pipe.ts b/src/app/features/settings/users/pipes/roles-to-display-text.pipe.ts
new file mode 100644
index 0000000..1889180
--- /dev/null
+++ b/src/app/features/settings/users/pipes/roles-to-display-text.pipe.ts
@@ -0,0 +1,44 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {EAPIUserRoles} from "../../../../core";
+import {arrayJoin} from "../../../../util";
+
+@Pipe({
+    name: "appRolesToDisplayText"
+})
+export class RolesToDisplayTextPipe implements PipeTransform {
+
+    public transform(roles: EAPIUserRoles[]): string[] {
+        return arrayJoin(roles)
+            .filter((_) => _ !== EAPIUserRoles.ROLE_SPA_ACCESS)
+            .sort()
+            .map((_) => roleToText(_))
+            .filter((_) => _ !== "");
+    }
+
+}
+
+export function roleToText(role: EAPIUserRoles): string {
+    switch (role) {
+        case EAPIUserRoles.DIVISION_MEMBER:
+        case EAPIUserRoles.SPA_ADMIN:
+        case EAPIUserRoles.SPA_APPROVER:
+        case EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE:
+        case EAPIUserRoles.SPA_CUSTOMER:
+            return "settings.users.role." + role;
+        default:
+            return role;
+    }
+}
diff --git a/src/app/features/settings/users/users-settings.module.ts b/src/app/features/settings/users/users-settings.module.ts
new file mode 100644
index 0000000..915dd84
--- /dev/null
+++ b/src/app/features/settings/users/users-settings.module.ts
@@ -0,0 +1,84 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CdkTableModule} from "@angular/cdk/table";
+import {CommonModule} from "@angular/common";
+import {NgModule} from "@angular/core";
+import {ReactiveFormsModule} from "@angular/forms";
+import {MatIconModule} from "@angular/material/icon";
+import {TranslateModule} from "@ngx-translate/core";
+import {CommonControlsModule} from "../../../shared/controls/common";
+import {SelectModule} from "../../../shared/controls/select";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
+import {CollapsibleModule} from "../../../shared/layout/collapsible";
+import {SearchbarModule} from "../../../shared/layout/searchbar";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
+import {SharedPipesModule} from "../../../shared/pipes";
+import {SharedSettingsModule} from "../shared";
+import {UsersSettingsComponent, UsersSettingsEditComponent, UsersSettingsSearchComponent, UsersSettingsTableComponent} from "./components";
+import {
+    FilterUserListPipe,
+    GetUserForIdPipe,
+    IsUserDivisionMemberPipe,
+    OptionsForDepartmentGroupPipe,
+    OptionsFromDepartmentStructurePipe,
+    RolesToDisplayTextPipe
+} from "./pipes";
+
+@NgModule({
+    imports: [
+        CommonModule,
+        TranslateModule,
+        MatIconModule,
+        ReactiveFormsModule,
+        CdkTableModule,
+
+        SharedSettingsModule,
+        ActionButtonModule,
+        CollapsibleModule,
+        SearchbarModule,
+        SelectModule,
+        SideMenuModule,
+        CommonControlsModule,
+        SharedPipesModule
+    ],
+    declarations: [
+        UsersSettingsSearchComponent,
+        UsersSettingsEditComponent,
+        UsersSettingsTableComponent,
+        UsersSettingsComponent,
+
+        FilterUserListPipe,
+        GetUserForIdPipe,
+        IsUserDivisionMemberPipe,
+        OptionsForDepartmentGroupPipe,
+        OptionsFromDepartmentStructurePipe,
+        RolesToDisplayTextPipe
+    ],
+    exports: [
+        UsersSettingsSearchComponent,
+        UsersSettingsEditComponent,
+        UsersSettingsTableComponent,
+        UsersSettingsComponent,
+
+        FilterUserListPipe,
+        GetUserForIdPipe,
+        IsUserDivisionMemberPipe,
+        OptionsForDepartmentGroupPipe,
+        OptionsFromDepartmentStructurePipe,
+        RolesToDisplayTextPipe
+    ]
+})
+export class UsersSettingsModule {
+
+}
diff --git a/src/app/shared/controls/common/common-controls.module.ts b/src/app/shared/controls/common/common-controls.module.ts
index d924cbd..dc525f5 100644
--- a/src/app/shared/controls/common/common-controls.module.ts
+++ b/src/app/shared/controls/common/common-controls.module.ts
@@ -11,17 +11,20 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 import {NgModule} from "@angular/core";
+import {AutoInsertTextFieldTokenDirective} from "./directives/auto-insert-text-field-token";
 import {AutoTextFieldResizeDirective} from "./directives/auto-text-field-resize";
 import {FormControlStatusDirective} from "./directives/form-control-status";
 
 @NgModule({
     declarations: [
         FormControlStatusDirective,
-        AutoTextFieldResizeDirective
+        AutoTextFieldResizeDirective,
+        AutoInsertTextFieldTokenDirective
     ],
     exports: [
         FormControlStatusDirective,
-        AutoTextFieldResizeDirective
+        AutoTextFieldResizeDirective,
+        AutoInsertTextFieldTokenDirective
     ]
 })
 export class CommonControlsModule {
diff --git a/src/app/shared/controls/common/directives/auto-insert-text-field-token/auto-insert-text-field-token.directive.ts b/src/app/shared/controls/common/directives/auto-insert-text-field-token/auto-insert-text-field-token.directive.ts
new file mode 100644
index 0000000..2a82d55
--- /dev/null
+++ b/src/app/shared/controls/common/directives/auto-insert-text-field-token/auto-insert-text-field-token.directive.ts
@@ -0,0 +1,62 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Directive, ElementRef} from "@angular/core";
+
+@Directive({
+    selector: "textarea [appAutoInsertTextFieldToken]",
+    exportAs: "appAutoInsertTextFieldToken"
+})
+export class AutoInsertTextFieldTokenDirective {
+
+    public constructor(public elementRef: ElementRef<HTMLTextAreaElement>) {
+
+    }
+
+    public insert(token: string, startToken?: string, requireLineBreak?: boolean) {
+        const withStartToken = startToken != null;
+        startToken = withStartToken ? startToken : "";
+        const selectionStart = this.elementRef.nativeElement.selectionStart;
+        const selectionEnd = this.elementRef.nativeElement.selectionEnd;
+        const value = this.elementRef.nativeElement.value;
+        const valueFirstPart = value.slice(0, selectionStart);
+        if (requireLineBreak && withStartToken && valueFirstPart.length > 0 && !valueFirstPart.endsWith("\n")) {
+            startToken = "\n" + startToken;
+        }
+
+        this.setValue(""
+            + valueFirstPart
+            + startToken
+            + value.slice(selectionStart, selectionEnd)
+            + token
+            + value.slice(selectionEnd)
+        );
+
+        this.setCursor(selectionEnd + startToken.length + (withStartToken ? 0 : token.length));
+    }
+
+    public setValue(value: string) {
+        this.elementRef.nativeElement.value = value;
+        this.elementRef.nativeElement.dispatchEvent(new InputEvent("input", {
+            inputType: "insertText",
+            data: value
+        }));
+    }
+
+    public setCursor(position: number) {
+        this.elementRef.nativeElement.selectionStart = position;
+        this.elementRef.nativeElement.selectionEnd = position;
+        this.elementRef.nativeElement.focus();
+    }
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/shared/controls/common/directives/auto-insert-text-field-token/index.ts
similarity index 90%
copy from src/app/features/settings/components/index.ts
copy to src/app/shared/controls/common/directives/auto-insert-text-field-token/index.ts
index 990bb42..5bc37e9 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/shared/controls/common/directives/auto-insert-text-field-token/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./auto-insert-text-field-token.directive";
diff --git a/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.ts b/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.ts
index 3e5a51c..2c0affc 100644
--- a/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.ts
+++ b/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.ts
@@ -10,12 +10,15 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
-import {Directive, ElementRef, HostListener, Input} from "@angular/core";
+import {Directive, ElementRef, HostListener, Input, OnChanges, SimpleChanges} from "@angular/core";
 
 @Directive({
     selector: "[appAutoTextFieldResize]"
 })
-export class AutoTextFieldResizeDirective {
+export class AutoTextFieldResizeDirective implements OnChanges {
+
+    @Input()
+    public appAutoResizeData: any;
 
     public constructor(public inputElement: ElementRef<HTMLInputElement>) {
     }
@@ -27,10 +30,17 @@
     }
 
     @HostListener("input")
-    onInput() {
+    public onInput() {
         this.resize();
     }
 
+    public ngOnChanges(changes: SimpleChanges) {
+        const keys: Array<keyof AutoTextFieldResizeDirective> = ["appAutoResizeData"];
+        if (keys.some((key) => changes[key] != null)) {
+            setTimeout(() => this.resize());
+        }
+    }
+
     public resize() {
         this.inputElement.nativeElement.style.height = "1px";
         this.inputElement.nativeElement.style.height = this.inputElement.nativeElement.scrollHeight + "px";
diff --git a/src/app/shared/controls/common/directives/index.ts b/src/app/shared/controls/common/directives/index.ts
index d646c4a..9d37dc3 100644
--- a/src/app/shared/controls/common/directives/index.ts
+++ b/src/app/shared/controls/common/directives/index.ts
@@ -11,5 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./auto-insert-text-field-token";
 export * from "./form-control-status";
 export * from "./auto-text-field-resize";
diff --git a/src/app/shared/controls/contact-select/contact-select.component.ts b/src/app/shared/controls/contact-select/contact-select.component.ts
index 3dcc299..6e04b09 100644
--- a/src/app/shared/controls/contact-select/contact-select.component.ts
+++ b/src/app/shared/controls/contact-select/contact-select.component.ts
@@ -17,6 +17,13 @@
 import {IAPIContactPersonDetails} from "../../../core/api/contacts/IAPIContactPersonDetails";
 import {AbstractControlValueAccessorComponent} from "../common";
 
+/**
+ * This component displays a paginated list of contacts.
+ * Contacts from the list can be selected by clicking on the row. On selection the detailed information for the selected contact, if it
+ * exists, is displayed right next to the table of contacts. The value of this component is the id of the selected contact.
+ * There is also a searchbar on top of the table to be able to perform a filtering search on the list of statements. The search bar input
+ * text is emitted to the parent component and the filtering of the actual array of statements needs to be done in that parent component.
+ */
 @Component({
     selector: "app-contact-select",
     templateUrl: "./contact-select.component.html",
diff --git a/src/app/shared/controls/date-control/component/date-control.component.ts b/src/app/shared/controls/date-control/component/date-control.component.ts
index fcc8631..f594899 100644
--- a/src/app/shared/controls/date-control/component/date-control.component.ts
+++ b/src/app/shared/controls/date-control/component/date-control.component.ts
@@ -20,6 +20,10 @@
 import {momentFormatDisplayNumeric, momentFormatInternal, parseMomentToDate, parseMomentToString} from "../../../../util";
 import {DropDownDirective} from "../../../layout/drop-down";
 
+/**
+ * This component displays a input field to input date values. On click a pop up of a calendar opens and dates can be selected from there.
+ * Those values are then put as appValue of this component. The popup always opens to display the month of the currently input value.
+ */
 @Component({
     selector: "app-date-control",
     templateUrl: "./date-control.component.html",
diff --git a/src/app/features/mail/pipes/sender-split-name-mail.pipe.ts b/src/app/shared/controls/select/pipes/array-to-select-options.pipe.ts
similarity index 61%
copy from src/app/features/mail/pipes/sender-split-name-mail.pipe.ts
copy to src/app/shared/controls/select/pipes/array-to-select-options.pipe.ts
index 3cfb33d..29eba99 100644
--- a/src/app/features/mail/pipes/sender-split-name-mail.pipe.ts
+++ b/src/app/shared/controls/select/pipes/array-to-select-options.pipe.ts
@@ -12,16 +12,14 @@
  ********************************************************************************/
 
 import {Pipe, PipeTransform} from "@angular/core";
-import {arrayJoin} from "../../../util/store";
+import {arrayJoin} from "../../../../util/store";
+import {ISelectOption} from "../model";
 
-@Pipe({
-    name: "appSenderSplitNameMail"
-})
-export class SenderSplitNameMailPipe implements PipeTransform {
+@Pipe({name: "arrayToSelectOptions"})
+export class ArrayToSelectOptionsPipe implements PipeTransform {
 
-    public transform(text: string): Array<string> {
-        const textAsArray = arrayJoin(text?.split(/(?=<)/g));
-        return [textAsArray[0], textAsArray.slice(1).join("")].filter(x => x);
-        // return text.split(/(?=<)(.+)/);
+    public transform(value: string[], indexAsValue?: boolean): ISelectOption[] {
+        return arrayJoin(value).map((label, index) => ({value: indexAsValue ? index : label, label}));
     }
+
 }
diff --git a/src/app/shared/controls/select/pipes/index.ts b/src/app/shared/controls/select/pipes/index.ts
index 631e0c0..be35ac4 100644
--- a/src/app/shared/controls/select/pipes/index.ts
+++ b/src/app/shared/controls/select/pipes/index.ts
@@ -11,5 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./array-to-select-options.pipe";
 export * from "./filter-options-by-group.pipe";
 export * from "./selected.pipe";
diff --git a/src/app/shared/controls/select/select.module.ts b/src/app/shared/controls/select/select.module.ts
index b7c8a2f..1664f3d 100644
--- a/src/app/shared/controls/select/select.module.ts
+++ b/src/app/shared/controls/select/select.module.ts
@@ -18,7 +18,7 @@
 import {DropDownModule} from "../../layout/drop-down";
 import {SharedPipesModule} from "../../pipes";
 import {SelectComponent, SelectGroupComponent} from "./components";
-import {FilterOptionsByGroupPipe, SelectedPipe} from "./pipes";
+import {ArrayToSelectOptionsPipe, FilterOptionsByGroupPipe, SelectedPipe} from "./pipes";
 
 @NgModule({
     imports: [
@@ -32,12 +32,14 @@
         SelectComponent,
         SelectGroupComponent,
         SelectedPipe,
+        ArrayToSelectOptionsPipe,
         FilterOptionsByGroupPipe
     ],
     exports: [
         SelectComponent,
         SelectGroupComponent,
         SelectedPipe,
+        ArrayToSelectOptionsPipe,
         FilterOptionsByGroupPipe
     ]
 })
diff --git a/src/app/shared/controls/statement-select/components/statement-select.component.ts b/src/app/shared/controls/statement-select/components/statement-select.component.ts
index 2f9c82b..cc3ee3d 100644
--- a/src/app/shared/controls/statement-select/components/statement-select.component.ts
+++ b/src/app/shared/controls/statement-select/components/statement-select.component.ts
@@ -19,6 +19,13 @@
 import {AbstractControlValueAccessorComponent} from "../../common";
 import {ISelectOption} from "../../select";
 
+/**
+ * This component displays a list of statements. The columns to displayed are specified in the columns constant.
+ * Statements from the list can be selected with a checkbox. The value of this component is the array of the selected statements ids.
+ * There is also a searchbar on top of the table to be able to perform a filtering search on the list of statements. The search bar input
+ * text is emitted to the parent component and the filtering of the actual array of statements needs to be done in that parent component.
+ * An initial value can be set to the search bar with the input property appSearchText.
+ */
 @Component({
     selector: "app-statement-select",
     templateUrl: "statement-select.component.html",
diff --git a/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.directive.spec.ts b/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.directive.spec.ts
new file mode 100644
index 0000000..eab36c3
--- /dev/null
+++ b/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.directive.spec.ts
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+
+import {ElementRef} from "@angular/core";
+import {AutoFocusAfterInitDirective} from "./auto-focus-after-init.directive";
+
+describe("AutoFocusAfterInitDirective", () => {
+
+    let directive: AutoFocusAfterInitDirective;
+
+    it("should focus element ref after ngAfterViewInit", () => {
+        const elementRef = {
+            nativeElement: {
+                focus: (options: FocusOptions) => null
+            }
+        } as ElementRef<HTMLElement>;
+        const focusOptions: FocusOptions = {};
+        const focusSpy = spyOn(elementRef.nativeElement, "focus");
+        directive = new AutoFocusAfterInitDirective(elementRef);
+        directive.appFocusOptions = focusOptions;
+        directive.ngAfterViewInit();
+        expect(focusSpy).toHaveBeenCalledWith(focusOptions);
+    });
+
+});
diff --git a/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.directive.ts b/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.directive.ts
new file mode 100644
index 0000000..83ef556
--- /dev/null
+++ b/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.directive.ts
@@ -0,0 +1,32 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {AfterViewInit, Directive, ElementRef, Input} from "@angular/core";
+
+@Directive({
+    selector: "[appAutoFocusAfterInit]"
+})
+export class AutoFocusAfterInitDirective implements AfterViewInit {
+
+    @Input()
+    public appFocusOptions: FocusOptions;
+
+    public constructor(public elementRef: ElementRef<HTMLElement>) {
+
+    }
+
+    public ngAfterViewInit() {
+        this.elementRef.nativeElement.focus(this.appFocusOptions);
+    }
+
+}
diff --git a/src/app/features/settings/settings.module.ts b/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.module.ts
similarity index 74%
rename from src/app/features/settings/settings.module.ts
rename to src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.module.ts
index 2f125dd..8588de6 100644
--- a/src/app/features/settings/settings.module.ts
+++ b/src/app/shared/layout/auto-focus-after-init/auto-focus-after-init.module.ts
@@ -10,22 +10,17 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
-
-import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {SettingsComponent} from "./components";
+import {AutoFocusAfterInitDirective} from "./auto-focus-after-init.directive";
 
 @NgModule({
-    imports: [
-        CommonModule
-    ],
     declarations: [
-        SettingsComponent
+        AutoFocusAfterInitDirective
     ],
     exports: [
-        SettingsComponent
+        AutoFocusAfterInitDirective
     ]
 })
-export class SettingsModule {
+export class AutoFocusAfterInitModule {
 
 }
diff --git a/src/app/features/settings/components/index.ts b/src/app/shared/layout/auto-focus-after-init/index.ts
similarity index 84%
copy from src/app/features/settings/components/index.ts
copy to src/app/shared/layout/auto-focus-after-init/index.ts
index 990bb42..2deca2b 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/shared/layout/auto-focus-after-init/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./auto-focus-after-init.directive";
+export * from "./auto-focus-after-init.module";
diff --git a/src/app/features/settings/components/index.ts b/src/app/shared/layout/list/components/index.ts
similarity index 94%
copy from src/app/features/settings/components/index.ts
copy to src/app/shared/layout/list/components/index.ts
index 990bb42..79d1963 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/shared/layout/list/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./list.component";
diff --git a/src/app/shared/layout/list/components/list.component.html b/src/app/shared/layout/list/components/list.component.html
new file mode 100644
index 0000000..036a50e
--- /dev/null
+++ b/src/app/shared/layout/list/components/list.component.html
@@ -0,0 +1,27 @@
+<!-------------------------------------------------------------------------------
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<span class="title">{{appTitle}}</span>
+
+<div class="list">
+  <div *ngFor="let item of appListItems; let i = index;" class="list--element">
+    <mat-icon class="list--element--icon">fiber_manual_record</mat-icon>
+    <span class="list--element--text">{{item.label}}</span>
+    <button (click)="appDelete.emit(i);"
+            *ngIf="item.add || appIsDeletable"
+            class="openk-mat-icon-button"
+            mat-icon-button>
+      <mat-icon>clear</mat-icon>
+    </button>
+  </div>
+</div>
diff --git a/src/app/shared/layout/list/components/list.component.scss b/src/app/shared/layout/list/components/list.component.scss
new file mode 100644
index 0000000..7755067
--- /dev/null
+++ b/src/app/shared/layout/list/components/list.component.scss
@@ -0,0 +1,48 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+@import "src/styles/openk.styles";
+
+:host {
+  display: block;
+}
+
+.title {
+  font-weight: 600;
+}
+
+.list {
+  display: flex;
+  flex-flow: row wrap;
+  padding: 0.25em;
+}
+
+.list--element {
+  flex: 0 1 calc(100% / 3 - 1.5em);
+  display: flex;
+  min-width: 15em;
+  align-items: center;
+  margin-right: 0.5em;
+}
+
+.list--element--text {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.list--element--icon {
+  width: initial;
+  height: initial;
+  font-size: 0.5em;
+  margin-right: 1em;
+}
diff --git a/src/app/features/settings/components/search/settings.component.spec.ts b/src/app/shared/layout/list/components/list.component.spec.ts
similarity index 67%
rename from src/app/features/settings/components/search/settings.component.spec.ts
rename to src/app/shared/layout/list/components/list.component.spec.ts
index 2bc2d9f..a3c4a2f 100644
--- a/src/app/features/settings/components/search/settings.component.spec.ts
+++ b/src/app/shared/layout/list/components/list.component.spec.ts
@@ -12,25 +12,30 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {SettingsComponent} from "./settings.component";
+import {I18nModule} from "../../../../core/i18n";
+import {ListModule} from "../list.module";
+import {ListComponent} from "./list.component";
 
-describe("SettingsComponent", () => {
-    let component: SettingsComponent;
-    let fixture: ComponentFixture<SettingsComponent>;
+describe("ListComponent", () => {
+    let component: ListComponent;
+    let fixture: ComponentFixture<ListComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            declarations: [SettingsComponent]
+            imports: [
+                ListModule,
+                I18nModule
+            ]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(SettingsComponent);
+        fixture = TestBed.createComponent(ListComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
 
-    it("should create", () => {
+    it("should be created", () => {
         expect(component).toBeTruthy();
     });
 });
diff --git a/src/app/shared/layout/list/components/list.component.stories.ts b/src/app/shared/layout/list/components/list.component.stories.ts
new file mode 100644
index 0000000..d070c0a
--- /dev/null
+++ b/src/app/shared/layout/list/components/list.component.stories.ts
@@ -0,0 +1,45 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+import {withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {ListModule} from "../list.module";
+
+const list = [
+    {label: "Medianet (Planung/Bau)"},
+    {label: "Medianet (Betrieb"},
+    {label: "Wasser (Planung/Bau)"},
+    {label: "Wasser (Betrieb)"},
+    {label: "Allg. Baulandentwicklung (Planung, Bau)"},
+    {label: "Allg. Baulandentwicklung (Betrieb)"},
+    {label: "Gas Hochdruck (Planung, Bau)"},
+    {label: "Gas Hochdruck (Betrieb)"},
+    {label: "GDRM/KKS/ELEX (Planung/Bau)"},
+    {label: "GDRM/KKS/ELEX (Betrieb)"}
+];
+
+storiesOf("Shared/Layout", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({imports: [ListModule]}))
+    .add("ListComponent", () => ({
+        template: `
+            <div style="padding: 1em; height: 100%; width: 100%; box-sizing: border-box;">
+                <app-list [appTitle]="'Allgemein'" [appListItems]="appListItems" [appIsDeletable]="true"></app-list>
+                <app-list [appTitle]="'Regionalstelle Entenhausen'" [appListItems]="appListItems"></app-list>
+            </div>
+        `,
+        props: {
+            appListItems: list
+        }
+    }));
+
+
diff --git a/src/app/shared/layout/list/components/list.component.ts b/src/app/shared/layout/list/components/list.component.ts
new file mode 100644
index 0000000..676b8f0
--- /dev/null
+++ b/src/app/shared/layout/list/components/list.component.ts
@@ -0,0 +1,35 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, EventEmitter, Input, Output} from "@angular/core";
+
+@Component({
+    selector: "app-list",
+    templateUrl: "./list.component.html",
+    styleUrls: ["./list.component.scss"]
+})
+export class ListComponent {
+
+    @Input()
+    public appTitle: string;
+
+    @Input()
+    public appListItems: { label: string; add?: boolean }[];
+
+    @Input()
+    public appIsDeletable: boolean;
+
+    @Output()
+    public appDelete = new EventEmitter<number>();
+
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/shared/layout/list/index.ts
similarity index 86%
copy from src/app/features/settings/components/index.ts
copy to src/app/shared/layout/list/index.ts
index 990bb42..29b1286 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/shared/layout/list/index.ts
@@ -11,4 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./components";
+export * from "./pipes";
+export * from "./list.module";
diff --git a/src/app/shared/controls/map-select/map-select.module.ts b/src/app/shared/layout/list/list.module.ts
similarity index 65%
rename from src/app/shared/controls/map-select/map-select.module.ts
rename to src/app/shared/layout/list/list.module.ts
index 278693d..4e550ef 100644
--- a/src/app/shared/controls/map-select/map-select.module.ts
+++ b/src/app/shared/layout/list/list.module.ts
@@ -10,26 +10,29 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
-
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {ActionButtonModule} from "../../layout/action-button";
-import {LeafletModule} from "../../leaflet";
-import {MapSelectComponent} from "./components";
+import {MatButtonModule} from "@angular/material/button";
+import {MatIconModule} from "@angular/material/icon";
+import {ListComponent} from "./components";
+import {StringArrayToLabelListPipe} from "./pipes";
+
 
 @NgModule({
     imports: [
         CommonModule,
-        LeafletModule,
-        ActionButtonModule
+        MatIconModule,
+        MatButtonModule
     ],
     declarations: [
-        MapSelectComponent
+        ListComponent,
+        StringArrayToLabelListPipe
     ],
     exports: [
-        MapSelectComponent
+        ListComponent,
+        StringArrayToLabelListPipe
     ]
 })
-export class MapSelectModule {
+export class ListModule {
 
 }
diff --git a/src/app/features/settings/components/index.ts b/src/app/shared/layout/list/pipes/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/shared/layout/list/pipes/index.ts
index 990bb42..f99e601 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/shared/layout/list/pipes/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./string-array-to-label-list.pipe";
diff --git a/src/app/shared/layout/list/pipes/string-array-to-label-list.pipe.spec.ts b/src/app/shared/layout/list/pipes/string-array-to-label-list.pipe.spec.ts
new file mode 100644
index 0000000..68d9cb8
--- /dev/null
+++ b/src/app/shared/layout/list/pipes/string-array-to-label-list.pipe.spec.ts
@@ -0,0 +1,51 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {StringArrayToLabelListPipe} from "./string-array-to-label-list.pipe";
+
+describe("StringArrayToLabelListPipe", () => {
+
+    const pipe = new StringArrayToLabelListPipe();
+
+    describe("transform", () => {
+
+        it("should add all array entries to objects as label property and return that new array", () => {
+
+            const strings = [
+                "first",
+                "second",
+                "third",
+                "fourth"
+            ];
+
+            let result = pipe.transform(strings);
+
+            expect(result).toEqual([
+                {label: "first"},
+                {label: "second"},
+                {label: "third"},
+                {label: "fourth"}
+            ]);
+
+            result = pipe.transform(null);
+            expect(result).toEqual([]);
+
+            result = pipe.transform(undefined);
+            expect(result).toEqual([]);
+
+            result = pipe.transform([]);
+            expect(result).toEqual([]);
+        });
+    });
+});
+
diff --git a/src/app/shared/controls/map-select/components/map-select.component.scss b/src/app/shared/layout/list/pipes/string-array-to-label-list.pipe.ts
similarity index 63%
copy from src/app/shared/controls/map-select/components/map-select.component.scss
copy to src/app/shared/layout/list/pipes/string-array-to-label-list.pipe.ts
index 7e59f1f..cfc33ed 100644
--- a/src/app/shared/controls/map-select/components/map-select.component.scss
+++ b/src/app/shared/layout/list/pipes/string-array-to-label-list.pipe.ts
@@ -11,10 +11,16 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
+import {Pipe, PipeTransform} from "@angular/core";
+import {arrayJoin} from "../../../../util/store";
 
-:host {
-  display: block;
-  width: 100%;
-  height: 100%;
+@Pipe({
+    name: "appStringArrayToLabelList"
+})
+export class StringArrayToLabelListPipe implements PipeTransform {
+
+    public transform(list: string[]): any {
+        return arrayJoin(list).map((_) => ({label: _}));
+    }
+
 }
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.spec.ts b/src/app/shared/leaflet/components/leaflet-map.component.spec.ts
deleted file mode 100644
index f839910..0000000
--- a/src/app/shared/leaflet/components/leaflet-map.component.spec.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
-import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {I18nModule} from "../../../core/i18n";
-import {LeafletModule} from "../leaflet.module";
-import {LeafletMapComponent} from "./leaflet-map.component";
-
-describe("LeafletMapComponent", () => {
-    let component: LeafletMapComponent;
-    let fixture: ComponentFixture<LeafletMapComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            imports: [LeafletModule, I18nModule]
-        }).compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(LeafletMapComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it("should create", () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/src/app/shared/leaflet/components/leaflet-map.component.ts b/src/app/shared/leaflet/components/leaflet-map.component.ts
deleted file mode 100644
index 53e8e48..0000000
--- a/src/app/shared/leaflet/components/leaflet-map.component.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
-import {Component, EventEmitter, forwardRef, Input, NgZone, Output, ViewChild} from "@angular/core";
-import {LeafletMouseEvent, PopupEvent} from "leaflet";
-import {ILeafletBounds, LeafletDirective, LeafletHandler} from "../directives";
-
-@Component({
-    selector: "app-leaflet-map",
-    templateUrl: "./leaflet-map.component.html",
-    styleUrls: ["./leaflet-map.component.scss"],
-    providers: [
-        {
-            provide: LeafletHandler,
-            useExisting: forwardRef(() => LeafletMapComponent)
-        }
-    ]
-})
-export class LeafletMapComponent extends LeafletHandler {
-
-    @Input()
-    public appDisabled: boolean;
-
-    @Input()
-    public appCenter: string;
-
-    @Output()
-    public appClick = new EventEmitter<LeafletMouseEvent>();
-
-    @Output()
-    public appPopupClose = new EventEmitter<PopupEvent>();
-
-    @Output()
-    public appCenterChange = new EventEmitter<string>();
-
-    @Input()
-    public appSubCaption: string;
-
-    @Output()
-    public appOpenGis = new EventEmitter<ILeafletBounds>();
-
-    @ViewChild(LeafletDirective, {static: true})
-    public leafletDirective: LeafletDirective;
-
-    public constructor(public readonly ngZone: NgZone) {
-        super();
-    }
-
-    public get instance() {
-        return this.leafletDirective.instance;
-    }
-
-}
diff --git a/src/app/shared/leaflet/directives/center-marker/index.ts b/src/app/shared/leaflet/directives/center-marker/index.ts
deleted file mode 100644
index a8d18a6..0000000
--- a/src/app/shared/leaflet/directives/center-marker/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
-export * from "./leaflet-center-marker.directive";
diff --git a/src/app/shared/leaflet/directives/center-marker/leaflet-center-marker.directive.spec.ts b/src/app/shared/leaflet/directives/center-marker/leaflet-center-marker.directive.spec.ts
deleted file mode 100644
index 6922af1..0000000
--- a/src/app/shared/leaflet/directives/center-marker/leaflet-center-marker.directive.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
-import {Component, ViewChild} from "@angular/core";
-import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {LatLng, LatLngLiteral} from "leaflet";
-import {Subject} from "rxjs";
-import {LeafletModule} from "../../leaflet.module";
-import {LeafletCenterMarkerDirective} from "./leaflet-center-marker.directive";
-
-describe("LeafletCenterMarkerDirective", () => {
-
-    const latLng: LatLngLiteral = {
-        lat: 52.520008,
-        lng: 13.404954
-    };
-
-    let component: LeafletCenterMarkerSpecComponent;
-    let fixture: ComponentFixture<LeafletCenterMarkerSpecComponent>;
-    let directive: LeafletCenterMarkerDirective;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            imports: [LeafletModule],
-            declarations: [LeafletCenterMarkerSpecComponent]
-        }).compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(LeafletCenterMarkerSpecComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-        directive = component.directive;
-    });
-
-    it("should update marker coordinates on move events", () => {
-        const center = new LatLng(latLng.lat, latLng.lng);
-        const move$ = new Subject<any>();
-        const setLatLngSpy = spyOn(directive.marker, "setLatLng").and.callThrough();
-        spyOn(directive.leafletHandler.instance, "getCenter").and.returnValue(center);
-        spyOn(directive, "on").and.returnValue(move$.asObservable());
-        directive.ngOnInit();
-        move$.next();
-        expect(setLatLngSpy).toHaveBeenCalledWith(center);
-    });
-
-});
-
-@Component({
-    selector: "app-leaflet-center-marker-spec",
-    template: `
-        <div appLeaflet>
-            <ng-container appLeafletCenterMarker>
-            </ng-container>
-        </div>
-    `
-})
-class LeafletCenterMarkerSpecComponent {
-
-    @ViewChild(LeafletCenterMarkerDirective, {static: true})
-    public directive: LeafletCenterMarkerDirective;
-
-}
diff --git a/src/app/shared/leaflet/directives/center-marker/leaflet-center-marker.directive.ts b/src/app/shared/leaflet/directives/center-marker/leaflet-center-marker.directive.ts
deleted file mode 100644
index 9db8965..0000000
--- a/src/app/shared/leaflet/directives/center-marker/leaflet-center-marker.directive.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/********************************************************************************
- * 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 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- ********************************************************************************/
-
-import {Directive, Inject, NgZone, OnDestroy, OnInit} from "@angular/core";
-import {merge, of, Subject} from "rxjs";
-import {takeUntil} from "rxjs/operators";
-import {runOutsideZone} from "../../../../util/rxjs";
-import {ILeafletConfiguration, LEAFLET_CONFIGURATION_TOKEN} from "../../leaflet-configuration.token";
-import {LeafletHandler} from "../leaflet";
-import {AbstractLeafletMarkerDirective} from "../marker/abstract-leaflet-marker.directive";
-
-@Directive({
-    selector: "[appLeafletCenterMarker]",
-    exportAs: "appLeafletCenterMarker"
-})
-export class LeafletCenterMarkerDirective extends AbstractLeafletMarkerDirective implements OnInit, OnDestroy {
-
-    protected destroy$ = new Subject();
-
-    public constructor(
-        ngZone: NgZone,
-        leafletHandler: LeafletHandler,
-        @Inject(LEAFLET_CONFIGURATION_TOKEN) configuration: ILeafletConfiguration,
-    ) {
-        super(ngZone, leafletHandler, configuration);
-    }
-
-    public async ngOnInit() {
-        merge(of(null), this.leafletHandler.on("move", true))
-            .pipe(takeUntil(this.destroy$), runOutsideZone(this.ngZone))
-            .subscribe(() => this.setLatLng(this.leafletHandler.instance.getCenter()));
-    }
-
-    public ngOnDestroy() {
-        super.ngOnDestroy();
-        this.destroy$.next();
-        this.destroy$.complete();
-    }
-
-}
diff --git a/src/app/shared/pipes/get-form-array/get-form-array.pipe.ts b/src/app/shared/pipes/get-form-array/get-form-array.pipe.ts
index 4f2817e..5f343b2 100644
--- a/src/app/shared/pipes/get-form-array/get-form-array.pipe.ts
+++ b/src/app/shared/pipes/get-form-array/get-form-array.pipe.ts
@@ -12,14 +12,14 @@
  ********************************************************************************/
 
 import {Pipe, PipeTransform} from "@angular/core";
-import {FormArray, FormGroup} from "@angular/forms";
+import {AbstractControl, FormArray, FormGroup} from "@angular/forms";
 
 @Pipe({
     name: "getFormArray"
 })
 export class GetFormArrayPipe implements PipeTransform {
 
-    public transform(group: FormGroup, key: string): FormArray {
+    public transform(group: FormGroup | AbstractControl, key: Array<string | number> | string): FormArray {
         const control = group?.get(key);
         return control instanceof FormArray ? control : undefined;
     }
diff --git a/src/app/shared/pipes/get-form-group/get-form-group.pipe.ts b/src/app/shared/pipes/get-form-group/get-form-group.pipe.ts
index 647e9ed..3b71227 100644
--- a/src/app/shared/pipes/get-form-group/get-form-group.pipe.ts
+++ b/src/app/shared/pipes/get-form-group/get-form-group.pipe.ts
@@ -12,14 +12,14 @@
  ********************************************************************************/
 
 import {Pipe, PipeTransform} from "@angular/core";
-import {FormGroup} from "@angular/forms";
+import {AbstractControl, FormGroup} from "@angular/forms";
 
 @Pipe({
     name: "getFormGroup"
 })
 export class GetFormGroupPipe implements PipeTransform {
 
-    public transform(group: FormGroup, key: string, getControls?: boolean): FormGroup {
+    public transform(group: FormGroup | AbstractControl, key: Array<string | number> | string, getControls?: boolean): FormGroup {
         const control = group?.get(key);
         return control instanceof FormGroup ? control : undefined;
     }
diff --git a/src/app/shared/pipes/index.ts b/src/app/shared/pipes/index.ts
index 0da4dbd..8233d97 100644
--- a/src/app/shared/pipes/index.ts
+++ b/src/app/shared/pipes/index.ts
@@ -15,6 +15,7 @@
 export * from "./get-form-array";
 export * from "./get-form-error";
 export * from "./get-form-group";
+export * from "./obj-keys-to-array";
 export * from "./obj-to-array";
 export * from "./pair";
 export * from "./strings-to-options";
diff --git a/src/app/features/settings/components/index.ts b/src/app/shared/pipes/obj-keys-to-array/index.ts
similarity index 92%
copy from src/app/features/settings/components/index.ts
copy to src/app/shared/pipes/obj-keys-to-array/index.ts
index 990bb42..98db981 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/shared/pipes/obj-keys-to-array/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./obj-keys-to-array.pipe";
diff --git a/src/app/shared/pipes/obj-keys-to-array/obj-keys-to-array.pipe.spec.ts b/src/app/shared/pipes/obj-keys-to-array/obj-keys-to-array.pipe.spec.ts
new file mode 100644
index 0000000..160f31b
--- /dev/null
+++ b/src/app/shared/pipes/obj-keys-to-array/obj-keys-to-array.pipe.spec.ts
@@ -0,0 +1,32 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {ObjKeysToArrayPipe} from "./obj-keys-to-array.pipe";
+
+describe("ObjToArrayPipe", () => {
+
+    const pipe = new ObjKeysToArrayPipe();
+
+    it("should convert an object to an array of its keys", () => {
+        const testObject = {
+            prop1: "propValue1",
+            prop2: undefined,
+            prop3: null
+        };
+        expect(pipe.transform(testObject)).toEqual(["prop1"]);
+        expect(pipe.transform(testObject, true)).toEqual(["prop1", "prop2", "prop3"]);
+        expect(pipe.transform(undefined)).toEqual([]);
+        expect(pipe.transform(null)).toEqual([]);
+    });
+
+});
diff --git a/src/app/shared/pipes/obj-keys-to-array/obj-keys-to-array.pipe.ts b/src/app/shared/pipes/obj-keys-to-array/obj-keys-to-array.pipe.ts
new file mode 100644
index 0000000..6a042fc
--- /dev/null
+++ b/src/app/shared/pipes/obj-keys-to-array/obj-keys-to-array.pipe.ts
@@ -0,0 +1,29 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Pipe, PipeTransform} from "@angular/core";
+import {objectToArray} from "../../../util/store";
+
+@Pipe({name: "objKeysToArray"})
+export class ObjKeysToArrayPipe implements PipeTransform {
+
+    /**
+     * Converts an object to an array containing its keys.
+     */
+    public transform<T extends object>(value: T, keepNullOrUndefined?: boolean): string[] {
+        return objectToArray<T>({...value}, keepNullOrUndefined)
+            .map((entry) => entry.key)
+            .sort();
+    }
+
+}
diff --git a/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.spec.ts b/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.spec.ts
index 84b1cb6..b0f5b98 100644
--- a/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.spec.ts
+++ b/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.spec.ts
@@ -17,7 +17,7 @@
 
     const pipe = new ObjToArrayPipe();
 
-    it("should convert a object to an array", () => {
+    it("should convert an object to an array of its key value pairs", () => {
         const testObject = {
             prop1: "propValue1",
             prop2: undefined,
diff --git a/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.ts b/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.ts
index 0dab401..fda7324 100644
--- a/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.ts
+++ b/src/app/shared/pipes/obj-to-array/obj-to-array.pipe.ts
@@ -10,22 +10,22 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+
 import {Pipe, PipeTransform} from "@angular/core";
 import {objectToArray} from "../../../util/store";
 
 
-/**
- * Converts an object to an array containing objects with key and value tags
- * example: Object{prop1: "propValue1", prop2: "propValue2"}
- * ->
- * [Object{key: 'prop1', value: 'propValue1'},
- * Object{key: 'prop2', value: 'propValue2'}]
- */
 @Pipe({name: "objToArray"})
 export class ObjToArrayPipe implements PipeTransform {
 
+    /**
+     * Converts an object to an array containing its key value pairs.
+     */
     public transform<T extends object>(value: T, keepNullOrUndefined: boolean = false) {
-        return objectToArray<T>(value, keepNullOrUndefined);
+        return objectToArray<T>({...value}, keepNullOrUndefined)
+            .sort((a, b) => {
+                return a.key.localeCompare(b.key);
+            });
     }
 
 }
diff --git a/src/app/shared/pipes/shared-pipes.module.ts b/src/app/shared/pipes/shared-pipes.module.ts
index 153655f..7bdc1f2 100644
--- a/src/app/shared/pipes/shared-pipes.module.ts
+++ b/src/app/shared/pipes/shared-pipes.module.ts
@@ -16,6 +16,7 @@
 import {GetFormArrayPipe} from "./get-form-array";
 import {GetFormErrorPipe} from "./get-form-error";
 import {GetFormGroupPipe} from "./get-form-group";
+import {ObjKeysToArrayPipe} from "./obj-keys-to-array";
 import {ObjToArrayPipe} from "./obj-to-array";
 import {PairPipe} from "./pair";
 import {StringsToOptionsPipe} from "./strings-to-options";
@@ -23,6 +24,7 @@
 @NgModule({
     declarations: [
         ObjToArrayPipe,
+        ObjKeysToArrayPipe,
         PairPipe,
         GetFormArrayPipe,
         GetFormGroupPipe,
@@ -32,6 +34,7 @@
     ],
     exports: [
         ObjToArrayPipe,
+        ObjKeysToArrayPipe,
         PairPipe,
         GetFormArrayPipe,
         GetFormGroupPipe,
diff --git a/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.html b/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.html
index e1d8824..7831398 100644
--- a/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.html
+++ b/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.html
@@ -16,11 +16,12 @@
      class="select openk-drag-list-with-hidden-placeholder">
 
   <app-text-block-list
-    (appAdd)="appAdd.emit($event)"
+    (appAdd)="appAdd.emit($event?.textBlock)"
     [appConnectedTo]="appConnectedTo"
     [appDuplicateOnDrag]="true"
     [appDisabled]="appDisabled"
-    [appListData]="standardBlocks">
+    [appListData]="standardBlocks"
+    [appWithoutTitlePrefix]="true">
   </app-text-block-list>
 
   <app-collapsible
@@ -32,11 +33,11 @@
     class="text-block-group">
 
     <app-text-block-list
-      (appAdd)="appAdd.emit($event)"
+      (appAdd)="appAdd.emit($event?.textBlock)"
       [appConnectedTo]="appConnectedTo"
       [appDuplicateOnDrag]="true"
       [appListData]="group?.textBlocks.slice()"
-      [appSelectedIds]="appSelectedIds"
+      [appHiddenIds]="appSelectedIds"
       [appDisabled]="appDisabled"
       [appReplacements]="appReplacements"
       [appShortMode]="appShortMode">
diff --git a/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.ts b/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.ts
index 9fe7502..1bbef1d 100644
--- a/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.ts
+++ b/src/app/shared/text-block/components/editor-textblock-select/text-block-select.component.ts
@@ -16,6 +16,12 @@
 import {IAPITextBlockGroupModel} from "../../../../core";
 import {IExtendedTextBlockModel} from "../../model";
 
+/**
+ * This component shows two lists of text blocks that can be selected, by drag and drop or pressing a button.
+ * First part are the standard blocks that are always available (newline, freetext, pagebreak). They can be placed multiple times so they
+ * stay in the list after selecting one. The other part are the text blocks, provided in groups. A textblock can only be placed once, so it
+ * is removed from the list after being selected once.
+ */
 @Component({
     selector: "app-text-block-select",
     templateUrl: "./text-block-select.component.html",
@@ -50,6 +56,9 @@
     @ViewChild(CdkDropList, {static: true})
     public dropList: CdkDropList;
 
+    /**
+     * Block models for the always available blocks. They are not part of the backend textblockconfig so they need to be defined here.
+     */
     public standardBlocks: IExtendedTextBlockModel[] = [
         {id: "Freitext", text: "", excludes: [], requires: [], type: "text"},
         {id: "Zeilenumbruch", text: "", excludes: [], requires: [], type: "newline"},
diff --git a/src/app/shared/text-block/components/text-block-list/text-block-list.component.html b/src/app/shared/text-block/components/text-block-list/text-block-list.component.html
index e8a9426..2cc0758 100644
--- a/src/app/shared/text-block/components/text-block-list/text-block-list.component.html
+++ b/src/app/shared/text-block/components/text-block-list/text-block-list.component.html
@@ -21,19 +21,35 @@
     (cdkDragEnded)="isDragging = false; stopDrag(block)"
     (cdkDragReleased)="isDragging = false"
     (cdkDragStarted)="isDragging = true; startDrag(block)"
-    (appButtonPress)="appAdd.emit(block)"
-    *ngFor="let block of appListData"
+    (appAdd)="appAdd.emit({ textBlock: block, index: index })"
+    (appDelete)="appDelete.emit({ textBlock: block, index: index })"
+    (appEdit)="appEdit.emit({ textBlock: block, index: index })"
+    (appDown)="appDown.emit({ textBlock: block, index: index })"
+    (appUp)="appUp.emit({ textBlock: block, index: index })"
+    *ngFor="let block of appListData; let index = index; trackBy: trackBy; let first = first; let last = last;"
+    [appForAdmin]="appForAdmin"
+    [appDownDisabled]="last"
+    [appUpDisabled]="first"
+    [appSelected]="appSelectedIds?.includes(block?.id)"
     [appShortMode]="appShortMode"
     [appShowNewLine]="false"
-    [appTextBlockData]="block | getBlockDataFromBlockModel: appReplacements"
+    [appTextBlockData]="block | getBlockDataFromBlockModel : appReplacements"
+    [appWithoutTitlePrefix]="appWithoutTitlePrefix"
     [appTitle]="block.id"
     [cdkDragData]="block"
-    [cdkDragDisabled]="(block?.id | findElementInArray : appSelectedIds) != null"
-    [class.text-block-list-entry---selected]="(block?.id | findElementInArray : appSelectedIds) != null"
+    [cdkDragDisabled]="appHiddenIds?.includes(block?.id)"
+    [class.text-block-list-entry---hidden]="appHiddenIds?.includes(block?.id)"
     [appDisabled]="appDisabled"
     cdkDrag
     [class.disable]="appDisabled"
     class="text-block-list-entry grab">
+
+    <ng-container *ngIf="appShowPreview">
+      <button *cdkDragPreview class="openk-button openk-chip">
+        {{'textBlocks.textBlock' | translate}} {{ block.id }}
+      </button>
+    </ng-container>
+
   </app-text-block>
 
 </div>
diff --git a/src/app/shared/text-block/components/text-block-list/text-block-list.component.scss b/src/app/shared/text-block/components/text-block-list/text-block-list.component.scss
index 004a177..1db2483 100644
--- a/src/app/shared/text-block/components/text-block-list/text-block-list.component.scss
+++ b/src/app/shared/text-block/components/text-block-list/text-block-list.component.scss
@@ -11,11 +11,17 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+@import "openk.styles";
+
 .text-block-list-entry {
   position: relative;
   margin-bottom: 0.5em;
 }
 
-.text-block-list-entry---selected {
+.text-block-list-entry---hidden {
   display: none;
 }
+
+.test {
+  background-color: get-color($openk-default-palette);
+}
diff --git a/src/app/shared/text-block/components/text-block-list/text-block-list.component.spec.ts b/src/app/shared/text-block/components/text-block-list/text-block-list.component.spec.ts
index 49ac9c7..1c91567 100644
--- a/src/app/shared/text-block/components/text-block-list/text-block-list.component.spec.ts
+++ b/src/app/shared/text-block/components/text-block-list/text-block-list.component.spec.ts
@@ -12,7 +12,7 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {IAPITextBlockModel} from "../../../../core/api/text";
+import {I18nModule, IAPITextBlockModel} from "../../../../core";
 import {TextBlockModule} from "../../text-block.module";
 import {TextBlocksListComponent} from "./text-block-list.component";
 
@@ -23,7 +23,8 @@
     beforeEach(async(() => {
         TestBed.configureTestingModule({
             imports: [
-                TextBlockModule
+                TextBlockModule,
+                I18nModule
             ]
         }).compileComponents();
     }));
diff --git a/src/app/shared/text-block/components/text-block-list/text-block-list.component.ts b/src/app/shared/text-block/components/text-block-list/text-block-list.component.ts
index 257454d..5c0fb5a 100644
--- a/src/app/shared/text-block/components/text-block-list/text-block-list.component.ts
+++ b/src/app/shared/text-block/components/text-block-list/text-block-list.component.ts
@@ -24,28 +24,55 @@
 export class TextBlocksListComponent {
 
     @Input()
-    public appListData: IAPITextBlockModel[];
-
-    @Input()
-    public appDuplicateOnDrag: boolean;
-
-    @Input()
     public appConnectedTo: CdkDropList | string | Array<CdkDropList | string>;
 
     @Input()
-    public appShortMode: boolean;
-
-    @Input()
-    public appSelectedIds: string[];
-
-    @Input()
     public appDisabled: boolean;
 
     @Input()
+    public appDuplicateOnDrag: boolean;
+
+    @Input()
+    public appEnterPredicate: (drag: CdkDrag, drop: CdkDropList) => boolean = returnFalse;
+
+    @Input()
+    public appForAdmin: boolean;
+
+    @Input()
+    public appHiddenIds: string[];
+
+    @Input()
+    public appListData: IAPITextBlockModel[];
+
+    @Input()
     public appReplacements: { [key: string]: string };
 
+    @Input()
+    public appSelectedIds: string[];
+
+    @Input()
+    public appShortMode: boolean;
+
+    @Input()
+    public appShowPreview: boolean;
+
+    @Input()
+    public appWithoutTitlePrefix: boolean;
+
     @Output()
-    public appAdd = new EventEmitter<IAPITextBlockModel>();
+    public appAdd = new EventEmitter<{ textBlock: IAPITextBlockModel; index: number; }>();
+
+    @Output()
+    public appDelete = new EventEmitter<{ textBlock: IAPITextBlockModel; index: number; }>();
+
+    @Output()
+    public appEdit = new EventEmitter<{ textBlock: IAPITextBlockModel; index: number; }>();
+
+    @Output()
+    public appUp = new EventEmitter<{ textBlock: IAPITextBlockModel; index: number; }>();
+
+    @Output()
+    public appDown = new EventEmitter<{ textBlock: IAPITextBlockModel; index: number; }>();
 
     public isDragging: boolean;
 
@@ -53,9 +80,8 @@
 
     }
 
-    @Input()
-    public appEnterPredicate(drag: CdkDrag, drop: CdkDropList): boolean {
-        return false;
+    public trackBy(index) {
+        return index;
     }
 
     public startDrag(block: IAPITextBlockModel) {
@@ -73,3 +99,7 @@
     }
 
 }
+
+function returnFalse() {
+    return false;
+}
diff --git a/src/app/shared/text-block/components/text-block/text-block.component.html b/src/app/shared/text-block/components/text-block/text-block.component.html
index 7b0eb34..c9d6f78 100644
--- a/src/app/shared/text-block/components/text-block/text-block.component.html
+++ b/src/app/shared/text-block/components/text-block/text-block.component.html
@@ -13,37 +13,94 @@
 
 <div #textBlock (mousedown)="(editText || appDisabled || editReplacement) ? $event.stopPropagation() : null"
      [class.disabled]="appDisabled"
+     [class.text-block---selected]="appSelected"
      [class.text-block---error]="appErrors?.length > 0" class="text-block openk-drag-element">
 
   <div class="title-bar">
-    <span class="title-bar--title">{{appTitle | translate}}</span>
-    <div class="title-bar--buttons">
-      <button (click)="convertToTextInput()" *ngIf="!editText && (appType === 'block' || appType === 'text')"
-              [disabled]="appDisabled" class="openk-mat-icon-button title-bar--buttons-btn" mat-icon-button>
-        <mat-icon>edit</mat-icon>
-      </button>
-      <button (click)="revert()"
-              *ngIf="(appType === 'block' && appBlockText != null) || (appType === 'text' && appBlockText)"
-              [disabled]="appDisabled" class="openk-mat-icon-button title-bar--buttons-btn"
-              mat-icon-button name="revertButton">
-        <mat-icon class="">backspace</mat-icon>
-      </button>
-      <button (click)="addNewLineAfter()" *ngIf="appShowNewLine && appTextBlockData?.length === 0"
-              [disabled]="appDisabled" class="openk-mat-icon-button" mat-icon-button>
-        <mat-icon class="">keyboard_return</mat-icon>
-      </button>
-    </div>
+    <span class="title-bar--title">
+      <ng-container *ngIf="!appWithoutTitlePrefix">
+        {{'textBlocks.textBlock' | translate}}
+      </ng-container>
+      {{appTitle | translate}}
+    </span>
 
-    <div class="title-bar--error-msg">
-      <span *ngIf="appErrors?.length > 0"
-            class="title-bar--block-error">{{(appErrors[0]?.message | translate) + appErrors[0]?.ids.toString()}}</span>
-    </div>
+    <ng-template #buttonsForAdminTemplateRef>
 
-    <div>
-      <button (click)="appButtonPress.emit()" [disabled]="appDisabled" class="openk-mat-icon-button" mat-icon-button>
-        <mat-icon class="">{{appShowClose ? "clear" : "add"}}</mat-icon>
-      </button>
-    </div>
+      <div class="title-bar--buttons title-bar--buttons---admin">
+
+        <button (click)="appDelete.emit()"
+                [disabled]="appDisabled"
+                class="openk-button openk-button-icon title-bar--buttons-btn">
+          <mat-icon>delete_forever</mat-icon>
+        </button>
+
+        <div style="flex: 1;"></div>
+
+        <button (click)="appUp.emit()"
+                [disabled]="appDisabled || appUpDisabled"
+                class="openk-button openk-button-icon title-bar--buttons-btn title-bar--buttons-btn---rotate-up">
+          <mat-icon>play_arrow</mat-icon>
+        </button>
+
+        <button (click)="appDown.emit()"
+                [disabled]="appDisabled || appDownDisabled"
+                class="openk-button openk-button-icon title-bar--buttons-btn title-bar--buttons-btn---rotate-down">
+          <mat-icon>play_arrow</mat-icon>
+        </button>
+
+        <button (click)="appEdit.emit()"
+                [disabled]="appDisabled"
+                class="openk-button openk-button-icon title-bar--buttons-btn">
+          <mat-icon>edit</mat-icon>
+        </button>
+
+        <button (click)="appAdd.emit()" [disabled]="appDisabled"
+                class="openk-button openk-button-icon title-bar--buttons-btn">
+          <mat-icon>add</mat-icon>
+        </button>
+
+      </div>
+
+    </ng-template>
+
+    <ng-container *ngIf="!appForAdmin; else buttonsForAdminTemplateRef">
+      <div class="title-bar--buttons">
+        <button (click)="convertToTextInput()" *ngIf="(appType === 'block' || appType === 'text')"
+                [disabled]="appDisabled" class="openk-button openk-button-icon title-bar--buttons-btn">
+          <mat-icon>edit</mat-icon>
+        </button>
+        <button (click)="revert()"
+                *ngIf="(appType === 'block' && appBlockText != null) || (appType === 'text' && appBlockText)"
+                [disabled]="appDisabled"
+                class="openk-button openk-button-icon title-bar--buttons-btn title-bar--buttons-btn---revert"
+                name="revertButton">
+          <mat-icon>backspace</mat-icon>
+        </button>
+        <button (click)="appNewLine.emit()" *ngIf="appShowNewLine && appTextBlockData?.length === 0"
+                [disabled]="appDisabled" class="openk-button openk-button-icon">
+          <mat-icon>keyboard_return</mat-icon>
+        </button>
+      </div>
+
+      <div class="title-bar--error-msg">
+        <span *ngIf="appErrors?.length > 0"
+              class="title-bar--block-error">
+          {{(appErrors[0]?.message | translate) + appErrors[0]?.ids.toString()}}
+        </span>
+      </div>
+
+      <div>
+        <button (click)="appDelete.emit()" *ngIf="appShowClose"
+                [disabled]="appDisabled" class="openk-button openk-button-icon">
+          <mat-icon>clear</mat-icon>
+        </button>
+
+        <button (click)="appAdd.emit()" *ngIf="!appShowClose"
+                [disabled]="appDisabled" class="openk-button openk-button-icon">
+          <mat-icon>add</mat-icon>
+        </button>
+      </div>
+    </ng-container>
 
   </div>
 
@@ -53,25 +110,36 @@
 
       <span *ngIf="block?.type === 'text'">{{block?.value}}</span>
 
-      <span *ngIf="block?.type === 'highlight-text'" class="highlight-text">{{block?.value}}</span>
+      <span *ngIf="block?.type === 'highlight-text'" class="highlight-text">
+        {{block?.value}}
+        <mat-icon *ngIf="block?.iconType === 'input'" class="editable-text--icon">edit</mat-icon>
+        <mat-icon *ngIf="block?.iconType === 'select'" class="editable-text--icon editable-text--icon---rotate">play_arrow</mat-icon>
+        <mat-icon *ngIf="block?.iconType === 'date'" class="editable-text--icon">today</mat-icon>
+      </span>
 
       <ng-container *ngIf="block?.type === 'newline'"><br></ng-container>
 
       <app-text-replacement (appValueChange)="valueChange(block?.value, $event, block?.type)"
                             *ngIf="block?.type === 'input' || block?.type === 'date' || block?.type === 'select'"
                             [appPlaceholder]="block?.value"
-                            [appOptions]="block?.options" [appType]="block?.type"
+                            [appOptions]="block?.options"
+                            [appType]="block?.type"
                             [appDisabled]="appDisabled"
                             (appEdit)="editReplacement = $event"
-                            [appMaxWidth]="textBlockWidth"
                             [appValue]="block?.placeholder">
       </app-text-replacement>
 
-      <span *ngIf="block?.type === 'text-fill'" [class.error]="!block?.placeholder"
-            class="highlight-text"><!--
-        -->{{block?.placeholder ? block.placeholder : block?.value}}
-        <mat-icon *ngIf="!block?.placeholder" class="error--icon">warning</mat-icon><!--
-      --></span>
+      <ng-container *ngIf="block?.type === 'text-fill'">
+
+        <span *ngIf="block?.placeholder" class="highlight-text"><!--
+          -->{{block.placeholder}}<!--
+        --></span>
+
+        <span *ngIf="!block?.placeholder" class="highlight-text error"><!--
+        -->{{ block.value }}
+          <mat-icon *ngIf="!block?.placeholder" class="error--icon">warning</mat-icon><!--
+        --></span>
+      </ng-container>
 
     </ng-container>
 
@@ -83,8 +151,8 @@
     </textarea>
   </div>
 
-  <button (click)="addNewLineAfter()" *ngIf="appShowNewLine && appTextBlockData?.length > 0"
-          [disabled]="appDisabled" class="openk-mat-icon-button" mat-icon-button>
+  <button (click)="appNewLine.emit()" *ngIf="appShowNewLine && appTextBlockData?.length > 0"
+          [disabled]="appDisabled" class="openk-button openk-button-icon">
     <mat-icon>keyboard_return</mat-icon>
   </button>
 
diff --git a/src/app/shared/text-block/components/text-block/text-block.component.scss b/src/app/shared/text-block/components/text-block/text-block.component.scss
index 42249fc..9be8586 100644
--- a/src/app/shared/text-block/components/text-block/text-block.component.scss
+++ b/src/app/shared/text-block/components/text-block/text-block.component.scss
@@ -14,18 +14,24 @@
 @import "src/styles/openk.styles";
 
 :host {
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
+  display: block;
 }
 
 .text-block {
-  padding: 0.3em;
+  display: flex;
+  flex-direction: column;
+  overflow-x: hidden;
+  padding: 0.125em 0.375em;
   border: 1px dashed get-color($openk-default-palette, 900);
   border-radius: 6px;
   background-color: get-color($openk-default-palette);
 }
 
+.text-block---selected {
+  border-color: get-color($openk-info-palette, A300);
+  background-color: rgba(get-color($openk-info-palette, A300), 0.04);
+}
+
 .text-block---error {
   border-color: $openk-error-color;
 }
@@ -33,32 +39,46 @@
 .title-bar {
   display: flex;
   flex-direction: row;
-  font-size: 0.8em;
   align-items: center;
 }
 
 .title-bar--title {
+  font-style: italic;
   margin-right: 0.2em;
+  font-size: 0.8em;
 }
 
 .title-bar--error-msg {
-  flex: 1;
-  margin-right: 1em;
+  flex: 1 1 auto;
+  margin-right: 0.25em;
+  font-size: 0.8em;
 }
 
 .title-bar--buttons {
+  flex: 1 1 auto;
   display: flex;
   flex-direction: row;
+  align-items: center;
+}
+
+.title-bar--buttons---admin {
+  justify-content: flex-end;
 }
 
 .title-bar--buttons-btn {
-  margin-right: 0.2em;
+  margin-left: 0.125em;
 }
 
-.title-bar--buttons-btn-icon {
-  padding: 0;
-  height: initial;
-  width: initial;
+.title-bar--buttons-btn---rotate-up {
+  transform: rotate(-90deg);
+}
+
+.title-bar--buttons-btn---rotate-down {
+  transform: rotate(90deg);
+}
+
+.title-bar--buttons-btn---revert {
+  --icon-scale-factor: 0.55;
 }
 
 .title-bar--block-info {
@@ -72,6 +92,7 @@
 
 .highlight-text {
   color: get-color($openk-info-palette, A300);
+  display: inline-flex;
 }
 
 .text-block--text {
@@ -120,3 +141,14 @@
   min-height: 2.5em;
   white-space: break-spaces;
 }
+
+.editable-text--icon {
+  height: initial;
+  width: initial;
+  font-size: 1em;
+  margin: auto;
+}
+
+.editable-text--icon---rotate {
+  transform: rotate(90deg);
+}
diff --git a/src/app/shared/text-block/components/text-block/text-block.component.spec.ts b/src/app/shared/text-block/components/text-block/text-block.component.spec.ts
index ff8ce19..c65c0b2 100644
--- a/src/app/shared/text-block/components/text-block/text-block.component.spec.ts
+++ b/src/app/shared/text-block/components/text-block/text-block.component.spec.ts
@@ -39,11 +39,8 @@
         expect(component).toBeTruthy();
     });
 
-    it("should emit appNewLine with appTitle as parameter", () => {
-        spyOn(component.appNewLine, "emit").and.callThrough();
-        component.appTitle = "TestTitle";
-        component.addNewLineAfter();
-        expect(component.appNewLine.emit).toHaveBeenCalledWith("TestTitle");
+    it("should track by index", () => {
+        expect(component.trackByIndex(19)).toBe(19);
     });
 
     it("should emit appDeleteComment with the comment id", () => {
@@ -60,25 +57,25 @@
         expect(component.editText).toBeTrue();
     });
 
-    it("should emit appChangeText with the given value", () => {
-        spyOn(component.appChangeText, "emit").and.callThrough();
+    it("should emit appTextChange with the given value", () => {
+        spyOn(component.appTextChange, "emit").and.callThrough();
         const value = "test value";
         component.onInput(value);
-        expect(component.appChangeText.emit).toHaveBeenCalledWith(value);
+        expect(component.appTextChange.emit).toHaveBeenCalledWith(value);
     });
 
-    it("should emit appChangeText with empty string for freetext and otherwise undefined and set editText to false", () => {
-        spyOn(component.appChangeText, "emit").and.callThrough();
+    it("should emit appTextChange with empty string for freetext and otherwise undefined and set editText to false", () => {
+        spyOn(component.appTextChange, "emit").and.callThrough();
         component.editText = true;
         component.appType = "text";
         component.revert();
-        expect(component.appChangeText.emit).toHaveBeenCalledWith("");
+        expect(component.appTextChange.emit).toHaveBeenCalledWith("");
         expect(component.editText).toBeFalse();
 
         component.editText = true;
         component.appType = "block";
         component.revert();
-        expect(component.appChangeText.emit).toHaveBeenCalledWith(undefined);
+        expect(component.appTextChange.emit).toHaveBeenCalledWith(undefined);
         expect(component.editText).toBeFalse();
     });
 
diff --git a/src/app/shared/text-block/components/text-block/text-block.component.ts b/src/app/shared/text-block/components/text-block/text-block.component.ts
index 6d5fe6a..273c4e0 100644
--- a/src/app/shared/text-block/components/text-block/text-block.component.ts
+++ b/src/app/shared/text-block/components/text-block/text-block.component.ts
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {AfterViewChecked, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from "@angular/core";
+import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from "@angular/core";
 import {ITextblockError} from "../../../../features/forms/statement-editor/pipes";
 import {ITextBlockRenderItem} from "../../model/ITextBlockRenderItem";
 import {addTypeToName} from "../../pipes/get-blockdata-array";
@@ -21,7 +21,16 @@
     templateUrl: "./text-block.component.html",
     styleUrls: ["./text-block.component.scss"]
 })
-export class TextBlockComponent implements AfterViewChecked {
+export class TextBlockComponent {
+
+    @Input()
+    public appDownDisabled: boolean;
+
+    @Input()
+    public appUpDisabled: boolean;
+
+    @Input()
+    public appForAdmin: boolean;
 
     @Input()
     public appErrors: ITextblockError[] = [];
@@ -50,43 +59,49 @@
     @Input()
     public appDisabled: boolean;
 
+    @Input()
+    public appSelected: boolean;
+
+    @Input()
+    public appWithoutTitlePrefix: boolean;
+
+    @Output()
+    public appAdd = new EventEmitter<void>();
+
+    @Output()
+    public appDelete = new EventEmitter<void>();
+
+    @Output()
+    public appDown = new EventEmitter<void>();
+
+    @Output()
+    public appEdit = new EventEmitter<void>();
+
+    @Output()
+    public appNewLine = new EventEmitter<void>();
+
+    @Output()
+    public appTextChange = new EventEmitter<string>();
+
     @Output()
     public appTextInput = new EventEmitter<void>();
 
     @Output()
-    public appNewLine = new EventEmitter<string>();
-
-    @Output()
-    public appButtonPress = new EventEmitter<void>();
+    public appUp = new EventEmitter<void>();
 
     @Output()
     public appValueChange = new EventEmitter<{ name: string, newValue: string }>();
 
-    @Output()
-    public appChangeText = new EventEmitter<string>();
-
     @ViewChild("inputElement") inputElement: ElementRef;
 
     @ViewChild("textBlock") textBlock: ElementRef;
 
-    public textBlockWidth = "0px";
-
     public editText = false;
 
     public editReplacement = false;
 
-    public constructor(public changeDetectorRef: ChangeDetectorRef) {
-    }
-
-    public ngAfterViewChecked() {
-        this.textBlockWidth = this.textBlock.nativeElement.offsetWidth;
-        this.changeDetectorRef.detectChanges();
-    }
-
-    public trackByIndex = (index: number) => index;
-
-    public addNewLineAfter() {
-        this.appNewLine.emit(this.appTitle);
+    public trackByIndex(index: number) {
+        return index;
     }
 
     public valueChange(name: string, newValue: string, type: string) {
@@ -96,16 +111,15 @@
     public convertToTextInput() {
         this.appTextInput.emit();
         this.editText = true;
-        this.changeDetectorRef.detectChanges();
         this.inputElement.nativeElement.focus();
     }
 
     public onInput(value: string) {
-        this.appChangeText.emit(value);
+        this.appTextChange.emit(value);
     }
 
     public revert() {
-        this.appChangeText.emit(this.appType === "text" ? "" : undefined);
+        this.appTextChange.emit(this.appType === "text" ? "" : undefined);
         this.editText = false;
     }
 
@@ -116,4 +130,5 @@
             this.editText = false;
         }
     }
+
 }
diff --git a/src/app/shared/text-block/components/text-replacement/text-replacement.component.html b/src/app/shared/text-block/components/text-replacement/text-replacement.component.html
index 61ad4c6..ee07ba2 100644
--- a/src/app/shared/text-block/components/text-replacement/text-replacement.component.html
+++ b/src/app/shared/text-block/components/text-replacement/text-replacement.component.html
@@ -61,7 +61,7 @@
                 [appPlaceholder]="appPlaceholder"
                 [appSmall]="true"
                 [appValue]="appValue"
-                [appMaxWidth]="appMaxWidth"
+                [appMaxWidth]="'33em'"
                 class="select">
     </app-select>
   </div>
diff --git a/src/app/shared/text-block/components/text-replacement/text-replacement.component.spec.ts b/src/app/shared/text-block/components/text-replacement/text-replacement.component.spec.ts
index 174cbe9..f8a98b4 100644
--- a/src/app/shared/text-block/components/text-replacement/text-replacement.component.spec.ts
+++ b/src/app/shared/text-block/components/text-replacement/text-replacement.component.spec.ts
@@ -14,6 +14,7 @@
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
 import {timer} from "rxjs";
+import {I18nModule} from "../../../../core";
 import {TextBlockModule} from "../../text-block.module";
 import {TextReplacementComponent} from "./text-replacement.component";
 
@@ -25,7 +26,8 @@
         TestBed.configureTestingModule({
             imports: [
                 TextBlockModule,
-                BrowserAnimationsModule
+                BrowserAnimationsModule,
+                I18nModule
             ]
         }).compileComponents();
     }));
diff --git a/src/app/shared/text-block/components/text-replacement/text-replacement.component.ts b/src/app/shared/text-block/components/text-replacement/text-replacement.component.ts
index 248d383..e885993 100644
--- a/src/app/shared/text-block/components/text-replacement/text-replacement.component.ts
+++ b/src/app/shared/text-block/components/text-replacement/text-replacement.component.ts
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from "@angular/core";
+import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from "@angular/core";
 import {timer} from "rxjs";
 import {DateControlComponent} from "../../../controls/date-control";
 import {SelectComponent} from "../../../controls/select/components/select";
@@ -41,9 +41,6 @@
     @Input()
     public appDisabled: boolean;
 
-    @Input()
-    public appMaxWidth: string;
-
     @Output()
     public appEdit = new EventEmitter<boolean>();
 
@@ -58,13 +55,9 @@
     @Output()
     public appValueChange = new EventEmitter<string>();
 
-    public constructor(private changeDetectorRef: ChangeDetectorRef) {
-    }
-
     public async onClick() {
         this.appEditable = true;
         this.appEdit.emit(this.appEditable);
-        this.changeDetectorRef.detectChanges();
         this.resizeInput();
         switch (this.appType) {
             case "input":
diff --git a/src/app/shared/text-block/model/ITextBlockRenderItem.ts b/src/app/shared/text-block/model/ITextBlockRenderItem.ts
index 3cac859..da77e1f 100644
--- a/src/app/shared/text-block/model/ITextBlockRenderItem.ts
+++ b/src/app/shared/text-block/model/ITextBlockRenderItem.ts
@@ -16,4 +16,5 @@
     value: string;
     options?: string[];
     placeholder?: string;
+    iconType?: string;
 }
diff --git a/src/app/shared/text-block/pipes/combine-blockdata-text/combine-blockdata-text.pipe.ts b/src/app/shared/text-block/pipes/combine-blockdata-text/combine-blockdata-text.pipe.ts
index ed3b4c5..152c7d1 100644
--- a/src/app/shared/text-block/pipes/combine-blockdata-text/combine-blockdata-text.pipe.ts
+++ b/src/app/shared/text-block/pipes/combine-blockdata-text/combine-blockdata-text.pipe.ts
@@ -16,6 +16,9 @@
 import {arrayJoin} from "../../../../util/store";
 import {ITextBlockRenderItem} from "../../model/ITextBlockRenderItem";
 
+/**
+ * Takes in an array of TextBlockRender items and combines adjacent ones of type text into one.
+ */
 @Pipe({
     name: "combineBlockdataText"
 })
diff --git a/src/app/shared/text-block/pipes/get-blockdata-array/IReplacement.ts b/src/app/shared/text-block/pipes/get-blockdata-array/IReplacement.ts
index 9106d8e..e63ba06 100644
--- a/src/app/shared/text-block/pipes/get-blockdata-array/IReplacement.ts
+++ b/src/app/shared/text-block/pipes/get-blockdata-array/IReplacement.ts
@@ -12,6 +12,7 @@
  ********************************************************************************/
 
 export interface IReplacement {
-    separator: RegExp;
+    separator: string | RegExp;
     type: string;
+    iconType?: string;
 }
diff --git a/src/app/shared/text-block/pipes/get-blockdata-array/block-data-helper.ts b/src/app/shared/text-block/pipes/get-blockdata-array/block-data-helper.ts
index 9a7311f..3533454 100644
--- a/src/app/shared/text-block/pipes/get-blockdata-array/block-data-helper.ts
+++ b/src/app/shared/text-block/pipes/get-blockdata-array/block-data-helper.ts
@@ -15,17 +15,30 @@
 import {ITextBlockRenderItem} from "../../model/ITextBlockRenderItem";
 import {IReplacement} from "./IReplacement";
 
+/**
+ * Regex for replacement tags in text blocks. These tags mark the position values have to be integrated into the textblock for display
+ * purposes.
+ * E.g. <f:name> is to be replaced free text input by the user.
+ */
 export const replacements: IReplacement[] = [
     {separator: /(<f:[A-Za-z0-9_\\-]+>)/g, type: "input"},
     {separator: /(<d:[A-Za-z0-9_\\-]+>)/g, type: "date"},
     {separator: /(<s:[A-Za-z0-9_\\-]+>)/g, type: "select"}
 ];
-
 export const alwaysReplace: IReplacement[] = [
     {separator: /(\n)/, type: "newline"},
     {separator: /(<t:[A-Za-z0-9_\\-]+>)/g, type: "text-fill"}
 ];
 
+
+/**
+ * Function that takes in a TextBlockModel and splits it up by the replacement tags into the different elements to display.
+ * The result array is of type TextBlockRenderItem.
+ * The normal text part is simply put as the value of the element with type "text".
+ * For the other types the placeholder is set as the name of the replacement tag and value is only set if there is a value already set
+ * (placeholderValues)
+ * Options are only set for select type.
+ */
 export function textToBlockDataArray(
     blockModel: IAPITextBlockModel,
     placeholderValues: { [key: string]: string },
@@ -34,7 +47,7 @@
     replace: boolean = true): ITextBlockRenderItem[] {
 
     const whatToReplace: IReplacement[] = replace ? [...alwaysReplace, ...replacements]
-        : [...alwaysReplace, ...replacements.map((_) => ({..._, type: "highlight-text"}))];
+        : [...alwaysReplace, ...replacements.map((_) => ({..._, type: "highlight-text", iconType: _.type}))];
 
     const blockData: ITextBlockRenderItem[] = [{value: blockModel ? blockModel.text : "", type: "text"}];
 
@@ -55,8 +68,7 @@
                 arrays.push(
                     ...replaceBySeparator(
                         textElement.value,
-                        replacement.separator,
-                        replacement.type,
+                        replacement,
                         placeholderValues,
                         replacementTexts,
                         selectOptions
@@ -72,32 +84,32 @@
 
 export function replaceBySeparator(
     inputValue: string,
-    separator: string | RegExp,
-    replaceType: string,
+    replacement: IReplacement,
     placeholderValues?: { [key: string]: string },
     replacementTexts?: { [key: string]: string },
     selectOptions?: { [key: string]: string[] }): ITextBlockRenderItem[] {
 
-    const splitBySeparator: string[] = inputValue.split(separator);
+    const splitBySeparator: string[] = inputValue.split(replacement?.separator);
     const blockData: ITextBlockRenderItem[] = [];
 
     for (const renderItem of splitBySeparator) {
-        const isText: boolean = !(new RegExp(separator)).test(renderItem);
+        const isText: boolean = !(new RegExp(replacement?.separator)).test(renderItem);
         const value: string = isText ? renderItem
             : renderItem.replace(/(<[fdts]:)/, "")
                 .replace(">", "")
                 .replace("\n", "");
         let placeholder: string = placeholderValues ? placeholderValues[renderItem] : undefined;
-        if (replaceType === "text-fill") {
+        if (replacement?.type === "text-fill") {
             placeholder = replacementTexts ? replacementTexts[value] : undefined;
         }
 
         if (value !== "" || !isText) {
             blockData.push({
                 value,
-                type: isText ? "text" : replaceType,
+                type: isText ? "text" : replacement?.type,
                 placeholder,
-                options: (!isText && replaceType === "select" && !!selectOptions) ? selectOptions[value] : undefined
+                options: (!isText && replacement?.type === "select" && !!selectOptions) ? selectOptions[value] : undefined,
+                iconType: isText ? undefined : replacement?.iconType
             });
         }
 
diff --git a/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-arrangement.pipe.ts b/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-arrangement.pipe.ts
index c6bc1a7..f7ec64f 100644
--- a/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-arrangement.pipe.ts
+++ b/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-arrangement.pipe.ts
@@ -18,6 +18,11 @@
 import {alwaysReplace, replaceTags, textToBlockDataArray} from "./block-data-helper";
 import {IReplacement} from "./IReplacement";
 
+/**
+ * Takes in a IAPITextArrangementItemModel and splits it into an array of ITextBlockRenderItem.
+ * Text, new lined and the replacements are separated. The replacements like freetext, date and select are grouped together as one type,
+ * text-fill. That is done to be able to mark them in the ui.
+ */
 @Pipe({
     name: "getBlockDataFromArrangement"
 })
diff --git a/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.spec.ts b/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.spec.ts
index 5eb6dee..025cad7 100644
--- a/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.spec.ts
+++ b/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.spec.ts
@@ -44,7 +44,8 @@
             },
             {
                 type: "highlight-text",
-                value: "freetext"
+                value: "freetext",
+                iconType: "input"
             },
             {
                 type: "text-fill",
@@ -52,11 +53,13 @@
             },
             {
                 type: "highlight-text",
-                value: "select"
+                value: "select",
+                iconType: "select"
             },
             {
                 type: "highlight-text",
-                value: "date"
+                value: "date",
+                iconType: "date"
             },
             {
                 type: "text",
diff --git a/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.ts b/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.ts
index c2c1494..548e7ec 100644
--- a/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.ts
+++ b/src/app/shared/text-block/pipes/get-blockdata-array/get-blockdata-array-from-block.pipe.ts
@@ -22,6 +22,16 @@
 
     public transform(blockModel: IAPITextBlockModel, replacements?: { [key: string]: string }):
         Array<{ value: string, type: string, placeholder?: string, options?: string[] }> {
-        return blockModel ? textToBlockDataArray(blockModel, undefined, replacements, undefined, false) : [];
+
+        if (blockModel == null) {
+            return [];
+        }
+
+        blockModel = {
+            ...blockModel,
+            text: blockModel.text?.endsWith("\n") ? blockModel.text + " " : blockModel.text
+        };
+
+        return textToBlockDataArray(blockModel, undefined, replacements, undefined, false);
     }
 }
diff --git a/src/app/store/attachments/attachments-store.module.ts b/src/app/store/attachments/attachments-store.module.ts
index 8b95c07..2db4d24 100644
--- a/src/app/store/attachments/attachments-store.module.ts
+++ b/src/app/store/attachments/attachments-store.module.ts
@@ -18,6 +18,7 @@
 import {FetchAttachmentsEffect, SubmitAttachmentsEffect} from "./effects";
 import {AttachmentDownloadEffect} from "./effects/download";
 import {SubmitConsiderationsEffect} from "./effects/submit/submit-considerations.effect";
+import {SubmitTagsEffect} from "./effects/submit/submit-tags.effect";
 
 @NgModule({
     imports: [
@@ -26,7 +27,8 @@
             AttachmentDownloadEffect,
             FetchAttachmentsEffect,
             SubmitAttachmentsEffect,
-            SubmitConsiderationsEffect
+            SubmitConsiderationsEffect,
+            SubmitTagsEffect
         ])
     ]
 })
diff --git a/src/app/store/attachments/effects/submit/submit-tags.effect.ts b/src/app/store/attachments/effects/submit/submit-tags.effect.ts
new file mode 100644
index 0000000..fa56e47
--- /dev/null
+++ b/src/app/store/attachments/effects/submit/submit-tags.effect.ts
@@ -0,0 +1,76 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Injectable} from "@angular/core";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {Action} from "@ngrx/store";
+import {concat, EMPTY, Observable, of} from "rxjs";
+import {catchError, endWith, ignoreElements, mergeMap, startWith, switchMap} from "rxjs/operators";
+import {AttachmentsApiService} from "../../../../core/api/attachments";
+import {endWithObservable, ignoreError} from "../../../../util";
+import {setErrorAction} from "../../../root/actions";
+import {EErrorCode} from "../../../root/model";
+import {setSettingsLoadingStateAction} from "../../../settings/actions";
+import {submitTagsAction} from "../../../statements/actions";
+import {fetchAttachmentTagsAction} from "../../actions";
+
+@Injectable({providedIn: "root"})
+export class SubmitTagsEffect {
+
+    public submit$ = createEffect(() => this.actions.pipe(
+        ofType(submitTagsAction),
+        switchMap((action) => {
+            return this.submit(action.labels).pipe(
+                ignoreError()
+            );
+        })
+    ));
+
+    public constructor(
+        public readonly actions: Actions,
+        public readonly attachmentsApiService: AttachmentsApiService
+    ) {
+
+    }
+
+    public submit(
+        labels: string[]
+    ): Observable<Action> {
+        const errors: string[] = [];
+        return this.addTags(labels, errors).pipe(
+            endWithObservable(() => {
+                return errors.length > 0 ? concat(
+                    of(setErrorAction({error: EErrorCode.COULD_NOT_ADD_TAG, errorValue: {value: errors.toString().replace(/,/g, ", ")}})),
+                    of(fetchAttachmentTagsAction())
+                ) : of(fetchAttachmentTagsAction());
+            }),
+            startWith(setSettingsLoadingStateAction({state: {addingTags: true}})),
+            endWith(setSettingsLoadingStateAction({state: {addingTags: false}}))
+        );
+    }
+
+    public addTags(labels: string[], errors: any[]): Observable<Action> {
+        return of(...labels).pipe(
+            mergeMap((item) => {
+                return this.attachmentsApiService.addNewTag(item).pipe(
+                    catchError(() => {
+                        errors.push(item);
+                        return EMPTY;
+                    }),
+                    ignoreElements()
+                );
+            })
+        );
+    }
+
+}
diff --git a/src/app/store/geo/actions/geo.actions.ts b/src/app/store/geo/actions/geo.actions.ts
index 9c1a7f8..305a6bc 100644
--- a/src/app/store/geo/actions/geo.actions.ts
+++ b/src/app/store/geo/actions/geo.actions.ts
@@ -12,9 +12,25 @@
  ********************************************************************************/
 
 import {createAction, props} from "@ngrx/store";
-import {ILeafletBounds} from "../../../shared/leaflet";
+import {IAPINominatimSearchResult} from "../../../core";
+import {ILeafletBounds} from "../../../features/map";
 
 export const openGisAction = createAction(
     "[Map] Open GIS",
     props<{ bounds: ILeafletBounds, user: string }>()
 );
+
+export const searchMapAction = createAction(
+    "[Map] Search for a place to display on map",
+    props<{ q: string }>()
+);
+
+export const setSearchResponseAction = createAction(
+    "[Store] Sets the search result",
+    props<{ response: IAPINominatimSearchResult[] }>()
+);
+
+export const setMapSearchLoadingAction = createAction(
+    "[Store] Sets the search loading state",
+    props<{ loading: boolean }>()
+);
diff --git a/src/app/store/geo/effects/index.ts b/src/app/store/geo/effects/index.ts
index 1cfc850..a77c8e6 100644
--- a/src/app/store/geo/effects/index.ts
+++ b/src/app/store/geo/effects/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./search";
 export * from "./open-gis";
diff --git a/src/app/store/geo/effects/open-gis/open-gis.effect.spec.ts b/src/app/store/geo/effects/open-gis/open-gis.effect.spec.ts
index c33cff7..6f23692 100644
--- a/src/app/store/geo/effects/open-gis/open-gis.effect.spec.ts
+++ b/src/app/store/geo/effects/open-gis/open-gis.effect.spec.ts
@@ -17,8 +17,8 @@
 import {Action} from "@ngrx/store";
 import {LatLngLiteral} from "leaflet";
 import {Observable, Subject, Subscription} from "rxjs";
-import {IAPIGeographicPositions, SPA_BACKEND_ROUTE, WINDOW} from "../../../../core";
-import {ILeafletBounds} from "../../../../shared/leaflet";
+import {IAPIGeographicPositions, SPA_BACKEND_ROUTE} from "../../../../core";
+import {ILeafletBounds} from "../../../../features/map";
 import {openGisAction} from "../../actions";
 import {OpenGisEffect} from "./open-gis.effect";
 
@@ -64,13 +64,6 @@
                 {
                     provide: SPA_BACKEND_ROUTE,
                     useValue: "/"
-                },
-                {
-                    provide: WINDOW,
-                    useValue: ({
-                        open(url?: string, target?: string, features?: string, replace?: boolean) {
-                        }
-                    })
                 }
             ]
         });
@@ -87,7 +80,6 @@
 
     it("should open GIS in new window", () => {
         const results: Action[] = [];
-        const spy = spyOn(effect.window, "open");
         const actionSubject = new Subject<Action>();
         actions$ = actionSubject;
 
@@ -95,7 +87,7 @@
 
         actionSubject.next(openGisAction({bounds, user}));
         expectTransformRequest(geographicPositions);
-        expect(spy).toHaveBeenCalledWith(gisUrl, "_blank");
+        expectGisRequest(gisUrl);
         expect(results).toEqual([]);
         httpTestingController.verify();
     });
@@ -117,6 +109,12 @@
         request.flush(body);
     }
 
+    function expectGisRequest(url: string) {
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("GET");
+        request.flush("");
+    }
+
 });
 
 function createLatLngMock(): LatLngLiteral {
diff --git a/src/app/store/geo/effects/open-gis/open-gis.effect.ts b/src/app/store/geo/effects/open-gis/open-gis.effect.ts
index 19da54f..3298232 100644
--- a/src/app/store/geo/effects/open-gis/open-gis.effect.ts
+++ b/src/app/store/geo/effects/open-gis/open-gis.effect.ts
@@ -11,14 +11,15 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {HttpClient} from "@angular/common/http";
 import {Inject, Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {Action} from "@ngrx/store";
-import {EMPTY, Observable, of} from "rxjs";
-import {exhaustMap, ignoreElements, switchMap} from "rxjs/operators";
+import {Observable, of} from "rxjs";
+import {ignoreElements, switchMap} from "rxjs/operators";
 import {APP_CONFIGURATION, GeoApiService, IAPIGeographicPositions, IAppConfiguration, WINDOW} from "../../../../core";
-import {ILeafletBounds} from "../../../../shared/leaflet";
-import {catchErrorTo} from "../../../../util/rxjs";
+import {ILeafletBounds} from "../../../../features/map";
+import {catchErrorTo, ignoreError} from "../../../../util/rxjs";
 import {EErrorCode, setErrorAction} from "../../../root";
 import {openGisAction} from "../../actions";
 
@@ -27,7 +28,7 @@
 
     public open$ = createEffect(() => this.actions.pipe(
         ofType(openGisAction),
-        exhaustMap((action) => this.openGis(action.bounds, action.user))
+        switchMap((action) => this.openGis(action.bounds, action.user))
     ));
 
     /**
@@ -53,6 +54,7 @@
     public constructor(
         public actions: Actions,
         public geoApiService: GeoApiService,
+        public http: HttpClient,
         @Inject(WINDOW) public window: Window,
         @Inject(APP_CONFIGURATION) public configuration: IAppConfiguration
     ) {
@@ -64,8 +66,7 @@
             switchMap(() => this.transform(this.extractGeographicPositionFromBounds(bounds))),
             switchMap((geographicPositions) => {
                 const gisUrl = this.generateUrl(geographicPositions, user);
-                this.window.open(gisUrl, "_blank");
-                return EMPTY;
+                return this.http.get(gisUrl, {responseType: "text"}).pipe(ignoreError());
             }),
             ignoreElements(),
             catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED}))
diff --git a/src/app/features/settings/components/index.ts b/src/app/store/geo/effects/search/index.ts
similarity index 91%
copy from src/app/features/settings/components/index.ts
copy to src/app/store/geo/effects/search/index.ts
index 990bb42..7a682ab 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/store/geo/effects/search/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./nominatim-search-effect.service";
diff --git a/src/app/store/geo/effects/search/nominatim-search-effect.service.ts b/src/app/store/geo/effects/search/nominatim-search-effect.service.ts
new file mode 100644
index 0000000..d145230
--- /dev/null
+++ b/src/app/store/geo/effects/search/nominatim-search-effect.service.ts
@@ -0,0 +1,54 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Injectable} from "@angular/core";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {Action} from "@ngrx/store";
+import {Observable, of} from "rxjs";
+import {endWith, startWith, switchMap, throttleTime} from "rxjs/operators";
+import {GeoApiService} from "../../../../core";
+import {catchErrorTo} from "../../../../util/rxjs";
+import {setErrorAction} from "../../../root/actions";
+import {EErrorCode} from "../../../root/model";
+import {searchMapAction, setMapSearchLoadingAction, setSearchResponseAction} from "../../actions";
+
+@Injectable({providedIn: "root"})
+export class NominatimSearchEffect {
+
+    public search$ = createEffect(() => this.actions.pipe(
+        ofType(searchMapAction),
+        throttleTime(200),
+        switchMap((action) => this.search(action.q))
+    ));
+
+    public constructor(
+        public actions: Actions,
+        public geoApiService: GeoApiService
+    ) {
+
+    }
+
+    public search(q: string): Observable<Action> {
+        return this.geoApiService.search(q).pipe(
+            switchMap((response) => {
+                return response.length > 0 ?
+                    of(setSearchResponseAction({response})) :
+                    of(setSearchResponseAction({response}), setErrorAction({error: EErrorCode.SEARCH_NO_RESULT}));
+            }),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setMapSearchLoadingAction({loading: true})),
+            endWith(setMapSearchLoadingAction({loading: false}))
+        );
+    }
+
+}
diff --git a/src/app/store/geo/geo-reducers.token.ts b/src/app/store/geo/geo-reducers.token.ts
index a1453d3..ac044c2 100644
--- a/src/app/store/geo/geo-reducers.token.ts
+++ b/src/app/store/geo/geo-reducers.token.ts
@@ -14,10 +14,15 @@
 import {InjectionToken} from "@angular/core";
 import {ActionReducerMap} from "@ngrx/store";
 import {IGeoStoreState} from "./model";
+import {mapLoadingReducer} from "./reducers/search/map-loading.reducer";
+import {mapSearchReducer} from "./reducers/search/map-search.reducer";
 
 export const GEO_NAME = "geo";
 
 export const GEO_REDUCERS = new InjectionToken<ActionReducerMap<IGeoStoreState>>("Geo store reducer", {
     providedIn: "root",
-    factory: () => ({})
+    factory: () => ({
+        loading: mapLoadingReducer,
+        responseContent: mapSearchReducer
+    })
 });
diff --git a/src/app/store/geo/geo-store.module.ts b/src/app/store/geo/geo-store.module.ts
index 1253f7d..9493e61 100644
--- a/src/app/store/geo/geo-store.module.ts
+++ b/src/app/store/geo/geo-store.module.ts
@@ -14,14 +14,15 @@
 import {NgModule} from "@angular/core";
 import {EffectsModule} from "@ngrx/effects";
 import {StoreModule} from "@ngrx/store";
-import {OpenGisEffect} from "./effects";
+import {NominatimSearchEffect, OpenGisEffect} from "./effects";
 import {GEO_NAME, GEO_REDUCERS} from "./geo-reducers.token";
 
 @NgModule({
     imports: [
         StoreModule.forFeature(GEO_NAME, GEO_REDUCERS),
         EffectsModule.forFeature([
-            OpenGisEffect
+            OpenGisEffect,
+            NominatimSearchEffect
         ])
     ]
 })
diff --git a/src/app/store/geo/index.ts b/src/app/store/geo/index.ts
index 5b5ab49..3d060a9 100644
--- a/src/app/store/geo/index.ts
+++ b/src/app/store/geo/index.ts
@@ -12,6 +12,8 @@
  ********************************************************************************/
 
 export * from "./actions";
+export * from "./effects";
 export * from "./model";
+export * from "./selectors";
 
 export * from "./geo-store.module";
diff --git a/src/app/store/geo/model/IGeoStoreState.ts b/src/app/store/geo/model/IGeoStoreState.ts
index c4a0812..8192227 100644
--- a/src/app/store/geo/model/IGeoStoreState.ts
+++ b/src/app/store/geo/model/IGeoStoreState.ts
@@ -10,9 +10,11 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+import {IAPINominatimSearchResult} from "../../../core";
 
 export interface IGeoStoreState {
 
-    loading?: any;
+    loading?: boolean;
 
+    responseContent: IAPINominatimSearchResult[];
 }
diff --git a/src/app/shared/leaflet/index.ts b/src/app/store/geo/reducers/search/map-loading.reducer.ts
similarity index 67%
copy from src/app/shared/leaflet/index.ts
copy to src/app/store/geo/reducers/search/map-loading.reducer.ts
index 849c149..f963ee0 100644
--- a/src/app/shared/leaflet/index.ts
+++ b/src/app/store/geo/reducers/search/map-loading.reducer.ts
@@ -10,10 +10,13 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+import {createReducer, on} from "@ngrx/store";
+import {setMapSearchLoadingAction} from "../../actions";
 
-export * from "./directives";
-export * from "./pipes";
-export * from "./util";
 
-export * from "./leaflet.module";
-export * from "./leaflet-configuration.token";
+export const mapLoadingReducer = createReducer<boolean>(
+    false,
+    on(setMapSearchLoadingAction, (state, payload) => {
+        return payload?.loading;
+    })
+);
diff --git a/src/app/store/settings/reducers/sectors.reducer.ts b/src/app/store/geo/reducers/search/map-search.reducer.ts
similarity index 66%
copy from src/app/store/settings/reducers/sectors.reducer.ts
copy to src/app/store/geo/reducers/search/map-search.reducer.ts
index 53cc6cf..9873d83 100644
--- a/src/app/store/settings/reducers/sectors.reducer.ts
+++ b/src/app/store/geo/reducers/search/map-search.reducer.ts
@@ -10,14 +10,14 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
-
 import {createReducer, on} from "@ngrx/store";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {IAPINominatimSearchResult} from "../../../../core";
+import {setSearchResponseAction} from "../../actions";
 
-export const sectorsReducer = createReducer<IAPISectorsModel>(
-    {},
-    on(setSectorsAction, (state, payload) => {
-        return payload.sectors ? {...payload.sectors} : state;
+
+export const mapSearchReducer = createReducer<IAPINominatimSearchResult[]>(
+    null,
+    on(setSearchResponseAction, (state, payload) => {
+        return payload?.response;
     })
 );
diff --git a/src/app/store/geo/selectors/geo.selectors.ts b/src/app/store/geo/selectors/geo.selectors.ts
new file mode 100644
index 0000000..4a15cd7
--- /dev/null
+++ b/src/app/store/geo/selectors/geo.selectors.ts
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {createFeatureSelector, createSelector} from "@ngrx/store";
+import {arrayJoin, selectArrayProjector, selectPropertyProjector} from "../../../util/store";
+import {GEO_NAME} from "../geo-reducers.token";
+import {IGeoStoreState} from "../model";
+
+export const geoStateSelector = createFeatureSelector<IGeoStoreState>(GEO_NAME);
+
+export const getNominatimResponseContentSelector = createSelector(
+    geoStateSelector,
+    selectArrayProjector("responseContent", [])
+);
+
+export const getNominatimSearchResultSelector = createSelector(
+    getNominatimResponseContentSelector,
+    (result) => {
+        return arrayJoin(result).length === 0 ? undefined : {lat: parseFloat(result[0].lat), lng: parseFloat(result[0].lon)};
+    }
+);
+
+export const getNominatimLoadingSelector = createSelector(
+    geoStateSelector,
+    selectPropertyProjector("loading")
+);
diff --git a/src/app/features/settings/components/index.ts b/src/app/store/geo/selectors/index.ts
similarity index 94%
rename from src/app/features/settings/components/index.ts
rename to src/app/store/geo/selectors/index.ts
index 990bb42..f2e1df6 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/store/geo/selectors/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./geo.selectors";
diff --git a/src/app/store/process/actions/process.actions.ts b/src/app/store/process/actions/process.actions.ts
index 538ff93..3db71d7 100644
--- a/src/app/store/process/actions/process.actions.ts
+++ b/src/app/store/process/actions/process.actions.ts
@@ -22,7 +22,13 @@
 
 export const claimAndCompleteTask = createAction(
     "[Details] Claim and complete Task",
-    props<{ statementId: number, taskId: string, variables: TCompleteTaskVariable, claimNext?: boolean | EAPIProcessTaskDefinitionKey }>()
+    props<{
+        statementId: number,
+        taskId: string,
+        assignee: string,
+        variables: TCompleteTaskVariable,
+        claimNext?: boolean | EAPIProcessTaskDefinitionKey
+    }>()
 );
 
 export const unclaimAllTasksAction = createAction(
diff --git a/src/app/store/process/effects/process-task.effect.spec.ts b/src/app/store/process/effects/process-task.effect.spec.ts
index 262f791..e7b8358 100644
--- a/src/app/store/process/effects/process-task.effect.spec.ts
+++ b/src/app/store/process/effects/process-task.effect.spec.ts
@@ -94,7 +94,8 @@
         const statementId = 19;
         const taskId = "191919";
         const variables = {};
-        actions$ = of(claimAndCompleteTask({statementId, taskId, variables, claimNext: true}));
+        const assignee = "hugo";
+        actions$ = of(claimAndCompleteTask({statementId, taskId, assignee, variables, claimNext: true}));
         const claimTaskSpy = spyOn(effect, "claimTask").and.returnValue(EMPTY);
         const completeTaskSpy = spyOn(effect, "completeTask").and.returnValue(EMPTY);
 
@@ -107,7 +108,7 @@
         subscription = effect.claimAndComplete$.subscribe((action) => results.push(action));
 
         expect(results).toEqual(expectedResult);
-        expect(claimTaskSpy).toHaveBeenCalledWith(statementId, taskId);
+        expect(claimTaskSpy).toHaveBeenCalledWith(statementId, taskId, assignee);
         expect(completeTaskSpy).toHaveBeenCalledWith(statementId, taskId, variables, true);
 
         httpTestingController.verify();
diff --git a/src/app/store/process/effects/process-task.effect.ts b/src/app/store/process/effects/process-task.effect.ts
index 39cdc55..f99da56 100644
--- a/src/app/store/process/effects/process-task.effect.ts
+++ b/src/app/store/process/effects/process-task.effect.ts
@@ -40,7 +40,7 @@
 
     public claim$ = createEffect(() => this.actions.pipe(
         ofType(claimTaskAction),
-        switchMap((action) => this.claimTask(action.statementId, action.taskId, true).pipe(
+        switchMap((action) => this.claimTask(action.statementId, action.taskId, null, true).pipe(
             catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
             startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
             endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
@@ -49,7 +49,9 @@
 
     public claimAndComplete$ = createEffect(() => this.actions.pipe(
         ofType(claimAndCompleteTask),
-        switchMap((action) => this.claimAndCompleteTask(action.statementId, action.taskId, action.variables, action.claimNext).pipe(
+        switchMap((action) => this.claimAndCompleteTask(
+            action.statementId, action.taskId, action.variables, action.assignee, action.claimNext
+        ).pipe(
             catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
             startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
             endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
@@ -58,7 +60,7 @@
 
     public claimAndSend$ = createEffect(() => this.actions.pipe(
         ofType(sendStatementViaMailAction),
-        switchMap((action) => this.claimAndSend(action.statementId, action.taskId).pipe(
+        switchMap((action) => this.claimAndSend(action.statementId, action.taskId, action.assignee).pipe(
             catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
             startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
             endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
@@ -87,13 +89,14 @@
 
     }
 
-    public claimTask(statementId: number, taskId: string, navigate?: boolean): Observable<Action> {
+    public claimTask(statementId: number, taskId: string, assignee?: string, navigate?: boolean): Observable<Action> {
         return this.processApiService.claimStatementTask(statementId, taskId).pipe(
             map((task) => setTaskEntityAction({task})),
             endWithObservable(() => navigate ? this.navigateTo(statementId, taskId) : EMPTY),
             catchHttpErrorTo(setErrorAction({
                 statementId,
-                error: EErrorCode.CLAIMED_BY_OTHER_USER
+                error: assignee ? EErrorCode.CLAIMED_BY_OTHER_USER : EErrorCode.ALREADY_CLAIMED,
+                errorValue: {user: assignee}
             }), EHttpStatusCodes.BAD_REQUEST),
             catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
             this.fetchTasksAfterError(statementId)
@@ -104,9 +107,10 @@
         statementId: number,
         taskId: string,
         variable: TCompleteTaskVariable,
+        assignee: string,
         claimNext?: boolean | EAPIProcessTaskDefinitionKey
     ): Observable<Action> {
-        return this.claimTask(statementId, taskId).pipe(
+        return this.claimTask(statementId, taskId, assignee).pipe(
             throwAfterActionType(setErrorAction),
             endWithObservable(() => this.completeTask(statementId, taskId, variable, claimNext)),
             ignoreError()
@@ -115,9 +119,10 @@
 
     public claimAndSend(
         statementId: number,
-        taskId: string
+        taskId: string,
+        assignee: string
     ): Observable<Action> {
-        return this.claimTask(statementId, taskId).pipe(
+        return this.claimTask(statementId, taskId, assignee).pipe(
             throwAfterActionType(setErrorAction),
             endWithObservable(() => concat(
                 this.sendMailAndComplete(statementId, taskId),
@@ -160,13 +165,17 @@
             ),
             this.claimNext(statementId, claimNext, false).pipe(
                 map((task) => {
-                    nextTaskId = task.taskId;
-                    return setTaskEntityAction({task});
+                    nextTaskId = task?.task?.taskId;
+                    const user = task?.task?.assignee;
+                    return task?.errorOccured ? setErrorAction({
+                        statementId,
+                        error: user == null ? EErrorCode.ALREADY_CLAIMED : EErrorCode.CLAIMED_BY_OTHER_USER,
+                        errorValue: {user}
+                    }) : setTaskEntityAction({task: task?.task});
                 }),
                 catchHttpErrorTo(setErrorAction({
-                    statementId,
-                    error: EErrorCode.CLAIMED_BY_OTHER_USER
-                }), EHttpStatusCodes.BAD_REQUEST)
+                    error: EErrorCode.UNEXPECTED,
+                }))
             ),
             this.fetchTasks(statementId)
         ).pipe(
@@ -202,20 +211,30 @@
         statementId: number,
         claimNext: boolean | EAPIProcessTaskDefinitionKey,
         navigate?: boolean
-    ): Observable<IAPIProcessTask> {
+    ): Observable<{ task: IAPIProcessTask, errorOccured?: boolean }> {
         let taskId: string;
+        let nextTask: IAPIProcessTask;
+        let error = false;
         return claimNext == null || claimNext === false ? EMPTY : this.getNextTask(statementId, claimNext).pipe(
-            switchMap((_taskId) => _taskId ? this.processApiService.claimStatementTask(statementId, taskId = _taskId) : EMPTY),
+            switchMap((task) => {
+                nextTask = task;
+                return task?.taskId ? this.processApiService.claimStatementTask(statementId, taskId = task?.taskId) : EMPTY;
+            }),
+            catchHttpError(() => {
+                error = true;
+                return EMPTY;
+            }, EHttpStatusCodes.BAD_REQUEST),
+            map(() => ({task: nextTask, errorOccured: error})),
             endWithObservable(() => navigate ? this.navigateTo(statementId, taskId) : EMPTY)
         );
     }
 
-    public getNextTask(statementId: number, next?: boolean | EAPIProcessTaskDefinitionKey): Observable<string> {
+    public getNextTask(statementId: number, next?: boolean | EAPIProcessTaskDefinitionKey): Observable<IAPIProcessTask> {
         return this.processApiService.getStatementTasks(statementId).pipe(
             map((tasks) => {
                 return arrayJoin(tasks).find((_) => {
                     return next === true || _.taskDefinitionKey === next;
-                })?.taskId;
+                });
             })
         );
     }
diff --git a/src/app/store/root/actions/error.actions.ts b/src/app/store/root/actions/error.actions.ts
index af04c23..bccc8a7 100644
--- a/src/app/store/root/actions/error.actions.ts
+++ b/src/app/store/root/actions/error.actions.ts
@@ -15,5 +15,5 @@
 
 export const setErrorAction = createAction(
     "[API] Set error",
-    props<{ error: string, statementId?: number | "new" }>()
+    props<{ error: string, errorValue?: { [key: string]: string }, statementId?: number | "new" }>()
 );
diff --git a/src/app/store/root/effects/toast.effect.ts b/src/app/store/root/effects/toast.effect.ts
index 56da623..2e8da0e 100644
--- a/src/app/store/root/effects/toast.effect.ts
+++ b/src/app/store/root/effects/toast.effect.ts
@@ -24,7 +24,7 @@
     public toast$ = createEffect(() => this.actions.pipe(
         ofType(setErrorAction),
         filter((action) => action.statementId == null && action.error != null),
-        mergeMap(async (action) => this.toast(action.error))
+        mergeMap(async (action) => this.toast(action.error, action.errorValue))
     ), {dispatch: false});
 
     public constructor(
@@ -34,17 +34,17 @@
     ) {
     }
 
-    public async toast(error: string) {
+    public async toast(error: string, errorValue?: { [key: string]: string }) {
         this.messageService.add({
             severity: "error",
             life: 7000,
             summary: await this.getTranslation("shared.errorMessages.title"),
-            detail: await this.getTranslation(error)
+            detail: await this.getTranslation(error, errorValue)
         });
     }
 
-    private async getTranslation(msg: string) {
-        return this.translateService.get(msg).pipe(take(1)).toPromise();
+    private async getTranslation(msg: string, value?: { [key: string]: string }) {
+        return this.translateService.get(msg, value).pipe(take(1)).toPromise();
     }
 
 }
diff --git a/src/app/store/root/model/EErrorCode.ts b/src/app/store/root/model/EErrorCode.ts
index 02a601c..5750bbb 100644
--- a/src/app/store/root/model/EErrorCode.ts
+++ b/src/app/store/root/model/EErrorCode.ts
@@ -14,6 +14,7 @@
 export enum EErrorCode {
     UNEXPECTED = "shared.errorMessages.unexpected",
     TASK_TO_COMPLETE_NOT_FOUND = "shared.errorMessages.taskToCompleteNotFound",
+    ALREADY_CLAIMED = "shared.errorMessages.alreadyClaimed",
     CLAIMED_BY_OTHER_USER = "shared.errorMessages.claimedByAnotherUser",
     MISSING_FORM_DATA = "shared.errorMessages.missingFormData",
     FAILED_LOADING_CONTACT = "shared.errorMessages.failedLoadingContact",
@@ -22,5 +23,9 @@
     FAILED_MAIL_TRANSFER = "shared.errorMessages.failedMailTransfer",
     INVALID_TEXT_ARRANGEMENT = "shared.errorMessages.invalidTextArrangement",
     COULD_NOT_LOAD_MAIL_DATA = "shared.errorMessages.couldNotLoadMailData",
-    COULD_NOT_SEND_MAIL = "shared.errorMessages.couldNotSendMail"
+    COULD_NOT_SEND_MAIL = "shared.errorMessages.couldNotSendMail",
+    COULD_NOT_ADD_TAG = "shared.errorMessages.couldNotAddTag",
+    INVALID_FILE_FORMAT = "shared.errorMessages.invalidFileFormat",
+    SEARCH_NO_RESULT = "shared.errorMessages.searchNoResult",
+    BAD_USER_DATA = "shared.errorMessages.badUserData"
 }
diff --git a/src/app/store/root/model/IRootStoreState.ts b/src/app/store/root/model/IRootStoreState.ts
index 2316bdf..6c209b1 100644
--- a/src/app/store/root/model/IRootStoreState.ts
+++ b/src/app/store/root/model/IRootStoreState.ts
@@ -17,7 +17,7 @@
 
 export interface IRootStoreState {
     /**
-     * Is true iff initialization has finished
+     * Is true if initialization has finished
      */
     isLoading?: boolean;
 
diff --git a/src/app/store/root/services/user-role-route-guard.service.ts b/src/app/store/root/services/user-role-route-guard.service.ts
index e7dc0fa..b271fee 100644
--- a/src/app/store/root/services/user-role-route-guard.service.ts
+++ b/src/app/store/root/services/user-role-route-guard.service.ts
@@ -72,6 +72,15 @@
 }
 
 @Injectable({providedIn: "root"})
+export class AdminRouteGuardService extends UserRoleRouteGuardService {
+
+    public constructor(store: Store, router: Router) {
+        super(store, router, {allowed: [EAPIUserRoles.SPA_ADMIN]});
+    }
+
+}
+
+@Injectable({providedIn: "root"})
 export class OfficialInChargeOrAdminRouteGuardService extends UserRoleRouteGuardService {
 
     public constructor(store: Store, router: Router) {
diff --git a/src/app/store/settings/actions/departments-settings.actions.ts b/src/app/store/settings/actions/departments-settings.actions.ts
new file mode 100644
index 0000000..2359e26
--- /dev/null
+++ b/src/app/store/settings/actions/departments-settings.actions.ts
@@ -0,0 +1,29 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {createAction, props} from "@ngrx/store";
+import {IAPIDepartmentTable} from "../../../core/api/settings";
+
+export const fetchDepartmentsSettingsAction = createAction(
+    "[Settings] Fetch departments settings"
+);
+
+export const setDepartmentsSettingsAction = createAction(
+    "[API] Set department settings",
+    props<{ data: IAPIDepartmentTable }>()
+);
+
+export const submitDepartmentsSettingsAction = createAction(
+    "[Settings] Submit departments settings",
+    props<{ data: IAPIDepartmentTable }>()
+);
diff --git a/src/app/store/settings/actions/index.ts b/src/app/store/settings/actions/index.ts
index 77e876e..552479d 100644
--- a/src/app/store/settings/actions/index.ts
+++ b/src/app/store/settings/actions/index.ts
@@ -11,4 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./departments-settings.actions";
 export * from "./settings.actions";
+export * from "./textblock-settings.actions";
+export * from "./users-settings.actions";
diff --git a/src/app/store/settings/actions/settings.actions.ts b/src/app/store/settings/actions/settings.actions.ts
index 75b4845..c3d20b9 100644
--- a/src/app/store/settings/actions/settings.actions.ts
+++ b/src/app/store/settings/actions/settings.actions.ts
@@ -12,8 +12,8 @@
  ********************************************************************************/
 
 import {createAction, props} from "@ngrx/store";
-import {IAPIStatementType} from "../../../core";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
+import {IAPISectorsModel, IAPIStatementType} from "../../../core";
+import {ISettingsLoadingState} from "../model";
 
 export const fetchSettingsAction = createAction(
     "[New] Fetch settings"
@@ -28,3 +28,8 @@
     "[API] Get sectors",
     props<{ sectors: IAPISectorsModel }>()
 );
+
+export const setSettingsLoadingStateAction = createAction(
+    "[App/API] Set settings loading state",
+    props<{ state: ISettingsLoadingState }>()
+);
diff --git a/src/app/store/settings/actions/textblock-settings.actions.ts b/src/app/store/settings/actions/textblock-settings.actions.ts
new file mode 100644
index 0000000..da5427d
--- /dev/null
+++ b/src/app/store/settings/actions/textblock-settings.actions.ts
@@ -0,0 +1,29 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {createAction, props} from "@ngrx/store";
+import {IAPITextBlockConfigurationModel} from "../../../core/api/text";
+
+export const fetchTextblockSettingsAction = createAction(
+    "[Settings] Fetch text block settings"
+);
+
+export const setTextblockSettingsAction = createAction(
+    "[API] Set text block settings",
+    props<{ data: IAPITextBlockConfigurationModel }>()
+);
+
+export const submitTextblockSettingsAction = createAction(
+    "[Settings] Submit text block settings",
+    props<{ data: IAPITextBlockConfigurationModel }>()
+);
diff --git a/src/app/store/settings/actions/users-settings.actions.ts b/src/app/store/settings/actions/users-settings.actions.ts
new file mode 100644
index 0000000..9cc8ced
--- /dev/null
+++ b/src/app/store/settings/actions/users-settings.actions.ts
@@ -0,0 +1,33 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {createAction, props} from "@ngrx/store";
+import {IAPIUserInfoExtended, IAPIUserSettings} from "../../../core";
+
+export const syncUserDataAction = createAction(
+    "[Settings] Syncs user data with auth-n-auth"
+);
+
+export const fetchUsersAction = createAction(
+    "[Settings] Fetches users from back end"
+);
+
+export const setUsersDataAction = createAction(
+    "[Store] Set user data",
+    props<{ data: IAPIUserInfoExtended[] }>()
+);
+
+export const submitUserSettingsAction = createAction(
+    "[Settings] Submits settings property for specific user",
+    props<{ userId: number, data: IAPIUserSettings }>()
+);
diff --git a/src/app/store/settings/effects/departments/departments-settings.effect.spec.ts b/src/app/store/settings/effects/departments/departments-settings.effect.spec.ts
new file mode 100644
index 0000000..2f06509
--- /dev/null
+++ b/src/app/store/settings/effects/departments/departments-settings.effect.spec.ts
@@ -0,0 +1,113 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
+import {TestBed} from "@angular/core/testing";
+import {provideMockActions} from "@ngrx/effects/testing";
+import {Action} from "@ngrx/store";
+import {Observable, Subject, Subscription} from "rxjs";
+import {IAPIDepartmentTable, SPA_BACKEND_ROUTE} from "../../../../core";
+import {
+    fetchDepartmentsSettingsAction,
+    setDepartmentsSettingsAction,
+    setSettingsLoadingStateAction,
+    submitDepartmentsSettingsAction
+} from "../../actions";
+import {DepartmentsSettingsEffect} from "./departments-settings.effect";
+
+describe("DepartmentsSettingsEffect", () => {
+
+    let actions$: Observable<Action>;
+    let httpTestingController: HttpTestingController;
+    let effect: DepartmentsSettingsEffect;
+    let subscription: Subscription;
+
+    beforeEach(async () => {
+        TestBed.configureTestingModule({
+            imports: [
+                HttpClientTestingModule
+            ],
+            providers: [
+                provideMockActions(() => actions$),
+                {
+                    provide: SPA_BACKEND_ROUTE,
+                    useValue: "/"
+                }
+            ]
+        });
+        effect = TestBed.inject(DepartmentsSettingsEffect);
+        httpTestingController = TestBed.inject(HttpTestingController);
+    });
+
+    afterEach(() => {
+        if (subscription != null) {
+            subscription.unsubscribe();
+        }
+    });
+
+    it("should fetch department settings", () => {
+        const results: Action[] = [];
+        const actionSubject = new Subject<Action>();
+        const data: IAPIDepartmentTable = {};
+        actions$ = actionSubject;
+        subscription = effect.fetch$.subscribe((_) => results.push(_));
+
+        actionSubject.next(fetchDepartmentsSettingsAction());
+        expectGetDepartmentsSettingsRequest(data);
+
+        expect(results).toEqual([
+            setSettingsLoadingStateAction({state: {fetchingDepartments: true}}),
+            setDepartmentsSettingsAction({data}),
+            setSettingsLoadingStateAction({state: {fetchingDepartments: false}})
+        ]);
+        httpTestingController.verify();
+    });
+
+    it("should submit departments settings", () => {
+        const results: Action[] = [];
+        const actionSubject = new Subject<Action>();
+        const data: IAPIDepartmentTable = {};
+        actions$ = actionSubject;
+        subscription = effect.submit$.subscribe((_) => results.push(_));
+
+        actionSubject.next(submitDepartmentsSettingsAction({data}));
+        expectPutDepartmentsSettingsRequest(data);
+        expectGetDepartmentsSettingsRequest(data);
+
+        expect(results).toEqual([
+            setSettingsLoadingStateAction({state: {submittingDepartments: true}}),
+            setSettingsLoadingStateAction({state: {fetchingDepartments: true}}),
+            setDepartmentsSettingsAction({data}),
+            setSettingsLoadingStateAction({state: {fetchingDepartments: false}}),
+            setSettingsLoadingStateAction({state: {submittingDepartments: false}})
+        ]);
+        httpTestingController.verify();
+    });
+
+    function expectGetDepartmentsSettingsRequest(data: IAPIDepartmentTable) {
+        const url = `/admin/departments`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("GET");
+        request.flush(data);
+    }
+
+    function expectPutDepartmentsSettingsRequest(data: IAPIDepartmentTable) {
+        const url = `/admin/departments`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("PUT");
+        expect(request.request.body).toEqual(data);
+        request.flush(data);
+    }
+
+});
+
diff --git a/src/app/store/settings/effects/departments/departments-settings.effect.ts b/src/app/store/settings/effects/departments/departments-settings.effect.ts
new file mode 100644
index 0000000..435f0cf
--- /dev/null
+++ b/src/app/store/settings/effects/departments/departments-settings.effect.ts
@@ -0,0 +1,65 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Injectable} from "@angular/core";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {Action} from "@ngrx/store";
+import {Observable} from "rxjs";
+import {endWith, map, startWith, switchMap} from "rxjs/operators";
+import {IAPIDepartmentTable, SettingsApiService} from "../../../../core";
+import {catchErrorTo, catchHttpErrorTo, EHttpStatusCodes} from "../../../../util";
+import {EErrorCode, setErrorAction} from "../../../root";
+import {
+    fetchDepartmentsSettingsAction,
+    setDepartmentsSettingsAction,
+    setSettingsLoadingStateAction,
+    submitDepartmentsSettingsAction
+} from "../../actions";
+
+@Injectable({providedIn: "root"})
+export class DepartmentsSettingsEffect {
+
+    public fetch$ = createEffect(() => this.actions.pipe(
+        ofType(fetchDepartmentsSettingsAction),
+        switchMap(() => this.fetch())
+    ));
+
+    public submit$ = createEffect(() => this.actions.pipe(
+        ofType(submitDepartmentsSettingsAction),
+        switchMap((action) => this.submit(action.data))
+    ));
+
+    public constructor(public actions: Actions, public settingsApiService: SettingsApiService) {
+
+    }
+
+    public fetch(): Observable<Action> {
+        return this.settingsApiService.getDepartmentTable().pipe(
+            map((data) => setDepartmentsSettingsAction({data})),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {fetchingDepartments: true}})),
+            endWith(setSettingsLoadingStateAction({state: {fetchingDepartments: false}}))
+        );
+    }
+
+    public submit(data: IAPIDepartmentTable): Observable<Action> {
+        return this.settingsApiService.putDepartmentTable(data).pipe(
+            switchMap(() => this.fetch()),
+            catchHttpErrorTo(setErrorAction({error: EErrorCode.INVALID_FILE_FORMAT}), EHttpStatusCodes.BAD_REQUEST),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {submittingDepartments: true}})),
+            endWith(setSettingsLoadingStateAction({state: {submittingDepartments: false}}))
+        );
+    }
+
+}
diff --git a/src/app/store/settings/effects/index.ts b/src/app/store/settings/effects/index.ts
index 8ea1eaa..b16c561 100644
--- a/src/app/store/settings/effects/index.ts
+++ b/src/app/store/settings/effects/index.ts
@@ -11,4 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./departments/departments-settings.effect";
+export * from "./textblock/textblock-settings.effect";
 export * from "./fetch-settings.effect";
diff --git a/src/app/store/settings/effects/textblock/textblock-settings.effect.spec.ts b/src/app/store/settings/effects/textblock/textblock-settings.effect.spec.ts
new file mode 100644
index 0000000..766b668
--- /dev/null
+++ b/src/app/store/settings/effects/textblock/textblock-settings.effect.spec.ts
@@ -0,0 +1,113 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
+import {TestBed} from "@angular/core/testing";
+import {provideMockActions} from "@ngrx/effects/testing";
+import {Action} from "@ngrx/store";
+import {Observable, Subject, Subscription} from "rxjs";
+import {IAPITextBlockConfigurationModel, SPA_BACKEND_ROUTE} from "../../../../core";
+import {
+    fetchTextblockSettingsAction,
+    setSettingsLoadingStateAction,
+    setTextblockSettingsAction,
+    submitTextblockSettingsAction
+} from "../../actions";
+import {TextblockSettingsEffect} from "./textblock-settings.effect";
+
+describe("TextblockSettingsEffect", () => {
+
+    let actions$: Observable<Action>;
+    let httpTestingController: HttpTestingController;
+    let effect: TextblockSettingsEffect;
+    let subscription: Subscription;
+
+    beforeEach(async () => {
+        TestBed.configureTestingModule({
+            imports: [
+                HttpClientTestingModule
+            ],
+            providers: [
+                provideMockActions(() => actions$),
+                {
+                    provide: SPA_BACKEND_ROUTE,
+                    useValue: "/"
+                }
+            ]
+        });
+        effect = TestBed.inject(TextblockSettingsEffect);
+        httpTestingController = TestBed.inject(HttpTestingController);
+    });
+
+    afterEach(() => {
+        if (subscription != null) {
+            subscription.unsubscribe();
+        }
+    });
+
+    it("should fetch department settings", () => {
+        const results: Action[] = [];
+        const actionSubject = new Subject<Action>();
+        const data: IAPITextBlockConfigurationModel = {...{} as IAPITextBlockConfigurationModel, selects: {selectField: ["option1"]}};
+        actions$ = actionSubject;
+        subscription = effect.fetch$.subscribe((_) => results.push(_));
+
+        actionSubject.next(fetchTextblockSettingsAction());
+        expectGetTextblockConfigRequest(data);
+
+        expect(results).toEqual([
+            setSettingsLoadingStateAction({state: {fetchingTextblocks: true}}),
+            setTextblockSettingsAction({data}),
+            setSettingsLoadingStateAction({state: {fetchingTextblocks: false}})
+        ]);
+        httpTestingController.verify();
+    });
+
+    it("should submit departments settings", () => {
+        const results: Action[] = [];
+        const actionSubject = new Subject<Action>();
+        const data: IAPITextBlockConfigurationModel = {...{} as IAPITextBlockConfigurationModel, selects: {selectField: ["option1"]}};
+        actions$ = actionSubject;
+        subscription = effect.submit$.subscribe((_) => results.push(_));
+
+        actionSubject.next(submitTextblockSettingsAction({data}));
+        expectPutTextblockConfigRequest(data);
+        expectGetTextblockConfigRequest(data);
+
+        expect(results).toEqual([
+            setSettingsLoadingStateAction({state: {submittingTextblocks: true}}),
+            setSettingsLoadingStateAction({state: {fetchingTextblocks: true}}),
+            setTextblockSettingsAction({data}),
+            setSettingsLoadingStateAction({state: {fetchingTextblocks: false}}),
+            setSettingsLoadingStateAction({state: {submittingTextblocks: false}})
+        ]);
+        httpTestingController.verify();
+    });
+
+    function expectGetTextblockConfigRequest(data: IAPITextBlockConfigurationModel) {
+        const url = `/admin/textblockconfig`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("GET");
+        request.flush(data);
+    }
+
+    function expectPutTextblockConfigRequest(data: IAPITextBlockConfigurationModel) {
+        const url = `/admin/textblockconfig`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("PUT");
+        expect(request.request.body).toEqual(data);
+        request.flush(data);
+    }
+
+});
+
diff --git a/src/app/store/settings/effects/textblock/textblock-settings.effect.ts b/src/app/store/settings/effects/textblock/textblock-settings.effect.ts
new file mode 100644
index 0000000..cc8db32
--- /dev/null
+++ b/src/app/store/settings/effects/textblock/textblock-settings.effect.ts
@@ -0,0 +1,64 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Injectable} from "@angular/core";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {Action} from "@ngrx/store";
+import {Observable} from "rxjs";
+import {endWith, map, startWith, switchMap} from "rxjs/operators";
+import {IAPITextBlockConfigurationModel, SettingsApiService} from "../../../../core";
+import {catchErrorTo} from "../../../../util";
+import {EErrorCode, setErrorAction} from "../../../root";
+import {
+    fetchTextblockSettingsAction,
+    setSettingsLoadingStateAction,
+    setTextblockSettingsAction,
+    submitTextblockSettingsAction
+} from "../../actions";
+
+@Injectable({providedIn: "root"})
+export class TextblockSettingsEffect {
+
+    public fetch$ = createEffect(() => this.actions.pipe(
+        ofType(fetchTextblockSettingsAction),
+        switchMap(() => this.fetch())
+    ));
+
+    public submit$ = createEffect(() => this.actions.pipe(
+        ofType(submitTextblockSettingsAction),
+        switchMap((action) => this.submit(action.data))
+    ));
+
+    public constructor(public actions: Actions, public settingsApiService: SettingsApiService) {
+
+    }
+
+    public fetch(): Observable<Action> {
+        return this.settingsApiService.getTextblockConfig().pipe(
+            map((data) => setTextblockSettingsAction({data})),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {fetchingTextblocks: true}})),
+            endWith(setSettingsLoadingStateAction({state: {fetchingTextblocks: false}}))
+        );
+    }
+
+    public submit(data: IAPITextBlockConfigurationModel): Observable<Action> {
+        return this.settingsApiService.putTextblockConfig(data).pipe(
+            switchMap(() => this.fetch()),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {submittingTextblocks: true}})),
+            endWith(setSettingsLoadingStateAction({state: {submittingTextblocks: false}}))
+        );
+    }
+
+}
diff --git a/src/app/store/settings/effects/users/users-settings-effect.service.ts b/src/app/store/settings/effects/users/users-settings-effect.service.ts
new file mode 100644
index 0000000..eb22c34
--- /dev/null
+++ b/src/app/store/settings/effects/users/users-settings-effect.service.ts
@@ -0,0 +1,85 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Injectable} from "@angular/core";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {Action} from "@ngrx/store";
+import {Observable} from "rxjs";
+import {endWith, map, startWith, switchMap} from "rxjs/operators";
+import {IAPIUserSettings, SettingsApiService} from "../../../../core";
+import {catchHttpErrorTo, EHttpStatusCodes} from "../../../../util";
+import {catchErrorTo} from "../../../../util/rxjs";
+import {EErrorCode, setErrorAction} from "../../../root";
+import {
+    fetchUsersAction,
+    setSettingsLoadingStateAction,
+    setUsersDataAction,
+    submitUserSettingsAction,
+    syncUserDataAction
+} from "../../actions";
+
+@Injectable({providedIn: "root"})
+export class UsersSettingsEffect {
+
+    public submit$ = createEffect(() => this.actions.pipe(
+        ofType(submitUserSettingsAction),
+        switchMap((action) => this.submit(action.userId, {...action.data}))
+    ));
+
+    public fetch$ = createEffect(() => this.actions.pipe(
+        ofType(fetchUsersAction),
+        switchMap(() => this.fetch())
+    ));
+
+    public sync$ = createEffect(() => this.actions.pipe(
+        ofType(syncUserDataAction),
+        switchMap(() => this.sync())
+    ));
+
+    public constructor(public actions: Actions, public settingsApiService: SettingsApiService) {
+    }
+
+    public sync(): Observable<Action> {
+        return this.settingsApiService.syncUserData().pipe(
+            switchMap(() => this.fetch()),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {fetchingUsers: true}})),
+            endWith(setSettingsLoadingStateAction({state: {fetchingUsers: false}}))
+        );
+    }
+
+    public fetch(): Observable<Action> {
+        return this.settingsApiService.fetchUsers().pipe(
+            map((data) => setUsersDataAction({data})),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {fetchingUsers: true}})),
+            endWith(setSettingsLoadingStateAction({state: {fetchingUsers: false}}))
+        );
+    }
+
+    public submit(userId: number, data: IAPIUserSettings): Observable<Action> {
+        const isDepartmentSet = data?.department?.group != null && data?.department?.name != null;
+        data = {
+            ...data,
+            department: isDepartmentSet ? data.department : undefined
+        };
+        return this.settingsApiService.setUserData(userId, data).pipe(
+            switchMap(() => this.fetch()),
+            catchHttpErrorTo(setErrorAction({error: EErrorCode.BAD_USER_DATA}), EHttpStatusCodes.BAD_REQUEST),
+            catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
+            startWith(setSettingsLoadingStateAction({state: {submittingUserData: true}})),
+            endWith(setSettingsLoadingStateAction({state: {submittingUserData: false}}))
+        );
+    }
+
+}
diff --git a/src/app/features/search/components/search-filter/IFilterToDisplay.ts b/src/app/store/settings/model/ISettingsLoadingState.ts
similarity index 60%
copy from src/app/features/search/components/search-filter/IFilterToDisplay.ts
copy to src/app/store/settings/model/ISettingsLoadingState.ts
index 203b033..c387831 100644
--- a/src/app/features/search/components/search-filter/IFilterToDisplay.ts
+++ b/src/app/store/settings/model/ISettingsLoadingState.ts
@@ -11,14 +11,15 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export interface IFilterToDisplay {
-    filterForType?: boolean;
-    status?: boolean;
-    editedByMe?: boolean;
-    dueDateFrom?: boolean;
-    dueDateTo?: boolean;
-    creationDateFrom?: boolean;
-    creationDateTo?: boolean;
-    receiptDateFrom?: boolean;
-    receiptDateTo?: boolean;
+/**
+ * Interface which models the loading state of the whole settings store module.
+ */
+export interface ISettingsLoadingState {
+    fetchingDepartments?: boolean;
+    submittingDepartments?: boolean;
+    fetchingTextblocks?: boolean;
+    submittingTextblocks?: boolean;
+    addingTags?: boolean;
+    fetchingUsers?: boolean;
+    submittingUserData?: boolean;
 }
diff --git a/src/app/store/settings/model/ISettingsStoreState.ts b/src/app/store/settings/model/ISettingsStoreState.ts
index c9012de..d4cb7c2 100644
--- a/src/app/store/settings/model/ISettingsStoreState.ts
+++ b/src/app/store/settings/model/ISettingsStoreState.ts
@@ -11,9 +11,15 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {IAPIStatementType} from "../../../core";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
+import {
+    IAPIDepartmentTable,
+    IAPISectorsModel,
+    IAPIStatementType,
+    IAPITextBlockConfigurationModel,
+    IAPIUserInfoExtended
+} from "../../../core";
 import {TStoreEntities} from "../../../util";
+import {ISettingsLoadingState} from "./ISettingsLoadingState";
 
 export interface ISettingsStoreState {
 
@@ -23,8 +29,28 @@
     statementTypes: TStoreEntities<IAPIStatementType>;
 
     /**
-     * Object all the available sectors for all newly created statements.
+     * Object with all the available sectors for newly created statements.
      */
     sectors: IAPISectorsModel;
 
+    /**
+     * Object with all departments and sectors; the key is a pair of city and district concatenated with a #.
+     */
+    departments: IAPIDepartmentTable;
+
+    /**
+     * Loading object which indicates which part of the application is loading.
+     */
+    loading?: ISettingsLoadingState;
+
+    /**
+     * Textblock configuration object for newly created statements.
+     */
+    textblock: IAPITextBlockConfigurationModel;
+
+    /**
+     * List of all users with access to the app
+     */
+    users: IAPIUserInfoExtended[];
+
 }
diff --git a/src/app/store/settings/model/index.ts b/src/app/store/settings/model/index.ts
index fe19fde..b24c876 100644
--- a/src/app/store/settings/model/index.ts
+++ b/src/app/store/settings/model/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./ISettingsLoadingState";
 export * from "./ISettingsStoreState";
diff --git a/src/app/store/settings/reducers/departments/departments.reducer.spec.ts b/src/app/store/settings/reducers/departments/departments.reducer.spec.ts
new file mode 100644
index 0000000..0b5c032
--- /dev/null
+++ b/src/app/store/settings/reducers/departments/departments.reducer.spec.ts
@@ -0,0 +1,25 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPIDepartmentTable} from "../../../../core";
+import {setDepartmentsSettingsAction} from "../../actions";
+import {departmentsSettingsReducer} from "./departments.reducer";
+
+describe("departmentsSettingsReducer", () => {
+
+    it("should set state on setDepartmentsSettingsAction", () => {
+        const data: IAPIDepartmentTable = {};
+        expect(departmentsSettingsReducer(null, setDepartmentsSettingsAction({data}))).toBe(data);
+    });
+
+});
diff --git a/src/app/store/settings/reducers/sectors.reducer.ts b/src/app/store/settings/reducers/departments/departments.reducer.ts
similarity index 67%
copy from src/app/store/settings/reducers/sectors.reducer.ts
copy to src/app/store/settings/reducers/departments/departments.reducer.ts
index 53cc6cf..4cb3569 100644
--- a/src/app/store/settings/reducers/sectors.reducer.ts
+++ b/src/app/store/settings/reducers/departments/departments.reducer.ts
@@ -12,12 +12,12 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {IAPIDepartmentTable} from "../../../../core/api/settings";
+import {setDepartmentsSettingsAction} from "../../actions";
 
-export const sectorsReducer = createReducer<IAPISectorsModel>(
+export const departmentsSettingsReducer = createReducer<IAPIDepartmentTable>(
     {},
-    on(setSectorsAction, (state, payload) => {
-        return payload.sectors ? {...payload.sectors} : state;
+    on(setDepartmentsSettingsAction, (state, action) => {
+        return action.data;
     })
 );
diff --git a/src/app/store/settings/reducers/index.ts b/src/app/store/settings/reducers/index.ts
index fd47105..098580f 100644
--- a/src/app/store/settings/reducers/index.ts
+++ b/src/app/store/settings/reducers/index.ts
@@ -11,4 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-types.reducer";
+export * from "./departments/departments.reducer";
+export * from "./loading/loading.reducer";
+export * from "./sectors/sectors.reducer";
+export * from "./statement-types/statement-types.reducer";
+export * from "./textblock/textblock.reducer";
diff --git a/src/app/store/settings/reducers/loading/loading.reducer.spec.ts b/src/app/store/settings/reducers/loading/loading.reducer.spec.ts
new file mode 100644
index 0000000..8cbd9ac
--- /dev/null
+++ b/src/app/store/settings/reducers/loading/loading.reducer.spec.ts
@@ -0,0 +1,24 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {setSettingsLoadingStateAction} from "../../actions";
+import {settingsLoadingReducer} from "./loading.reducer";
+
+describe("settingsLoadingReducer", () => {
+
+    it("should update state on setSettingsLoadingStateAction", () => {
+        const action = setSettingsLoadingStateAction({state: {fetchingDepartments: false}});
+        expect(settingsLoadingReducer({fetchingDepartments: true}, action)).toEqual({fetchingDepartments: false});
+    });
+
+});
diff --git a/src/app/store/settings/reducers/sectors.reducer.ts b/src/app/store/settings/reducers/loading/loading.reducer.ts
similarity index 66%
copy from src/app/store/settings/reducers/sectors.reducer.ts
copy to src/app/store/settings/reducers/loading/loading.reducer.ts
index 53cc6cf..5149686 100644
--- a/src/app/store/settings/reducers/sectors.reducer.ts
+++ b/src/app/store/settings/reducers/loading/loading.reducer.ts
@@ -12,12 +12,12 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {setSettingsLoadingStateAction} from "../../actions";
+import {ISettingsLoadingState} from "../../model";
 
-export const sectorsReducer = createReducer<IAPISectorsModel>(
-    {},
-    on(setSectorsAction, (state, payload) => {
-        return payload.sectors ? {...payload.sectors} : state;
+export const settingsLoadingReducer = createReducer<ISettingsLoadingState>(
+    undefined,
+    on(setSettingsLoadingStateAction, (state, action) => {
+        return {...state, ...action.state};
     })
 );
diff --git a/src/app/store/settings/reducers/sectors.reducer.spec.ts b/src/app/store/settings/reducers/sectors/sectors.reducer.spec.ts
similarity index 90%
rename from src/app/store/settings/reducers/sectors.reducer.spec.ts
rename to src/app/store/settings/reducers/sectors/sectors.reducer.spec.ts
index a228615..07d779f 100644
--- a/src/app/store/settings/reducers/sectors.reducer.spec.ts
+++ b/src/app/store/settings/reducers/sectors/sectors.reducer.spec.ts
@@ -11,8 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {IAPISectorsModel} from "../../../../core";
+import {setSectorsAction} from "../../actions";
 import {sectorsReducer} from "./sectors.reducer";
 
 describe("sectorsReducer", () => {
diff --git a/src/app/store/settings/reducers/sectors.reducer.ts b/src/app/store/settings/reducers/sectors/sectors.reducer.ts
similarity index 86%
rename from src/app/store/settings/reducers/sectors.reducer.ts
rename to src/app/store/settings/reducers/sectors/sectors.reducer.ts
index 53cc6cf..2a30ab7 100644
--- a/src/app/store/settings/reducers/sectors.reducer.ts
+++ b/src/app/store/settings/reducers/sectors/sectors.reducer.ts
@@ -12,8 +12,8 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {IAPISectorsModel} from "../../../../core";
+import {setSectorsAction} from "../../actions";
 
 export const sectorsReducer = createReducer<IAPISectorsModel>(
     {},
diff --git a/src/app/store/settings/reducers/statement-types.reducer.spec.ts b/src/app/store/settings/reducers/statement-types/statement-types.reducer.spec.ts
similarity index 91%
rename from src/app/store/settings/reducers/statement-types.reducer.spec.ts
rename to src/app/store/settings/reducers/statement-types/statement-types.reducer.spec.ts
index 9143e32..bf66ee7 100644
--- a/src/app/store/settings/reducers/statement-types.reducer.spec.ts
+++ b/src/app/store/settings/reducers/statement-types/statement-types.reducer.spec.ts
@@ -11,9 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {IAPIStatementType} from "../../../core/api/settings";
-import {TStoreEntities} from "../../../util/store";
-import {setStatementTypesAction} from "../actions";
+import {IAPIStatementType} from "../../../../core/api/settings";
+import {TStoreEntities} from "../../../../util/store";
+import {setStatementTypesAction} from "../../actions";
 import {statementTypesReducer} from "./statement-types.reducer";
 
 describe("statementTypesReducer", () => {
diff --git a/src/app/store/settings/reducers/statement-types.reducer.ts b/src/app/store/settings/reducers/statement-types/statement-types.reducer.ts
similarity index 83%
rename from src/app/store/settings/reducers/statement-types.reducer.ts
rename to src/app/store/settings/reducers/statement-types/statement-types.reducer.ts
index bbadf4b..44bb241 100644
--- a/src/app/store/settings/reducers/statement-types.reducer.ts
+++ b/src/app/store/settings/reducers/statement-types/statement-types.reducer.ts
@@ -12,9 +12,9 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {IAPIStatementType} from "../../../core";
-import {arrayToEntities, TStoreEntities} from "../../../util";
-import {setStatementTypesAction} from "../actions";
+import {IAPIStatementType} from "../../../../core";
+import {arrayToEntities, TStoreEntities} from "../../../../util";
+import {setStatementTypesAction} from "../../actions";
 
 export const statementTypesReducer = createReducer<TStoreEntities<IAPIStatementType>>(
     {},
diff --git a/src/app/store/settings/reducers/textblock/textblock.reducer.spec.ts b/src/app/store/settings/reducers/textblock/textblock.reducer.spec.ts
new file mode 100644
index 0000000..0efbf62
--- /dev/null
+++ b/src/app/store/settings/reducers/textblock/textblock.reducer.spec.ts
@@ -0,0 +1,25 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {IAPITextBlockConfigurationModel} from "../../../../core";
+import {setTextblockSettingsAction} from "../../actions";
+import {textblockSettingsReducer} from "./textblock.reducer";
+
+describe("textblockSettingsReducer", () => {
+
+    it("should set state on setTextblockSettingsAction", () => {
+        const data: IAPITextBlockConfigurationModel = {} as IAPITextBlockConfigurationModel;
+        expect(textblockSettingsReducer(null, setTextblockSettingsAction({data}))).toBe(data);
+    });
+
+});
diff --git a/src/app/store/settings/reducers/sectors.reducer.ts b/src/app/store/settings/reducers/textblock/textblock.reducer.ts
similarity index 65%
copy from src/app/store/settings/reducers/sectors.reducer.ts
copy to src/app/store/settings/reducers/textblock/textblock.reducer.ts
index 53cc6cf..c1a8957 100644
--- a/src/app/store/settings/reducers/sectors.reducer.ts
+++ b/src/app/store/settings/reducers/textblock/textblock.reducer.ts
@@ -12,12 +12,12 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {IAPITextBlockConfigurationModel} from "../../../../core/api/text";
+import {setTextblockSettingsAction} from "../../actions";
 
-export const sectorsReducer = createReducer<IAPISectorsModel>(
-    {},
-    on(setSectorsAction, (state, payload) => {
-        return payload.sectors ? {...payload.sectors} : state;
+export const textblockSettingsReducer = createReducer<IAPITextBlockConfigurationModel>(
+    undefined,
+    on(setTextblockSettingsAction, (state, action) => {
+        return action.data;
     })
 );
diff --git a/src/app/store/settings/reducers/sectors.reducer.ts b/src/app/store/settings/reducers/users/users.reducer.ts
similarity index 67%
copy from src/app/store/settings/reducers/sectors.reducer.ts
copy to src/app/store/settings/reducers/users/users.reducer.ts
index 53cc6cf..42c20ff 100644
--- a/src/app/store/settings/reducers/sectors.reducer.ts
+++ b/src/app/store/settings/reducers/users/users.reducer.ts
@@ -12,12 +12,12 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {IAPISectorsModel} from "../../../core/api/statements/IAPISectorsModel";
-import {setSectorsAction} from "../actions";
+import {IAPIUserInfoExtended} from "../../../../core";
+import {setUsersDataAction} from "../../actions";
 
-export const sectorsReducer = createReducer<IAPISectorsModel>(
-    {},
-    on(setSectorsAction, (state, payload) => {
-        return payload.sectors ? {...payload.sectors} : state;
+export const usersSettingsReducer = createReducer<IAPIUserInfoExtended[]>(
+    [],
+    on(setUsersDataAction, (state, action) => {
+        return action.data;
     })
 );
diff --git a/src/app/store/settings/selectors/settings.selectors.ts b/src/app/store/settings/selectors/settings.selectors.ts
index 367f788..bf352f3 100644
--- a/src/app/store/settings/selectors/settings.selectors.ts
+++ b/src/app/store/settings/selectors/settings.selectors.ts
@@ -13,7 +13,7 @@
 
 import {createFeatureSelector, createSelector} from "@ngrx/store";
 import {ISelectOption} from "../../../shared/controls/select";
-import {entitiesToArray} from "../../../util";
+import {entitiesToArray, selectPropertyProjector} from "../../../util";
 import {ISettingsStoreState} from "../model";
 import {SETTINGS_FEATURE_NAME} from "../settings-reducers.token";
 
@@ -26,3 +26,28 @@
             .map<ISelectOption<number>>((t) => ({label: t?.name, value: t?.id}));
     }
 );
+
+const settingsLoadingStateSelector = createSelector(
+    settingsStoreSelector,
+    selectPropertyProjector("loading", {})
+);
+
+export const getSettingsLoadingSelector = createSelector(
+    settingsLoadingStateSelector,
+    (loading): boolean => Object.values({...loading}).some((_) => _)
+);
+
+export const getDepartmentsSettingsSelector = createSelector(
+    settingsStoreSelector,
+    selectPropertyProjector("departments", {})
+);
+
+export const getUsersSettingsSelector = createSelector(
+    settingsStoreSelector,
+    selectPropertyProjector("users", [])
+);
+
+export const getTextblockSettingsSelector = createSelector(
+    settingsStoreSelector,
+    selectPropertyProjector("textblock", {selects: {}, groups: [], negativeGroups: []})
+);
diff --git a/src/app/store/settings/settings-reducers.token.ts b/src/app/store/settings/settings-reducers.token.ts
index a45a536..539d7cb 100644
--- a/src/app/store/settings/settings-reducers.token.ts
+++ b/src/app/store/settings/settings-reducers.token.ts
@@ -14,15 +14,25 @@
 import {InjectionToken} from "@angular/core";
 import {ActionReducerMap} from "@ngrx/store";
 import {ISettingsStoreState} from "./model";
-import {statementTypesReducer} from "./reducers";
-import {sectorsReducer} from "./reducers/sectors.reducer";
+import {
+    departmentsSettingsReducer,
+    sectorsReducer,
+    settingsLoadingReducer,
+    statementTypesReducer,
+    textblockSettingsReducer
+} from "./reducers";
+import {usersSettingsReducer} from "./reducers/users/users.reducer";
 
 export const SETTINGS_FEATURE_NAME = "settings";
 
 export const SETTINGS_REDUCER = new InjectionToken<ActionReducerMap<ISettingsStoreState>>("Settings store reducer", {
     providedIn: "root",
     factory: () => ({
+        departments: departmentsSettingsReducer,
+        loading: settingsLoadingReducer,
+        sectors: sectorsReducer,
         statementTypes: statementTypesReducer,
-        sectors: sectorsReducer
+        users: usersSettingsReducer,
+        textblock: textblockSettingsReducer
     })
 });
diff --git a/src/app/store/settings/settings-store.module.ts b/src/app/store/settings/settings-store.module.ts
index 1a3f1c4..65b2e89 100644
--- a/src/app/store/settings/settings-store.module.ts
+++ b/src/app/store/settings/settings-store.module.ts
@@ -14,14 +14,18 @@
 import {NgModule} from "@angular/core";
 import {EffectsModule} from "@ngrx/effects";
 import {StoreModule} from "@ngrx/store";
-import {FetchSettingsEffect} from "./effects";
+import {UsersSettingsEffect} from "./effects/users/users-settings-effect.service";
+import {DepartmentsSettingsEffect, FetchSettingsEffect, TextblockSettingsEffect} from "./effects";
 import {SETTINGS_FEATURE_NAME, SETTINGS_REDUCER} from "./settings-reducers.token";
 
 @NgModule({
     imports: [
         StoreModule.forFeature(SETTINGS_FEATURE_NAME, SETTINGS_REDUCER),
         EffectsModule.forFeature([
-            FetchSettingsEffect
+            DepartmentsSettingsEffect,
+            FetchSettingsEffect,
+            UsersSettingsEffect,
+            TextblockSettingsEffect
         ])
     ]
 })
diff --git a/src/app/store/statements/actions/submit.actions.ts b/src/app/store/statements/actions/submit.actions.ts
index 003026a..cccae5c 100644
--- a/src/app/store/statements/actions/submit.actions.ts
+++ b/src/app/store/statements/actions/submit.actions.ts
@@ -83,7 +83,14 @@
     }>()
 );
 
+export const submitTagsAction = createAction(
+    "[Edit] Submit tags",
+    props<{
+        labels: string[]
+    }>()
+);
+
 export const sendStatementViaMailAction = createAction(
     "[Details] Resend statement via email",
-    props<{ statementId: number, taskId: string }>()
+    props<{ statementId: number, taskId: string, assignee: string }>()
 );
diff --git a/src/app/store/statements/effects/submit-information-form/submit-statement-information-form.effect.ts b/src/app/store/statements/effects/submit-information-form/submit-statement-information-form.effect.ts
index 06a42db..3525bbe 100644
--- a/src/app/store/statements/effects/submit-information-form/submit-statement-information-form.effect.ts
+++ b/src/app/store/statements/effects/submit-information-form/submit-statement-information-form.effect.ts
@@ -124,8 +124,8 @@
         return this.taskEffect.claimNext(statementId, EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA).pipe(
             filter((task) => task != null),
             map((task) => {
-                afterClaim(task.taskId);
-                return setTaskEntityAction({task});
+                afterClaim(task?.task?.taskId);
+                return setTaskEntityAction({task: task?.task});
             })
         );
     }
diff --git a/src/app/util/file/file.util.spec.ts b/src/app/util/file/file.util.spec.ts
new file mode 100644
index 0000000..c616a30
--- /dev/null
+++ b/src/app/util/file/file.util.spec.ts
@@ -0,0 +1,34 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {parseCsv, reduceToCsv} from "./file.util";
+
+describe("FileUtil", () => {
+    const text = "1;2;3;4\r\n5;6;7;\r\n8;9;;";
+
+    const data = [
+        ["1", "2", "3", "4"],
+        ["5", "6", "7"],
+        ["8", "9"]
+    ];
+
+    it("parseCsv", () => {
+        const expectedData = [data[0], [...data[1], ""], [...data[2], "", ""]];
+        expect(parseCsv(text, ";")).toEqual(expectedData);
+    });
+
+    it("reduceToCsv", () => {
+        expect(reduceToCsv(data, ";")).toEqual(text);
+    });
+
+});
diff --git a/src/app/util/file/file.util.ts b/src/app/util/file/file.util.ts
new file mode 100644
index 0000000..5bd985f
--- /dev/null
+++ b/src/app/util/file/file.util.ts
@@ -0,0 +1,58 @@
+/********************************************************************************
+ * 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 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {arrayJoin} from "../store";
+
+/**
+ * Parses a string as CSV file and returns it entries in a list of arrays.
+ * @param text String in CSV format.
+ * @param separator Separator which is used in the CSV format.
+ */
+export function parseCsv(text: string, separator: string): string[][] {
+    return text
+        .replace(new RegExp("\r", "g"), "")
+        .split("\n")
+        .map((row) => row.split(separator));
+}
+
+/**
+ * Reduces a single row of entries to a CSV string.
+ * @param dataRow List of values to reduce.
+ * @param separator Separator which is used in the CSV format.
+ */
+export function reduceRowToCsv(dataRow: string[], separator: string): string {
+    return arrayJoin(dataRow)
+        .reduce<string>((result, rowString) => result + separator + rowString, "")
+        .slice(separator.length);
+}
+
+/**
+ * Reduces a table of entries to a CSV string.
+ * @param data Entries of the table to reduce.
+ * @param separator Separator which is used in the CSV format.
+ */
+export function reduceToCsv(data: string[][], separator: string): string {
+    const rowLength = Math.max(...data.map((row) => row.length));
+    return reduceRowToCsv(
+        data
+            .map<string[]>((row) => {
+                row = [...row];
+                while (row.length < rowLength) {
+                    row.push("");
+                }
+                return row;
+            })
+            .map<string>((row) => reduceRowToCsv(row, separator)),
+        "\r\n"
+    );
+}
diff --git a/src/app/features/settings/components/index.ts b/src/app/util/file/index.ts
similarity index 94%
copy from src/app/features/settings/components/index.ts
copy to src/app/util/file/index.ts
index 990bb42..d7dd6c0 100644
--- a/src/app/features/settings/components/index.ts
+++ b/src/app/util/file/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./search";
+export * from "./file.util";
diff --git a/src/app/util/index.ts b/src/app/util/index.ts
index 1efd8d2..cd3b388 100644
--- a/src/app/util/index.ts
+++ b/src/app/util/index.ts
@@ -12,9 +12,9 @@
  ********************************************************************************/
 
 export * from "./events";
+export * from "./file";
 export * from "./forms";
 export * from "./http";
+export * from "./moment";
 export * from "./rxjs";
 export * from "./store";
-
-export * from "./moment";
diff --git a/src/app/util/moment/moment.util.ts b/src/app/util/moment/moment.util.ts
index d475589..9f48a25 100644
--- a/src/app/util/moment/moment.util.ts
+++ b/src/app/util/moment/moment.util.ts
@@ -14,10 +14,19 @@
 import * as moment from "moment";
 import {MomentFormatSpecification, MomentInput} from "moment";
 
+/**
+ * Date format for internal use. Field values, component inputs. Is not used for display.
+ */
 export const momentFormatInternal = "YYYY-MM-DD";
 
+/**
+ * Date format to be used for displaying a date in UI components where no time is necessary.
+ */
 export const momentFormatDisplayNumeric = "DD.MM.YYYY";
 
+/**
+ * Date format including time to be used for displaying a date in UI components.
+ */
 export const momentFormatDisplayFullDateAndTime = "DD.MM.YYYY HH:mm:ss";
 
 /**
diff --git a/src/app/util/store/store-projectors.util.ts b/src/app/util/store/store-projectors.util.ts
index ea5f0a0..7a1d920 100644
--- a/src/app/util/store/store-projectors.util.ts
+++ b/src/app/util/store/store-projectors.util.ts
@@ -11,6 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+/**
+ * Function that returns the store entitiy for the specified key and id. If no value for that key and id exist, returns the default value if
+ * specified.
+ */
 export function selectEntityWithIdProjector<TState, TKey extends keyof TState, TId extends keyof TState[TKey]>(
     defaultValue: TState[TKey][TId], key: TKey
 ): (state: TState, id: TId) => TState[TKey][TId];
@@ -23,6 +27,9 @@
         (state, id) => state == null || state[key] == null || state[key][id] == null ? defaultValue : state[key][id];
 }
 
+/**
+ * Function that returns the object property for the specified key. If no value for that key exist, returns the default value if specified.
+ */
 export function selectPropertyProjector<TState, TKey extends keyof TState>(
     key: TKey,
     defaultValue?: TState[TKey]
@@ -30,6 +37,10 @@
     return (state) => state == null || state[key] == null ? defaultValue : state[key];
 }
 
+/**
+ * Function that returns the object property for the specified key only if it is an array.
+ * If no value for that key exist, returns the default value if specified.
+ */
 export function selectArrayProjector<TState, TKey extends keyof TState>(
     key: TKey,
     defaultValue?: TState[TKey] & Array<any>
diff --git a/src/app/util/store/store.util.ts b/src/app/util/store/store.util.ts
index 76e1439..c7958c9 100644
--- a/src/app/util/store/store.util.ts
+++ b/src/app/util/store/store.util.ts
@@ -50,6 +50,9 @@
     }), {});
 }
 
+/**
+ * For an object adds a the items of a list as properties with an id.
+ */
 export function setEntitiesObject<T>(
     object: TStoreEntities<T>,
     list: T[],
diff --git a/src/assets/docu/userDocumentation.pdf b/src/assets/docu/userDocumentation.pdf
new file mode 100644
index 0000000..c60a9df
--- /dev/null
+++ b/src/assets/docu/userDocumentation.pdf
Binary files differ
diff --git a/src/assets/i18n/de.i18.json b/src/assets/i18n/de.i18.json
index 3b1bd96..3198d03 100644
--- a/src/assets/i18n/de.i18.json
+++ b/src/assets/i18n/de.i18.json
@@ -88,6 +88,7 @@
       "title": "Eingangsdokumente",
       "filter": "Filter",
       "noResult": "Keine Anhänge vorhanden.",
+      "added": "Manuell hinzugefügte Anhänge",
       "emailDocuments": "Email-Dokumente",
       "email": "Email",
       "placeholder": "Es sind keine Anhänge zu der Stellungnahme vorhanden."
@@ -108,11 +109,18 @@
       "placeholder": "Es wurden keine Fachbereiche zur Bearbeitung ausgewählt."
     },
     "geoPositions": {
+      "title": "Geographische Position",
       "placeholder": "Es wurde keine Position zu der Stellungnahme hinterlegt."
     },
     "linkedStatements": {
       "title": "Verknüpfte Vorgänge",
       "placeholder": "Es gibt keine verlinkten Stellungnahmen."
+    },
+    "information": {
+      "title": "Allgemeine Informationen"
+    },
+    "processInformation": {
+      "title": "Prozessinformationen"
     }
   },
   "edit": {
@@ -145,7 +153,8 @@
     "title": "Kommentare",
     "showPrevious": "Vorherige anzeigen...",
     "showAll": "Alle anzeigen...",
-    "placeholder": "Einen Kommentar anlegen..."
+    "placeholder": "Einen Kommentar anlegen...",
+    "confirmDelete": "Möchten Sie den Kommentar wirklich löschen?"
   },
   "contacts": {
     "title": "Kontakte",
@@ -231,7 +240,7 @@
       "size": "Einträge pro Seite"
     },
     "map": {
-      "openGIS": "Im GIS öffnen"
+      "openGIS": "Im GIS anzeigen"
     },
     "linkedStatements": {
       "precedingStatements": "Vorhergehende Vorgänge",
@@ -265,7 +274,8 @@
       "title": "Fehlerbenachrichtung",
       "unexpected": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es nocheinmal oder kontaktieren Sie den Support.",
       "taskToCompleteNotFound": "Der aktuelle Bearbeitungsschritt wurde bereits abgeschlossen. Bitte prüfen Sie die Daten der Stellungnahmen.",
-      "claimedByAnotherUser": "Die Stellungnahme ist bereits von einem anderen Nutzer in Bearbeitung. Bitte kehren Sie zu einem späteren Zeitpunkt zurück.",
+      "alreadyClaimed": "Die Stellungnahme wird bereits von einem anderen Nutzer bearbeitet. Bitte kehren Sie zu einem späteren Zeitpunkt zurück.",
+      "claimedByAnotherUser": "Die Stellungnahme wird bereits von einem anderen Nutzer ({{user}}) bearbeitet. Bitte kehren Sie zu einem späteren Zeitpunkt zurück.",
       "missingFormData": "Ein Feld des Formulars benötigt noch einen Wert. Bitte prüfen Sie Ihre Eingaben auf Vollständigkeit.",
       "failedLoadingContact": "Die Details für den ausgewählten Kontakt konnten nicht geladen werden. Bitte prüfen Sie Ihre Auswahl auf Vollständigkeit.",
       "failedFileUpload": "Beim Hochladen einer Datei ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support.",
@@ -273,10 +283,15 @@
       "invalidTextArrangement": "Die Zusammenstellung der Stellungnahme weist Fehler auf. Bitte prüfen Sie die Auswahl an Textbausteinen.",
       "couldNotLoadMailData": "Die Daten der ausgewählten Email konnten nicht geladen werden. Eventuell besteht ein Problem mit der Verbindung zum Mail-Server. Bitte versuchen Sie es nocheinmal oder kontaktieren Sie den Support.",
       "couldNotSendMail": "Die Email konnte nicht an die hinterlegte Email-Adresse verschickt werden. Bitte prüfen Sie die Kontaktinformationen oder versenden Sie die Stellungnahme manuell.",
-      "noAccessToContactModule": "Der Zugriff auf das Kontaktstammdatenmodul ist nicht möglich. Bitte kontaktieren Sie den Support."
+      "noAccessToContactModule": "Der Zugriff auf das Kontaktstammdatenmodul ist nicht möglich. Bitte kontaktieren Sie den Support.",
+      "couldNotAddTag": "Folgende Tags konnten nicht angelegt werden: {{value}}",
+      "invalidFileFormat": "Das Format der ausgewählten Datei ist ungültig. Bitte prüfen Sie den Inhalt und versuchen Sie es dann erneut.",
+      "searchNoResult": "Zu der Suchanfrage gab es keine passenden Ergebnisse. Passen Sie die Suchbegriffe an und versuchen es erneut.",
+      "badUserData": "Die eingestellten Nutzerdaten konnten nicht gespeichert werden. Überprüfen Sie das Format der Email-Adresse."
     }
   },
   "textBlocks": {
+    "textBlock": "Textbaustein",
     "errors": {
       "after": "Muss platziert werden nach: ",
       "excludes": "Schließt aus: ",
@@ -298,7 +313,8 @@
     "from": "von:",
     "at": "vom:",
     "inbox": "Email Eingang",
-    "attachments": "Anhänge"
+    "attachments": "Anhänge",
+    "confirmDelete": "Möchten Sie die selektierte E-Mail wirklich löschen?"
   },
   "search": {
     "title": "Suche",
@@ -313,6 +329,110 @@
     "dueDateTo": "Frist bis:",
     "finished": "Abgeschlossen",
     "open": "Offen",
-    "editedByMe": "Eigene Vorgänge"
+    "editedByMe": "Eigene Vorgänge",
+    "placeHolder": "Nach Ort suchen..."
+  },
+  "settings": {
+    "title": "Einstellungen",
+    "save": "Änderungen übernehmen",
+    "departments": {
+      "title": "Fachbereiche und Sparten",
+      "downloadCurrent": "Aktuelle Konfigurationsdatei herunterladen",
+      "selectNew": "Neue Konfigurationsdatei auswählen",
+      "submit": "Konfiguration speichern",
+      "selectedFile": "Ausgewählte Datei:",
+      "departments": "Fachbereiche",
+      "sectors": "Sparten",
+      "organisation": "Organisation",
+      "placeholderNoDepartments": "Es sind keine Fachbereiche vorhanden.",
+      "placeholderNoSectors": "Es sind keine Sparten vorhanden.",
+      "placeholderSearch": "Suchbegriffe eingeben...",
+      "search": "Suche",
+      "table": {
+        "city": "Ort",
+        "district": "Ortsteil",
+        "sectors": "Sparten",
+        "departments": "Fachbereiche"
+      }
+    },
+    "documents": {
+      "title": "Dokumente",
+      "tags": "Marken",
+      "tag": "Marke",
+      "placeholder": "Einen Namen für die Marke eingeben...",
+      "listTitle": "Liste von Marken",
+      "save": "Änderungen übernehmen"
+    },
+    "textBlocks": {
+      "title": "Textbausteine",
+      "titleNegative": "Textbausteine (Negativantwort)",
+      "select": "Auswahlliste:",
+      "entries": "Einträge:",
+      "name": "Name:",
+      "selects": "Auswahllisten",
+      "invalidSelectKey": "Der Name der Auswahlliste ist ungültig.",
+      "pickTextBlock": "Bitte wählen Sie einen Textbaustein zur Bearbeitung aus.",
+      "default": {
+        "selectKey": "Auswahlliste",
+        "selectEntry": "Neuer Eintrag"
+      },
+      "rules": {
+        "excludes": "Schließt aus:",
+        "requiresAnd": "Erfordert alle von:",
+        "requiresOr": "Erfordert mindestens einen von:",
+        "requiresXOR": "Erfordert genau einen von:"
+      },
+      "replacements": {
+        "freeText": "Freitext",
+        "date": "Datum",
+        "city": "Stadt",
+        "district": "Ortsteil",
+        "sectors": "Sparten",
+        "id": "Unser Zeichen",
+        "receiptDate": "Eingangsdatum",
+        "dueDate": "Fristende",
+        "creationDate": "Erstellungsdatum",
+        "customerReference": "Ihr Zeichen",
+        "title": "Titel",
+        "c-community": "Ort (Kontakt)",
+        "c-communitySuffix": "Addressenzusatz (Kontakt)",
+        "c-company": "Firma (Kontakt)",
+        "c-email": "Email (Kontakt)",
+        "c-firstName": "Vorname (Kontakt)",
+        "c-lastName": "Nachname (Kontakt)",
+        "c-houseNumber": "Hausnummer (Kontakt)",
+        "c-postCode": "Postleitzahl (Kontakt)",
+        "c-salutation": "Anrede (Kontakt)",
+        "c-street": "Straße (Kontakt)",
+        "c-title": "Titel (Kontakt)"
+      }
+    },
+    "users": {
+      "title": "Nutzerverwaltung",
+      "email": "Email",
+      "emailPlaceholder": "Email-Adresse für Nutzer festlegen...",
+      "departmentGroup": "Fachbereichsgruppe",
+      "departmentGroupPlaceholder": "Fachbereichgruppe auswählen...",
+      "department": "Fachbereich",
+      "departmentPlaceholder": "Fachbereich auswählen...",
+      "save": "Nutzerdaten speichern",
+      "editTitle": "Nutzer: ",
+      "table": {
+        "userName": "Nutzerkennung",
+        "firstName": "Vorname",
+        "lastName": "Nachname",
+        "email": "Email",
+        "role": "Nutzerrolle"
+      },
+      "role": {
+        "ROLE_SPA_DIVISION_MEMBER": "Fachbearbeiter",
+        "ROLE_SPA_APPROVER": "Genehmiger",
+        "ROLE_SPA_OFFICIAL_IN_CHARGE": "Sachbearbeiter",
+        "ROLE_SPA_ADMIN": "Admin",
+        "ROLE_SPA_CUSTOMER": "Kunde"
+      },
+      "sync": "Nutzer aus Auth-N-Auth synchronisieren",
+      "noDepartment": "Kein Fachbereich"
+    }
   }
 }
diff --git a/src/theme/user-controls/_button.theme.scss b/src/theme/user-controls/_button.theme.scss
index 835deb3..e3aba61 100644
--- a/src/theme/user-controls/_button.theme.scss
+++ b/src/theme/user-controls/_button.theme.scss
@@ -102,6 +102,7 @@
   }
 }
 
+.openk-button.openk-button-icon,
 .openk-button.openk-button-rounded {
   --icon-scale-factor: 0.7;
 
@@ -121,6 +122,22 @@
   }
 }
 
+.openk-button.openk-button-icon {
+  font-size: 0.66em;
+  border: 0;
+  color: get-color($openk-default-palette, 500, contrast);
+
+  &:not(.openk-info) {
+    background-color: transparent;
+  }
+
+  &:not(.openk-info):active,
+  &:not(.openk-info):focus,
+  &:not(.openk-info):hover {
+    background-color: $openk-background-highlight;
+  }
+}
+
 .openk-primary {
 
   .openk-button,