[TOB-24] feat: Add functionality to edit raw text in statement editor

 * Add directive for automatically resizing text areas
 * Display replacement texts in preview
 * Use currently set values when converting text blocks to free text
 * Prevent shrink of multiple whitespaces in preview

[TOB-58] feat: Add functionality to edit text palceholders in statement editor

 * Disable statement editor when loading back end data
 * Improve styling and UX of input elements for statement editor
 * Add input elements for text placeholders in statement editor

[TOB-189] feat: Add tagging functionality to attachment form

 * Add abstract component for form arrays
 * Add global styles for checkbox and chips
 * Add rxjs operator to end with another observable
 * Add download service
 * Use abstract form array class in arrangement form
 * Add back end calls for editing attachment tags
 * Use form arrays in existing attachment forms
 * Add user control component for attachments
 * Integrate attachment form in statement editor form
 * Integrate attachment form in statement info form
 * Extend attachment store for tagging
 * Download attachment instead of opening it
 * Add translations

[TOB-63] feat: Select optional departments in workflow data form

 * Add indeterminate functionality to multi select component
 * Adjust back end calls for selecting optional departments

[TOB-282] feat: Add side menu

 * Add navigation links to header bar
 * Add module for side menu
 * Integrate side menu to main navigation page
 * Add empty components for future routes
 * Add side menu to statement details page
 * Add side menu to statement info form
 * Add side menu to workflow data form
 * Add side menu to statement editor form
 * Add translations

[TOB-69] feat: Add functionality for contributions/finalization of statements

 * Add back end calls for contributions/finalization
 * Add component for viewing pdfs
 * Integrate pdf preview to statement editor
 * Add contribution select to statement editor
 * Reorganize statement editor form
 * Prevent parallel get requests in forms

[TOB-181] feat: Add functionality to approve statements

 * Add rxjs operator to emit on completion
 * Reorganize effects for process store
 * Integrate process task effect in statement store
 * Unclaim all tasks when leaving edit page
 * Disable side menus on claim/unclaim
 * Reorganize user roles in core module

[TOB-317] fix: Fix minor bugs

 * Fix positioninig of dropw down template in header bar
 * Properly resize input in comment component
 * Create only one license file for all chunks in webpack plugin

Signed-off-by: Christopher Keim <keim@develop-group.de>
diff --git a/package.json b/package.json
index 03fe855..84d25e5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "openkonsequenz-statement-public-affairs",
-  "version": "0.6.0",
+  "version": "0.7.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 75385d5..4dfa9ce 100644
--- a/src/app/app-routing.module.spec.ts
+++ b/src/app/app-routing.module.spec.ts
@@ -79,4 +79,23 @@
         expect(isRoutingSuccessful).toBeTruthy();
         expect(location.path()).toBe("/new");
     });
+
+    it("should navigate to /mail", async () => {
+        const isRoutingSuccessful = await callInZone(() => router.navigate(["mail"]));
+        expect(isRoutingSuccessful).toBeTruthy();
+        expect(location.path()).toBe("/mail");
+    });
+
+    it("should navigate to /search", async () => {
+        const isRoutingSuccessful = await callInZone(() => router.navigate(["search"]));
+        expect(isRoutingSuccessful).toBeTruthy();
+        expect(location.path()).toBe("/search");
+    });
+
+    it("should navigate to /settings", async () => {
+        const isRoutingSuccessful = await callInZone(() => router.navigate(["settings"]));
+        expect(isRoutingSuccessful).toBeTruthy();
+        expect(location.path()).toBe("/settings");
+    });
+
 });
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 025d1ce..7ebe298 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -17,23 +17,38 @@
 export const appRoutes: Routes = [
     {
         path: "",
-        loadChildren: () => import("./features/dashboard")
-            .then((m) => m.DashboardModule)
+        loadChildren: () => import("./features/dashboard/dashboard-routing.module")
+            .then((m) => m.DashboardRoutingModule)
     },
     {
         path: "details",
-        loadChildren: () => import("./features/details")
-            .then((m) => m.StatementDetailsModule)
+        loadChildren: () => import("./features/details/statement-details-routing.module")
+            .then((m) => m.StatementDetailsRoutingModule)
     },
     {
         path: "new",
-        loadChildren: () => import("./features/new")
-            .then((m) => m.NewStatementModule)
+        loadChildren: () => import("./features/new/new-statement-routing.module")
+            .then((m) => m.NewStatementRoutingModule)
+    },
+    {
+        path: "mail",
+        loadChildren: () => import("./features/mail/mail-routing.module")
+            .then((m) => m.MailRoutingModule)
+    },
+    {
+        path: "search",
+        loadChildren: () => import("./features/search/search-routing.module")
+            .then((m) => m.SearchRoutingModule)
     },
     {
         path: "edit",
-        loadChildren: () => import("./features/edit")
-            .then((m) => m.StatementEditModule)
+        loadChildren: () => import("./features/edit/statement-edit-routing.module")
+            .then((m) => m.StatementEditRoutingModule)
+    },
+    {
+        path: "settings",
+        loadChildren: () => import("./features/settings/settings-routing.module")
+            .then((m) => m.SettingsRoutingModule)
     },
     // The wildcard has to be placed as the last item (otherwise, its overriding routes)
     {
diff --git a/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts b/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts
index 76b091b..e25c4b3 100644
--- a/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts
+++ b/src/app/core/api/attachments/EAPIStaticAttachmentTagIds.ts
@@ -26,3 +26,11 @@
     COVER_LETTER = "cover-letter"
 
 }
+
+export const AUTO_SELECTED_TAGS = [
+    EAPIStaticAttachmentTagIds.EMAIL_TEXT,
+    EAPIStaticAttachmentTagIds.EMAIL,
+    EAPIStaticAttachmentTagIds.OUTBOX,
+    EAPIStaticAttachmentTagIds.CONSIDERATION,
+    EAPIStaticAttachmentTagIds.STATEMENT
+];
diff --git a/src/app/features/details/selectors/index.ts b/src/app/core/api/attachments/IAPIAttachmentTag.ts
similarity index 87%
copy from src/app/features/details/selectors/index.ts
copy to src/app/core/api/attachments/IAPIAttachmentTag.ts
index aeb78c3..ad50e4b 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/core/api/attachments/IAPIAttachmentTag.ts
@@ -11,4 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export interface IAPIAttachmentTag {
+    label: string;
+    id: string;
+}
diff --git a/src/app/core/api/attachments/attachments-api.service.ts b/src/app/core/api/attachments/attachments-api.service.ts
index b29eae3..e0c2709 100644
--- a/src/app/core/api/attachments/attachments-api.service.ts
+++ b/src/app/core/api/attachments/attachments-api.service.ts
@@ -16,6 +16,7 @@
 import {objectToHttpParams, urlJoin} from "../../../util/http";
 import {SPA_BACKEND_ROUTE} from "../../external-routes";
 import {IAPIAttachmentModel} from "./IAPIAttachmentModel";
+import {IAPIAttachmentTag} from "./IAPIAttachmentTag";
 
 @Injectable({providedIn: "root"})
 export class AttachmentsApiService {
@@ -54,4 +55,28 @@
         return this.httpClient.delete(urlJoin(this.baseUrl, endPoint));
     }
 
+    /**
+     * Changes the tags of a given attachment in the back end data base.
+     */
+    public postAttachmentTags(statementId: number, taskId: string, attachmentId: number, ...tagId: string[]) {
+        const endPoint = `/process/statements/${statementId}/task/${taskId}/attachments/${attachmentId}/tags`;
+        return this.httpClient.post(urlJoin(this.baseUrl, endPoint), tagId);
+    }
+
+    /**
+     * Fetches the list of all available tags in the back end data base.
+     */
+    public getTagList() {
+        const endPoint = `/tags`;
+        return this.httpClient.get<IAPIAttachmentTag[]>(urlJoin(this.baseUrl, endPoint));
+    }
+
+    /**
+     * Creates a new tag in the back end data base.
+     */
+    public addNewTag(label: string) {
+        const endPoint = `/tags`;
+        return this.httpClient.put(urlJoin(this.baseUrl, endPoint), {label});
+    }
+
 }
diff --git a/src/app/core/api/attachments/index.ts b/src/app/core/api/attachments/index.ts
index 764c147..09279fd 100644
--- a/src/app/core/api/attachments/index.ts
+++ b/src/app/core/api/attachments/index.ts
@@ -14,3 +14,4 @@
 export * from "./attachments-api.service";
 export * from "./EAPIStaticAttachmentTagIds";
 export * from "./IAPIAttachmentModel";
+export * from "./IAPIAttachmentTag";
diff --git a/src/app/store/root/model/ESPAUserRoles.ts b/src/app/core/api/core/EAPIUserRoles.ts
similarity index 80%
rename from src/app/store/root/model/ESPAUserRoles.ts
rename to src/app/core/api/core/EAPIUserRoles.ts
index fa06e3e..6414122 100644
--- a/src/app/store/root/model/ESPAUserRoles.ts
+++ b/src/app/core/api/core/EAPIUserRoles.ts
@@ -11,9 +11,15 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export enum ESPAUserRoles {
+export enum EAPIUserRoles {
     DIVISION_MEMBER = "ROLE_SPA_DIVISION_MEMBER",
     ROLE_SPA_ACCESS = "ROLE_SPA_ACCESS",
     SPA_APPROVER = "ROLE_SPA_APPROVER",
     SPA_OFFICIAL_IN_CHARGE = "ROLE_SPA_OFFICIAL_IN_CHARGE"
 }
+
+export const ALL_NON_TRIVIAL_USER_ROLES = [
+    EAPIUserRoles.DIVISION_MEMBER,
+    EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE,
+    EAPIUserRoles.SPA_APPROVER
+];
diff --git a/src/app/core/api/core/IAPIUserInfo.ts b/src/app/core/api/core/IAPIUserInfo.ts
index 1a3beb9..00eeedd 100644
--- a/src/app/core/api/core/IAPIUserInfo.ts
+++ b/src/app/core/api/core/IAPIUserInfo.ts
@@ -11,8 +11,11 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {EAPIUserRoles} from "./EAPIUserRoles";
+
 export interface IAPIUserInfo {
     firstName: string;
     lastName: string;
-    roles: string[];
+    roles: EAPIUserRoles[];
+    userName: string;
 }
diff --git a/src/app/core/api/core/index.ts b/src/app/core/api/core/index.ts
index 3e58f23..c1adf32 100644
--- a/src/app/core/api/core/index.ts
+++ b/src/app/core/api/core/index.ts
@@ -12,5 +12,6 @@
  ********************************************************************************/
 
 export * from "./core-api.service";
+export * from "./EAPIUserRoles";
 export * from "./IAPIUserInfo";
 export * from "./IAPIVersion";
diff --git a/src/app/core/api/process/EAPIProcessTaskDefinitionKey.ts b/src/app/core/api/process/EAPIProcessTaskDefinitionKey.ts
index d9a14ea..65f4ff1 100644
--- a/src/app/core/api/process/EAPIProcessTaskDefinitionKey.ts
+++ b/src/app/core/api/process/EAPIProcessTaskDefinitionKey.ts
@@ -19,11 +19,11 @@
 
     CREATE_DRAFT = "createDraft",
 
+    CREATE_NEGATIVE_RESPONSE = "createNegativeResponse",
+
     ENRICH_DRAFT = "enrichDraft",
 
-    CHECK_FOR_COMPLETENESS = "checkForCompleteness",
-
-    FINALIZE_STATEMENT = "finalizeStatement",
+    CHECK_AND_FORMULATE_RESPONSE = "checkAndFormulateResponse",
 
     APPROVE_STATEMENT = "approveStatement",
 
diff --git a/src/app/core/api/process/IAPIProcessObject.ts b/src/app/core/api/process/IAPIProcessObject.ts
index ef0edcb..82be632 100644
--- a/src/app/core/api/process/IAPIProcessObject.ts
+++ b/src/app/core/api/process/IAPIProcessObject.ts
@@ -26,3 +26,26 @@
     | { [key: string]: { type: "String", value: string } }
     | { [key: string]: { type: "Number", value: number } };
 
+
+export type TAddBasicInfoDataCompleteVariable = {
+    responsible: { type: "Boolean", value: boolean }
+};
+
+export type TCreateNegativeResponseCompleteVariable = {
+    response_created: { type: "Boolean", value: boolean }
+};
+
+export type TCheckAndFormulateResponseCompleteVariable = {
+    response_created: { type: "Boolean", value: boolean },
+    data_complete: { type: "Boolean", value: boolean }
+};
+
+export type TApproveStatementCompleteVariable = {
+    approved_statement: { type: "Boolean", value: boolean }
+};
+
+export type TCompleteTaskVariable = {}
+    | TAddBasicInfoDataCompleteVariable
+    | TCreateNegativeResponseCompleteVariable
+    | TCheckAndFormulateResponseCompleteVariable
+    | TApproveStatementCompleteVariable;
diff --git a/src/app/core/api/statements/IAPIWorkflowData.ts b/src/app/core/api/statements/IAPIWorkflowData.ts
index 41f1ffc..60075e9 100644
--- a/src/app/core/api/statements/IAPIWorkflowData.ts
+++ b/src/app/core/api/statements/IAPIWorkflowData.ts
@@ -19,9 +19,14 @@
 export interface IAPIWorkflowData {
 
     /**
-     * Object which contains all departments assigned to a statement.
+     * Object which contains all mandatory departments assigned to a statement.
      */
-    selectedDepartments: IAPIDepartmentGroups;
+    mandatoryDepartments: IAPIDepartmentGroups;
+
+    /**
+     * Object which contains all optional departments assigned to a statement.
+     */
+    optionalDepartments: IAPIDepartmentGroups;
 
     /**
      * String which represents the geographic position assigned to a statement.
diff --git a/src/app/core/api/statements/statements-api.service.ts b/src/app/core/api/statements/statements-api.service.ts
index 769991a..a8e89ff 100644
--- a/src/app/core/api/statements/statements-api.service.ts
+++ b/src/app/core/api/statements/statements-api.service.ts
@@ -15,6 +15,7 @@
 import {Inject, Injectable} from "@angular/core";
 import {objectToHttpParams, urlJoin} from "../../../util";
 import {SPA_BACKEND_ROUTE} from "../../external-routes";
+import {IAPIDepartmentGroups} from "../settings";
 import {IAPIPaginationResponse, IAPISearchOptions} from "../shared";
 import {IAPICommentModel} from "./IAPICommentModel";
 import {IAPISectorsModel} from "./IAPISectorsModel";
@@ -94,6 +95,22 @@
     }
 
     /**
+     * Fetches the current status of the departments that have finished their contribution to the statement..
+     */
+    public getContributions(statementId: number) {
+        const endPoint = `/process/statements/${statementId}/workflow/contributions`;
+        return this.httpClient.get<IAPIDepartmentGroups>(urlJoin(this.baseUrl, endPoint));
+    }
+
+    /**
+     * Creates or updates the contributions in the back end data base.
+     */
+    public postContributions(statementId: number, taskId: string, body: IAPIDepartmentGroups) {
+        const endPoint = `/process/statements/${statementId}/task/${taskId}/workflow/contributions`;
+        return this.httpClient.post(urlJoin(this.baseUrl, endPoint), body);
+    }
+
+    /**
      * Fetches the IDs of all parents to a specific statement.
      */
     public getParentIds(statementId: number) {
@@ -141,4 +158,13 @@
         return this.httpClient.get<IAPISectorsModel>(urlJoin(this.baseUrl, endPoint));
     }
 
+
+    /**
+     * Updates the contribution status of the user's department to true for the given statement.
+     */
+    public contribute(statementId: number, taskId: string) {
+        const endPoint = `/process/statements/${statementId}/task/${taskId}/workflow/contribute`;
+        return this.httpClient.patch(urlJoin(this.baseUrl, endPoint), null);
+    }
+
 }
diff --git a/src/app/core/download/download.service.ts b/src/app/core/download/download.service.ts
new file mode 100644
index 0000000..d69dbdd
--- /dev/null
+++ b/src/app/core/download/download.service.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 {DOCUMENT} from "@angular/common";
+import {Inject, Injectable} from "@angular/core";
+
+@Injectable({providedIn: "root"})
+export class DownloadService {
+
+    public constructor(@Inject(DOCUMENT) public readonly document: Document) {
+
+    }
+
+    public startDownload(url: string, token?: string) {
+        url += token == null ? "" : `?accessToken=${token}`;
+        const anchor = this.document.createElement("a");
+        anchor.style.display = "none";
+        anchor.target = "_blank";
+        anchor.rel = "noreferrer";
+        anchor.href = url;
+        anchor.download = "";
+        anchor.click();
+        anchor.href = null;
+    }
+
+}
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/core/download/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/core/download/index.ts
index f223969..01e28d7 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/core/download/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./download.service";
diff --git a/src/app/core/index.ts b/src/app/core/index.ts
index d0b0fcf..c5164a0 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 "./dom";
+export * from "./download";
 export * from "./external-routes";
 export * from "./i18n";
 
diff --git a/src/app/features/dashboard/components/dashboard/dashboard.component.html b/src/app/features/dashboard/components/dashboard/dashboard.component.html
index c632cf7..fff4a03 100644
--- a/src/app/features/dashboard/components/dashboard/dashboard.component.html
+++ b/src/app/features/dashboard/components/dashboard/dashboard.component.html
@@ -15,17 +15,6 @@
 
   <span class="dashboard-header-title">Stellungnahmen öffentlicher Belange</span>
 
-  <div class="dashboard-header-actions">
-    <button class="openk-button openk-info" disabled>
-      <mat-icon>view_list</mat-icon>
-      Alle Stellungnahmen anzeigen
-    </button>
-
-    <a *ngIf="isOfficialInCharge$ | async" class="openk-button openk-info" routerLink="/new">
-      <mat-icon>note_add</mat-icon>
-      Neue Stellungnahme hinzufügen
-    </a>
-  </div>
 </div>
 
 <div class="dashboard">
diff --git a/src/app/features/dashboard/dashboard-routing.module.ts b/src/app/features/dashboard/dashboard-routing.module.ts
index 7b06f4e..3899d64 100644
--- a/src/app/features/dashboard/dashboard-routing.module.ts
+++ b/src/app/features/dashboard/dashboard-routing.module.ts
@@ -14,6 +14,7 @@
 import {NgModule} from "@angular/core";
 import {RouterModule, Routes} from "@angular/router";
 import {DashboardComponent} from "./components";
+import {DashboardModule} from "./dashboard.module";
 
 const routes: Routes = [
     {
@@ -25,6 +26,7 @@
 
 @NgModule({
     imports: [
+        DashboardModule,
         RouterModule.forChild(routes)
     ],
     exports: [
diff --git a/src/app/features/dashboard/dashboard.module.ts b/src/app/features/dashboard/dashboard.module.ts
index 53e0125..faee66a 100644
--- a/src/app/features/dashboard/dashboard.module.ts
+++ b/src/app/features/dashboard/dashboard.module.ts
@@ -14,15 +14,15 @@
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
 import {MatIconModule} from "@angular/material/icon";
+import {RouterModule} from "@angular/router";
 import {CardModule} from "../../shared/layout/card";
 import {SharedPipesModule} from "../../shared/pipes";
 import {DashboardComponent, DashboardItemComponent, DashboardListComponent} from "./components";
-import {DashboardRoutingModule} from "./dashboard-routing.module";
 
 @NgModule({
     imports: [
         CommonModule,
-        DashboardRoutingModule,
+        RouterModule,
         MatIconModule,
         CardModule,
         SharedPipesModule
diff --git a/src/app/features/details/components/index.ts b/src/app/features/details/components/index.ts
index fcbbe4c..afadae7 100644
--- a/src/app/features/details/components/index.ts
+++ b/src/app/features/details/components/index.ts
@@ -12,4 +12,5 @@
  ********************************************************************************/
 
 export * from "./statement-details";
+export * from "./side-menu";
 export * from "./process-information";
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/details/components/side-menu/index.ts
similarity index 90%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/details/components/side-menu/index.ts
index aeb78c3..7acfb2e 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/details/components/side-menu/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./statement-details-side-menu.component";
diff --git a/src/app/features/details/components/side-menu/statement-details-side-menu.component.html b/src/app/features/details/components/side-menu/statement-details-side-menu.component.html
new file mode 100644
index 0000000..9484272
--- /dev/null
+++ b/src/app/features/details/components/side-menu/statement-details-side-menu.component.html
@@ -0,0 +1,49 @@
+<!-------------------------------------------------------------------------------
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ -------------------------------------------------------------------------------->
+
+<ng-container *ngIf="buttonLayout?.length > 0">
+
+  <ng-container *appSideMenu="'top'; title: 'details.sideMenu.title' | translate">
+    <app-action-button
+      [appIcon]="'home'"
+      [appRouterLink]="'/'"
+      [appDisabled]="appLoading"
+      class="side-menu-button openk-info">
+      {{'details.sideMenu.backToDashboard' | translate}}
+    </app-action-button>
+  </ng-container>
+
+
+  <ng-container *appSideMenu="'bottom'">
+
+    <app-action-button
+      (appClick)="button.emit(button.task)"
+      *ngFor="let button of buttonLayout"
+      [appDisabled]="appLoading"
+      [appIcon]="button.icon"
+      [ngClass]="button.cssClass"
+      class="side-menu-button">
+      {{button.label | translate}}
+    </app-action-button>
+
+  </ng-container>
+
+</ng-container>
+
+<ng-container *ngIf="appLoading">
+  <app-side-menu-status
+    *appSideMenu="'center'"
+    [appLoadingMessage]="'core.loading' | translate"
+    [appLoading]="appLoading">
+  </app-side-menu-status>
+</ng-container>
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/details/components/side-menu/statement-details-side-menu.component.scss
similarity index 86%
rename from src/app/shared/controls/text-field/text-field.component.scss
rename to src/app/features/details/components/side-menu/statement-details-side-menu.component.scss
index 563859b..3796710 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/details/components/side-menu/statement-details-side-menu.component.scss
@@ -11,9 +11,14 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
+:host {
+  display: none;
+}
+
+.side-menu-button {
   width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
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
new file mode 100644
index 0000000..7369cca
--- /dev/null
+++ b/src/app/features/details/components/side-menu/statement-details-side-menu.component.spec.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 {SimpleChange} from "@angular/core";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {EAPIProcessTaskDefinitionKey, EAPIUserRoles, IAPIProcessTask} from "../../../../core";
+import {StatementDetailsModule} from "../../statement-details.module";
+import {StatementDetailsSideMenuComponent} from "./statement-details-side-menu.component";
+
+describe("StatementDetailsSideMenuComponent", () => {
+    let component: StatementDetailsSideMenuComponent;
+    let fixture: ComponentFixture<StatementDetailsSideMenuComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [StatementDetailsModule]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(StatementDetailsSideMenuComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should call update on input changes", () => {
+        const updateSpy = spyOn(component, "update");
+        const keys: Array<keyof StatementDetailsSideMenuComponent> = ["appUserRoles", "appTasks"];
+        updateSpy.calls.reset();
+
+        component.ngOnChanges({});
+        expect(updateSpy).not.toHaveBeenCalled();
+
+        keys.map((_) => ({[_]: new SimpleChange(0, 1, false)}))
+            .forEach((changes) => {
+                component.ngOnChanges(changes);
+                expect(updateSpy).toHaveBeenCalledWith();
+            });
+    });
+
+    it("should update add buttons based upon current user roles and tasks", () => {
+        component.update();
+        expect(component.buttonLayout).toEqual([]);
+
+        component.appUserRoles = [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE];
+        component.appTasks = [{
+            ...{} as IAPIProcessTask,
+            statementId: 19,
+            taskId: "abcde",
+            taskDefinitionKey: EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA
+        }];
+        component.update();
+        expect(component.buttonLayout.length).toEqual(2);
+    });
+});
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
new file mode 100644
index 0000000..76611a1
--- /dev/null
+++ b/src/app/features/details/components/side-menu/statement-details-side-menu.component.ts
@@ -0,0 +1,220 @@
+/********************************************************************************
+ * 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 {Action} from "@ngrx/store";
+import {
+    ALL_NON_TRIVIAL_USER_ROLES,
+    EAPIProcessTaskDefinitionKey,
+    EAPIUserRoles,
+    IAPIProcessTask,
+    TCompleteTaskVariable
+} from "../../../../core";
+import {claimAndCompleteTask, claimTaskAction, sendStatementViaMailAction} from "../../../../store";
+import {arrayJoin, filterDistinctValues} from "../../../../util/store";
+
+export interface IStatementDetailsSideMenuActionButton {
+
+    emit?: (task?: IAPIProcessTask) => void;
+
+    task?: IAPIProcessTask;
+
+    action?: Action;
+
+    label?: string;
+
+    icon?: string;
+
+    cssClass?: string;
+
+}
+
+interface ITaskUserLayoutMap<T> {
+
+    [taskKey: string]: {
+        [role: string]: T[]
+    };
+
+}
+
+@Component({
+    selector: "app-statement-details-side-menu",
+    templateUrl: "./statement-details-side-menu.component.html",
+    styleUrls: ["./statement-details-side-menu.component.scss"]
+})
+export class StatementDetailsSideMenuComponent implements OnChanges {
+
+    @Input()
+    appLoading: boolean;
+
+    @Input()
+    public appUserRoles: EAPIUserRoles[];
+
+    @Input()
+    public appTasks: IAPIProcessTask[];
+
+    @Output()
+    public appDispatch = new EventEmitter<Action>();
+
+    public buttonLayout: IStatementDetailsSideMenuActionButton[] = [];
+
+    private taskUserLayoutMap: ITaskUserLayoutMap<IStatementDetailsSideMenuActionButton> = {
+        [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE]: {
+            [EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA]: [
+                {
+                    emit: this.emitClaimAndCompleteFactory(
+                        {responsible: {type: "Boolean", value: false}},
+                        EAPIProcessTaskDefinitionKey.CREATE_NEGATIVE_RESPONSE
+                    ),
+                    label: "details.sideMenu.createNegativeStatement",
+                    icon: "edit",
+                    cssClass: "openk-danger"
+                },
+                {
+                    emit: this.emitClaimTaskFatory(),
+                    label: "details.sideMenu.editInfoData",
+                    icon: "subject",
+                    cssClass: "openk-success"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.CREATE_NEGATIVE_RESPONSE]: [
+                {
+                    emit: this.emitClaimAndCompleteFactory({response_created: {type: "Boolean", value: false}}),
+                    label: "details.sideMenu.backToInfoData",
+                    icon: "subject",
+                    cssClass: "openk-info"
+                },
+                {
+                    emit: this.emitClaimTaskFatory(),
+                    label: "details.sideMenu.createNegativeStatement",
+                    icon: "edit",
+                    cssClass: "openk-info"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.ADD_WORK_FLOW_DATA]: [
+                {
+                    emit: this.emitClaimTaskFatory(),
+                    label: "details.sideMenu.editWorkflowData",
+                    icon: "subject",
+                    cssClass: "openk-info"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.CREATE_DRAFT]: [
+                {
+                    emit: this.emitClaimTaskFatory(),
+                    label: "details.sideMenu.createDraft",
+                    icon: "edit",
+                    cssClass: "openk-info"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.ENRICH_DRAFT]: [
+                {
+                    emit: this.emitClaimAndCompleteFactory({}, EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE),
+                    label: "details.sideMenu.checkDraft",
+                    icon: "description",
+                    cssClass: "openk-success"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE]: [
+                {
+                    emit: this.emitClaimTaskFatory(),
+                    label: "details.sideMenu.completeDraft",
+                    icon: "description",
+                    cssClass: "openk-info"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.SEND_STATEMENT]: [
+                {
+                    emit: (task) => this.appDispatch.emit(sendStatementViaMailAction({
+                        statementId: task?.statementId,
+                        taskId: task?.taskId
+                    })),
+                    label: "details.sideMenu.sendEmail",
+                    icon: "send",
+                    cssClass: "openk-success"
+                },
+                {
+                    emit: this.emitClaimAndCompleteFactory({}),
+                    label: "details.sideMenu.completeIssue",
+                    icon: "done",
+                    cssClass: "openk-success"
+                }
+            ]
+        },
+        [EAPIUserRoles.DIVISION_MEMBER]: {
+            [EAPIProcessTaskDefinitionKey.ENRICH_DRAFT]: [
+                {
+                    emit: this.emitClaimTaskFatory(),
+                    label: "details.sideMenu.editDraft",
+                    icon: "description",
+                    cssClass: "openk-info"
+                }
+            ]
+        },
+        [EAPIUserRoles.SPA_APPROVER]: {
+            [EAPIProcessTaskDefinitionKey.APPROVE_STATEMENT]: [
+                {
+                    emit: this.emitClaimAndCompleteFactory({approved_statement: {type: "Boolean", value: false}}),
+                    label: "details.sideMenu.disapprove",
+                    icon: "replay",
+                    cssClass: "openk-danger"
+                },
+                {
+                    emit: this.emitClaimAndCompleteFactory({approved_statement: {type: "Boolean", value: true}}),
+                    label: "details.sideMenu.approve",
+                    icon: "send",
+                    cssClass: "openk-success"
+                }
+            ]
+        }
+    };
+
+    public ngOnChanges(changes: SimpleChanges) {
+        const keys: Array<keyof StatementDetailsSideMenuComponent> = ["appUserRoles", "appTasks"];
+        if (keys.some((_) => changes[_] != null)) {
+            this.update();
+        }
+    }
+
+    public update() {
+        let roles = filterDistinctValues(this.appUserRoles);
+        roles = ALL_NON_TRIVIAL_USER_ROLES.filter((_) => roles.indexOf(_) > -1);
+        const tasks = filterDistinctValues(this.appTasks);
+        const actionsForRoles = filterDistinctValues(roles).map((role) => {
+            return tasks.map((task) => this.getLayoutForRoleAndTask(role, task));
+        });
+        this.buttonLayout = arrayJoin(...arrayJoin(...actionsForRoles));
+    }
+
+    private emitClaimAndCompleteFactory(variables: TCompleteTaskVariable, claimNext?: EAPIProcessTaskDefinitionKey) {
+        return (task: IAPIProcessTask) => this.appDispatch.emit(claimAndCompleteTask({
+            statementId: task?.statementId,
+            taskId: task?.taskId,
+            variables,
+            claimNext
+        }));
+    }
+
+    private emitClaimTaskFatory() {
+        return (task: IAPIProcessTask) => this.appDispatch.emit(claimTaskAction({
+            statementId: task?.statementId,
+            taskId: task?.taskId
+        }));
+    }
+
+    private getLayoutForRoleAndTask(role: EAPIUserRoles, task: IAPIProcessTask): IStatementDetailsSideMenuActionButton[] {
+        const userActions = this.taskUserLayoutMap[role];
+        return userActions == null ? [] : arrayJoin(userActions[task.taskDefinitionKey]).map((_) => ({..._, task}));
+    }
+
+}
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 6eb8c9c..f9d245f 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
@@ -11,57 +11,48 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<div class="dashboard-header">
+<div *ngIf="title$ | async" class="dashboard-header">
 
-  <span class="dashboard-header-title">Stellungnahmen öffentlicher Belange - Detailansicht</span>
+  <span class="dashboard-header-title">
+    {{title$ | async}}
+  </span>
 
-  <div class="dashboard-header-actions">
-
-    <a #homeAnchor class="openk-button openk-info" routerLink="/">
-      <mat-icon>arrow_back</mat-icon>
-      Zurück zur Übersicht
-    </a>
-
-  </div>
 </div>
 
+<app-statement-details-side-menu
+  (appDispatch)="store.dispatch($event)"
+  [appLoading]="loading$ | async"
+  [appTasks]="tasks$ | async"
+  [appUserRoles]="userRoles$ | async">
+</app-statement-details-side-menu>
+
 <ng-container *ngIf="(statement$ | async) != null">
   <app-collapsible
-    [appTitle]="(statementId$ | async) + ' Allgemeine Informationen: ' + (statement$ | async)?.info?.title"
+    [appCollapsed]="true"
+    [appTitle]="'Allgemeine Informationen'"
     class="statement-details">
 
     <div class="statement-details-content">
-      <div *ngFor="let obj of (statement$ | async).info | objToArray">
-        {{obj?.key}}: {{obj?.value}}
-      </div>
-
-      <div class="statement-details-footer">
-
-        <button
-          (click)="editTask(button?.task, button?.options)"
-          *ngFor="let button of buttons$ | async"
-          [ngClass]="button?.class"
-          class="openk-button statement-details-footer-button">
-          <mat-icon>{{button?.icon}}</mat-icon>
-          <span>{{button?.label | translate}}</span>
-        </button>
-
+      <div *ngFor="let obj of (statement$ | async) | objToArray">
+        <div *ngFor="let detail of obj.value | objToArray">
+          {{detail?.key}}: {{detail?.value | json}}
+        </div>
       </div>
     </div>
   </app-collapsible>
 
   <app-collapsible
     [appCollapsed]="true"
-    [appTitle]="'Eingehende Email und Anhänge'"
+    [appTitle]="'Eingangsdokumente'"
     class="statement-details">
-    <div class="placeholder"></div>
+    <div class="placeholder">Not yet implemented.</div>
   </app-collapsible>
 
   <app-collapsible
     [appCollapsed]="true"
     [appTitle]="'Betroffene Fachbereiche'"
     class="statement-details">
-    <div class="placeholder"></div>
+    <div class="placeholder">Not yet implemented.</div>
   </app-collapsible>
 
   <app-comments-form
@@ -71,16 +62,17 @@
   </app-comments-form>
 
   <app-collapsible
-    [appCollapsed]="false"
+    [appCollapsed]="true"
     [appTitle]="'Prozessinformationen'"
     class="statement-details">
-    <app-process-information [appCurrentActivities]="processCurrentActivityIds$ | async"
-                             [appProcessHistoryData]="processHistoryData$ | async"
-                             [appProcessName]="processName$ | async"
-                             [appProcessVersion]="processVersion$ | async"
-                             [appStatementId]="statementId$ | async"
-                             [appWorkflowXml]="processDiagram$ | async"
-                             class="statement-details-content statement-details-content--process-information">
+    <app-process-information
+      [appCurrentActivities]="processCurrentActivityIds$ | async"
+      [appProcessHistoryData]="processHistory$ | async | getProcessHistoryEntries"
+      [appProcessName]="processName$ | async"
+      [appProcessVersion]="processVersion$ | async"
+      [appStatementId]="statementId$ | async"
+      [appWorkflowXml]="processDiagram$ | async"
+      class="statement-details-content statement-details-content--process-information">
     </app-process-information>
   </app-collapsible>
 
@@ -88,8 +80,7 @@
     [appCollapsed]="true"
     [appTitle]="'Verknüpfte Vorgänge'"
     class="statement-details">
-    <div class="placeholder"></div>
+    <div class="placeholder"> Not yet implemented.</div>
   </app-collapsible>
 
 </ng-container>
-
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 62b1613..8c589c3 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
@@ -22,15 +22,17 @@
     currentActivityIds,
     deleteCommentAction,
     fetchStatementDetailsAction,
+    historySelector,
     processDiagramSelector,
     processNameSelector,
     processVersionSelector,
     queryParamsIdSelector,
-    statementCommentsSelector,
-    statementSelector
+    statementLoadingSelector,
+    statementSelector,
+    statementTasksSelector,
+    statementTitleSelector,
+    userRolesSelector
 } from "../../../../store";
-import {statementDetailsButtonSelector} from "../../selectors";
-import {processHistoryDataSelector} from "../../selectors/process-details.selectors";
 
 @Component({
     selector: "app-statement-details",
@@ -41,25 +43,29 @@
 
     public statementId$ = this.store.pipe(select(queryParamsIdSelector));
 
-    public buttons$ = this.store.pipe(select(statementDetailsButtonSelector));
-
-    public comments$ = this.store.pipe(select(statementCommentsSelector));
-
     public statement$ = this.store.pipe(select(statementSelector));
 
+    public title$ = this.store.pipe(select(statementTitleSelector));
+
     public processName$ = this.store.pipe(select(processNameSelector));
 
     public processVersion$ = this.store.pipe(select(processVersionSelector));
 
-    public processHistoryData$ = this.store.pipe(select(processHistoryDataSelector));
+    public processHistory$ = this.store.pipe(select(historySelector));
 
     public processDiagram$ = this.store.pipe(select(processDiagramSelector));
 
     public processCurrentActivityIds$ = this.store.pipe(select(currentActivityIds));
 
+    public userRoles$ = this.store.pipe(select(userRolesSelector));
+
+    public tasks$ = this.store.pipe(select(statementTasksSelector));
+
+    public loading$ = this.store.pipe(select(statementLoadingSelector));
+
     private subscription: Subscription;
 
-    public constructor(private readonly store: Store) {
+    public constructor(public readonly store: Store) {
 
     }
 
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
new file mode 100644
index 0000000..c35f0cc
--- /dev/null
+++ b/src/app/features/details/pipes/get-process-history-entries.pipe.ts
@@ -0,0 +1,53 @@
+/********************************************************************************
+ * 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 {IAPIProcessActivity, IAPIStatementHistory} from "../../../core/api/process";
+import {arrayJoin} from "../../../util/store";
+import {IProcessHistoryData} from "../components/process-information/process-history";
+
+@Pipe({name: "getProcessHistoryEntries"})
+export class GetProcessHistoryEntriesPipe implements PipeTransform {
+
+    private userTaskIcon = "account_circle";
+
+    private serviceTaskIcon = "group_work";
+
+    public transform(value: IAPIStatementHistory): IProcessHistoryData[] {
+        return arrayJoin(value?.finishedProcessActivities, value?.currentProcessActivities)
+            .filter((activity) => activity.activityType === "userTask" || activity.activityType === "serviceTask")
+            .map((activity) => this.mapToEntry(activity));
+    }
+
+    private mapToEntry(activity: IAPIProcessActivity): IProcessHistoryData {
+        return {
+            icon: this.getIcon(activity.activityType),
+            activityName: activity.activityName,
+            assignee: activity.assignee,
+            endTime: activity.endTime,
+            cancelled: activity.canceled
+        };
+    }
+
+    private getIcon(activityType: string) {
+        switch (activityType) {
+            case "userTask":
+                return this.userTaskIcon;
+            case "serviceTask":
+                return this.serviceTaskIcon;
+            default:
+                return "";
+        }
+    }
+
+}
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/details/pipes/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/details/pipes/index.ts
index aeb78c3..a33442e 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/details/pipes/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./get-process-history-entries.pipe";
diff --git a/src/app/features/details/selectors/process-details.selectors.ts b/src/app/features/details/selectors/process-details.selectors.ts
deleted file mode 100644
index 72a2412..0000000
--- a/src/app/features/details/selectors/process-details.selectors.ts
+++ /dev/null
@@ -1,40 +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 {createSelector} from "@ngrx/store";
-import {historySelector} from "../../../store";
-import {arrayJoin} from "../../../util/store";
-import {IProcessHistoryData} from "../components";
-
-export const processHistoryDataSelector = createSelector(
-    historySelector,
-    (processHistoryData): IProcessHistoryData[] => {
-        if (processHistoryData == null) {
-            return [];
-        }
-        return arrayJoin(processHistoryData.finishedProcessActivities, processHistoryData.currentProcessActivities)
-            .filter((activity) => activity.activityType === "userTask" || activity.activityType === "serviceTask")
-            .map((activity) => {
-                let icon = activity.activityType === "userTask" ? "account_circle" : "";
-                icon = activity.activityType === "serviceTask" ? "group_work" : icon;
-                return {
-                    icon,
-                    activityName: activity.activityName,
-                    assignee: activity.assignee,
-                    endTime: activity.endTime,
-                    cancelled: activity.canceled
-                };
-            });
-    }
-);
-
diff --git a/src/app/features/details/selectors/statement-details-button.selector.ts b/src/app/features/details/selectors/statement-details-button.selector.ts
deleted file mode 100644
index 5938696..0000000
--- a/src/app/features/details/selectors/statement-details-button.selector.ts
+++ /dev/null
@@ -1,51 +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 {createSelector} from "@ngrx/store";
-import {IAPIProcessTask} from "../../../core/api/process";
-import {EAPIProcessTaskDefinitionKey} from "../../../core/api/process/EAPIProcessTaskDefinitionKey";
-import {statementTasksSelector} from "../../../store";
-
-interface IStatementDetailsButton {
-    icon: string;
-    label: string;
-    task: IAPIProcessTask;
-    options?: any;
-    class?: string;
-}
-
-export const statementDetailsButtonSelector = createSelector(
-    statementTasksSelector,
-    (tasks): IStatementDetailsButton[] => {
-        const result: IStatementDetailsButton[] = [];
-
-        for (const task of tasks) {
-            if (task.taskDefinitionKey === EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA) {
-                result.push({
-                    icon: "description",
-                    label: "details.button.createDraftForNegativeAnswer",
-                    options: {negative: true},
-                    task
-                });
-            }
-            result.push({
-                icon: "create",
-                label: "details.button." + task.taskDefinitionKey,
-                class: "openk-success",
-                task
-            });
-        }
-
-        return result;
-    }
-);
diff --git a/src/app/features/details/statement-details-routing.module.ts b/src/app/features/details/statement-details-routing.module.ts
index 42360d4..9ddaf90 100644
--- a/src/app/features/details/statement-details-routing.module.ts
+++ b/src/app/features/details/statement-details-routing.module.ts
@@ -14,6 +14,7 @@
 import {NgModule} from "@angular/core";
 import {RouterModule, Routes} from "@angular/router";
 import {StatementDetailsComponent} from "./components";
+import {StatementDetailsModule} from "./statement-details.module";
 
 const routes: Routes = [
     {
@@ -25,6 +26,7 @@
 
 @NgModule({
     imports: [
+        StatementDetailsModule,
         RouterModule.forChild(routes)
     ],
     exports: [
diff --git a/src/app/features/details/statement-details.module.ts b/src/app/features/details/statement-details.module.ts
index d731669..13a1b6f 100644
--- a/src/app/features/details/statement-details.module.ts
+++ b/src/app/features/details/statement-details.module.ts
@@ -16,15 +16,24 @@
 import {FormsModule} from "@angular/forms";
 import {MatIconModule} from "@angular/material/icon";
 import {MatTableModule} from "@angular/material/table";
+import {RouterModule} from "@angular/router";
 import {TranslateModule} from "@ngx-translate/core";
 import {DateControlModule} from "../../shared/controls/date-control";
+import {ActionButtonModule} from "../../shared/layout/action-button";
 import {CardModule} from "../../shared/layout/card";
 import {CollapsibleModule} from "../../shared/layout/collapsible";
+import {SideMenuModule} from "../../shared/layout/side-menu";
 import {SharedPipesModule} from "../../shared/pipes";
 import {CommentsFormModule} from "../forms/comments";
-import {ProcessDiagramComponent, ProcessHistoryComponent, ProcessInformationComponent, StatementDetailsComponent} from "./components";
+import {
+    ProcessDiagramComponent,
+    ProcessHistoryComponent,
+    ProcessInformationComponent,
+    StatementDetailsComponent,
+    StatementDetailsSideMenuComponent
+} from "./components";
 import {BpmnDirective} from "./directives";
-import {StatementDetailsRoutingModule} from "./statement-details-routing.module";
+import {GetProcessHistoryEntriesPipe} from "./pipes";
 
 @NgModule({
     imports: [
@@ -33,27 +42,33 @@
         MatIconModule,
         MatTableModule,
         TranslateModule,
+        RouterModule,
 
-        StatementDetailsRoutingModule,
         CardModule,
         SharedPipesModule,
         DateControlModule,
         CollapsibleModule,
-        CommentsFormModule
+        CommentsFormModule,
+        SideMenuModule,
+        ActionButtonModule
     ],
     declarations: [
         StatementDetailsComponent,
         ProcessHistoryComponent,
         ProcessDiagramComponent,
         ProcessInformationComponent,
-        BpmnDirective
+        BpmnDirective,
+        StatementDetailsSideMenuComponent,
+        GetProcessHistoryEntriesPipe
     ],
     exports: [
         StatementDetailsComponent,
         ProcessHistoryComponent,
         ProcessDiagramComponent,
         ProcessInformationComponent,
-        BpmnDirective
+        BpmnDirective,
+        StatementDetailsSideMenuComponent,
+        GetProcessHistoryEntriesPipe
     ]
 })
 export class StatementDetailsModule {
diff --git a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.html b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.html
index add84ed..595ef11 100644
--- a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.html
+++ b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.html
@@ -11,10 +11,11 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<app-page-header [appActions]="headerActions$ | async"
-                 [appTitle]="'edit.title'"
-                 style="margin-bottom: 1em;">
-</app-page-header>
+<div *ngIf="(title$ | async)?.trim().length > 0" class="title">
+  <label class="title--label">
+    {{title$ | async}}
+  </label>
+</div>
 
 <ng-container [ngSwitch]="site$ | async">
   <div *ngSwitchCase="EStatementEditorSites.LOADING"
diff --git a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.scss b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.scss
index 0b45d08..ddbcd0f 100644
--- a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.scss
+++ b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.scss
@@ -22,6 +22,16 @@
   padding: 1em;
 }
 
+.title {
+  margin-bottom: 1em;
+}
+
+.title--label {
+  font-size: x-large;
+  font-weight: 600;
+  line-height: 1.1;
+}
+
 .loading-page {
   width: 100%;
   height: 100%;
diff --git a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.spec.ts b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.spec.ts
index d7cfc6e..be2e116 100644
--- a/src/app/features/edit/components/edit-portal/statement-edit-portal.component.spec.ts
+++ b/src/app/features/edit/components/edit-portal/statement-edit-portal.component.spec.ts
@@ -16,7 +16,6 @@
 import {RouterTestingModule} from "@angular/router/testing";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
 import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../../../../shared/layout/page-header";
 import {SharedPipesModule} from "../../../../shared/pipes";
 import {ProgressSpinnerModule} from "../../../../shared/progress-spinner";
 import {completeTaskAction, fetchStatementDetailsAction, queryParamsIdSelector, taskSelector} from "../../../../store";
@@ -37,7 +36,6 @@
                 CommonModule,
                 I18nModule,
                 StatementEditRoutingModule,
-                PageHeaderModule,
                 ProgressSpinnerModule,
                 CommentsFormModule,
                 StatementInformationFormModule,
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 e999eea..64d4f56 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
@@ -14,12 +14,20 @@
 import {Component, OnDestroy, OnInit} from "@angular/core";
 import {select, Store} from "@ngrx/store";
 import {Subject} from "rxjs";
-import {switchMap, take, takeUntil} from "rxjs/operators";
+import {map, take, takeUntil} from "rxjs/operators";
 import {IAPIProcessObject} from "../../../../core";
-import {completeTaskAction, fetchStatementDetailsAction, queryParamsIdSelector, queryParamsSelector, taskSelector} from "../../../../store";
+import {
+    completeTaskAction,
+    fetchStatementDetailsAction,
+    queryParamsIdSelector,
+    statementInfoSelector,
+    taskSelector,
+    unclaimAllTasksAction,
+    userNameSelector
+} from "../../../../store";
 
 import {EStatementEditSites} from "../../model";
-import {statementEditHeaderButtonSelector, statementEditSiteSelector} from "../../selectors";
+import {statementEditSiteSelector} from "../../selectors";
 
 @Component({
     selector: "app-statement-edit-portal",
@@ -32,13 +40,13 @@
 
     public site$ = this.store.pipe(select(statementEditSiteSelector));
 
-    public queryParams$ = this.store.pipe(select(queryParamsSelector));
-
     public statementId$ = this.store.pipe(select(queryParamsIdSelector));
 
+    public title$ = this.store.pipe(select(statementInfoSelector)).pipe(map((_) => _?.title));
+
     public task$ = this.store.pipe(select(taskSelector));
 
-    public headerActions$ = this.store.pipe(select(statementEditHeaderButtonSelector));
+    public userName$ = this.store.pipe(select(userNameSelector));
 
     private destroy$ = new Subject();
 
@@ -47,17 +55,24 @@
     }
 
     public ngOnInit() {
-        this.queryParams$.pipe(takeUntil(this.destroy$), switchMap(() => this.statementId$))
+        this.statementId$.pipe(takeUntil(this.destroy$))
             .subscribe((statementId) => {
                 this.store.dispatch(fetchStatementDetailsAction({statementId}));
             });
     }
 
     public ngOnDestroy() {
+        this.unclaimAllTasks();
         this.destroy$.next();
         this.destroy$.complete();
     }
 
+    public async unclaimAllTasks() {
+        const statementId = await this.statementId$.pipe(take(1)).toPromise();
+        const assignee = await this.userName$.pipe(take(1)).toPromise();
+        this.store.dispatch(unclaimAllTasksAction({statementId, assignee}));
+    }
+
     public async completeTask(variables: IAPIProcessObject) {
         const task = await this.task$.pipe(take(1)).toPromise();
         this.store.dispatch(completeTaskAction({
diff --git a/src/app/features/edit/selectors/index.ts b/src/app/features/edit/selectors/index.ts
index 7bf5929..94bb597 100644
--- a/src/app/features/edit/selectors/index.ts
+++ b/src/app/features/edit/selectors/index.ts
@@ -11,5 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-edit-header-button.selector";
 export * from "./statement-edit-site.selector";
diff --git a/src/app/features/edit/selectors/statement-edit-header-button.selector.ts b/src/app/features/edit/selectors/statement-edit-header-button.selector.ts
deleted file mode 100644
index 402926b..0000000
--- a/src/app/features/edit/selectors/statement-edit-header-button.selector.ts
+++ /dev/null
@@ -1,31 +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 {createSelector} from "@ngrx/store";
-import {IPageHeaderAction} from "../../../shared/layout/page-header";
-import {taskSelector} from "../../../store";
-
-export const statementEditHeaderButtonSelector = createSelector(
-    taskSelector,
-    (task): IPageHeaderAction[] => {
-        return [task?.statementId != null ? {
-            name: "core.actions.backToDetails",
-            icon: "arrow_back",
-            routerLink: "/details",
-            queryParams: {id: task?.statementId}
-        } : {
-            name: "core.actions.backToDashboard",
-            icon: "arrow_back",
-            routerLink: "/",
-        }];
-    }
-);
diff --git a/src/app/features/edit/selectors/statement-edit-site.selector.ts b/src/app/features/edit/selectors/statement-edit-site.selector.ts
index 84d5843..59eb015 100644
--- a/src/app/features/edit/selectors/statement-edit-site.selector.ts
+++ b/src/app/features/edit/selectors/statement-edit-site.selector.ts
@@ -13,27 +13,27 @@
 
 import {createSelector} from "@ngrx/store";
 import {EAPIProcessTaskDefinitionKey} from "../../../core";
-import {queryParamsSelector, taskSelector} from "../../../store";
+import {taskSelector} from "../../../store";
 import {EStatementEditSites} from "../model";
 
 export const statementEditSiteSelector = createSelector(
     taskSelector,
-    queryParamsSelector,
-    (task, queryParams): EStatementEditSites => {
+    (task): EStatementEditSites => {
         if (task == null) {
             return EStatementEditSites.LOADING;
         }
 
         switch (task.taskDefinitionKey) {
             case EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA:
-                return queryParams?.negative ?
-                    EStatementEditSites.DRAFT_FOR_NEGATIVE_ANSWER_FORM :
-                    EStatementEditSites.STATEMENT_INFORMATION_FORM;
+                return EStatementEditSites.STATEMENT_INFORMATION_FORM;
+
             case EAPIProcessTaskDefinitionKey.ADD_WORK_FLOW_DATA:
                 return EStatementEditSites.WORKFLOW_DATA_FORM;
+
+            case EAPIProcessTaskDefinitionKey.CREATE_NEGATIVE_RESPONSE:
             case EAPIProcessTaskDefinitionKey.CREATE_DRAFT:
             case EAPIProcessTaskDefinitionKey.ENRICH_DRAFT:
-            case EAPIProcessTaskDefinitionKey.FINALIZE_STATEMENT:
+            case EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE:
                 return EStatementEditSites.STATEMENT_EDITOR_FORM;
         }
 
diff --git a/src/app/features/edit/statement-edit-routing.module.ts b/src/app/features/edit/statement-edit-routing.module.ts
index d3693f6..8ce8f60 100644
--- a/src/app/features/edit/statement-edit-routing.module.ts
+++ b/src/app/features/edit/statement-edit-routing.module.ts
@@ -14,6 +14,7 @@
 import {NgModule} from "@angular/core";
 import {RouterModule, Routes} from "@angular/router";
 import {StatementEditPortalComponent} from "./components/edit-portal";
+import {StatementEditModule} from "./statement-edit.module";
 
 const routes: Routes = [
     {
@@ -25,6 +26,7 @@
 
 @NgModule({
     imports: [
+        StatementEditModule,
         RouterModule.forChild(routes)
     ],
     exports: [
diff --git a/src/app/features/edit/statement-edit.module.ts b/src/app/features/edit/statement-edit.module.ts
index 1d21b7e..80d40af 100644
--- a/src/app/features/edit/statement-edit.module.ts
+++ b/src/app/features/edit/statement-edit.module.ts
@@ -14,7 +14,6 @@
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
 import {TranslateModule} from "@ngx-translate/core";
-import {PageHeaderModule} from "../../shared/layout/page-header";
 import {SharedPipesModule} from "../../shared/pipes";
 import {ProgressSpinnerModule} from "../../shared/progress-spinner";
 import {CommentsFormModule} from "../forms/comments";
@@ -22,16 +21,12 @@
 import {StatementInformationFormModule} from "../forms/statement-information";
 import {WorkflowDataFormModule} from "../forms/workflow-data/workflow-data-form.module";
 import {EditDebugComponent, StatementEditPortalComponent} from "./components";
-import {StatementEditRoutingModule} from "./statement-edit-routing.module";
 
 @NgModule({
     imports: [
         CommonModule,
         TranslateModule,
 
-        StatementEditRoutingModule,
-
-        PageHeaderModule,
         ProgressSpinnerModule,
         CommentsFormModule,
         StatementInformationFormModule,
diff --git a/src/app/features/forms/abstract/abstract-reactive-form-array.component.spec.ts b/src/app/features/forms/abstract/abstract-reactive-form-array.component.spec.ts
new file mode 100644
index 0000000..67a62f2
--- /dev/null
+++ b/src/app/features/forms/abstract/abstract-reactive-form-array.component.spec.ts
@@ -0,0 +1,103 @@
+/********************************************************************************
+ * 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} from "@angular/core";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {FormArray, FormControl} from "@angular/forms";
+import {provideMockStore} from "@ngrx/store/testing";
+import {createFormGroup} from "../../../util/forms";
+import {AbstractReactiveFormArrayComponent} from "./abstract-reactive-form-array.component";
+
+@Component({templateUrl: ""})
+class AbstractReactiveFormArraySpecComponent extends AbstractReactiveFormArrayComponent<string> {
+
+    public appFormArray = new FormArray([
+        new FormControl("A"),
+        new FormControl("B"),
+        new FormControl("C")
+    ]);
+
+    public appFormGroup = createFormGroup<any>({
+        array: this.appFormArray
+    });
+
+    public appFormArrayName = "array";
+
+}
+
+describe("AbstractReactiveFormArrayComponent", () => {
+
+    let component: AbstractReactiveFormArraySpecComponent;
+    let fixture: ComponentFixture<AbstractReactiveFormArraySpecComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [
+                AbstractReactiveFormArraySpecComponent
+            ],
+            providers: [
+                provideMockStore()
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(AbstractReactiveFormArraySpecComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should set and return the current form value", () => {
+        expect(component.getValue()).toEqual(["A", "B", "C"]);
+        component.setValue(["D", "E"]);
+        expect(component.getValue()).toEqual(["D", "E"]);
+    });
+
+    it("should return the form array if present", () => {
+        expect(component.getFormArray()).toBe(component.appFormArray);
+        expect(component.getFormArrayControl(1)).toBe(component.appFormArray.at(1));
+
+        component.appFormGroup = null;
+        expect(component.getFormArray()).not.toBeDefined();
+        expect(component.getFormArrayControl(1)).not.toBeDefined();
+    });
+
+    it("should add controls", () => {
+        component.addControl("X", 1);
+        expect(component.getFormArray().length).toBe(4);
+        expect(component.getValue()).toEqual(["A", "X", "B", "C"]);
+
+        component.addControl("Y");
+        expect(component.getFormArray().length).toBe(5);
+        expect(component.getValue()).toEqual(["A", "X", "B", "C", "Y"]);
+    });
+
+    it("should remain disabled when new controls are added", () => {
+        component.appFormGroup.disable();
+        component.addControl("Z");
+        expect(component.appFormGroup.disabled).toBeTrue();
+    });
+
+    it("should remove controls", () => {
+        component.removeControlAt(1);
+        expect(component.getFormArray().length).toBe(2);
+        expect(component.getValue()).toEqual(["A", "C"]);
+    });
+
+    it("should move controls", () => {
+        const control = component.getFormArrayControl(1);
+        component.moveControl(1, 0);
+        expect(component.getFormArrayControl(0)).toBe(control);
+    });
+
+});
diff --git a/src/app/features/forms/abstract/abstract-reactive-form-array.component.ts b/src/app/features/forms/abstract/abstract-reactive-form-array.component.ts
new file mode 100644
index 0000000..8c53b00
--- /dev/null
+++ b/src/app/features/forms/abstract/abstract-reactive-form-array.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 {Input} from "@angular/core";
+import {FormArray, FormControl, FormGroup} from "@angular/forms";
+import {arrayJoin} from "../../../util/store";
+
+export abstract class AbstractReactiveFormArrayComponent<T> {
+
+    @Input()
+    public appFormGroup: FormGroup;
+
+    @Input()
+    public appFormArrayName: string;
+
+    public getValue(): T[] {
+        return arrayJoin(this.getFormArray().value);
+    }
+
+    public setValue(value: T[]) {
+        this.getFormArray().clear();
+        arrayJoin(value).forEach((_) => {
+            this.addControl(_);
+        });
+    }
+
+    public getFormArray(): FormArray {
+        const result = this.appFormGroup?.get(this.appFormArrayName);
+        return result instanceof FormArray ? result : undefined;
+    }
+
+    public getFormArrayControl(index: number) {
+        return this.appFormGroup?.get([this.appFormArrayName, index]);
+    }
+
+    public addControl(value: T, atIndex?: number) {
+        this.getFormArray().insert(atIndex != null ? atIndex : this.getFormArray().length, this.createControl(value));
+    }
+
+    public moveControl(fromIndex: number, toIndex: number) {
+        const control = this.getFormArrayControl(fromIndex);
+        this.removeControlAt(fromIndex);
+        this.getFormArray().insert(toIndex, control);
+    }
+
+    public removeControlAt(index: number) {
+        this.getFormArray().removeAt(index);
+    }
+
+    public createControl(value: T) {
+        const control = new FormControl(value);
+        if (this.getFormArray().disabled) {
+            control.disable();
+        }
+        return control;
+    }
+
+}
diff --git a/src/app/features/forms/abstract/abstract-reactive-form.component.spec.ts b/src/app/features/forms/abstract/abstract-reactive-form.component.spec.ts
index 4e66572..c4f02ac 100644
--- a/src/app/features/forms/abstract/abstract-reactive-form.component.spec.ts
+++ b/src/app/features/forms/abstract/abstract-reactive-form.component.spec.ts
@@ -13,15 +13,18 @@
 
 import {Component} from "@angular/core";
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {FormControl} from "@angular/forms";
+import {FormArray, FormControl, FormGroup} from "@angular/forms";
 import {provideMockStore} from "@ngrx/store/testing";
 import {createFormGroup} from "../../../util/forms";
 import {AbstractReactiveFormComponent} from "./abstract-reactive-form.component";
 
 @Component({templateUrl: ""})
-class AbstractReactiveFormSpecComponent extends AbstractReactiveFormComponent<{ test: string }> {
+class AbstractReactiveFormSpecComponent extends AbstractReactiveFormComponent<{ field: string, array: number[] }> {
 
-    public appFormGroup = createFormGroup<any>({test: new FormControl("")});
+    public appFormGroup = createFormGroup({
+        field: new FormControl(""),
+        array: new FormArray([])
+    });
 
 }
 
@@ -52,12 +55,42 @@
         expect(() => component.ngOnDestroy()).not.toThrow();
     });
 
-    it("should patch value to form", () => {
-        let value: any = null;
+    it("should get and patch form values", () => {
+        let value = null;
+        const field = "19123";
+        const array = [];
         component.appValueChange.subscribe((_) => value = _);
-        component.patchValue({test: "19123"});
-        expect(component.getValue()).toEqual({test: "19123"});
-        expect(value).toEqual({test: "19123"});
+        component.patchValue({field});
+        expect(component.getValue()).toEqual({field, array});
+        expect(component.getValue("field")).toBe(field);
+        expect(component.getValue("array")).toEqual(array);
+        expect(value).toEqual({field, array});
+
+        component.appFormGroup = new FormGroup({});
+        expect(component.getValue("field")).not.toBeDefined();
+        component.appFormGroup = null;
+        expect(component.getValue("field")).not.toBeDefined();
+    });
+
+    it("should set up form arrays", () => {
+        const array = [1, 9];
+        const formArray = component.appFormGroup.get("array") as FormArray;
+
+        component.disable(true);
+        component.setValueForArray(array, "array");
+        expect(formArray).toBeInstanceOf(FormArray);
+        expect(formArray.length).toBe(array.length);
+        expect(component.getValue("array")).toEqual(array);
+        expect(component.appFormGroup.disabled).toBeTrue();
+
+        component.disable(false);
+        component.setValueForArray(array, "array");
+        expect(component.appFormGroup.disabled).toBeFalse();
+
+        expect(() => component.createControl(null, null)).not.toThrow();
+        expect(() => component.createControl(null, "array")).not.toThrow();
+        component.appFormGroup = null;
+        expect(() => component.createControl(null, null)).not.toThrow();
     });
 
 });
diff --git a/src/app/features/forms/abstract/abstract-reactive-form.component.ts b/src/app/features/forms/abstract/abstract-reactive-form.component.ts
index 912c276..d1ca8e9 100644
--- a/src/app/features/forms/abstract/abstract-reactive-form.component.ts
+++ b/src/app/features/forms/abstract/abstract-reactive-form.component.ts
@@ -12,9 +12,10 @@
  ********************************************************************************/
 
 import {Input, OnDestroy, Output} from "@angular/core";
-import {FormGroup} from "@angular/forms";
+import {FormArray, FormControl, FormGroup} from "@angular/forms";
 import {defer, merge, of, Subject} from "rxjs";
 import {map} from "rxjs/operators";
+import {arrayJoin} from "../../../util/store";
 
 export abstract class AbstractReactiveFormComponent<T extends object> implements OnDestroy {
 
@@ -35,14 +36,39 @@
         this.destroy$.complete();
     }
 
-    @Input("appValue")
+    public getValue(): T;
+    public getValue<TKey extends keyof T & string>(key: TKey): T[TKey];
+    public getValue(key?: string) {
+        return (typeof key !== "string" ? this.appFormGroup : this.appFormGroup?.get(key))?.value;
+    }
+
     public patchValue(value: Partial<T>) {
         this.appFormGroup.patchValue(value);
     }
 
-    public getValue(): T {
-        return this.appFormGroup.value;
+    public setValueForArray<TKey extends keyof T & string>(values: T[TKey] & any[], key: TKey) {
+        const array = this.appFormGroup.get(key) as FormArray;
+        array.clear();
+        arrayJoin(values)
+            .forEach((_) => array.push(this.createControl(_, key)));
     }
 
+    public createControl(value, path?: string | Array<string | number>) {
+        const control = new FormControl(value);
+        const group = path == null ? this.appFormGroup : this.appFormGroup.get(path);
+        if (group?.disabled) {
+            control.disable();
+        }
+        return control;
+    }
+
+    public disable(disable: boolean) {
+        if (disable && this.appFormGroup.enabled) {
+            this.appFormGroup.disable();
+        }
+        if (!disable && this.appFormGroup.disabled) {
+            this.appFormGroup.enable();
+        }
+    }
 
 }
diff --git a/src/app/features/forms/abstract/index.ts b/src/app/features/forms/abstract/index.ts
index e4c0865..af050e1 100644
--- a/src/app/features/forms/abstract/index.ts
+++ b/src/app/features/forms/abstract/index.ts
@@ -12,3 +12,4 @@
  ********************************************************************************/
 
 export * from "./abstract-reactive-form.component";
+export * from "./abstract-reactive-form-array.component";
diff --git a/src/app/features/forms/attachments/attachments-form.module.ts b/src/app/features/forms/attachments/attachments-form.module.ts
index 5aa1c8f..523a67b 100644
--- a/src/app/features/forms/attachments/attachments-form.module.ts
+++ b/src/app/features/forms/attachments/attachments-form.module.ts
@@ -16,26 +16,33 @@
 import {ReactiveFormsModule} from "@angular/forms";
 import {MatIconModule} from "@angular/material/icon";
 import {TranslateModule} from "@ngx-translate/core";
-import {FileDropModule} from "../../../shared/controls/file-drop";
-import {FileSelectModule} from "../../../shared/controls/file-select";
 import {CollapsibleModule} from "../../../shared/layout/collapsible";
-import {AttachmentsFormGroupComponent} from "./components";
+import {FileDropModule} from "../../../shared/layout/file-drop";
+import {SharedPipesModule} from "../../../shared/pipes";
+import {AttachmentControlComponent, AttachmentListFormComponent, AttachmentsFormGroupComponent} from "./components";
+import {AttachmentFileDropFormComponent} from "./components/attachment-file-drop-form";
 
 @NgModule({
-    declarations: [
-        AttachmentsFormGroupComponent
-    ],
     imports: [
         CommonModule,
         ReactiveFormsModule,
         MatIconModule,
         TranslateModule,
         CollapsibleModule,
-        FileSelectModule,
-        FileDropModule
+        FileDropModule,
+        SharedPipesModule
+    ],
+    declarations: [
+        AttachmentsFormGroupComponent,
+        AttachmentControlComponent,
+        AttachmentFileDropFormComponent,
+        AttachmentListFormComponent
     ],
     exports: [
-        AttachmentsFormGroupComponent
+        AttachmentsFormGroupComponent,
+        AttachmentControlComponent,
+        AttachmentFileDropFormComponent,
+        AttachmentListFormComponent
     ]
 })
 export class AttachmentsFormModule {
diff --git a/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.html b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.html
new file mode 100644
index 0000000..e3b4dab
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.html
@@ -0,0 +1,73 @@
+<!-------------------------------------------------------------------------------
+ * 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
+ -------------------------------------------------------------------------------->
+
+<input #inputElement
+       (input)="toggleSelection(!appValue?.isSelected)"
+       (keydown.enter)="inputElement.click()"
+       *ngIf="appIsSelectable"
+       [checked]="appValue?.isSelected"
+       [id]="appId"
+       [disabled]="appDisabled"
+       class="openk-checkbox cursor-pointer"
+       type="checkbox">
+
+<button (click)="appCancel.emit()"
+        (pointerdown)="$event.preventDefault();"
+        *ngIf="appIsCancelable"
+        [disabled]="appDisabled"
+        class="openk-button openk-button-rounded"
+        type="button">
+  <mat-icon>clear</mat-icon>
+</button>
+
+
+<span class="name">
+  <label [class.cursor-pointer]="appIsSelectable && !appDisabled"
+         [class.name--label---line-through]="appIsSelectable && !appValue?.isSelected"
+         [for]="appIsSelectable ? appId : null"
+         class="name--label">
+    {{appValue?.name}}
+  </label>
+
+  <button (click)="appDownloadAttachment.emit(appValue?.id)"
+          *ngIf="appIsDownloadable"
+          [disabled]="appDisabled"
+          class="openk-button openk-button-rounded"
+          type="button">
+    <mat-icon>get_app</mat-icon>
+  </button>
+
+  <label [class.cursor-pointer]="appIsSelectable && !appDisabled"
+         [for]="appIsSelectable ? appId : null"
+         class="name--label name--label---fill">
+  </label>
+</span>
+
+<ng-container *ngFor="let tag of appTagList">
+  <button (click)="toggleTag(tag?.id, appValue?.tagIds?.indexOf(tag?.id) > - 1)"
+          *ngIf="!appHideNotUsedTags || appValue?.tagIds?.length > 0 && appValue.tagIds.indexOf(tag?.id) > -1"
+          [class.openk-info]="appValue?.tagIds?.length > 0 && appValue.tagIds.indexOf(tag?.id) > -1"
+          [disabled]="appDisabled"
+          class="openk-button openk-chip">
+    {{tag?.label}}
+  </button>
+</ng-container>
+
+<button (click)="appHideNotUsedTags = !appHideNotUsedTags"
+        *ngIf="appTagList?.length > 0"
+        [class.tag-toggle---plus]="appHideNotUsedTags"
+        [disabled]="appDisabled"
+        class="openk-button openk-button-rounded tag-toggle"
+        type="button">
+  <mat-icon>local_offer</mat-icon>
+</button>
diff --git a/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.scss b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.scss
new file mode 100644
index 0000000..f3acd74
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.scss
@@ -0,0 +1,89 @@
+/********************************************************************************
+ * 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 "openk.styles";
+
+:host {
+  display: flex;
+  flex-flow: row wrap;
+  align-items: center;
+  justify-content: flex-end;
+  line-height: 1.25;
+  transition: background-color ease-in-out 100ms;
+  padding: 0 0.2em;
+
+  &:hover {
+    background-color: $openk-background-highlight;
+  }
+}
+
+.name {
+  flex: 1 1 auto;
+  display: inline-flex;
+  align-items: center;
+  background-color: inherit;
+}
+
+.name--label {
+  display: inline-block;
+  padding: 0.2em 0.4em 0.2em 0.25em;
+  min-height: 1em;
+  flex-shrink: 1 !important;
+}
+
+.name--label---line-through {
+  text-decoration: line-through;
+}
+
+.name--label---fill {
+  flex: 1;
+}
+
+.tag-toggle {
+  --icon-scale-factor: 0.6;
+  position: relative;
+}
+
+.tag-toggle---plus::after {
+  content: "+";
+  position: absolute;
+  margin: auto;
+  width: 100%;
+  height: 100%;
+  display: inline-flex;
+  justify-content: center;
+  align-items: center;
+  color: get-color($openk-info-palette, 500, contrast);
+}
+
+.openk-button-rounded {
+  font-size: 0.66em;
+  border: 0;
+  color: get-color($openk-info-palette);
+
+  &: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 {
+  margin: 0.1em 0.25em 0.1em 0;
+}
+
diff --git a/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.spec.ts b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.spec.ts
new file mode 100644
index 0000000..d307fb0
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.spec.ts
@@ -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 {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {I18nModule} from "../../../../../core";
+import {AttachmentsFormModule} from "../../attachments-form.module";
+import {AttachmentControlComponent} from "./attachment-control.component";
+
+describe("AttachmentControlComponent", () => {
+    let component: AttachmentControlComponent;
+    let fixture: ComponentFixture<AttachmentControlComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                I18nModule,
+                AttachmentsFormModule
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(AttachmentControlComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should toggle isSelected", () => {
+        component.toggleSelection(true);
+        expect(component.appValue.isSelected).toBeTrue();
+        component.toggleSelection(false);
+        expect(component.appValue.isSelected).toBeFalse();
+        component.appDisabled = true;
+        component.toggleSelection(true);
+        expect(component.appValue.isSelected).toBeFalse();
+    });
+
+    it("should toggle selected tags", () => {
+        component.toggleTag("C", false);
+        component.toggleTag("A", false);
+        component.toggleTag("B", false);
+        expect(component.appValue.tagIds).toEqual(["A", "B", "C"]);
+
+        component.toggleTag("B", true);
+        expect(component.appValue.tagIds).toEqual(["A", "C"]);
+
+        component.appDisabled = true;
+        component.toggleTag("A", true);
+        expect(component.appValue.tagIds).toEqual(["A", "C"]);
+    });
+
+});
diff --git a/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.stories.ts b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.stories.ts
new file mode 100644
index 0000000..9e0c347
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.stories.ts
@@ -0,0 +1,43 @@
+/********************************************************************************
+ * 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 {boolean, withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {I18nModule} from "../../../../../core/i18n";
+import {createAttachmentTagList} from "../../../../../test";
+import {AttachmentsFormModule} from "../../attachments-form.module";
+
+storiesOf("Features / Forms / Attachments", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({
+        imports: [
+            I18nModule,
+            AttachmentsFormModule
+        ]
+    }))
+    .add("AttachmentControlComponent", () => ({
+        template: `
+            <app-attachment-control style="margin: 1em; box-sizing: border-box"
+                [appValue]="appValue"
+                [appTagList]="appTagList"
+                [appIsCancelable]="forManualFileUpload()"
+                [appIsSelectable]="!forManualFileUpload()"
+                [appIsDownloadable]="!forManualFileUpload()">
+            </app-attachment-control>
+        `,
+        props: {
+            appValue: {name: "Anhang.pdf", tagIds: []},
+            appTagList: createAttachmentTagList(),
+            forManualFileUpload: () => boolean("forManualFileUpload", false)
+        }
+    }));
diff --git a/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.ts b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.ts
new file mode 100644
index 0000000..75b687e
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-control/attachment-control.component.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 {Component, EventEmitter, forwardRef, Input, Output} from "@angular/core";
+import {NG_VALUE_ACCESSOR} from "@angular/forms";
+import {IAPIAttachmentTag} from "../../../../../core";
+import {AbstractControlValueAccessorComponent} from "../../../../../shared/controls/common";
+import {IAttachmentControlValue} from "../../../../../store";
+import {arrayJoin, filterDistinctValues} from "../../../../../util/store";
+
+@Component({
+    selector: "app-attachment-control",
+    templateUrl: "./attachment-control.component.html",
+    styleUrls: ["./attachment-control.component.scss"],
+    providers: [
+        {
+            provide: NG_VALUE_ACCESSOR,
+            useExisting: forwardRef(() => AttachmentControlComponent),
+            multi: true
+        }
+    ]
+})
+export class AttachmentControlComponent extends AbstractControlValueAccessorComponent<IAttachmentControlValue> {
+
+    private static id = 0;
+
+    @Input()
+    public appId = `AttachmentControlComponent-${AttachmentControlComponent.id++}`;
+
+    @Input()
+    public appTagList: IAPIAttachmentTag[];
+
+    @Input()
+    public appIsSelectable: boolean;
+
+    @Input()
+    public appIsCancelable: boolean;
+
+    @Input()
+    public appIsDownloadable: boolean;
+
+    @Output()
+    public appCancel = new EventEmitter<void>();
+
+    @Output()
+    public appDownloadAttachment = new EventEmitter<number>();
+
+    @Input()
+    public appHideNotUsedTags: boolean;
+
+    public toggleSelection(isSelected: boolean) {
+        if (this.appDisabled) {
+            return;
+        }
+        this.writeValue({...this.appValue, isSelected}, true);
+    }
+
+    public toggleTag(tagId: string, isRemoved: boolean) {
+        if (this.appDisabled) {
+            return;
+        }
+
+        const tagIds = isRemoved ?
+            this.getSelectedTagIds().filter((_) => _ !== tagId) :
+            filterDistinctValues(arrayJoin(this.getSelectedTagIds(), [tagId])).sort();
+
+        this.writeValue({...this.appValue, tagIds}, true);
+    }
+
+    public getSelectedTagIds(): string[] {
+        return arrayJoin(this.appValue?.tagIds);
+    }
+
+}
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/forms/attachments/components/attachment-control/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/forms/attachments/components/attachment-control/index.ts
index aeb78c3..78df3e5 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/forms/attachments/components/attachment-control/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./attachment-control.component";
diff --git a/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.html b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.html
new file mode 100644
index 0000000..004e315
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.html
@@ -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
+ -------------------------------------------------------------------------------->
+
+<ng-container [formGroup]="appFormGroup">
+
+  <label>
+    {{appTitle}}
+  </label>
+
+  <app-file-drop
+    #fileDropComponent
+    (appFileDrop)="addControlForFiles($event)"
+    [appDisabled]="appFormGroup.disabled"
+    [appPlaceholder]="'attachments.fileDropPlaceholder' | translate"
+    [formArrayName]="appFormArrayName"
+    class="file-drop">
+
+    <app-attachment-control
+      (appCancel)="removeControlAt(i)"
+      *ngFor="let control of (appFormGroup | getFormArray : appFormArrayName)?.controls; let i = index;"
+      [appIsCancelable]="true"
+      [appTagList]="appTagList"
+      [formControlName]="i"
+      class="file-drop--control">
+
+    </app-attachment-control>
+
+  </app-file-drop>
+
+  <button (click)="fileDropComponent.openDialog()"
+          [disabled]="fileDropComponent.appDisabled"
+          class="openk-button attachments--select-file-button">
+    <mat-icon>attach_file</mat-icon>
+    {{"attachments.selectFile" | translate }}
+  </button>
+
+</ng-container>
+
diff --git a/src/app/shared/layout/page-header/component/page-header.component.scss b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.scss
similarity index 67%
copy from src/app/shared/layout/page-header/component/page-header.component.scss
copy to src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.scss
index 7ed6bcc..0383056 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.scss
+++ b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.scss
@@ -11,26 +11,32 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+@import "openk.styles";
+
 :host {
+  display: flex;
+  flex-flow: column;
+}
+
+.file-drop {
+  flex: 1 1 100%;
+  margin: 0.25em 0 0.5em 0;
+}
+
+.file-drop--control {
+  font-size: small;
   width: 100%;
-  display: flex;
-  flex-flow: row wrap;
-  align-items: center;
-}
+  box-sizing: border-box;
 
-.page-header-title {
-  margin-right: auto;
-  font-size: x-large;
-  font-weight: 600;
-}
-
-.page-header-actions {
-  display: flex;
-  flex-flow: row wrap;
-  margin-left: auto;
-  justify-content: flex-end;
-
-  .openk-button {
-    margin: 0.25em;
+  &:first-of-type {
+    padding-top: 0.05em;
   }
+
+  &:last-of-type {
+    padding-bottom: 0.05em;
+  }
+}
+
+.attachments--select-file-button {
+  margin-left: auto;
 }
diff --git a/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.spec.ts b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.spec.ts
new file mode 100644
index 0000000..1f81788
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.spec.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 {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {I18nModule} from "../../../../../core/i18n";
+import {AttachmentsFormModule} from "../../attachments-form.module";
+import {AttachmentFileDropFormComponent} from "./attachment-file-drop-form.component";
+
+describe("AttachmentFileDropFormComponent", () => {
+    let component: AttachmentFileDropFormComponent;
+    let fixture: ComponentFixture<AttachmentFileDropFormComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [
+                I18nModule,
+                AttachmentsFormModule
+            ]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(AttachmentFileDropFormComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should add user controls for dropped files", async () => {
+        const files = [
+            new File(["A"], "FileA.txt"),
+            new File(["B"], "FileB.txt"),
+            new File(["C"], "FileC.txt")
+        ];
+        const tagIds = ["X", "Y"];
+
+        component.appAutoTagIds = tagIds;
+        component.addControlForFiles(files);
+
+        fixture.detectChanges();
+        await fixture.whenStable();
+
+        expect(component.getFormArray().length).toBe(files.length);
+
+        files.forEach((_, i) => {
+            expect(component.getValue()[i]).toEqual({name: _.name, file: _, tagIds});
+        });
+    });
+
+});
diff --git a/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.stories.ts b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.stories.ts
new file mode 100644
index 0000000..0bf9efc
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.stories.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 {text, withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {I18nModule} from "../../../../../core/i18n";
+import {createAttachmentTagList} from "../../../../../test";
+import {AttachmentsFormModule} from "../../attachments-form.module";
+
+storiesOf("Features / Forms / Attachments", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({
+        imports: [
+            I18nModule,
+            AttachmentsFormModule
+        ]
+    }))
+    .add("AttachmentFileDropFormComponent", () => ({
+        template: `
+            <app-attachment-file-drop-form style="margin: 1em; box-sizing: border-box"
+                [appTitle]="appTitle()"
+                [appTagList]="appTagList"
+            >
+            </app-attachment-file-drop-form>
+        `,
+        props: {
+            appTitle: () => text("appTitle", "Anhänge übernehmen"),
+            appTagList: createAttachmentTagList()
+        }
+    }));
diff --git a/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.ts b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.ts
new file mode 100644
index 0000000..50aba7b
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-file-drop-form/attachment-file-drop-form.component.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 {Component, Input} from "@angular/core";
+import {IAPIAttachmentTag} from "../../../../../core/api/attachments";
+import {createAttachmentForm, IAttachmentControlValue, IAttachmentFormValue} from "../../../../../store";
+import {arrayJoin} from "../../../../../util/store";
+import {AbstractReactiveFormArrayComponent} from "../../../abstract";
+
+@Component({
+    selector: "app-attachment-file-drop-form",
+    templateUrl: "./attachment-file-drop-form.component.html",
+    styleUrls: ["./attachment-file-drop-form.component.scss"]
+})
+export class AttachmentFileDropFormComponent extends AbstractReactiveFormArrayComponent<IAttachmentControlValue> {
+
+    @Input()
+    public appTitle: string;
+
+    @Input()
+    public appAutoTagIds: string[];
+
+    @Input()
+    public appTagList: IAPIAttachmentTag[];
+
+    @Input()
+    public appFormGroup = createAttachmentForm();
+
+    @Input()
+    public appFormArrayName: keyof IAttachmentFormValue = "add";
+
+    public addControlForFiles(files: File[]) {
+        arrayJoin(files).forEach((file) => {
+            this.addControl({
+                name: file.name,
+                file,
+                tagIds: arrayJoin(this.appAutoTagIds)
+            });
+        });
+    }
+
+}
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/forms/attachments/components/attachment-file-drop-form/index.ts
similarity index 90%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/forms/attachments/components/attachment-file-drop-form/index.ts
index aeb78c3..b390181 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/forms/attachments/components/attachment-file-drop-form/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./attachment-file-drop-form.component";
diff --git a/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.html b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.html
new file mode 100644
index 0000000..c2676f6
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.html
@@ -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
+ -------------------------------------------------------------------------------->
+
+<ng-container [formGroup]="appFormGroup">
+
+  <label>
+    {{appTitle}}
+  </label>
+
+  <div [formArrayName]="appFormArrayName" class="attachment-list">
+    <app-attachment-control
+      (appDownloadAttachment)="appDownloadAttachment.emit($event)"
+      *ngFor="let control of (appFormGroup | getFormArray : appFormArrayName)?.controls; let i = index;"
+      [appHideNotUsedTags]="true"
+      [appIsDownloadable]="true"
+      [appIsSelectable]="true"
+      [appTagList]="appTagList"
+      [formControlName]="i"
+      class="attachment-list--control">
+    </app-attachment-control>
+  </div>
+
+</ng-container>
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.scss
similarity index 80%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.scss
index 563859b..392019f 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.scss
@@ -11,9 +11,17 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
+:host {
+  display: flex;
+  flex-flow: column;
+}
+
+.attachment-list {
+  flex: 1 1 100%;
+  margin: 0.25em 0 0.5em 0;
+}
+
+.attachment-list--control {
   width: 100%;
   box-sizing: border-box;
-  min-height: 2.5em;
 }
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.spec.ts
similarity index 63%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.spec.ts
index 20e1e36..2181082 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.spec.ts
@@ -12,23 +12,25 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {I18nModule} from "../../../../../core/i18n";
+import {AttachmentsFormModule} from "../../attachments-form.module";
+import {AttachmentListFormComponent} from "./attachment-list-form.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("AttachmentListFormComponent", () => {
+    let component: AttachmentListFormComponent;
+    let fixture: ComponentFixture<AttachmentListFormComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [
+                I18nModule,
+                AttachmentsFormModule
+            ]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(AttachmentListFormComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
@@ -36,4 +38,5 @@
     it("should create", () => {
         expect(component).toBeTruthy();
     });
+
 });
diff --git a/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.stories.ts b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.stories.ts
new file mode 100644
index 0000000..e751da2
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.stories.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 {FormArray, FormControl} from "@angular/forms";
+import {text, withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {I18nModule} from "../../../../../core";
+import {IAttachmentControlValue, IAttachmentFormValue} from "../../../../../store";
+import {createAttachmentTagList} from "../../../../../test";
+import {createFormGroup} from "../../../../../util/forms";
+import {AttachmentsFormModule} from "../../attachments-form.module";
+
+function createAttachment(name: string, id: number): IAttachmentControlValue {
+    return {
+        name,
+        id,
+        tagIds: ["" + Math.floor(5 * Math.random())],
+        isSelected: true
+    };
+}
+
+const appFormGroup = createFormGroup<IAttachmentFormValue>({
+    add: new FormArray([]),
+    edit: new FormArray([
+        "Übersicht.pdf", "Anhang.pdf", "Bauplan.pdf"
+    ].map((_, i) => new FormControl(createAttachment(_, i))))
+});
+
+storiesOf("Features / Forms / Attachments", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({
+        imports: [
+            I18nModule,
+            AttachmentsFormModule
+        ]
+    }))
+    .add("AttachmentListFormComponent", () => ({
+        template: `
+            <app-attachment-list-form style="margin: 1em; box-sizing: border-box"
+                [appFormGroup]="appFormGroup"
+                [appTagList]="appTagList"
+                [appTitle]="appTitle()"
+            >
+            </app-attachment-list-form>
+        `,
+        props: {
+            appTitle: () => text("appTitle", "Anhänge übernehmen"),
+            appFormGroup,
+            appTagList: createAttachmentTagList()
+        }
+    }));
diff --git a/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.ts b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.ts
new file mode 100644
index 0000000..65a0d1b
--- /dev/null
+++ b/src/app/features/forms/attachments/components/attachment-list-form/attachment-list-form.component.ts
@@ -0,0 +1,43 @@
+/********************************************************************************
+ * 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 {IAPIAttachmentTag} from "../../../../../core";
+import {createAttachmentForm, IAttachmentControlValue, IAttachmentFormValue} from "../../../../../store";
+import {AbstractReactiveFormArrayComponent} from "../../../abstract";
+
+@Component({
+    selector: "app-attachment-list-form",
+    templateUrl: "./attachment-list-form.component.html",
+    styleUrls: ["./attachment-list-form.component.scss"]
+})
+export class AttachmentListFormComponent extends AbstractReactiveFormArrayComponent<IAttachmentControlValue> {
+
+    @Input()
+    public appFormGroup = createAttachmentForm();
+
+    @Input()
+    public appTagList: IAPIAttachmentTag[];
+
+    @Input()
+    public appFormArrayName: keyof IAttachmentFormValue = "edit";
+
+    @Input()
+    public appTitle: string;
+
+    @Output()
+    public appDownloadAttachment = new EventEmitter<number>();
+
+}
+
+
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/forms/attachments/components/attachment-list-form/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/forms/attachments/components/attachment-list-form/index.ts
index aeb78c3..7913eba 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/forms/attachments/components/attachment-list-form/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./attachment-list-form.component";
diff --git a/src/app/features/forms/attachments/components/attachments-form-group.component.html b/src/app/features/forms/attachments/components/attachments-form-group.component.html
index 4a57669..d53947f 100644
--- a/src/app/features/forms/attachments/components/attachments-form-group.component.html
+++ b/src/app/features/forms/attachments/components/attachments-form-group.component.html
@@ -13,41 +13,23 @@
 
 <div [formGroup]="appFormGroup" class="attachments">
 
-  <div *ngIf="(attachments$ | async)?.length > 0"
-       class="attachments--container">
+  <app-attachment-list-form *ngIf="(attachments$ | async)?.length > 0"
+                            (appDownloadAttachment)="downloadAttachment($event)"
+                            [appFormArrayName]="'edit'"
+                            [appFormGroup]="appFormGroup"
+                            [appTagList]="appWithoutTagControl ? null : (tagList$ | async)"
+                            [appTitle]="'attachments.edit' | translate"
+                            class="attachments--container">
+  </app-attachment-list-form>
 
-    <label>
-      {{"attachments.edit" | translate }}
-    </label>
+  <app-attachment-file-drop-form
+    [appAutoTagIds]="appAutoTagIds"
+    [appFormArrayName]="'add'"
+    [appFormGroup]="appFormGroup"
+    [appTagList]="appWithoutTagControl ? null : (tagList$ | async)"
+    [appTitle]="'attachments.add' | translate"
+    class="attachments--container">
 
-    <app-file-select
-      (appOpenAttachment)="openAttachment($event)"
-      [appAttachments]="attachments$ | async"
-      [formControlName]="'edit'">
-
-    </app-file-select>
-  </div>
-
-  <div class="attachments--container">
-
-    <label>
-      {{"attachments.add" | translate }}
-    </label>
-
-    <app-file-drop
-      #fileDropComponent
-      [appAutoTagIds]="appAutoTagIds"
-      [formControlName]="'add'"
-      class="attachments--container--control">
-    </app-file-drop>
-
-    <button (click)="fileDropComponent.openDialog()"
-            [disabled]="fileDropComponent.appDisabled"
-            class="openk-button attachments--select-file-button">
-      <mat-icon>attach_file</mat-icon>
-      {{"attachments.selectFile" | translate }}
-    </button>
-
-  </div>
+  </app-attachment-file-drop-form>
 
 </div>
diff --git a/src/app/features/forms/attachments/components/attachments-form-group.component.scss b/src/app/features/forms/attachments/components/attachments-form-group.component.scss
index 55bd916..800c806 100644
--- a/src/app/features/forms/attachments/components/attachments-form-group.component.scss
+++ b/src/app/features/forms/attachments/components/attachments-form-group.component.scss
@@ -11,6 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+$break-point: 16em;
+
 :host {
   display: block;
   width: 100%;
@@ -22,22 +24,12 @@
   min-height: 15em;
   padding: 0.5em;
   box-sizing: border-box;
-  max-height: 25em;
   overflow: auto;
 }
 
 .attachments--container {
-  flex: 1 1 calc(50% - 2em);
+  flex: 1 1 max(calc(50% - 2em), #{$break-point});
   display: flex;
   flex-flow: column;
   margin: 0.5em;
 }
-
-.attachments--container--control {
-  flex: 1 1 100%;
-  margin: 0.25em 0 0.5em 0;
-}
-
-.attachments--select-file-button {
-  margin-left: auto;
-}
diff --git a/src/app/features/forms/attachments/components/attachments-form-group.component.spec.ts b/src/app/features/forms/attachments/components/attachments-form-group.component.spec.ts
index 4205275..c856dfc 100644
--- a/src/app/features/forms/attachments/components/attachments-form-group.component.spec.ts
+++ b/src/app/features/forms/attachments/components/attachments-form-group.component.spec.ts
@@ -13,12 +13,8 @@
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {clearFileCacheAction} from "../../../../store/attachments/actions";
-import {IAttachmentWithTags} from "../../../../store/attachments/model";
-import {getStatementFileCacheSelector} from "../../../../store/attachments/selectors";
-import {openAttachmentAction} from "../../../../store/root/actions";
-import {queryParamsIdSelector} from "../../../../store/root/selectors";
+import {I18nModule} from "../../../../core";
+import {clearAttachmentCacheAction, queryParamsIdSelector, startAttachmentDownloadAction} from "../../../../store";
 import {AttachmentsFormModule} from "../attachments-form.module";
 import {AttachmentsFormGroupComponent} from "./attachments-form-group.component";
 
@@ -48,6 +44,7 @@
                 })
             ]
         }).compileComponents();
+
     }));
 
     beforeEach(() => {
@@ -62,54 +59,17 @@
     });
 
     it("should clear file cache on destruction", async () => {
-        const dispatchSpy = spyOn(storeMock, "dispatch");
+        const dispatchSpy = spyOn(storeMock, "dispatch").and.callThrough();
         component.ngOnDestroy();
         await fixture.whenStable();
-        expect(dispatchSpy).toHaveBeenCalledWith(clearFileCacheAction({statementId}));
+        expect(dispatchSpy).toHaveBeenCalledWith(clearAttachmentCacheAction({statementId}));
     });
 
-    it("should open attachments", async () => {
+    it("should download attachments", async () => {
         const dispatchSpy = spyOn(storeMock, "dispatch");
         const attachmentId = 1919;
-        await component.openAttachment(attachmentId);
-        expect(dispatchSpy).toHaveBeenCalledWith(openAttachmentAction({statementId, attachmentId}));
-    });
-
-    // Override selector doesn't work
-    it("should add files via the file cache selector", () => {
-        const file: File = new File([], "test.pdf");
-        const files: IAttachmentWithTags<File>[] = [
-            {
-                attachment: file,
-                tags: []
-            }
-        ];
-        component.patchValue({add: files});
-        storeMock.overrideSelector(getStatementFileCacheSelector, files);
-        storeMock.refreshState();
-        expect(component.getValue().add).toBe(files);
-    });
-
-    it("should remove selected attachments from form value if not available anymore", () => {
-        const removeAttachments: IAttachmentWithTags<number>[] = [
-            {
-                attachment: 19,
-                tags: [],
-                remove: true
-            },
-            {
-                attachment: 20,
-                tags: [],
-                remove: true
-            }
-        ];
-        component.patchValue({
-            edit: removeAttachments
-        });
-
-        // storeMock.overrideSelector(getStatementAttachmentsSelector, [createAttachmentModelMock(19, "1")]);
-        // storeMock.refreshState();
-        // expect(component.getValue().edit).toEqual([removeAttachments[0]]);
+        await component.downloadAttachment(attachmentId);
+        expect(dispatchSpy).toHaveBeenCalledWith(startAttachmentDownloadAction({statementId, attachmentId}));
     });
 
 });
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 85b91ba..50db04b 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
@@ -11,20 +11,23 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {Component, Input, OnDestroy, OnInit} from "@angular/core";
-import {FormControl} from "@angular/forms";
+import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from "@angular/core";
 import {select, Store} from "@ngrx/store";
-import {defer} from "rxjs";
-import {take, takeUntil} from "rxjs/operators";
+import {BehaviorSubject} from "rxjs";
+import {delay, switchMap, take, takeUntil} from "rxjs/operators";
+import {AUTO_SELECTED_TAGS} from "../../../../core/api/attachments";
 import {
-    clearFileCacheAction,
-    getStatementAttachmentsSelector,
-    getStatementFileCacheSelector,
+    clearAttachmentCacheAction,
+    createAttachmentForm,
+    fetchAttachmentTagsAction,
+    getAttachmentControlValueSelector,
+    getFilteredAttachmentTagsSelector,
+    getStatementAttachmentCacheSelector,
+    IAttachmentControlValue,
     IAttachmentFormValue,
-    openAttachmentAction,
-    queryParamsIdSelector
+    queryParamsIdSelector,
+    startAttachmentDownloadAction
 } from "../../../../store";
-import {arrayJoin, createFormGroup} from "../../../../util";
 import {AbstractReactiveFormComponent} from "../../abstract";
 
 @Component({
@@ -32,7 +35,9 @@
     templateUrl: "./attachments-form-group.component.html",
     styleUrls: ["./attachments-form-group.component.scss"]
 })
-export class AttachmentsFormGroupComponent extends AbstractReactiveFormComponent<IAttachmentFormValue> implements OnInit, OnDestroy {
+export class AttachmentsFormGroupComponent
+    extends AbstractReactiveFormComponent<IAttachmentFormValue>
+    implements OnInit, OnChanges, OnDestroy {
 
     @Input()
     public appAutoTagIds: string[];
@@ -50,68 +55,59 @@
     public appCollapsed: boolean;
 
     @Input()
-    public appFormGroup = createFormGroup<IAttachmentFormValue>({
-        add: new FormControl([]),
-        edit: new FormControl([])
-    });
+    public appFormGroup = createAttachmentForm();
 
-    @Input()
-    public appTitle: string;
+    public attachments: IAttachmentControlValue[] = [];
 
     public statementId$ = this.store.pipe(select(queryParamsIdSelector));
 
-    public attachments$ = defer(() => this.store.pipe(
-        select(getStatementAttachmentsSelector, {
-            restrictedTagIds: this.appRestrictedTagIds,
-            forbiddenTagIds: this.appForbiddenTagIds
-        })
-    ));
+    public attachments$ = this.store.pipe(select(getAttachmentControlValueSelector, {}));
 
-    public fileCache$ = this.store.pipe(select(getStatementFileCacheSelector));
+    public fileCache$ = this.store.pipe(select(getStatementAttachmentCacheSelector));
+
+    public tagList$ = this.store.pipe(select(getFilteredAttachmentTagsSelector, {without: AUTO_SELECTED_TAGS}));
+
+    private attachmentProps$ = new BehaviorSubject<{ restrictedTagIds: string[], forbiddenTagIds: string[] }>({
+        restrictedTagIds: [],
+        forbiddenTagIds: []
+    });
 
     public constructor(public store: Store) {
         super();
     }
 
-    public async ngOnInit() {
-        // This await is required to avoid the ExpressionChangedAfterItHasBeenCheckedError:
-        await Promise.resolve();
-        this.filterAttachmentsInCurrentValue();
-        this.updateFiles();
+    public ngOnInit() {
+        this.attachments$ = this.attachmentProps$.pipe(
+            switchMap((props) => this.store.pipe(
+                select(getAttachmentControlValueSelector, props)
+            ))
+        );
+        this.attachments$.pipe(delay(0), takeUntil(this.destroy$))
+            .subscribe((values) => this.setValueForArray(values, "edit"));
+        this.fileCache$.pipe(delay(0), takeUntil(this.destroy$))
+            .subscribe((values) => this.setValueForArray(values, "add"));
+        this.store.dispatch(fetchAttachmentTagsAction());
+    }
+
+    public ngOnChanges(changes: SimpleChanges) {
+        const keys: Array<keyof AttachmentsFormGroupComponent> = ["appRestrictedTagIds", "appForbiddenTagIds"];
+        if (keys.some((_) => changes[_] != null)) {
+            this.attachmentProps$.next({
+                restrictedTagIds: this.appRestrictedTagIds,
+                forbiddenTagIds: this.appForbiddenTagIds
+            });
+        }
     }
 
     public ngOnDestroy() {
-        this.clearFileCache();
+        this.statementId$.pipe(take(1))
+            .subscribe((statementId) => this.store.dispatch(clearAttachmentCacheAction({statementId})));
         super.ngOnDestroy();
     }
 
-    public async openAttachment(attachmentId: number) {
+    public async downloadAttachment(attachmentId: number) {
         const statementId = await this.statementId$.pipe(take(1)).toPromise();
-        this.store.dispatch(openAttachmentAction({statementId, attachmentId}));
-    }
-
-    private clearFileCache() {
-        this.statementId$.pipe(take(1))
-            .subscribe((statementId) => this.store.dispatch(clearFileCacheAction({statementId})));
-    }
-
-    private filterAttachmentsInCurrentValue() {
-        this.attachments$.pipe(takeUntil(this.destroy$)).subscribe((attachments) => {
-            const value = this.getValue();
-            this.patchValue({
-                ...value,
-                edit: arrayJoin(value.edit).filter((_) => {
-                    return attachments.some((attachment) => _.attachment === attachment.id);
-                })
-            });
-        });
-    }
-
-    private updateFiles() {
-        this.fileCache$.pipe(takeUntil(this.destroy$)).subscribe((files) => {
-            const add = files;
-            this.patchValue({add});
-        });
+        this.store.dispatch(startAttachmentDownloadAction({statementId, attachmentId}));
     }
 
 }
diff --git a/src/app/features/forms/attachments/components/index.ts b/src/app/features/forms/attachments/components/index.ts
index 32dd0e6..4fe31fc 100644
--- a/src/app/features/forms/attachments/components/index.ts
+++ b/src/app/features/forms/attachments/components/index.ts
@@ -11,4 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./attachment-control";
+export * from "./attachment-file-drop-form";
+export * from "./attachment-list-form";
+
 export * from "./attachments-form-group.component";
diff --git a/src/app/features/forms/comments/comments-form.module.ts b/src/app/features/forms/comments/comments-form.module.ts
index a88e385..95d7b4c 100644
--- a/src/app/features/forms/comments/comments-form.module.ts
+++ b/src/app/features/forms/comments/comments-form.module.ts
@@ -16,6 +16,7 @@
 import {MatButtonModule} from "@angular/material/button";
 import {MatIconModule} from "@angular/material/icon";
 import {TranslateModule} from "@ngx-translate/core";
+import {CommonControlsModule} from "../../../shared/controls/common";
 import {DateControlModule} from "../../../shared/controls/date-control";
 import {CollapsibleModule} from "../../../shared/layout/collapsible";
 import {CommentsControlComponent, CommentsFormComponent} from "./components";
@@ -27,7 +28,8 @@
         TranslateModule,
         MatButtonModule,
         DateControlModule,
-        CollapsibleModule
+        CollapsibleModule,
+        CommonControlsModule
     ],
     declarations: [
         CommentsControlComponent,
diff --git a/src/app/features/forms/comments/components/comments-control/comments-control.component.html b/src/app/features/forms/comments/components/comments-control/comments-control.component.html
index 5dffe56..1a8a0bf 100644
--- a/src/app/features/forms/comments/components/comments-control/comments-control.component.html
+++ b/src/app/features/forms/comments/components/comments-control/comments-control.component.html
@@ -52,14 +52,16 @@
 </div>
 <div class="comments--newcomment">
 
-    <textarea #textAreaElement (input)="onInput()"
+    <textarea #textAreaElement
               [placeholder]="'comments.placeholder' | translate"
-              [value]="''"
+              (input)="textValue = textAreaElement.value"
+              [value]="textValue"
               class="openk-textarea comments--newcomment--textfield"
+              appAutoTextFieldResize
               rows="1">
     </textarea>
 
-  <div *ngIf="hasInputSomething" class="comments--newcomment--textfield--buttons">
+  <div *ngIf="textAreaElement.value !== ''" class="comments--newcomment--textfield--buttons">
     <button #test (click)="clear()"
             class="comments--newcomment--textfield--buttons--first-button openk-button openk-danger">
       {{"shared.actions.delete" | translate}}
diff --git a/src/app/features/forms/comments/components/comments-control/comments-control.component.spec.ts b/src/app/features/forms/comments/components/comments-control/comments-control.component.spec.ts
index 110b143..4224a04 100644
--- a/src/app/features/forms/comments/components/comments-control/comments-control.component.spec.ts
+++ b/src/app/features/forms/comments/components/comments-control/comments-control.component.spec.ts
@@ -92,8 +92,7 @@
 
     it("should emit appNewComment with the comment text", () => {
         spyOn(childComponent.appAdd, "emit").and.callThrough();
-        const textField = fixture.debugElement.query(By.css(".comments--newcomment--textfield"));
-        textField.nativeElement.value = "test comment text";
+        childComponent.textValue = "test comment text";
         childComponent.onSave();
         expect(childComponent.appAdd.emit).toHaveBeenCalledWith("test comment text");
     });
@@ -105,7 +104,6 @@
     });
 
     it("should always show the full textarea", () => {
-        spyOn(childComponent, "resize").and.callThrough();
         const textField = fixture.debugElement.query(By.css(".comments--newcomment--textfield")).nativeElement;
         expect(textField.scrollTop).toBe(0);
         textField.value = `A long comment that wraps to multiple rows.
@@ -113,34 +111,21 @@
         textField.dispatchEvent(new Event("input"));
         fixture.detectChanges();
         expect(textField.scrollTop).toBe(0);
-        expect(childComponent.resize).toHaveBeenCalled();
     });
 
     it("should reset value on save and delete", () => {
-        const textField = fixture.debugElement.query(By.css(".comments--newcomment--textfield")).nativeElement;
-        textField.value = `Example value`;
+        childComponent.textValue = `Example value`;
         childComponent.onSave();
-        expect(textField.value).toEqual("");
-        textField.value = `Example value`;
+        expect(childComponent.textValue).toEqual("");
+        childComponent.textValue = `Example value`;
         childComponent.clear();
-        expect(textField.value).toEqual("");
-    });
-
-    it("should set hasInputSomething on input", () => {
-        const textField = fixture.debugElement.query(By.css(".comments--newcomment--textfield")).nativeElement;
-        expect(childComponent.hasInputSomething).toBe(false);
-        textField.value = "Example value";
-        textField.dispatchEvent(new Event("input"));
-        expect(childComponent.hasInputSomething).toBe(true);
-        textField.value = "";
-        textField.dispatchEvent(new Event("input"));
-        expect(childComponent.hasInputSomething).toBe(false);
+        expect(childComponent.textValue).toEqual("");
     });
 
     it("should only show save button when there was a text input", () => {
         let button = fixture.debugElement.query(By.css(".openk-button.openk-success"));
         expect(button).toBeFalsy();
-        childComponent.hasInputSomething = true;
+        childComponent.textAreaRef.nativeElement.value = "value";
         fixture.detectChanges();
         button = fixture.debugElement.query(By.css(".openk-button.openk-success"));
         expect(button).toBeTruthy();
diff --git a/src/app/features/forms/comments/components/comments-control/comments-control.component.ts b/src/app/features/forms/comments/components/comments-control/comments-control.component.ts
index cc7ac4d..0c6fca2 100644
--- a/src/app/features/forms/comments/components/comments-control/comments-control.component.ts
+++ b/src/app/features/forms/comments/components/comments-control/comments-control.component.ts
@@ -31,8 +31,6 @@
     @Input()
     public appComments: Array<IAPICommentModel>;
 
-    public hasInputSomething = false;
-
     @Output()
     public appDelete: EventEmitter<number> = new EventEmitter();
 
@@ -45,21 +43,13 @@
     @Output()
     public appCommentsToShowChange = new EventEmitter<number>();
 
+    public textValue = "";
+
     @ViewChild("textAreaElement")
-    private textAreaRef: ElementRef<HTMLTextAreaElement>;
-
-    public onInput() {
-        this.hasInputSomething = this.textAreaRef.nativeElement.value !== "";
-        this.resize();
-    }
-
-    public resize() {
-        this.textAreaRef.nativeElement.style.height = "1px";
-        this.textAreaRef.nativeElement.style.height = this.textAreaRef.nativeElement.scrollHeight + "px";
-    }
+    public textAreaRef: ElementRef<HTMLTextAreaElement>;
 
     public onSave() {
-        this.appAdd.emit(this.textAreaRef.nativeElement.value);
+        this.appAdd.emit(this.textValue);
         this.clear();
     }
 
@@ -68,9 +58,7 @@
     }
 
     public clear() {
-        this.textAreaRef.nativeElement.value = "";
-        this.hasInputSomething = false;
-        this.resize();
+        this.textValue = "";
     }
 
     public showMore(all = false) {
diff --git a/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.html b/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.html
index 392b619..6e8ffc0 100644
--- a/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.html
+++ b/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.html
@@ -19,18 +19,18 @@
          (cdkDropListDropped)="drop($event)"
          [cdkDropListConnectedTo]="textBlockSelectComponent.dropList"
          [cdkDropListData]="appControls"
-         [formArrayName]="'arrangement'"
+         [formArrayName]="appFormArrayName"
          cdkDropList
          class="editor--arrangement--panel">
 
       <app-text-block-control
-        (appAdd)="add($event,i + 1)"
-        (appRemove)="remove(i)"
+        (appAdd)="addControl($event,i + 1)"
+        (appRemove)="removeControlAt(i)"
         (cdkDragEnded)="isDragging = false"
         (cdkDragReleased)="isDragging = false"
         (cdkDragStarted)="isDragging = true"
         *ngFor="let control of appControls; let i = index; trackBy: trackBy;"
-        [appErrors]="appFormGroup | getFormError : 'arrangement' : 'arrangement' : i | errorToMessages"
+        [appErrors]="appFormGroup | getFormError : 'arrangement' : appFormArrayName: i | errorToMessages"
         [appReplacements]="control.replacements"
         [appSelects]="control.selects"
         [appTextBlockModel]="control.textBlock"
@@ -46,10 +46,12 @@
       <app-text-block-select
         #textBlockSelectComponent
         (appAdd)="addTextBlock($event)"
-        (appRemove)="remove($event)"
+        (appRemove)="removeControlAt($event)"
         [appConnectedTo]="cdkDropListRef"
         [appGroups]="appTextBlockGroups"
         [appSelectedIds]="appSelectedTextBlockIds"
+        [appDisabled]="appFormGroup.disabled"
+        [appReplacements]="appReplacements"
         [appShortMode]="appShortMode">
       </app-text-block-select>
     </div>
@@ -57,11 +59,10 @@
 
   <mat-drawer [(opened)]="appShowPreview" [autoFocus]="false" [position]="'end'" class="editor--arrangement--preview">
     <div class="editor--arrangement--preview--text">
-      <app-statement-preview
-        [appTextArrangements]="appControls | arrangementToPreview | combineBlockdataText"></app-statement-preview>
+      <app-statement-preview [appTextArrangements]="appControls | arrangementToPreview | combineBlockdataText"
+                             [class.disable]="appFormGroup.disabled"></app-statement-preview>
     </div>
   </mat-drawer>
-
 </mat-drawer-container>
 
 <ng-container
diff --git a/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.spec.ts b/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.spec.ts
index c0b0016..703d8cf 100644
--- a/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.spec.ts
+++ b/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.spec.ts
@@ -16,13 +16,13 @@
 import {FormArray} from "@angular/forms";
 import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
 import {provideMockStore} from "@ngrx/store/testing";
-import {IAPITextArrangementItemModel} from "../../../../../core/api/text";
 import {I18nModule} from "../../../../../core/i18n";
 import {IExtendedTextBlockModel} from "../../../../../shared/text-block/model";
 import {StatementEditorModule} from "../../statement-editor.module";
 import {ArrangementFormGroupComponent} from "./arrangement-form-group.component";
 
 describe("ArrangementFormGroupComponent", () => {
+
     let component: ArrangementFormGroupComponent;
     let fixture: ComponentFixture<ArrangementFormGroupComponent>;
 
@@ -45,63 +45,10 @@
         fixture.detectChanges();
     });
 
-    const arrangement: IAPITextArrangementItemModel = {
-        type: "newline",
-        placeholderValues: {}
-    };
-    const anotherArrangement: IAPITextArrangementItemModel = {
-        type: "pagebreak",
-        placeholderValues: {}
-    };
-
     it("should create", () => {
         expect(component).toBeTruthy();
     });
 
-    it("should add the itemmodel to the control", () => {
-        const control: FormArray = component.getArrangementControl();
-        expect(control.length).toEqual(0);
-
-        component.add(arrangement);
-        expect(control.length).toEqual(1);
-
-        component.add(arrangement);
-        expect(control.length).toEqual(2);
-
-        component.add(anotherArrangement, 1);
-        expect(control.length).toEqual(3);
-        expect(control.value[1]).toBe(anotherArrangement);
-    });
-
-    it("should get the formcontrol at the given index", () => {
-        component.add(arrangement);
-
-        const control = component.getControl(0);
-        expect(control.value).toEqual(arrangement);
-    });
-
-    it("should remove the element at the given index", () => {
-        component.add(arrangement);
-        component.add(arrangement);
-        component.remove(1);
-        component.remove(0);
-        const control: FormArray = component.getArrangementControl();
-        expect(control.length).toEqual(0);
-    });
-
-    it("should move the element from index to target index", () => {
-        component.add(arrangement);
-        component.add(anotherArrangement);
-        const firstControl = component.getControl(0);
-        expect(firstControl.value).toEqual(arrangement);
-        const secondControl = component.getControl(1);
-        expect(secondControl.value).toEqual(anotherArrangement);
-
-        component.move(1, 0);
-        const controlAfterMove = component.getControl(1);
-        expect(controlAfterMove.value).toEqual(arrangement);
-    });
-
     it("should add a text arrangement model from a blockmodel", () => {
         const textBlock: IExtendedTextBlockModel = {
             id: "id",
@@ -109,24 +56,25 @@
             excludes: [],
             requires: []
         };
-        const control: FormArray = component.getArrangementControl();
-        expect(control.length).toEqual(0);
+        const formArray: FormArray = component.getFormArray();
+        expect(formArray.length).toEqual(0);
 
         component.addTextBlock(textBlock);
-        expect(control.length).toEqual(1);
-        const firstControl = component.getControl(0);
+        expect(formArray.length).toEqual(1);
+        const firstControl = component.getFormArrayControl(0);
         expect(firstControl.value.textblockId).toEqual(textBlock.id);
 
         component.addTextBlock(undefined);
-        expect(control.length).toEqual(1);
+        expect(formArray.length).toEqual(1);
 
         component.addTextBlock({...textBlock, type: "newline"});
-        const secondControl = component.getControl(1);
+        const secondControl = component.getFormArrayControl(1);
         expect(secondControl.value.textblockId).toEqual(undefined);
     });
 
     it("should call move when dropped inside same container, otherwise add", () => {
-        spyOn(component, "move");
+        const moveSpy = spyOn(component, "moveControl");
+        const addTextBlockSpy = spyOn(component, "addTextBlock");
         const container = {};
         const event: CdkDragDrop<any> = {
             previousContainer: container,
@@ -137,12 +85,12 @@
                 data: {}
             }
         } as CdkDragDrop<any>;
-        component.drop(event);
-        expect(component.move).toHaveBeenCalledWith(1, 0);
 
-        spyOn(component, "addTextBlock");
+        component.drop(event);
+        expect(moveSpy).toHaveBeenCalledWith(1, 0);
+
         const eventDifferentContainers: CdkDragDrop<any> = {...event, container: {}} as CdkDragDrop<any>;
         component.drop(eventDifferentContainers);
-        expect(component.addTextBlock).toHaveBeenCalledWith(eventDifferentContainers.item.data, 0);
+        expect(addTextBlockSpy).toHaveBeenCalledWith(eventDifferentContainers.item.data, 0);
     });
 });
diff --git a/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.ts b/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.ts
index aa792a4..c48228b 100644
--- a/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.ts
+++ b/src/app/features/forms/statement-editor/components/arrangement-form-group/arrangement-form-group.component.ts
@@ -11,28 +11,23 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-
 import {CdkDragDrop} from "@angular/cdk/drag-drop";
 import {Component, Input} from "@angular/core";
-import {FormArray, FormControl, FormGroup} from "@angular/forms";
-import {IAPITextArrangementErrorModel, IAPITextArrangementItemModel, IAPITextBlockGroupModel} from "../../../../../core/api/text";
+import {FormGroup} from "@angular/forms";
+import {IAPITextArrangementItemModel, IAPITextBlockGroupModel} from "../../../../../core";
 import {IExtendedTextBlockModel} from "../../../../../shared/text-block/model";
-import {IStatementEditorControlConfiguration, IStatementEditorFormValue} from "../../../../../store/statements/model";
-import {createFormGroup} from "../../../../../util/forms";
-import {arrayJoin} from "../../../../../util/store";
-import {AbstractReactiveFormComponent} from "../../../abstract";
+import {createStatementEditorForm, IStatementEditorControlConfiguration} from "../../../../../store";
+import {AbstractReactiveFormArrayComponent} from "../../../abstract";
 
 @Component({
     selector: "app-arrangement-form-group",
     templateUrl: "./arrangement-form-group.component.html",
     styleUrls: ["./arrangement-form-group.component.scss"]
 })
-export class ArrangementFormGroupComponent extends AbstractReactiveFormComponent<IStatementEditorFormValue> {
+export class ArrangementFormGroupComponent extends AbstractReactiveFormArrayComponent<IAPITextArrangementItemModel> {
 
     @Input()
-    public appFormGroup: FormGroup = createFormGroup<IStatementEditorFormValue>({
-        arrangement: new FormArray([])
-    });
+    public appFormGroup: FormGroup = createStatementEditorForm();
 
     @Input()
     public appControls: IStatementEditorControlConfiguration[];
@@ -49,60 +44,25 @@
     @Input()
     public appShowPreview: boolean;
 
+    @Input()
+    public appReplacements: { [key: string]: string };
+
+    public appFormArrayName = "arrangement";
+
     public isDragging = false;
 
-    @Input()
-    public set appArrangementValue(value: IAPITextArrangementItemModel[]) {
-        this.getArrangementControl().clear();
-        arrayJoin(value).forEach((item) => this.add(item));
-    }
-
-    @Input()
-    public set appArrangementError(list: IAPITextArrangementErrorModel[]) {
-        this.getArrangementControl()?.controls
-            .forEach((control) => control.setErrors({arrangement: null}));
-        arrayJoin(list).forEach((error) => {
-            this.appFormGroup.get(["arrangement", error.arrangementId])?.setErrors({arrangement: error});
-        });
-        this.getArrangementControl().updateValueAndValidity();
-    }
-
     public trackBy = (index: number) => {
-        const control = this.getArrangementControl();
-        return control == null ? index : control.at(index);
+        const control = this.getFormArrayControl(index);
+        return control == null ? index : control;
         // tslint:disable-next-line:semicolon
     };
 
-    public getArrangementControl(): FormArray {
-        const formArray = this.appFormGroup.get("arrangement");
-        return formArray instanceof FormArray ? formArray : undefined;
-    }
-
-    public getControl(index: number) {
-        return this.getArrangementControl().get([index]);
-    }
-
-    public add(value: IAPITextArrangementItemModel, index?: number) {
-        const control = this.getArrangementControl();
-        control.insert(index == null ? control.length : index, new FormControl(value));
-    }
-
-    public remove(index: number) {
-        this.getArrangementControl().removeAt(index);
-    }
-
-    public move(index: number, targetIndex: number) {
-        const control = this.getControl(index);
-        this.getArrangementControl().removeAt(index);
-        this.getArrangementControl().insert(targetIndex, control);
-    }
-
     public drop(event: CdkDragDrop<any>) {
         if (event.previousContainer !== event.container) {
             this.addTextBlock(event.item?.data, event.currentIndex);
         }
         if (event.previousContainer === event.container) {
-            this.move(event.previousIndex, event.currentIndex);
+            this.moveControl(event.previousIndex, event.currentIndex);
         }
     }
 
@@ -114,10 +74,10 @@
         const {type, id} = block;
         if (type == null || type === "block") {
             if (id != null) {
-                this.add({type: "block", textblockId: id, placeholderValues: {}}, index);
+                this.addControl({type: "block", textblockId: id, placeholderValues: {}}, index);
             }
         } else {
-            this.add({type, placeholderValues: {}, replacement: type === "text" ? "" : undefined}, index);
+            this.addControl({type, placeholderValues: {}, replacement: type === "text" ? "" : undefined}, index);
         }
     }
 
diff --git a/src/app/features/forms/statement-editor/components/index.ts b/src/app/features/forms/statement-editor/components/index.ts
index 0531a66..128bae3 100644
--- a/src/app/features/forms/statement-editor/components/index.ts
+++ b/src/app/features/forms/statement-editor/components/index.ts
@@ -12,6 +12,7 @@
  ********************************************************************************/
 
 export * from "./arrangement-form-group";
+export * from "./side-menu";
 export * from "./statement-editor-form";
 export * from "./statement-preview";
 export * from "./text-block-control";
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/forms/statement-editor/components/side-menu/index.ts
similarity index 90%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/forms/statement-editor/components/side-menu/index.ts
index aeb78c3..67148ca 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/forms/statement-editor/components/side-menu/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./statement-editor-side-menu.component";
diff --git a/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.html b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.html
new file mode 100644
index 0000000..f610571
--- /dev/null
+++ b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.html
@@ -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
+ -------------------------------------------------------------------------------->
+
+<ng-container *appSideMenu="'top'; title: 'statementEditorForm.sideMenu.title' | translate">
+  <app-action-button
+    [appDisabled]="appLoading || appTask?.statementId == null"
+    [appIcon]="'arrow_back'"
+    [appRouterLink]="'/details'"
+    [appStatementId]="appTask?.statementId"
+    class="side-menu-button openk-info">
+    {{"statementEditorForm.sideMenu.backToDetails" | translate}}
+  </app-action-button>
+
+  <ng-container *ngIf="!appForFinalization">
+    <app-action-button
+      (appClick)="appSave.emit()"
+      [appDisabled]="appLoading"
+      [appIcon]="'save'"
+      class="side-menu-button openk-info">
+      {{"statementEditorForm.sideMenu.save" | translate}}
+    </app-action-button>
+
+    <app-action-button
+      (appClick)="appValidate.emit()"
+      [appDisabled]="appLoading"
+      [appIcon]="'bug_report'"
+      class="side-menu-button openk-info">
+      {{"statementEditorForm.sideMenu.validate" | translate}}
+    </app-action-button>
+
+    <app-action-button
+      (appClick)="appCompile.emit()"
+      [appDisabled]="appLoading"
+      [appIcon]="'build'"
+      class="side-menu-button openk-info">
+      {{"statementEditorForm.sideMenu.compile" | translate}}
+    </app-action-button>
+  </ng-container>
+</ng-container>
+
+<app-side-menu-status
+  *appSideMenu="'center'"
+  [appLoadingMessage]="'core.submitting' | translate"
+  [appLoading]="appLoading">
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+
+  <app-action-button
+    (appClick)="button.emit()"
+    *ngFor="let button of appForFinalization ? buttonLayoutForFinalization : buttonLayout"
+    [appDisabled]="button.emit == null || appLoading"
+    [appIcon]="button.icon"
+    [ngClass]="button.cssClass"
+    class="side-menu-button">
+    {{button.label | translate}}
+  </app-action-button>
+
+</ng-container>
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.scss
similarity index 86%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.scss
index 563859b..3796710 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.scss
@@ -11,9 +11,14 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
+:host {
+  display: none;
+}
+
+.side-menu-button {
   width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.spec.ts
similarity index 64%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.spec.ts
index 20e1e36..ca26708 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.spec.ts
@@ -12,23 +12,22 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {I18nModule} from "../../../../../core/i18n";
+import {StatementEditModule} from "../../../../edit";
+import {StatementEditorSideMenuComponent} from "./statement-editor-side-menu.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("StatementEditorSideMenuComponent", () => {
+    let component: StatementEditorSideMenuComponent;
+    let fixture: ComponentFixture<StatementEditorSideMenuComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [StatementEditModule, I18nModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(StatementEditorSideMenuComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.ts b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.ts
new file mode 100644
index 0000000..f7b3de1
--- /dev/null
+++ b/src/app/features/forms/statement-editor/components/side-menu/statement-editor-side-menu.component.ts
@@ -0,0 +1,194 @@
+/********************************************************************************
+ * 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 {EAPIProcessTaskDefinitionKey, EAPIUserRoles, IAPIProcessTask, TCompleteTaskVariable} from "../../../../../core";
+import {arrayJoin} from "../../../../../util/store";
+
+export interface IStatementEditorSideMenuActionButton<T = any> {
+
+    emit: () => void;
+
+    label?: string;
+
+    icon?: string;
+
+    cssClass?: string;
+
+}
+
+interface ITaskUserLayoutMap<T> {
+
+    [taskKey: string]: {
+        [role: string]: T[]
+    };
+
+}
+
+@Component({
+    selector: "app-statement-editor-side-menu",
+    templateUrl: "./statement-editor-side-menu.component.html",
+    styleUrls: ["./statement-editor-side-menu.component.scss"]
+})
+export class StatementEditorSideMenuComponent implements OnChanges {
+
+    @Input()
+    public appForFinalization: boolean;
+
+    @Input()
+    public appLoading: boolean;
+
+    @Input()
+    public appUserRoles: EAPIUserRoles[];
+
+    @Input()
+    public appTask: IAPIProcessTask;
+
+
+    @Output()
+    public appSave = new EventEmitter<void>();
+
+    @Output()
+    public appValidate = new EventEmitter<void>();
+
+    @Output()
+    public appCompile = new EventEmitter<void>();
+
+
+    @Output()
+    public appSaveAndCompleteTask = new EventEmitter<{
+        variables: TCompleteTaskVariable,
+        claimNext?: boolean | EAPIProcessTaskDefinitionKey
+    }>();
+
+    @Output()
+    public appSaveAndContribute = new EventEmitter<void>();
+
+    @Output()
+    public appSaveAndFinalize = new EventEmitter<void>();
+
+    @Output()
+    public appCompleteFinalization = new EventEmitter<boolean>();
+
+
+    public buttonLayout: IStatementEditorSideMenuActionButton[] = [];
+
+    public buttonLayoutForFinalization: IStatementEditorSideMenuActionButton[] = [
+        {
+            emit: emitFactory(this.appCompleteFinalization, false),
+            label: "statementEditorForm.sideMenu.continue",
+            icon: "edit",
+            cssClass: "openk-info"
+        },
+        {
+            emit: emitFactory(this.appCompleteFinalization, true),
+            label: "statementEditorForm.sideMenu.releaseForApproval",
+            icon: "redo",
+            cssClass: "openk-success"
+        }
+    ];
+
+    private userTaskLayoutMap: ITaskUserLayoutMap<IStatementEditorSideMenuActionButton> = {
+        [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE]: {
+            [EAPIProcessTaskDefinitionKey.CREATE_DRAFT]: [
+                {
+                    emit: emitFactory(this.appSaveAndCompleteTask, {variables: {}}),
+                    label: "statementEditorForm.sideMenu.releaseForDepartments",
+                    icon: "redo",
+                    cssClass: "openk-success"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE]: [
+                {
+                    emit: emitFactory(this.appSaveAndCompleteTask, {
+                        variables: {
+                            data_complete: {type: "Boolean", value: true},
+                            response_created: {type: "Boolean", value: false}
+                        },
+                        claimNext: true
+                    }),
+                    label: "statementEditorForm.sideMenu.backToInfoData",
+                    icon: "subject",
+                    cssClass: "openk-danger"
+                },
+                {
+                    emit: emitFactory(this.appSaveAndCompleteTask, {
+                        variables: {
+                            data_complete: {type: "Boolean", value: false},
+                            response_created: {type: "Boolean", value: false}
+                        }
+                    }),
+                    label: "statementEditorForm.sideMenu.backToDepartments",
+                    icon: "undo",
+                    cssClass: "openk-danger"
+                },
+                {
+                    emit: emitFactory(this.appSaveAndFinalize),
+                    label: "statementEditorForm.sideMenu.finalize",
+                    icon: "launch",
+                    cssClass: "openk-success"
+                }
+            ],
+            [EAPIProcessTaskDefinitionKey.CREATE_NEGATIVE_RESPONSE]: [
+                {
+                    emit: emitFactory(this.appSaveAndCompleteTask, {
+                        variables: {
+                            response_created: {type: "Boolean", value: false}
+                        },
+                        claimNext: EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA
+                    }),
+                    label: "statementEditorForm.sideMenu.backToInfoData",
+                    icon: "subject",
+                    cssClass: "openk-info"
+                },
+                {
+                    emit: emitFactory(this.appSaveAndFinalize),
+                    label: "statementEditorForm.sideMenu.finalize",
+                    icon: "launch",
+                    cssClass: "openk-success"
+                }
+            ]
+        },
+        [EAPIUserRoles.DIVISION_MEMBER]: {
+            [EAPIProcessTaskDefinitionKey.ENRICH_DRAFT]: [
+                {
+                    emit: emitFactory(this.appSaveAndContribute),
+                    label: "statementEditorForm.sideMenu.contribute",
+                    icon: "check",
+                    cssClass: "openk-success"
+                }
+            ]
+        }
+    };
+
+    public ngOnChanges(changes: SimpleChanges) {
+        const keys: Array<keyof StatementEditorSideMenuComponent> = ["appTask", "appUserRoles"];
+        if (keys.some((_) => changes[_] != null)) {
+            this.updateActions();
+        }
+    }
+
+    public updateActions() {
+        const taskDefinitionKey = this.appTask?.taskDefinitionKey;
+        this.buttonLayout = arrayJoin(...this.appUserRoles
+            .map((_) => this.userTaskLayoutMap[_] == null ? [] : this.userTaskLayoutMap[_][taskDefinitionKey])
+        );
+    }
+
+}
+
+function emitFactory(ev: EventEmitter<void>): () => void;
+function emitFactory<T>(ev: EventEmitter<T>, value: T): () => void;
+function emitFactory(eventEmitter: EventEmitter<any>, value?: any): () => void {
+    return () => eventEmitter.emit(value);
+}
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 0aea60c..061b58c 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
@@ -11,19 +11,54 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
+<app-file-preview
+  *ngIf="file$ | async"
+  [appFile]="file$ | async"
+  class="pdf-overlay">
+</app-file-preview>
+
+<app-statement-editor-side-menu
+  (appCompile)="compile()"
+  (appCompleteFinalization)="finalize($event)"
+  (appSave)="submit()"
+  (appSaveAndCompleteTask)="submit({ completeTask: $event.variables, claimNext: $event.claimNext})"
+  (appSaveAndContribute)="submit({ contribute: true })"
+  (appSaveAndFinalize)="submit({ compile: true })"
+  (appValidate)="validate()"
+  [appForFinalization]="(file$ | async) != null"
+  [appLoading]="isLoading$ | async"
+  [appTask]="task$ | async"
+  [appUserRoles]="userRoles$ | async">
+
+</app-statement-editor-side-menu>
+
+<app-collapsible
+  *ngIf="showContributions$ | async"
+  [appTitle]="('statementEditorForm.container.contributionStatus' | translate)
+     + ' (' + (selectedContributionsCount$ | async) + '/' + (requiredContributionOptions$ | async).length + ')'"
+  [formGroup]="appFormGroup">
+
+  <app-select-group
+    [appGroups]="requiredContributionGroups$ | async"
+    [appOptions]="requiredContributionOptions$ | async"
+    [formControlName]="'contributions'"
+    style="padding: 1em;">
+  </app-select-group>
+
+</app-collapsible>
+
 <app-collapsible
   [appHeaderTemplateRef]="arrangementHeaderRef"
-  [appTitle]="'Entwurf'"
+  [appTitle]="'statementEditorForm.container.draft' | translate"
   [formGroup]="appFormGroup">
 
   <app-arrangement-form-group
-    [appArrangementError]="arrangementError$ | async"
-    [appArrangementValue]="arrangement$ | async"
     [appControls]="controls$ | async"
     [appFormGroup]="appFormGroup"
     [appSelectedTextBlockIds]="selectedTextBlockIds$ | async"
     [appShortMode]="appShortMode"
     [appShowPreview]="appShowPreview"
+    [appReplacements]="replacements$ | async"
     [appTextBlockGroups]="textBlockGroups$ | async">
   </app-arrangement-form-group>
 
@@ -31,42 +66,29 @@
 
 <app-collapsible
   [appCollapsed]="false"
-  [appTitle]="'Anhänge'">
+  [appTitle]="'statementEditorForm.container.attachments' | translate">
 
   <app-attachments-form-group
     [appAutoTagIds]="[outboxTagId]"
     [appFormGroup]="appFormGroup | getFormGroup: 'attachments'"
     [appRestrictedTagIds]="[outboxTagId]"
+    [appForbiddenTagIds]="[statementTagId]"
     [appWithoutTagControl]="true">
   </app-attachments-form-group>
 
 </app-collapsible>
 
-<div class="editor--buttons">
-
-  <button (click)="validate()" class="openk-button openk-info">Validate</button>
-
-  <button (click)="compile()" class="openk-button openk-info">Compile</button>
-
-  <button (click)="submit()" class="openk-button openk-info">Submit</button>
-
-  <button (click)="complete(true)" class="openk-button openk-success">Complete</button>
-
-</div>
-
 <ng-template #arrangementHeaderRef>
   <div class="editor--arrangement--header">
     <button (click)="appShortMode = !appShortMode"
-            class="openk-button editor--arrangement--header--button"
-            type="button">
+            class="openk-button editor--arrangement--header--button" type="button">
       <mat-icon class="editor--arrangement--header--button--icon">
         {{appShortMode ? "unfold_more" : "unfold_less"}}
       </mat-icon>
     </button>
 
     <button (click)="appShowPreview = !appShowPreview"
-            class="openk-button editor--arrangement--header--button"
-            type="button">
+            class="openk-button editor--arrangement--header--button" type="button">
       <mat-icon class="editor--arrangement--header--button--icon">
         {{appShowPreview ? "chevron_right" : "chevron_left"}}
       </mat-icon>
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 96f25ac..a0ee48e 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
@@ -22,6 +22,13 @@
   }
 }
 
+.pdf-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 500;
+}
+
 .editor--arrangement--header {
   display: flex;
   justify-content: flex-end;
@@ -50,12 +57,3 @@
   height: initial;
   font-size: 1em;
 }
-
-.editor--buttons {
-  display: flex;
-  margin-left: auto;
-
-  & > * {
-    margin-left: 0.5em;
-  }
-}
diff --git a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.spec.ts b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.spec.ts
index 5e7e3a4..f5842d9 100644
--- a/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.spec.ts
+++ b/src/app/features/forms/statement-editor/components/statement-editor-form/statement-editor-form.component.spec.ts
@@ -13,8 +13,8 @@
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
-import {Store} from "@ngrx/store";
-import {provideMockStore} from "@ngrx/store/testing";
+import {MockStore, provideMockStore} from "@ngrx/store/testing";
+import {EAPIProcessTaskDefinitionKey, IAPIProcessTask} from "../../../../../core/api/process";
 import {I18nModule} from "../../../../../core/i18n";
 import {taskSelector} from "../../../../../store/process/selectors";
 import {
@@ -26,7 +26,15 @@
 import {StatementEditorFormComponent} from "./statement-editor-form.component";
 
 describe("StatementEditorFormComponent", () => {
-    let store: Store;
+    const statementId = 19;
+    const taskId = "taskId";
+    const task: IAPIProcessTask = {
+        ...{} as IAPIProcessTask,
+        taskId,
+        statementId
+    };
+
+    let store: MockStore;
     let component: StatementEditorFormComponent;
     let fixture: ComponentFixture<StatementEditorFormComponent>;
 
@@ -43,10 +51,7 @@
                     selectors: [
                         {
                             selector: taskSelector,
-                            value: {
-                                taskId: "taskId",
-                                statementId: 1
-                            }
+                            value: task
                         }
                     ]
                 })
@@ -58,7 +63,7 @@
         fixture = TestBed.createComponent(StatementEditorFormComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
-        store = fixture.componentRef.injector.get(Store);
+        store = fixture.componentRef.injector.get(MockStore);
     });
 
     it("should create", () => {
@@ -69,21 +74,36 @@
         const dispatchSpy = spyOn(store, "dispatch");
         await component.validate();
         expect(dispatchSpy).toHaveBeenCalledWith(
-            validateStatementArrangementAction({statementId: 1, taskId: "taskId", arrangement: []}));
+            validateStatementArrangementAction({statementId, taskId, arrangement: []}));
     });
 
     it("should dispatch compileStatementArrangementAction on compile", async () => {
         const dispatchSpy = spyOn(store, "dispatch");
         await component.compile();
         expect(dispatchSpy).toHaveBeenCalledWith(
-            compileStatementArrangementAction({statementId: 1, taskId: "taskId", arrangement: []}));
+            compileStatementArrangementAction({statementId, taskId, arrangement: []}));
     });
 
     it("should dispatch submitStatementEditorFormAction on submit", async () => {
         const dispatchSpy = spyOn(store, "dispatch");
         await component.submit();
-        expect(dispatchSpy).toHaveBeenCalledWith(
-            submitStatementEditorFormAction(
-                {statementId: 1, taskId: "taskId", value: {arrangement: [], attachments: {edit: [], add: []}}}));
+        expect(dispatchSpy).toHaveBeenCalledWith(submitStatementEditorFormAction({
+            statementId,
+            taskId,
+            value: {arrangement: [], attachments: {edit: [], add: []}, contributions: null},
+            options: undefined
+        }));
+
+        store.overrideSelector(taskSelector, {
+            ...task,
+            taskDefinitionKey: EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE
+        });
+        await component.submit();
+        expect(dispatchSpy).toHaveBeenCalledWith(submitStatementEditorFormAction({
+            statementId,
+            taskId,
+            value: {arrangement: [], attachments: {edit: [], add: []}, contributions: {selected: [], indeterminate: []}},
+            options: undefined
+        }));
     });
 });
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 90dc27f..73b2d7d 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
@@ -11,28 +11,40 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {Component, OnInit} from "@angular/core";
-import {FormArray, FormControl} from "@angular/forms";
+import {Component, OnDestroy, OnInit} from "@angular/core";
+import {FormArray} from "@angular/forms";
 import {select, Store} from "@ngrx/store";
-import {Observable} from "rxjs";
-import {filter, map, switchMap, take, takeUntil} from "rxjs/operators";
-import {EAPIStaticAttachmentTagIds, IAPIProcessObject} from "../../../../../core";
+import {defer, Observable} from "rxjs";
+import {filter, map, skip, switchMap, take, takeUntil} from "rxjs/operators";
+import {
+    EAPIProcessTaskDefinitionKey,
+    EAPIStaticAttachmentTagIds,
+    IAPITextArrangementErrorModel,
+    TCompleteTaskVariable
+} from "../../../../../core";
 import {
     compileStatementArrangementAction,
-    completeTaskAction,
+    createStatementEditorForm,
     fetchStatementTextArrangementAction,
+    getContributionsSelector,
     getStatementArrangementErrorSelector,
     getStatementEditorControlConfigurationSelector,
-    getStatementLoadingSelector,
+    getStatementStaticTextReplacementsSelector,
     getStatementTextBlockGroups,
-    IAttachmentFormValue,
     IStatementEditorFormValue,
+    queryParamsIdSelector,
+    requiredContributionsGroupsSelector,
+    requiredContributionsOptionsSelector,
     statementArrangementSelector,
+    statementFileSelector,
+    statementLoadingSelector,
     submitStatementEditorFormAction,
     taskSelector,
+    updateStatementEntityAction,
+    userRolesSelector,
     validateStatementArrangementAction
 } from "../../../../../store";
-import {arrayJoin, createFormGroup, filterDistinctValues} from "../../../../../util";
+import {arrayJoin, filterDistinctValues} from "../../../../../util";
 import {AbstractReactiveFormComponent} from "../../../abstract";
 
 @Component({
@@ -40,24 +52,42 @@
     templateUrl: "./statement-editor-form.component.html",
     styleUrls: ["./statement-editor-form.component.scss"]
 })
-export class StatementEditorFormComponent extends AbstractReactiveFormComponent<IStatementEditorFormValue> implements OnInit {
+export class StatementEditorFormComponent extends AbstractReactiveFormComponent<IStatementEditorFormValue> implements OnInit, OnDestroy {
 
     public outboxTagId = EAPIStaticAttachmentTagIds.OUTBOX;
 
+    public statementTagId = EAPIStaticAttachmentTagIds.STATEMENT;
+
     public appShortMode: boolean;
 
     public appShowPreview: boolean;
 
-    public appFormGroup = createFormGroup<IStatementEditorFormValue>({
-        arrangement: new FormArray([]),
-        attachments: createFormGroup<IAttachmentFormValue>({
-            edit: new FormControl([]),
-            add: new FormControl([])
-        })
-    });
+    public appFormGroup = createStatementEditorForm();
 
     public task$ = this.store.pipe(select(taskSelector));
 
+    public statementId$ = this.store.pipe(select(queryParamsIdSelector));
+
+    public showContributions$ = defer(() => this.task$).pipe(
+        map((task) => task?.taskDefinitionKey),
+        map((taskDefinitionKey) => {
+            return taskDefinitionKey === EAPIProcessTaskDefinitionKey.CREATE_DRAFT
+                || taskDefinitionKey === EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE;
+        })
+    );
+
+    public requiredContributionOptions$ = this.store.pipe(select(requiredContributionsOptionsSelector));
+
+    public requiredContributionGroups$ = this.store.pipe(select(requiredContributionsGroupsSelector));
+
+    public contributions$ = this.store.pipe(select(getContributionsSelector));
+
+    public selectedContributionsCount$ = this.value$.pipe(
+        map((value) => value?.contributions?.selected?.length)
+    );
+
+    public userRoles$ = this.store.pipe(select(userRolesSelector));
+
     public controls$ = this.value$.pipe(
         filter((value) => value != null),
         switchMap((value) => {
@@ -65,17 +95,21 @@
         })
     );
 
+    public replacements$ = this.store.pipe(select(getStatementStaticTextReplacementsSelector));
+
     public selectedTextBlockIds$: Observable<string[]> = this.value$.pipe(
         map((value) => filterDistinctValues(value.arrangement.map((item) => item.textblockId)))
     );
 
     public arrangement$ = this.store.pipe(select(statementArrangementSelector));
 
+    public file$ = this.store.pipe(select(statementFileSelector));
+
     public textBlockGroups$ = this.store.pipe(select(getStatementTextBlockGroups));
 
     public arrangementError$ = this.store.pipe(select(getStatementArrangementErrorSelector));
 
-    public statementLoading$ = this.store.pipe(select(getStatementLoadingSelector));
+    public isLoading$ = this.store.pipe(select(statementLoadingSelector));
 
     public constructor(public store: Store) {
         super();
@@ -84,6 +118,22 @@
     public ngOnInit() {
         this.updateForm();
         this.fetchTextArrangement();
+        this.deleteStatementFile();
+    }
+
+    public ngOnDestroy() {
+        super.ngOnDestroy();
+        this.deleteStatementFile();
+    }
+
+    public setArrangementErrors(errors: IAPITextArrangementErrorModel[]) {
+        const array = this.appFormGroup.get("arrangement");
+        if (array instanceof FormArray) {
+            array.controls
+                .forEach((control) => control.setErrors({arrangement: null}));
+            arrayJoin(errors)
+                .forEach((e) => array.get([e?.arrangementId])?.setErrors({arrangement: e}));
+        }
     }
 
     public async validate() {
@@ -104,27 +154,47 @@
         }));
     }
 
-    public async submit() {
+    public async submit(options?: {
+        completeTask?: TCompleteTaskVariable,
+        claimNext?: boolean | EAPIProcessTaskDefinitionKey,
+        compile?: boolean,
+        contribute?: boolean,
+        file?: File
+    }) {
         const task = await this.task$.pipe(take(1)).toPromise();
+        const value = this.getValue();
         this.store.dispatch(submitStatementEditorFormAction({
             statementId: task.statementId,
             taskId: task.taskId,
-            value: this.getValue()
+            value: {
+                ...value,
+                contributions: await this.showContributions$.pipe(take(1)).toPromise() ? value.contributions : null
+            },
+            options
         }));
     }
 
-    public async complete(value: boolean) {
-        const task = await this.task$.pipe(take(1)).toPromise();
-        const variables: IAPIProcessObject = {};
-        Object.keys(task.requiredVariables)
-            .filter((name) => task.requiredVariables[name] === "Boolean")
-            .forEach((name) => variables[name] = {type: "Boolean", value});
-        this.store.dispatch(completeTaskAction({
-            statementId: task.statementId,
-            taskId: task.taskId,
-            claimNext: true,
-            variables
-        }));
+    public async finalize(complete?: boolean) {
+        if (complete) {
+            const file = await this.file$.pipe(take(1)).toPromise();
+            return this.submit({
+                completeTask: {
+                    data_complete: {type: "Boolean", value: true},
+                    response_created: {type: "Boolean", value: true}
+                },
+                file
+            });
+        } else {
+            return this.deleteStatementFile();
+        }
+    }
+
+    private async deleteStatementFile() {
+        const file = await this.file$.pipe(take(1)).toPromise();
+        if (file != null) {
+            const statementId = await this.statementId$.pipe(take(1)).toPromise();
+            this.store.dispatch(updateStatementEntityAction({statementId, entity: {file: null}}));
+        }
     }
 
     private fetchTextArrangement() {
@@ -133,8 +203,24 @@
     }
 
     private updateForm() {
-        this.statementLoading$.subscribe((loading) => {
-            loading?.submittingStatementEditorForm ? this.appFormGroup.disable() : this.appFormGroup.enable();
+        this.isLoading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => this.disable(loading));
+        this.arrangement$.pipe(takeUntil(this.destroy$)).subscribe((arrangement) => {
+            this.setValueForArray(arrangement, "arrangement");
+        });
+        this.arrangementError$.pipe(
+            skip(1), // The first value is skipped when the use enters the site.
+            switchMap((errors) => {
+                // Errors are only displayed when the form is ready to use.
+                return this.appFormGroup.statusChanges.pipe(
+                    filter(() => this.appFormGroup.enabled),
+                    map(() => errors),
+                    take(1)
+                );
+            }),
+            takeUntil(this.destroy$),
+        ).subscribe((errors) => this.setArrangementErrors(errors));
+        this.contributions$.pipe(takeUntil(this.destroy$)).subscribe((contributions) => {
+            this.patchValue({contributions});
         });
     }
 
diff --git a/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.html b/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.html
index f34eed7..35f0750 100644
--- a/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.html
+++ b/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.html
@@ -11,22 +11,26 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<ng-container *ngFor="let block of appTextArrangements">
+<div class="preview">
 
-  <span *ngIf="block.type === 'text'" class="text">
-    {{block.value}}
-  </span>
+  <ng-container *ngFor="let block of appTextArrangements">
 
-  <ng-container *ngIf="block.type === 'newline'">
-    <br>
+  <span *ngIf="block.type === 'text'" class="text"><!--
+    -->{{block.value}}<!--
+  --></span>
+
+    <ng-container *ngIf="block.type === 'newline'"><!--
+      --><br><!--
+    --></ng-container>
+
+    <div *ngIf="block.type === 'pagebreak'" class="page-break">
+      <div class="page-break--line">
+      </div>
+      <span class="page-break--text">Seitenumbruch</span>
+      <div class="page-break--line">
+      </div>
+    </div>
+
   </ng-container>
 
-  <div *ngIf="block.type === 'pagebreak'" class="page-break">
-    <div class="page-break--line">
-    </div>
-    <span class="page-break--text">Seitenumbruch</span>
-    <div class="page-break--line">
-    </div>
-  </div>
-
-</ng-container>
+</div>
diff --git a/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.scss b/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.scss
index 301e8c8..8700870 100644
--- a/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.scss
+++ b/src/app/features/forms/statement-editor/components/statement-preview/statement-preview.component.scss
@@ -13,6 +13,10 @@
 
 @import "openk.styles";
 
+.preview {
+  white-space: break-spaces;
+}
+
 .page-break {
   display: inline-flex;
   flex-direction: row;
@@ -35,4 +39,7 @@
 .text {
   padding: 0 0.5em 0 0.5em;
   display: inline-block;
+  max-width: 100%;
+  box-sizing: border-box;
+  word-wrap: break-word;
 }
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 08fbb5a..c10e5c2 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
@@ -21,5 +21,7 @@
   [appShowClose]="true"
   [appTitle]="appValue | getTitle"
   [appBlockText]="appValue?.replacement"
-  [appType]="appValue?.type">
+  [appDisabled]="appDisabled"
+  [appType]="appValue?.type"
+  [class.disable]="appDisabled">
 </app-text-block>
diff --git a/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.spec.ts b/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.spec.ts
index 34a25e8..3331403 100644
--- a/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.spec.ts
+++ b/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.spec.ts
@@ -71,7 +71,7 @@
         expect(component.appValue).toEqual({...value, replacement});
 
         component.appValue.replacement = undefined;
-        const textBlockModel: IAPITextBlockModel = {id: "1", text: "text block text", excludes: [], requires: []};
+        const textBlockModel: IAPITextBlockModel = {id: "1", text: "text \n\nblock text", excludes: [], requires: []};
         component.appTextBlockModel = textBlockModel;
         component.convertToFreeText();
         expect(component.appValue).toEqual({...value, replacement: textBlockModel.text});
diff --git a/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.ts b/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.ts
index b78e2ec..99a25ec 100644
--- a/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.ts
+++ b/src/app/features/forms/statement-editor/components/text-block-control/text-block-control.component.ts
@@ -15,6 +15,7 @@
 import {NG_VALUE_ACCESSOR} from "@angular/forms";
 import {IAPITextArrangementItemModel, IAPITextBlockModel} from "../../../../../core";
 import {AbstractControlValueAccessorComponent} from "../../../../../shared/controls/common";
+import {textToBlockDataArray} from "../../../../../shared/text-block/pipes/get-blockdata-array";
 import {ITextblockError} from "../../pipes";
 
 @Component({
@@ -60,10 +61,26 @@
     }
 
     public convertToFreeText() {
-        const replacementText = this.appValue?.replacement ? this.appValue.replacement : this.appTextBlockModel?.text;
+        let replacementText = "";
+        if (this.appValue?.replacement == null) {
+            const blockData = textToBlockDataArray(
+                this.appTextBlockModel, this.appValue.placeholderValues, this.appReplacements, this.appSelects);
+            for (const block of blockData) {
+                if (block.type === "newline") {
+                    replacementText += "\n";
+                } else if (block.type === "select") {
+                    replacementText += block.options[block.placeholder] ? block.options[block.placeholder] : "";
+                } else {
+                    replacementText += block.placeholder ? block.placeholder : block.value;
+                }
+            }
+        } else {
+            replacementText = this.appValue.replacement;
+        }
+
         this.writeValue({
             ...this.appValue,
-            replacement: replacementText ? replacementText : ""
+            replacement: replacementText
         }, true);
     }
 
diff --git a/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.spec.ts b/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.spec.ts
index 499b328..1f65210 100644
--- a/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.spec.ts
+++ b/src/app/features/forms/statement-editor/pipes/arrangement-to-preview.pipe.spec.ts
@@ -42,7 +42,7 @@
                 type: "block",
                 textblockId: "textBlockId",
                 placeholderValues: {
-                    testing: "INSERT_INPUT_VALUE"
+                    "<f:testing>": "INSERT_INPUT_VALUE"
                 }
             }
         },
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 b99ec85..ad230af 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
@@ -26,19 +26,17 @@
 
         const result: ITextBlockRenderItem[] = [];
         for (const item of arrayJoin(config)) {
-            if (item.value?.type === "block" && item.textBlock != null) {
+            if (item.value?.replacement) {
                 result.push({
                     type: "text",
-                    value: this.replaceInText(item.textBlock.text, item.value?.placeholderValues, item.replacements)
+                    value: item.value.replacement
                 });
-            }
-            if (item.value?.type === "text") {
+            } else if (item.value?.type === "block" && item.textBlock != null) {
                 result.push({
                     type: "text",
-                    value: item.value.replacement ? item.value.replacement : ""
+                    value: this.replaceInText(item.textBlock.text, item.value?.placeholderValues, item.replacements, item.selects)
                 });
-            }
-            if (item.value?.type === "newline" || item.value?.type === "pagebreak") {
+            } else if (item.value?.type === "newline" || item.value?.type === "pagebreak") {
                 result.push({
                     type: item.value.type,
                     value: ""
@@ -51,13 +49,17 @@
         ]);
     }
 
-    public replaceInText(text: string, placeholderValues: { [key: string]: string } = {}, replacements: { [key: string]: string }): string {
+    public replaceInText(text: string,
+                         placeholderValues: { [key: string]: string } = {},
+                         replacements: { [key: string]: string },
+                         selects: { [key: string]: string[] }): string {
         let arrangementText: string = text;
         let textToReplace: string[] = arrangementText.match(/<([fdts]):([0-9a-zA-Z_\\-]+)>/);
 
         while (textToReplace != null) {
 
-            let replacementValue: string = textToReplace[1] === "t" ? replacements[textToReplace[2]] : placeholderValues[textToReplace[2]];
+            let replacementValue: string = textToReplace[1] === "t" ? replacements[textToReplace[2]] : placeholderValues[textToReplace[0]];
+            replacementValue = textToReplace[1] === "s" ? selects[textToReplace[2]][replacementValue] : replacementValue;
             replacementValue = replacementValue ? replacementValue : "______";
 
             arrangementText = arrangementText.replace(textToReplace[0], replacementValue);
diff --git a/src/app/features/forms/statement-editor/statement-editor.module.ts b/src/app/features/forms/statement-editor/statement-editor.module.ts
index 03208a8..31cd96b 100644
--- a/src/app/features/forms/statement-editor/statement-editor.module.ts
+++ b/src/app/features/forms/statement-editor/statement-editor.module.ts
@@ -17,15 +17,21 @@
 import {ReactiveFormsModule} from "@angular/forms";
 import {MatIconModule} from "@angular/material/icon";
 import {MatSidenavModule} from "@angular/material/sidenav";
+import {TranslateModule} from "@ngx-translate/core";
+import {SelectModule} from "../../../shared/controls/select";
+import {FilePreviewModule} from "../../../shared/file-preview";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
 import {CollapsibleModule} from "../../../shared/layout/collapsible";
 import {GlobalClassToggleModule} from "../../../shared/layout/global-class-toggle";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
 import {SharedPipesModule} from "../../../shared/pipes";
 import {TextBlockModule} from "../../../shared/text-block";
-import {AttachmentsFormModule} from "../attachments";
 import {CombineBlockdataTextPipe} from "../../../shared/text-block/pipes/combine-blockdata-text/combine-blockdata-text.pipe";
+import {AttachmentsFormModule} from "../attachments";
 import {
     ArrangementFormGroupComponent,
     StatementEditorFormComponent,
+    StatementEditorSideMenuComponent,
     StatementPreviewComponent,
     TextBlockControlComponent
 } from "./components";
@@ -43,7 +49,12 @@
         MatIconModule,
         GlobalClassToggleModule,
         MatSidenavModule,
-        AttachmentsFormModule
+        AttachmentsFormModule,
+        SideMenuModule,
+        ActionButtonModule,
+        TranslateModule,
+        FilePreviewModule,
+        SelectModule
     ],
     declarations: [
         StatementEditorFormComponent,
@@ -54,13 +65,15 @@
         ArrangementToPreviewPipe,
         CombineBlockdataTextPipe,
         ArrangementFormGroupComponent,
-        CombineBlockdataTextPipe
+        CombineBlockdataTextPipe,
+        StatementEditorSideMenuComponent
     ],
     exports: [
         StatementEditorFormComponent,
         StatementPreviewComponent,
         ArrangementToPreviewPipe,
-        ArrangementFormGroupComponent
+        ArrangementFormGroupComponent,
+        StatementEditorSideMenuComponent
     ]
 })
 export class StatementEditorModule {
diff --git a/src/app/features/forms/statement-information/components/general-information-form-group/general-information-form-group.component.ts b/src/app/features/forms/statement-information/components/general-information-form-group/general-information-form-group.component.ts
index 2f9e4f9..4089fb5 100644
--- a/src/app/features/forms/statement-information/components/general-information-form-group/general-information-form-group.component.ts
+++ b/src/app/features/forms/statement-information/components/general-information-form-group/general-information-form-group.component.ts
@@ -12,11 +12,10 @@
  ********************************************************************************/
 
 import {Component, Input} from "@angular/core";
-import {FormControl, FormGroup} from "@angular/forms";
+import {FormGroup} from "@angular/forms";
 import {IAPISectorsModel} from "../../../../../core/api/statements/IAPISectorsModel";
 import {ISelectOption} from "../../../../../shared/controls/select/model";
-import {IStatementInformationFormValue} from "../../../../../store/statements/model";
-import {createFormGroup} from "../../../../../util/forms";
+import {createStatementInformationForm} from "../../../../../store/statements/model";
 
 @Component({
     selector: "app-general-information-form-group",
@@ -33,14 +32,7 @@
     public appStatementTypeOptions: ISelectOption[] = [];
 
     @Input()
-    public appFormGroup: FormGroup = createFormGroup<Partial<IStatementInformationFormValue>>({
-        title: new FormControl(),
-        dueDate: new FormControl(),
-        receiptDate: new FormControl(),
-        typeId: new FormControl(),
-        city: new FormControl(),
-        district: new FormControl()
-    });
+    public appFormGroup: FormGroup = createStatementInformationForm();
 
     @Input()
     public appSectors: IAPISectorsModel = {};
diff --git a/src/app/features/forms/statement-information/components/index.ts b/src/app/features/forms/statement-information/components/index.ts
index ab97d26..a0e9dc1 100644
--- a/src/app/features/forms/statement-information/components/index.ts
+++ b/src/app/features/forms/statement-information/components/index.ts
@@ -12,4 +12,5 @@
  ********************************************************************************/
 
 export * from "./general-information-form-group";
+export * from "./side-menu";
 export * from "./statement-information-form";
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/forms/statement-information/components/side-menu/index.ts
similarity index 89%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/forms/statement-information/components/side-menu/index.ts
index aeb78c3..cd8ad34 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/forms/statement-information/components/side-menu/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./statement-information-side-menu.component";
diff --git a/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.html b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.html
new file mode 100644
index 0000000..62f1285
--- /dev/null
+++ b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.html
@@ -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
+ -------------------------------------------------------------------------------->
+
+<ng-container *appSideMenu="'top'; title: (appForNewStatement ? titleNew : title) | translate">
+
+  <app-action-button
+    *ngIf="appForNewStatement"
+    [appDisabled]="appDisabled"
+    [appIcon]="'email'"
+    [appRouterLink]="'/mail'"
+    class="openk-info side-menu-button">
+    {{ "statementInformationForm.sideMenu.backToInbox" | translate }}
+  </app-action-button>
+
+  <app-action-button
+    *ngIf="!appForNewStatement"
+    [appDisabled]="appDisabled"
+    [appIcon]="'arrow_back'"
+    [appRouterLink]="'/details'"
+    [appStatementId]="appStatementId"
+    class="openk-info side-menu-button">
+    {{ "statementInformationForm.sideMenu.backToDetails" | translate }}
+  </app-action-button>
+
+  <app-action-button
+    (appClick)="appSubmit.emit()"
+    *ngIf="!appForNewStatement"
+    [appDisabled]="appDisabled"
+    [appIcon]="'save'"
+    class="openk-info side-menu-button">
+    {{ "statementInformationForm.sideMenu.submit" | translate}}
+  </app-action-button>
+
+</ng-container>
+
+<app-side-menu-status
+  *appSideMenu="'center'"
+  [appLoadingMessage]="'core.submitting' | translate"
+  [appLoading]="appLoading">
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+
+  <app-action-button
+    (appClick)="appSubmit.emit(false)"
+    [appDisabled]="appDisabled"
+    [appIcon]="'edit'"
+    class="openk-info openk-danger side-menu-button">
+    {{ "statementInformationForm.sideMenu.submitAndReject" | translate}}
+  </app-action-button>
+
+  <app-action-button
+    (appClick)="appSubmit.emit(true)"
+    [appDisabled]="appDisabled"
+    [appIcon]="'launch'"
+    class="openk-info openk-success side-menu-button">
+    {{ "statementInformationForm.sideMenu.submitAndComplete" | translate}}
+  </app-action-button>
+
+</ng-container>
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.scss
similarity index 86%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.scss
index 563859b..3796710 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.scss
@@ -11,9 +11,14 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
+:host {
+  display: none;
+}
+
+.side-menu-button {
   width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.spec.ts
similarity index 62%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.spec.ts
index 20e1e36..51244c2 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.spec.ts
@@ -13,22 +13,22 @@
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {I18nModule} from "../../../../../core";
+import {StatementInformationFormModule} from "../../statement-information-form.module";
+import {StatementInformationSideMenuComponent} from "./statement-information-side-menu.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("StatementInformationSideMenuComponent", () => {
+    let component: StatementInformationSideMenuComponent;
+    let fixture: ComponentFixture<StatementInformationSideMenuComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [StatementInformationFormModule, RouterTestingModule, I18nModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(StatementInformationSideMenuComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.ts b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.ts
new file mode 100644
index 0000000..49f03c8
--- /dev/null
+++ b/src/app/features/forms/statement-information/components/side-menu/statement-information-side-menu.component.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 {Component, EventEmitter, Input, Output} from "@angular/core";
+
+@Component({
+    selector: "app-statement-information-side-menu",
+    templateUrl: "./statement-information-side-menu.component.html",
+    styleUrls: ["./statement-information-side-menu.component.scss"]
+})
+export class StatementInformationSideMenuComponent {
+
+    @Input()
+    public appStatementId: number;
+
+    @Input()
+    public appForNewStatement: boolean;
+
+    @Input()
+    public appLoading: boolean;
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Output()
+    public appSubmit = new EventEmitter<void | boolean>();
+
+    public title = "statementInformationForm.sideMenu.title";
+
+    public titleNew = "statementInformationForm.sideMenu.titleNew";
+
+}
diff --git a/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.html b/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.html
index cbf739b..0e06839 100644
--- a/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.html
+++ b/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.html
@@ -11,10 +11,18 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
+<app-statement-information-side-menu
+  (appSubmit)="submit($event)"
+  [appDisabled]="appFormGroup.disabled"
+  [appForNewStatement]="appForNewStatement"
+  [appLoading]="statementLoading$ | async"
+  [appStatementId]="(task$ | async)?.statementId">
+
+</app-statement-information-side-menu>
+
 <div [formGroup]="appFormGroup" class="info-form">
 
   <app-collapsible
-    [appCollapsed]="false"
     [appTitle]="'statementInformationForm.container.general' | translate">
 
     <app-general-information-form-group
@@ -26,7 +34,6 @@
   </app-collapsible>
 
   <app-collapsible
-    [appCollapsed]="false"
     [appTitle]="'statementInformationForm.container.contact' | translate">
 
     <app-contact-select
@@ -46,7 +53,6 @@
   </app-collapsible>
 
   <app-collapsible
-    [appCollapsed]="false"
     [appTitle]="'statementInformationForm.container.inboxAttachments' | translate">
 
     <app-attachments-form-group
@@ -59,29 +65,3 @@
   <ng-content></ng-content>
 
 </div>
-
-<div class="form-actions">
-
-  <button (click)="submit(false)"
-          [disabled]="appFormGroup.disabled"
-          class="openk-button openk-danger form-actions--button">
-    <mat-icon>redo</mat-icon>
-    {{ "statementInformationForm.submitAndReject" | translate}}
-  </button>
-
-  <button (click)="submit()"
-          *ngIf="!appForNewStatement"
-          [disabled]="appFormGroup.disabled"
-          class="openk-button openk-info form-actions--button">
-    <mat-icon>redo</mat-icon>
-    {{ "statementInformationForm.submit" | translate}}
-  </button>
-
-  <button (click)="submit(true)"
-          [disabled]="appFormGroup.disabled"
-          class="openk-button openk-success form-actions--button">
-    <mat-icon>redo</mat-icon>
-    {{ "statementInformationForm.submitAndComplete" | translate}}
-  </button>
-
-</div>
diff --git a/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.scss b/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.scss
index 25dec60..48301e9 100644
--- a/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.scss
+++ b/src/app/features/forms/statement-information/components/statement-information-form/statement-information-form.component.scss
@@ -33,24 +33,6 @@
   box-sizing: border-box;
 }
 
-.form-actions {
-  margin-top: 1em;
-  display: flex;
-  width: 100%;
-  justify-content: flex-end;
-  align-items: flex-start;
-}
-
-.form-actions--button {
-  margin-left: 1em;
-  min-width: 14.5em;
-  display: flex;
-
-  &:first-child {
-    margin: 0;
-  }
-}
-
 .attachments {
   display: flex;
   flex-flow: row wrap;
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 72bfcd1..05fbe60 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
@@ -12,30 +12,29 @@
  ********************************************************************************/
 
 import {Component, Input, OnInit} from "@angular/core";
-import {FormControl, Validators} from "@angular/forms";
 import {select, Store} from "@ngrx/store";
 import {concat, defer, of} from "rxjs";
 import {distinctUntilChanged, filter, map, switchMap, take, takeUntil} from "rxjs/operators";
-import {EAPIStaticAttachmentTagIds, IAPISearchOptions} from "../../../../../core";
+import {AUTO_SELECTED_TAGS, IAPISearchOptions} from "../../../../../core";
 import {
+    createStatementInformationForm,
     fetchContactDetailsAction,
     fetchSettingsAction,
     getContactDetailsSelector,
     getContactLoadingSelector,
     getContactSearchContentSelector,
     getContactSearchSelector,
-    getStatementLoadingSelector,
     getStatementSectorsSelector,
-    IAttachmentFormValue,
     IStatementInformationFormValue,
     openContactDataBaseAction,
     startContactSearchAction,
     statementInformationFormValueSelector,
+    statementLoadingSelector,
     statementTypesSelector,
     submitStatementInformationFormAction,
     taskSelector
 } from "../../../../../store";
-import {arrayJoin, createFormGroup} from "../../../../../util";
+import {arrayJoin} from "../../../../../util";
 import {AbstractReactiveFormComponent} from "../../../abstract";
 
 @Component({
@@ -46,18 +45,12 @@
 export class StatementInformationFormComponent extends AbstractReactiveFormComponent<IStatementInformationFormValue> implements OnInit {
 
     @Input()
-    public appForbiddenTagIds = [
-        EAPIStaticAttachmentTagIds.CONSIDERATION,
-        EAPIStaticAttachmentTagIds.STATEMENT,
-        EAPIStaticAttachmentTagIds.OUTBOX,
-        EAPIStaticAttachmentTagIds.EMAIL,
-        EAPIStaticAttachmentTagIds.EMAIL_TEXT
-    ];
+    public appForbiddenTagIds = AUTO_SELECTED_TAGS;
 
     @Input()
     public appForNewStatement: boolean;
 
-    public statementLoading$ = this.store.pipe(select(getStatementLoadingSelector));
+    public statementLoading$ = this.store.pipe(select(statementLoadingSelector));
 
     public task$ = this.store.pipe(select(taskSelector));
 
@@ -77,19 +70,7 @@
 
     public searchText: string;
 
-    public appFormGroup = createFormGroup<IStatementInformationFormValue>({
-        title: new FormControl(undefined, [Validators.required]),
-        dueDate: new FormControl(undefined, [Validators.required]),
-        receiptDate: new FormControl(undefined, [Validators.required]),
-        typeId: new FormControl(undefined, [Validators.required]),
-        city: new FormControl(undefined, [Validators.required]),
-        district: new FormControl(undefined, [Validators.required]),
-        contactId: new FormControl(undefined, [Validators.required]),
-        attachments: createFormGroup<IAttachmentFormValue>({
-            add: new FormControl([]),
-            edit: new FormControl([])
-        })
-    });
+    public appFormGroup = createStatementInformationForm();
 
     public selectedContactId$ = defer(() => concat(of(null), this.appFormGroup.valueChanges)).pipe(
         map(() => this.getValue().contactId)
@@ -170,10 +151,12 @@
     }
 
     private updateForm() {
-        this.form$.pipe(takeUntil(this.destroy$), filter(() => !this.appForNewStatement))
-            .subscribe((value) => this.patchValue(value));
-        this.statementLoading$.pipe(takeUntil(this.destroy$))
-            .subscribe((loading) => loading?.submittingStatementInformation ? this.appFormGroup.disable() : this.appFormGroup.enable());
+        this.form$.pipe(takeUntil(this.destroy$), filter(() => !this.appForNewStatement)).subscribe((value) => {
+            this.patchValue(value);
+        });
+        this.statementLoading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => {
+            loading ? this.appFormGroup.disable() : this.appFormGroup.enable();
+        });
     }
 
 }
diff --git a/src/app/features/forms/statement-information/statement-information-form.module.ts b/src/app/features/forms/statement-information/statement-information-form.module.ts
index 01e558f..703aab9 100644
--- a/src/app/features/forms/statement-information/statement-information-form.module.ts
+++ b/src/app/features/forms/statement-information/statement-information-form.module.ts
@@ -19,14 +19,15 @@
 import {CommonControlsModule} from "../../../shared/controls/common";
 import {ContactSelectModule} from "../../../shared/controls/contact-select";
 import {DateControlModule} from "../../../shared/controls/date-control";
-import {FileDropModule} from "../../../shared/controls/file-drop";
-import {FileSelectModule} from "../../../shared/controls/file-select";
 import {SelectModule} from "../../../shared/controls/select";
+import {ActionButtonModule} from "../../../shared/layout/action-button";
 import {CollapsibleModule} from "../../../shared/layout/collapsible";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
 import {SharedPipesModule} from "../../../shared/pipes";
+import {ProgressSpinnerModule} from "../../../shared/progress-spinner";
 import {AttachmentsFormModule} from "../attachments";
 import {CommentsFormModule} from "../comments";
-import {GeneralInformationFormGroupComponent, StatementInformationFormComponent} from "./components";
+import {GeneralInformationFormGroupComponent, StatementInformationFormComponent, StatementInformationSideMenuComponent} from "./components";
 import {SectorPipe} from "./pipes/sector.pipe";
 
 @NgModule({
@@ -40,21 +41,24 @@
         CollapsibleModule,
         SelectModule,
         DateControlModule,
-        FileDropModule,
         CommonControlsModule,
         ContactSelectModule,
-        FileSelectModule,
         AttachmentsFormModule,
-        SharedPipesModule
+        SharedPipesModule,
+        SideMenuModule,
+        ProgressSpinnerModule,
+        ActionButtonModule
     ],
     declarations: [
         StatementInformationFormComponent,
         GeneralInformationFormGroupComponent,
+        StatementInformationSideMenuComponent,
         SectorPipe
     ],
     exports: [
         StatementInformationFormComponent,
         GeneralInformationFormGroupComponent,
+        StatementInformationSideMenuComponent,
         SectorPipe
     ]
 })
diff --git a/src/app/features/forms/workflow-data/components/index.ts b/src/app/features/forms/workflow-data/components/index.ts
index 9f0f79a..60a5441 100644
--- a/src/app/features/forms/workflow-data/components/index.ts
+++ b/src/app/features/forms/workflow-data/components/index.ts
@@ -11,4 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./side-menu";
+
 export * from "./workflow-data-form.component";
diff --git a/src/app/features/details/selectors/index.ts b/src/app/features/forms/workflow-data/components/side-menu/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/features/forms/workflow-data/components/side-menu/index.ts
index aeb78c3..c67f21a 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/features/forms/workflow-data/components/side-menu/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./workflow-data-side-menu.component";
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
new file mode 100644
index 0000000..41972c8
--- /dev/null
+++ b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.html
@@ -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
+ -------------------------------------------------------------------------------->
+
+<ng-container *appSideMenu="'top'; title: 'workflowDataForm.sideMenu.title' | translate">
+
+  <app-action-button
+    [appDisabled]="appDisabled"
+    [appIcon]="'arrow_back'"
+    [appRouterLink]="'/details'"
+    [appStatementId]="appStatementId"
+    class="openk-info side-menu-button">
+    {{ "workflowDataForm.sideMenu.backToDetails" | translate }}
+  </app-action-button>
+
+  <app-action-button
+    (appClick)="appSubmit.emit(false)"
+    [appDisabled]="appDisabled"
+    [appIcon]="'save'"
+    class="openk-info side-menu-button">
+    {{ "workflowDataForm.sideMenu.submit" | translate}}
+  </app-action-button>
+
+</ng-container>
+
+<app-side-menu-status
+  *appSideMenu="'center'"
+  [appLoadingMessage]="'core.submitting' | translate"
+  [appLoading]="appLoading">
+</app-side-menu-status>
+
+<ng-container *appSideMenu="'bottom'">
+
+  <app-action-button
+    (appClick)="appSubmit.emit(true)"
+    [appDisabled]="appDisabled"
+    [appIcon]="'launch'"
+    class="openk-info openk-success side-menu-button">
+    {{ "workflowDataForm.sideMenu.submitAndComplete" | translate}}
+  </app-action-button>
+
+</ng-container>
+
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.scss
similarity index 86%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.scss
index 563859b..3796710 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.scss
@@ -11,9 +11,14 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
+:host {
+  display: none;
+}
+
+.side-menu-button {
   width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+
+  & + & {
+    margin-top: 1em;
+  }
 }
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.spec.ts
similarity index 64%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.spec.ts
index 20e1e36..958f602 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.spec.ts
@@ -12,23 +12,22 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {I18nModule} from "../../../../../core/i18n";
+import {WorkflowDataFormModule} from "../../workflow-data-form.module";
+import {WorkflowDataSideMenuComponent} from "./workflow-data-side-menu.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("WorkflowDataSideMenuComponent", () => {
+    let component: WorkflowDataSideMenuComponent;
+    let fixture: ComponentFixture<WorkflowDataSideMenuComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [WorkflowDataFormModule, I18nModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(WorkflowDataSideMenuComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.ts b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.component.ts
new file mode 100644
index 0000000..649cfb5
--- /dev/null
+++ b/src/app/features/forms/workflow-data/components/side-menu/workflow-data-side-menu.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-workflow-data-side-menu",
+    templateUrl: "./workflow-data-side-menu.component.html",
+    styleUrls: ["./workflow-data-side-menu.component.scss"]
+})
+export class WorkflowDataSideMenuComponent {
+
+    @Input()
+    public appStatementId: number;
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appLoading: boolean;
+
+    @Output()
+    public appSubmit = new EventEmitter<boolean>();
+
+}
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 12a5cdf..89c6a94 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
@@ -11,21 +11,39 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
+<app-workflow-data-side-menu
+  (appSubmit)="submit($event)"
+  [appDisabled]="appFormGroup.disabled"
+  [appLoading]="isStatementLoading$ | async"
+  [appStatementId]="(task$ | async)?.statementId">
+
+</app-workflow-data-side-menu>
+
+
 <ng-container [formGroup]="appFormGroup">
 
   <app-collapsible
     [appCollapsed]="true"
     [appTitle]="'workflowDataForm.container.general' | translate">
+
+    <div style="padding: 1em;"> Not yet implemented.</div>
+
   </app-collapsible>
 
   <app-collapsible
     [appCollapsed]="true"
     [appTitle]="'workflowDataForm.container.inboxAttachments' | translate">
+
+    <div style="padding: 1em;"> Not yet implemented.</div>
+
   </app-collapsible>
 
   <app-collapsible
     [appCollapsed]="true"
     [appTitle]="'workflowDataForm.container.geographicPosition' | translate">
+
+    <div style="padding: 1em;"> Not yet implemented.</div>
+
   </app-collapsible>
 
   <app-collapsible
@@ -35,8 +53,8 @@
       [appGroups]="departmentGroups$ | async"
       [appOptions]="departmentOptions$ | async"
       [formControlName]="'departments'"
+      [appIndeterminate]="true"
       class="departments">
-
     </app-select-group>
 
   </app-collapsible>
@@ -47,7 +65,7 @@
 
     <app-statement-select
       (appSearch)="search($event)"
-      [appIsLoading]="(isStatementLoading$ | async)?.search"
+      [appIsLoading]="isStatementLoading$ | async"
       [appSearchContent]="searchContent$ | async"
       [appStatementTypeOptions]="statementTypes$ | async"
       [formControlName]="'parentIds'"
@@ -59,23 +77,4 @@
   <ng-content>
   </ng-content>
 
-  <div class="form-actions">
-
-    <button (click)="submit(false)"
-            [disabled]="appFormGroup.disabled"
-            class="openk-button openk-info form-actions--button"
-            type="button">
-      <mat-icon>redo</mat-icon>
-      {{'workflowDataForm.submit' | translate}}
-    </button>
-
-    <button (click)="submit(true)"
-            [disabled]="appFormGroup.disabled"
-            class="openk-button openk-success form-actions--button"
-            type="button">
-      <mat-icon>redo</mat-icon>
-      {{'workflowDataForm.submitAndComplete' | translate}}
-    </button>
-  </div>
-
 </ng-container>
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 f6d60db..7964991 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
@@ -61,7 +61,10 @@
         const dispatchSpy = spyOn(mockStore, "dispatch");
         const value: IWorkflowFormValue = {
             geographicPosition: "1919",
-            departments: [],
+            departments: {
+                selected: [],
+                indeterminate: []
+            },
             parentIds: [19, 199]
         };
         const formMock = {value} as FormGroup;
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 b976857..6a39dd9 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
@@ -12,23 +12,22 @@
  ********************************************************************************/
 
 import {Component, OnInit} from "@angular/core";
-import {FormControl} from "@angular/forms";
 import {select, Store} from "@ngrx/store";
-import {take, takeUntil} from "rxjs/operators";
+import {distinctUntilChanged, take, takeUntil} from "rxjs/operators";
 import {IAPISearchOptions} from "../../../../core/api";
 import {
+    createWorkflowForm,
     departmentGroupsSelector,
     departmentOptionsSelector,
     getSearchContentStatementsSelector,
-    getStatementLoadingSelector,
     IWorkflowFormValue,
     startStatementSearchAction,
+    statementLoadingSelector,
     statementTypesSelector,
     submitWorkflowDataFormAction,
     taskSelector,
     workflowFormValueSelector
 } from "../../../../store";
-import {createFormGroup} from "../../../../util";
 import {AbstractReactiveFormComponent} from "../../abstract";
 
 @Component({
@@ -48,13 +47,9 @@
 
     public departmentGroups$ = this.store.pipe(select(departmentGroupsSelector));
 
-    public isStatementLoading$ = this.store.pipe(select(getStatementLoadingSelector));
+    public isStatementLoading$ = this.store.pipe(select(statementLoadingSelector));
 
-    public appFormGroup = createFormGroup<IWorkflowFormValue>({
-        departments: new FormControl(),
-        geographicPosition: new FormControl(),
-        parentIds: new FormControl()
-    });
+    public appFormGroup = createWorkflowForm();
 
     private form$ = this.store.pipe(select(workflowFormValueSelector));
 
@@ -63,7 +58,9 @@
     }
 
     public ngOnInit() {
-        this.patchValue({geographicPosition: "", departments: [], parentIds: []});
+        this.patchValue({geographicPosition: "", departments: {selected: [], indeterminate: []}, parentIds: []});
+        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));
         this.task$.pipe(takeUntil(this.destroy$))
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 b8d5c76..44ec32f 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
@@ -10,6 +10,7 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
 import {ReactiveFormsModule} from "@angular/forms";
@@ -17,8 +18,10 @@
 import {TranslateModule} from "@ngx-translate/core";
 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 {WorkflowDataFormComponent} from "./components";
+import {SideMenuModule} from "../../../shared/layout/side-menu";
+import {WorkflowDataFormComponent, WorkflowDataSideMenuComponent} from "./components";
 
 @NgModule({
     imports: [
@@ -29,13 +32,17 @@
 
         CollapsibleModule,
         SelectModule,
-        StatementSelectModule
+        StatementSelectModule,
+        SideMenuModule,
+        ActionButtonModule
     ],
     declarations: [
-        WorkflowDataFormComponent
+        WorkflowDataFormComponent,
+        WorkflowDataSideMenuComponent
     ],
     exports: [
-        WorkflowDataFormComponent
+        WorkflowDataFormComponent,
+        WorkflowDataSideMenuComponent
     ]
 })
 export class WorkflowDataFormModule {
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/mail/components/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/mail/components/index.ts
index f223969..710dc8a 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/mail/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./mail";
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/mail/components/mail/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/mail/components/mail/index.ts
index f223969..8d5d21d 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/mail/components/mail/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./mail.component";
diff --git a/src/app/shared/controls/text-field/text-field.component.html b/src/app/features/mail/components/mail/mail.component.html
similarity index 72%
copy from src/app/shared/controls/text-field/text-field.component.html
copy to src/app/features/mail/components/mail/mail.component.html
index 129e808..8982b11 100644
--- a/src/app/shared/controls/text-field/text-field.component.html
+++ b/src/app/features/mail/components/mail/mail.component.html
@@ -11,10 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<textarea #inputElement
-          (focusout)="onFocusOut()"
-          (input)="onInput(inputElement.value)"
-          [value]="appValue"
-          class="openk-textarea text-input"
-          rows="1">
-</textarea>
+<div *appSideMenu="'top'; left: true; title: 'Posteingang'">
+  <div>
+    Not yet implemented.
+  </div>
+</div>
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/mail/components/mail/mail.component.scss
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/mail/components/mail/mail.component.scss
index f223969..06db89a 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/mail/components/mail/mail.component.scss
@@ -11,4 +11,3 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/mail/components/mail/mail.component.spec.ts
similarity index 65%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/mail/components/mail/mail.component.spec.ts
index 20e1e36..b8f4c49 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/mail/components/mail/mail.component.spec.ts
@@ -12,23 +12,22 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {SideMenuModule} from "../../../../shared/layout/side-menu";
+import {MailComponent} from "./mail.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("MailComponent", () => {
+    let component: MailComponent;
+    let fixture: ComponentFixture<MailComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            declarations: [MailComponent],
+            imports: [SideMenuModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(MailComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/mail/components/mail/mail.component.ts
similarity index 73%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/features/mail/components/mail/mail.component.ts
index 563859b..7f248f6 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/mail/components/mail/mail.component.ts
@@ -11,9 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+import {Component} from "@angular/core";
+
+@Component({
+    selector: "app-mail",
+    templateUrl: "./mail.component.html",
+    styleUrls: ["./mail.component.scss"]
+})
+export class MailComponent {
+
 }
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/mail/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/mail/index.ts
index f223969..1c09caf 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/mail/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./mail.module";
diff --git a/src/app/shared/controls/text-field/text-field.module.ts b/src/app/features/mail/mail-routing.module.ts
similarity index 63%
copy from src/app/shared/controls/text-field/text-field.module.ts
copy to src/app/features/mail/mail-routing.module.ts
index d4a7bef..4b28b15 100644
--- a/src/app/shared/controls/text-field/text-field.module.ts
+++ b/src/app/features/mail/mail-routing.module.ts
@@ -11,21 +11,28 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {TextFieldComponent} from "./text-field.component";
+import {RouterModule, Routes} from "@angular/router";
+import {MailComponent} from "./components";
+import {MailModule} from "./mail.module";
+
+const routes: Routes = [
+    {
+        path: "",
+        pathMatch: "full",
+        component: MailComponent
+    }
+];
 
 @NgModule({
     imports: [
-        CommonModule
-    ],
-    declarations: [
-        TextFieldComponent
+        MailModule,
+        RouterModule.forChild(routes)
     ],
     exports: [
-        TextFieldComponent
+        RouterModule
     ]
 })
-export class TextInputFieldModule {
+export class MailRoutingModule {
 
 }
diff --git a/src/app/shared/controls/text-field/text-field.module.ts b/src/app/features/mail/mail.module.ts
similarity index 76%
copy from src/app/shared/controls/text-field/text-field.module.ts
copy to src/app/features/mail/mail.module.ts
index d4a7bef..b20203d 100644
--- a/src/app/shared/controls/text-field/text-field.module.ts
+++ b/src/app/features/mail/mail.module.ts
@@ -13,19 +13,21 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {TextFieldComponent} from "./text-field.component";
+import {SideMenuModule} from "../../shared/layout/side-menu";
+import {MailComponent} from "./components";
 
 @NgModule({
     imports: [
-        CommonModule
+        CommonModule,
+        SideMenuModule
     ],
     declarations: [
-        TextFieldComponent
+        MailComponent
     ],
     exports: [
-        TextFieldComponent
+        MailComponent
     ]
 })
-export class TextInputFieldModule {
+export class MailModule {
 
 }
diff --git a/src/app/features/navigation/app-navigation-frame.module.ts b/src/app/features/navigation/app-navigation-frame.module.ts
index da73fea..ff5691e 100644
--- a/src/app/features/navigation/app-navigation-frame.module.ts
+++ b/src/app/features/navigation/app-navigation-frame.module.ts
@@ -18,6 +18,7 @@
 import {RouterModule} from "@angular/router";
 import {TranslateModule} from "@ngx-translate/core";
 import {DropDownModule} from "../../shared/layout/drop-down";
+import {SideMenuModule} from "../../shared/layout/side-menu";
 import {ExitPageComponent, NavDropDownComponent, NavFrameComponent, NavHeaderComponent, NavigationComponent} from "./components";
 
 @NgModule({
@@ -28,7 +29,8 @@
         TranslateModule,
 
         DropDownModule,
-        ScrollingModule
+        ScrollingModule,
+        SideMenuModule
     ],
     declarations: [
         ExitPageComponent,
diff --git a/src/app/features/navigation/components/nav-frame/nav-frame.component.html b/src/app/features/navigation/components/nav-frame/nav-frame.component.html
index ce21c8a..0c66802 100644
--- a/src/app/features/navigation/components/nav-frame/nav-frame.component.html
+++ b/src/app/features/navigation/components/nav-frame/nav-frame.component.html
@@ -13,11 +13,13 @@
 
 <app-nav-header
   (appLogout)="appLogout.emit()"
+  [appCurrentUrl]="appCurrentUrl"
   [appMenuHidden]="appMenuHidden"
-  [appUserName]="appUserName">
+  [appUserName]="appUserName"
+  [appUserRoles]="appUserRoles">
 </app-nav-header>
 
-<div cdk-scrollable class="nav-frame-content">
+<app-side-menu-container cdk-scrollable class="nav-frame-content">
 
   <div class="nav-frame-content-main">
 
@@ -43,4 +45,4 @@
     {{ 'core.version' | translate: {version: appVersion} }}
   </div>
 
-</div>
+</app-side-menu-container>
diff --git a/src/app/features/navigation/components/nav-frame/nav-frame.component.scss b/src/app/features/navigation/components/nav-frame/nav-frame.component.scss
index b7ad9c5..2458004 100644
--- a/src/app/features/navigation/components/nav-frame/nav-frame.component.scss
+++ b/src/app/features/navigation/components/nav-frame/nav-frame.component.scss
@@ -22,10 +22,6 @@
   flex: 1 1 100%;
   height: 100%;
   width: 100%;
-  display: flex;
-  flex-flow: column;
-  overflow-x: auto;
-  overflow-y: scroll;
 }
 
 .nav-frame-content-main {
diff --git a/src/app/features/navigation/components/nav-frame/nav-frame.component.ts b/src/app/features/navigation/components/nav-frame/nav-frame.component.ts
index cebeb90..a23fa41 100644
--- a/src/app/features/navigation/components/nav-frame/nav-frame.component.ts
+++ b/src/app/features/navigation/components/nav-frame/nav-frame.component.ts
@@ -12,6 +12,7 @@
  ********************************************************************************/
 
 import {Component, EventEmitter, Input, Output} from "@angular/core";
+import {EAPIUserRoles} from "../../../../core/api/core";
 import {EExitCode} from "../../../../store";
 
 @Component({
@@ -22,6 +23,9 @@
 export class NavFrameComponent {
 
     @Input()
+    public appCurrentUrl: string;
+
+    @Input()
     public appExitCode: EExitCode;
 
     @Input()
@@ -37,6 +41,9 @@
     public appUserName: string;
 
     @Input()
+    public appUserRoles: EAPIUserRoles[];
+
+    @Input()
     public appVersion: string;
 
     @Output()
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 dcc5ddc..819c91d 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
@@ -31,7 +31,21 @@
     </a>
 
     <div *ngIf="!appMenuHidden" class="nav-header-menu">
-      <app-nav-drop-down (appLogOut)="logOut()">
+
+      <ng-container *ngFor="let route of appRoutes">
+        <a *ngIf="isLinkDisplayed(route.roles)"
+           [class.nav-header-menu-anchor---large-icon]="route.largeIcon"
+           [class.openk-info]="!isLinkActive(route.link, route.exact)"
+           [class.openk-primary]="isLinkActive(route.link, route.exact)"
+           [routerLink]="route.link"
+           [target]="route.target"
+           class="openk-button openk-button-rounded nav-header-menu-anchor">
+          <mat-icon>{{route.icon}}</mat-icon>
+        </a>
+      </ng-container>
+
+      <app-nav-drop-down
+        (appLogOut)="logOut()">
         {{appUserName}}
       </app-nav-drop-down>
     </div>
diff --git a/src/app/features/navigation/components/nav-header/nav-header.component.scss b/src/app/features/navigation/components/nav-header/nav-header.component.scss
index 9f3eb62..9a0945c 100644
--- a/src/app/features/navigation/components/nav-header/nav-header.component.scss
+++ b/src/app/features/navigation/components/nav-header/nav-header.component.scss
@@ -28,6 +28,14 @@
   background: $openk-header-gradient;
 }
 
+.nav-header-menu-anchor {
+  margin-right: 0.5em;
+}
+
+.nav-header-menu-anchor---large-icon {
+  --icon-scale-factor: 0.9;
+}
+
 .nav-header-title {
   text-decoration: none !important;
   outline: none;
diff --git a/src/app/features/navigation/components/nav-header/nav-header.component.spec.ts b/src/app/features/navigation/components/nav-header/nav-header.component.spec.ts
index 191a9f6..1dcc190 100644
--- a/src/app/features/navigation/components/nav-header/nav-header.component.spec.ts
+++ b/src/app/features/navigation/components/nav-header/nav-header.component.spec.ts
@@ -13,6 +13,7 @@
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
 import {RouterTestingModule} from "@angular/router/testing";
+import {EAPIUserRoles} from "../../../../core";
 import {AppNavigationFrameModule} from "../../app-navigation-frame.module";
 import {NavHeaderComponent} from "./nav-header.component";
 
@@ -36,5 +37,23 @@
     it("should create", () => {
         expect(component).toBeTruthy();
     });
+
+    it("should check if link is active", () => {
+        expect(component.isLinkActive("/new")).toBe(false);
+        component.appCurrentUrl = "/new";
+        expect(component.isLinkActive("/new")).toBe(true);
+        expect(component.isLinkActive("/")).toBe(true);
+        expect(component.isLinkActive("/", true)).toBe(false);
+    });
+
+    it("should check if link is displayed", () => {
+        expect(component.isLinkDisplayed(null)).toBe(true);
+        expect(component.isLinkDisplayed([])).toBe(true);
+        component.appUserRoles = [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE];
+        expect(component.isLinkDisplayed([])).toBe(true);
+        expect(component.isLinkDisplayed([EAPIUserRoles.SPA_APPROVER])).toBe(false);
+        expect(component.isLinkDisplayed([EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE])).toBe(true);
+    });
+
 });
 
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 950a87c..7b90b36 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,26 +11,90 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {Component, EventEmitter, Input, Output} from "@angular/core";
+import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from "@angular/core";
+import {EAPIUserRoles} from "../../../../core";
+import {arrayJoin} from "../../../../util/store";
+
+export interface INavHeaderRoute {
+    icon: string;
+    link: string;
+    exact?: boolean;
+    roles?: EAPIUserRoles[];
+    target?: string;
+    largeIcon?: boolean;
+}
 
 @Component({
     selector: "app-nav-header",
     templateUrl: "./nav-header.component.html",
-    styleUrls: ["./nav-header.component.scss"]
+    styleUrls: ["./nav-header.component.scss"],
+    changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class NavHeaderComponent {
 
     @Input()
+    public appCurrentUrl: string;
+
+    @Input()
     public appMenuHidden = false;
 
     @Input()
     public appUserName: string;
 
+    @Input()
+    public appUserRoles: EAPIUserRoles[];
+
     @Output()
     public readonly appLogout = new EventEmitter<void>();
 
+    public appRoutes: INavHeaderRoute[] = [
+        {
+            icon: "home",
+            link: "/",
+            exact: true
+        },
+        {
+            icon: "find_in_page",
+            link: "/search"
+        },
+        {
+            icon: "email",
+            link: "/mail",
+            roles: [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE]
+        },
+        {
+            icon: "note_add",
+            link: "/new",
+            roles: [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE]
+        },
+        {
+            icon: "settings",
+            link: "/settings",
+            roles: [EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE]
+        },
+        {
+            icon: "help_outline",
+            link: "/help",
+            target: "_blank",
+            largeIcon: true
+        }
+    ];
+
     public logOut() {
         this.appLogout.emit();
     }
 
+    public isLinkDisplayed(roles: EAPIUserRoles[]) {
+        return arrayJoin(roles).length === 0 || roles.some((_) => arrayJoin(this.appUserRoles).indexOf(_) > -1);
+    }
+
+    public isLinkActive(link: string, exact?: boolean) {
+        if (this.appCurrentUrl == null) {
+            return false;
+        }
+        const currentUrl = this.appCurrentUrl.toLowerCase();
+        link = link.toLowerCase();
+        return exact ? currentUrl === link : currentUrl.startsWith(link);
+    }
+
 }
diff --git a/src/app/features/navigation/components/navigation/navigation.component.html b/src/app/features/navigation/components/navigation/navigation.component.html
index 39feeb3..abe945e 100644
--- a/src/app/features/navigation/components/navigation/navigation.component.html
+++ b/src/app/features/navigation/components/navigation/navigation.component.html
@@ -14,11 +14,13 @@
 <app-nav-frame
   (appCloseWindow)="closeWindow()"
   (appLogout)="logOut()"
+  [appCurrentUrl]="router.url"
   [appExitCode]="exitCode$ | async"
   [appIsLoading]="isLoading$ | async"
   [appMenuHidden]="(isLoggedIn$ | async) !== true"
   [appPortalRoute]="appPortalRoute"
   [appUserName]="userName$ | async"
+  [appUserRoles]="userRoles$ | async"
   [appVersion]="version$ | async">
 
   <ng-content></ng-content>
diff --git a/src/app/features/navigation/components/navigation/navigation.component.ts b/src/app/features/navigation/components/navigation/navigation.component.ts
index 6810630..b609249 100644
--- a/src/app/features/navigation/components/navigation/navigation.component.ts
+++ b/src/app/features/navigation/components/navigation/navigation.component.ts
@@ -12,6 +12,7 @@
  ********************************************************************************/
 
 import {Component, Inject} from "@angular/core";
+import {Router} from "@angular/router";
 import {select, Store} from "@ngrx/store";
 import {PORTAL_ROUTE, WINDOW} from "../../../../core";
 import {
@@ -20,7 +21,8 @@
     isLoggedInSelector,
     logOutAction,
     overallVersionSelector,
-    userNameSelector
+    userFullNameSelector,
+    userRolesSelector
 } from "../../../../store";
 
 @Component({
@@ -36,12 +38,15 @@
 
     public isLoggedIn$ = this.store.pipe(select(isLoggedInSelector));
 
-    public userName$ = this.store.pipe(select(userNameSelector));
+    public userName$ = this.store.pipe(select(userFullNameSelector));
+
+    public userRoles$ = this.store.pipe(select(userRolesSelector));
 
     public version$ = this.store.pipe(select(overallVersionSelector));
 
     public constructor(
         public readonly store: Store,
+        public readonly router: Router,
         @Inject(WINDOW) public readonly window: Window,
         @Inject(PORTAL_ROUTE) public readonly appPortalRoute: string
     ) {
diff --git a/src/app/features/new/components/new-statement/new-statement.component.html b/src/app/features/new/components/new-statement/new-statement.component.html
index 7ce4251..d060e4c 100644
--- a/src/app/features/new/components/new-statement/new-statement.component.html
+++ b/src/app/features/new/components/new-statement/new-statement.component.html
@@ -11,11 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<app-page-header
-  [appActions]="pageHeaderActions"
-  [appTitle]="'statementInformationForm.titleNew'">
-</app-page-header>
-
 <app-statement-information-form
   [appForNewStatement]="true">
 </app-statement-information-form>
diff --git a/src/app/features/new/components/new-statement/new-statement.component.spec.ts b/src/app/features/new/components/new-statement/new-statement.component.spec.ts
index ff2dd26..e4f6812 100644
--- a/src/app/features/new/components/new-statement/new-statement.component.spec.ts
+++ b/src/app/features/new/components/new-statement/new-statement.component.spec.ts
@@ -15,7 +15,6 @@
 import {RouterTestingModule} from "@angular/router/testing";
 import {provideMockStore} from "@ngrx/store/testing";
 import {I18nModule} from "../../../../core";
-import {PageHeaderModule} from "../../../../shared/layout/page-header";
 import {StatementInformationFormModule} from "../../../forms/statement-information";
 import {NewStatementComponent} from "./new-statement.component";
 
@@ -32,7 +31,6 @@
             imports: [
                 I18nModule,
                 RouterTestingModule,
-                PageHeaderModule,
                 StatementInformationFormModule
             ],
             providers: [
diff --git a/src/app/features/new/components/new-statement/new-statement.component.ts b/src/app/features/new/components/new-statement/new-statement.component.ts
index a1ae176..e48369c 100644
--- a/src/app/features/new/components/new-statement/new-statement.component.ts
+++ b/src/app/features/new/components/new-statement/new-statement.component.ts
@@ -12,7 +12,6 @@
  ********************************************************************************/
 
 import {Component} from "@angular/core";
-import {IPageHeaderAction} from "../../../../shared/layout/page-header";
 
 @Component({
     selector: "app-new-statement",
@@ -21,12 +20,4 @@
 })
 export class NewStatementComponent {
 
-    public readonly pageHeaderActions: IPageHeaderAction[] = [
-        {
-            name: "core.actions.backToDashboard",
-            icon: "arrow_back",
-            routerLink: "/"
-        }
-    ];
-
 }
diff --git a/src/app/features/new/new-statement-routing.module.ts b/src/app/features/new/new-statement-routing.module.ts
index dff6ea7..96b36bb 100644
--- a/src/app/features/new/new-statement-routing.module.ts
+++ b/src/app/features/new/new-statement-routing.module.ts
@@ -14,6 +14,7 @@
 import {NgModule} from "@angular/core";
 import {RouterModule, Routes} from "@angular/router";
 import {NewStatementComponent} from "./components";
+import {NewStatementModule} from "./new-statement.module";
 import {NewStatementRouteGuardService} from "./services/new-statement-route-guard.service";
 
 const routes: Routes = [
@@ -27,6 +28,7 @@
 
 @NgModule({
     imports: [
+        NewStatementModule,
         RouterModule.forChild(routes)
     ],
     exports: [
diff --git a/src/app/features/new/new-statement.module.ts b/src/app/features/new/new-statement.module.ts
index 5013826..117f972 100644
--- a/src/app/features/new/new-statement.module.ts
+++ b/src/app/features/new/new-statement.module.ts
@@ -12,15 +12,11 @@
  ********************************************************************************/
 
 import {NgModule} from "@angular/core";
-import {PageHeaderModule} from "../../shared/layout/page-header";
 import {StatementInformationFormModule} from "../forms/statement-information";
 import {NewStatementComponent} from "./components";
-import {NewStatementRoutingModule} from "./new-statement-routing.module";
 
 @NgModule({
     imports: [
-        NewStatementRoutingModule,
-        PageHeaderModule,
         StatementInformationFormModule
     ],
     declarations: [
diff --git a/src/app/features/new/services/new-statement-route-guard.service.spec.ts b/src/app/features/new/services/new-statement-route-guard.service.spec.ts
index 2d641a9..2556c0d 100644
--- a/src/app/features/new/services/new-statement-route-guard.service.spec.ts
+++ b/src/app/features/new/services/new-statement-route-guard.service.spec.ts
@@ -19,12 +19,13 @@
 import {MemoizedSelector} from "@ngrx/store";
 import {MockStore, provideMockStore} from "@ngrx/store/testing";
 import {appRoutes} from "../../../app-routing.module";
-import {ESPAUserRoles, isLoadingSelector, userRolesSelector} from "../../../store";
+import {EAPIUserRoles} from "../../../core";
+import {isLoadingSelector, userRolesSelector} from "../../../store";
 
 describe("NewStatementRouteGuard", () => {
     let router: Router;
     let location: Location;
-    let userRolesSelectorMock: MemoizedSelector<any, ESPAUserRoles[]>;
+    let userRolesSelectorMock: MemoizedSelector<any, string[]>;
 
     function callInZone<T>(fn: () => T | Promise<T>): Promise<T> {
         const ngZone = TestBed.inject(NgZone);
@@ -51,7 +52,7 @@
     }));
 
     it("should allow access to /new if user is official in charge ", async () => {
-        userRolesSelectorMock.setResult([ESPAUserRoles.SPA_OFFICIAL_IN_CHARGE]);
+        userRolesSelectorMock.setResult([EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE]);
         const isRoutingSuccessful = await callInZone(() => router.navigate(["new"]));
         expect(isRoutingSuccessful).toBeTruthy();
         expect(location.path()).toBe("/new");
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/search/components/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/search/components/index.ts
index f223969..990bb42 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/search/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./search";
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/search/components/search/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/search/components/search/index.ts
index f223969..5eb9c7f 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/search/components/search/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./search.component";
diff --git a/src/app/shared/controls/text-field/text-field.component.html b/src/app/features/search/components/search/search.component.html
similarity index 72%
copy from src/app/shared/controls/text-field/text-field.component.html
copy to src/app/features/search/components/search/search.component.html
index 129e808..0bc2dce 100644
--- a/src/app/shared/controls/text-field/text-field.component.html
+++ b/src/app/features/search/components/search/search.component.html
@@ -11,10 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<textarea #inputElement
-          (focusout)="onFocusOut()"
-          (input)="onInput(inputElement.value)"
-          [value]="appValue"
-          class="openk-textarea text-input"
-          rows="1">
-</textarea>
+<div style="padding: 1em;">
+  Not yet implemented.
+</div>
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/search/components/search/search.component.scss
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/search/components/search/search.component.scss
index f223969..06db89a 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/search/components/search/search.component.scss
@@ -11,4 +11,3 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/search/components/search/search.component.spec.ts
similarity index 65%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/search/components/search/search.component.spec.ts
index 20e1e36..ec7b1b3 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/search/components/search/search.component.spec.ts
@@ -12,23 +12,20 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {SearchComponent} from "./search.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("SearchComponent", () => {
+    let component: SearchComponent;
+    let fixture: ComponentFixture<SearchComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            declarations: [SearchComponent]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(SearchComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/store/attachments/model/IAttachmentWithTags.ts b/src/app/features/search/components/search/search.component.ts
similarity index 73%
copy from src/app/store/attachments/model/IAttachmentWithTags.ts
copy to src/app/features/search/components/search/search.component.ts
index b25a992..27f29f1 100644
--- a/src/app/store/attachments/model/IAttachmentWithTags.ts
+++ b/src/app/features/search/components/search/search.component.ts
@@ -11,12 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export interface IAttachmentWithTags<T> {
+import {Component} from "@angular/core";
 
-    attachment: T;
-
-    tags: string[];
-
-    remove?: boolean;
+@Component({
+    selector: "app-search",
+    templateUrl: "./search.component.html",
+    styleUrls: ["./search.component.scss"]
+})
+export class SearchComponent {
 
 }
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/search/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/search/index.ts
index f223969..cfd6650 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/search/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./search.module";
diff --git a/src/app/shared/controls/text-field/text-field.module.ts b/src/app/features/search/search-routing.module.ts
similarity index 63%
copy from src/app/shared/controls/text-field/text-field.module.ts
copy to src/app/features/search/search-routing.module.ts
index d4a7bef..6d938a9 100644
--- a/src/app/shared/controls/text-field/text-field.module.ts
+++ b/src/app/features/search/search-routing.module.ts
@@ -11,21 +11,28 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {TextFieldComponent} from "./text-field.component";
+import {RouterModule, Routes} from "@angular/router";
+import {SearchComponent} from "./components";
+import {SearchModule} from "./search.module";
+
+const routes: Routes = [
+    {
+        path: "",
+        pathMatch: "full",
+        component: SearchComponent
+    }
+];
 
 @NgModule({
     imports: [
-        CommonModule
-    ],
-    declarations: [
-        TextFieldComponent
+        SearchModule,
+        RouterModule.forChild(routes)
     ],
     exports: [
-        TextFieldComponent
+        RouterModule
     ]
 })
-export class TextInputFieldModule {
+export class SearchRoutingModule {
 
 }
diff --git a/src/app/shared/controls/text-field/text-field.module.ts b/src/app/features/search/search.module.ts
similarity index 83%
rename from src/app/shared/controls/text-field/text-field.module.ts
rename to src/app/features/search/search.module.ts
index d4a7bef..9e594b1 100644
--- a/src/app/shared/controls/text-field/text-field.module.ts
+++ b/src/app/features/search/search.module.ts
@@ -13,19 +13,19 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {TextFieldComponent} from "./text-field.component";
+import {SearchComponent} from "./components";
 
 @NgModule({
     imports: [
         CommonModule
     ],
     declarations: [
-        TextFieldComponent
+        SearchComponent
     ],
     exports: [
-        TextFieldComponent
+        SearchComponent
     ]
 })
-export class TextInputFieldModule {
+export class SearchModule {
 
 }
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/settings/components/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/settings/components/index.ts
index f223969..990bb42 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/settings/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./search";
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/settings/components/search/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/settings/components/search/index.ts
index f223969..154cd69 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/settings/components/search/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./settings.component";
diff --git a/src/app/shared/controls/text-field/text-field.component.html b/src/app/features/settings/components/search/settings.component.html
similarity index 72%
copy from src/app/shared/controls/text-field/text-field.component.html
copy to src/app/features/settings/components/search/settings.component.html
index 129e808..0bc2dce 100644
--- a/src/app/shared/controls/text-field/text-field.component.html
+++ b/src/app/features/settings/components/search/settings.component.html
@@ -11,10 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<textarea #inputElement
-          (focusout)="onFocusOut()"
-          (input)="onInput(inputElement.value)"
-          [value]="appValue"
-          class="openk-textarea text-input"
-          rows="1">
-</textarea>
+<div style="padding: 1em;">
+  Not yet implemented.
+</div>
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/settings/components/search/settings.component.scss
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/settings/components/search/settings.component.scss
index f223969..06db89a 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/settings/components/search/settings.component.scss
@@ -11,4 +11,3 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/features/settings/components/search/settings.component.spec.ts
similarity index 65%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/features/settings/components/search/settings.component.spec.ts
index 20e1e36..2bc2d9f 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/features/settings/components/search/settings.component.spec.ts
@@ -12,23 +12,20 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {SettingsComponent} from "./settings.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("SettingsComponent", () => {
+    let component: SettingsComponent;
+    let fixture: ComponentFixture<SettingsComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            declarations: [SettingsComponent]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(SettingsComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/features/settings/components/search/settings.component.ts
similarity index 72%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/features/settings/components/search/settings.component.ts
index 563859b..edced66 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/features/settings/components/search/settings.component.ts
@@ -11,9 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+import {Component} from "@angular/core";
+
+@Component({
+    selector: "app-settings",
+    templateUrl: "./settings.component.html",
+    styleUrls: ["./settings.component.scss"]
+})
+export class SettingsComponent {
+
 }
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/features/settings/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/features/settings/index.ts
index f223969..d659e68 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/features/settings/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./settings.module";
diff --git a/src/app/shared/controls/file-select/file-select.module.ts b/src/app/features/settings/settings-routing.module.ts
similarity index 62%
copy from src/app/shared/controls/file-select/file-select.module.ts
copy to src/app/features/settings/settings-routing.module.ts
index 3dced60..ad6e152 100644
--- a/src/app/shared/controls/file-select/file-select.module.ts
+++ b/src/app/features/settings/settings-routing.module.ts
@@ -11,25 +11,28 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {FormsModule} from "@angular/forms";
-import {MatIconModule} from "@angular/material/icon";
-import {FileSelectComponent} from "./component/file-select.component";
+import {RouterModule, Routes} from "@angular/router";
+import {SettingsComponent} from "./components";
+import {SettingsModule} from "./settings.module";
+
+const routes: Routes = [
+    {
+        path: "",
+        pathMatch: "full",
+        component: SettingsComponent
+    }
+];
 
 @NgModule({
     imports: [
-        CommonModule,
-        FormsModule,
-        MatIconModule
-    ],
-    declarations: [
-        FileSelectComponent
+        SettingsModule,
+        RouterModule.forChild(routes)
     ],
     exports: [
-        FileSelectComponent
+        RouterModule
     ]
 })
-export class FileSelectModule {
+export class SettingsRoutingModule {
 
 }
diff --git a/src/app/shared/controls/text-field/text-field.module.ts b/src/app/features/settings/settings.module.ts
similarity index 83%
copy from src/app/shared/controls/text-field/text-field.module.ts
copy to src/app/features/settings/settings.module.ts
index d4a7bef..2f125dd 100644
--- a/src/app/shared/controls/text-field/text-field.module.ts
+++ b/src/app/features/settings/settings.module.ts
@@ -13,19 +13,19 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {TextFieldComponent} from "./text-field.component";
+import {SettingsComponent} from "./components";
 
 @NgModule({
     imports: [
         CommonModule
     ],
     declarations: [
-        TextFieldComponent
+        SettingsComponent
     ],
     exports: [
-        TextFieldComponent
+        SettingsComponent
     ]
 })
-export class TextInputFieldModule {
+export class SettingsModule {
 
 }
diff --git a/src/app/shared/controls/common/common-controls.module.ts b/src/app/shared/controls/common/common-controls.module.ts
index 6fa5e1f..d924cbd 100644
--- a/src/app/shared/controls/common/common-controls.module.ts
+++ b/src/app/shared/controls/common/common-controls.module.ts
@@ -11,14 +11,17 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 import {NgModule} from "@angular/core";
-import {FormControlStatusDirective} from "./directives";
+import {AutoTextFieldResizeDirective} from "./directives/auto-text-field-resize";
+import {FormControlStatusDirective} from "./directives/form-control-status";
 
 @NgModule({
     declarations: [
-        FormControlStatusDirective
+        FormControlStatusDirective,
+        AutoTextFieldResizeDirective
     ],
     exports: [
-        FormControlStatusDirective
+        FormControlStatusDirective,
+        AutoTextFieldResizeDirective
     ]
 })
 export class CommonControlsModule {
diff --git a/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.spec.ts b/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.spec.ts
new file mode 100644
index 0000000..6b9f146
--- /dev/null
+++ b/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.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 {Component, ElementRef, ViewChild} from "@angular/core";
+import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {CommonControlsModule} from "../../common-controls.module";
+
+@Component({
+    selector: `app-host-component`,
+    template: `
+        <textarea #input appAutoTextFieldResize></textarea>`
+})
+class TestHostComponent {
+    @ViewChild("input") public input: ElementRef;
+}
+
+describe("TextFieldDirective", () => {
+
+    let component: TestHostComponent;
+    let fixture: ComponentFixture<TestHostComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [CommonControlsModule],
+            declarations: [TestHostComponent],
+            providers: []
+        }).compileComponents();
+    }));
+
+    beforeEach(async () => {
+        fixture = TestBed.createComponent(TestHostComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+        await fixture.whenStable();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should resize the textarea on value change", async () => {
+        component.input.nativeElement.style.height = "1px";
+        component.input.nativeElement.value =
+            "A text that has to be of a certain length so it needs multiple lines to display. This should be enough.";
+        fixture.detectChanges();
+        await fixture.whenStable();
+        expect(component.input.nativeElement.scrollTop).toBe(0);
+    });
+
+    it("should resize the textarea on input", async () => {
+        component.input.nativeElement.style.height = "1px";
+        component.input.nativeElement.value =
+            "A text that has to be of a certain length so it needs multiple lines to display. This should be enough.";
+        component.input.nativeElement.dispatchEvent(new Event("input"));
+        await fixture.whenStable();
+        expect(component.input.nativeElement.scrollTop).toBe(0);
+    });
+});
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
new file mode 100644
index 0000000..3e5a51c
--- /dev/null
+++ b/src/app/shared/controls/common/directives/auto-text-field-resize/auto-text-field-resize.directive.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 {Directive, ElementRef, HostListener, Input} from "@angular/core";
+
+@Directive({
+    selector: "[appAutoTextFieldResize]"
+})
+export class AutoTextFieldResizeDirective {
+
+    public constructor(public inputElement: ElementRef<HTMLInputElement>) {
+    }
+
+    @Input()
+    public set value(value: string) {
+        this.inputElement.nativeElement.value = value;
+        this.resize();
+    }
+
+    @HostListener("input")
+    onInput() {
+        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/features/details/selectors/index.ts b/src/app/shared/controls/common/directives/auto-text-field-resize/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/shared/controls/common/directives/auto-text-field-resize/index.ts
index aeb78c3..3d3a54a 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/shared/controls/common/directives/auto-text-field-resize/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./auto-text-field-resize.directive";
diff --git a/src/app/shared/controls/common/directives/form-control-status.directive.ts b/src/app/shared/controls/common/directives/form-control-status/form-control-status.directive.ts
similarity index 92%
rename from src/app/shared/controls/common/directives/form-control-status.directive.ts
rename to src/app/shared/controls/common/directives/form-control-status/form-control-status.directive.ts
index 1dc59a9..c50d468 100644
--- a/src/app/shared/controls/common/directives/form-control-status.directive.ts
+++ b/src/app/shared/controls/common/directives/form-control-status/form-control-status.directive.ts
@@ -21,7 +21,7 @@
 
     public constructor(
         @Optional() @Self() public appFormControl: NgControl,
-        public elmentRef: ElementRef<HTMLElement>
+        public elementRef: ElementRef<HTMLElement>
     ) {
 
     }
@@ -32,7 +32,7 @@
     }
 
     @HostBinding("class.openk-success")
-    public get classSucecss() {
+    public get classSuccess() {
         return this.appFormControl?.valid;
     }
 
diff --git a/src/app/features/details/selectors/index.ts b/src/app/shared/controls/common/directives/form-control-status/index.ts
similarity index 91%
rename from src/app/features/details/selectors/index.ts
rename to src/app/shared/controls/common/directives/form-control-status/index.ts
index aeb78c3..8607d95 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/shared/controls/common/directives/form-control-status/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./form-control-status.directive";
diff --git a/src/app/shared/controls/common/directives/index.ts b/src/app/shared/controls/common/directives/index.ts
index 8607d95..d646c4a 100644
--- a/src/app/shared/controls/common/directives/index.ts
+++ b/src/app/shared/controls/common/directives/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./form-control-status.directive";
+export * from "./form-control-status";
+export * from "./auto-text-field-resize";
diff --git a/src/app/shared/controls/common/index.ts b/src/app/shared/controls/common/index.ts
index 79021f5..3547997 100644
--- a/src/app/shared/controls/common/index.ts
+++ b/src/app/shared/controls/common/index.ts
@@ -12,5 +12,4 @@
  ********************************************************************************/
 
 export * from "./abstract/abstract-control-value-accessor.component";
-export * from "./directives";
 export * from "./common-controls.module";
diff --git a/src/app/shared/controls/contact-select/contact-select.component.html b/src/app/shared/controls/contact-select/contact-select.component.html
index f9bb7d1..a045f94 100644
--- a/src/app/shared/controls/contact-select/contact-select.component.html
+++ b/src/app/shared/controls/contact-select/contact-select.component.html
@@ -29,7 +29,7 @@
 
       <app-contact-table
         (appSelectedIdChange)="writeValue($event, true)"
-        [appDisabled]="appDisabled"
+        [appDisabled]="appDisabled || appIsLoading"
         [appEntries]="appEntries"
         [appSelectedId]="appValue">
       </app-contact-table>
diff --git a/src/app/shared/controls/date-control/component/date-control.component.html b/src/app/shared/controls/date-control/component/date-control.component.html
index 5359281..e8d9a89 100644
--- a/src/app/shared/controls/date-control/component/date-control.component.html
+++ b/src/app/shared/controls/date-control/component/date-control.component.html
@@ -14,6 +14,7 @@
 <span #dropDown="appDropDown" [appConnectedPositions]="connectedPositions"
       [appDisabled]="appDisabled"
       [appDropDown]="calendarRef"
+      (appClose)="emitValue()"
       class="container">
 
   <input
@@ -32,6 +33,7 @@
     [disabled]="appDisabled"
     [id]="appId"
     [ngModel]="(displayedValue | appMomentPipe : appInternalFormat).format(appDisplayFormat)"
+    [class.calendar-control-input---small]="appSmall"
     autocomplete="off"
     class="openk-input calendar-control-input"
     name="dueDate"
diff --git a/src/app/shared/controls/date-control/component/date-control.component.scss b/src/app/shared/controls/date-control/component/date-control.component.scss
index 8b843b7..38257c2 100644
--- a/src/app/shared/controls/date-control/component/date-control.component.scss
+++ b/src/app/shared/controls/date-control/component/date-control.component.scss
@@ -54,6 +54,12 @@
   padding-right: 1.5em;
 }
 
+.calendar-control-input---small {
+  padding-top: 0;
+  padding-bottom: 0;
+  padding-left: 0.4em;
+}
+
 .calendar-control-input-opened-top {
   @include rounded-border-mixin($top-right: 0);
 }
diff --git a/src/app/shared/controls/date-control/component/date-control.component.spec.ts b/src/app/shared/controls/date-control/component/date-control.component.spec.ts
index 7ac14b0..7840931 100644
--- a/src/app/shared/controls/date-control/component/date-control.component.spec.ts
+++ b/src/app/shared/controls/date-control/component/date-control.component.spec.ts
@@ -140,7 +140,7 @@
         expect(getCalendar()).not.toBeDefined();
     });
 
-    it("should close overlay when tabbing from the text input", async () => {
+    it("should close overlay when tabbing or pressing enter from the text input", async () => {
         textInput.dispatchEvent(new Event("mousedown"));
         fixture.detectChanges();
         await fixture.whenStable();
@@ -151,6 +151,17 @@
         await fixture.whenStable();
 
         expect(getCalendar()).not.toBeDefined();
+
+        textInput.dispatchEvent(new Event("mousedown"));
+        fixture.detectChanges();
+        await fixture.whenStable();
+        expect(getCalendar()).toBeDefined();
+
+        textInput.dispatchEvent(new KeyboardEvent("keydown", {key: "Enter"}));
+        fixture.detectChanges();
+        await fixture.whenStable();
+
+        expect(getCalendar()).not.toBeDefined();
     });
 
     it("should close overlay when tabbing from the calendar", async () => {
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 461d984..fcc8631 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
@@ -57,12 +57,18 @@
     @Output()
     public appValueChange = new EventEmitter<string>();
 
+    @Output()
+    public appValueSet = new EventEmitter<string>();
+
     @Input()
     public appInternalFormat = momentFormatInternal;
 
     @Input()
     public appDisplayFormat = momentFormatDisplayNumeric;
 
+    @Input()
+    public appSmall = false;
+
     public value: Date = new Date();
 
     public displayedValue: Date = this.value;
@@ -103,7 +109,7 @@
     ];
 
     @ViewChild("inputElement")
-    private readonly inputElement: ElementRef<HTMLInputElement>;
+    public readonly inputElement: ElementRef<HTMLInputElement>;
 
     @ViewChild(DropDownDirective)
     private dropDownDirective: DropDownDirective;
@@ -129,15 +135,25 @@
                 this.dropDownDirective.nativeElement.focus();
             }
         }
+        if (event.key === "Enter") {
+            this.toggle(false);
+            this.emitValue();
+        }
     }
 
     public onCalendarChange(event: any) {
         this.writeValue(event, true);
+        this.emitValue();
         this.onInputBlur();
         this.inputElement.nativeElement.focus();
         timer(0).toPromise().then(() => this.toggle(false));
     }
 
+    public emitValue() {
+        const internalValue = parseMomentToString(this.value, this.appInternalFormat, this.appInternalFormat);
+        this.appValueSet.emit(internalValue);
+    }
+
     public onInputChange(value: string) {
         const date = parseMomentToDate(value, this.appDisplayFormat);
         if (date != null) {
diff --git a/src/app/shared/controls/file-drop/component/file-drop.component.spec.ts b/src/app/shared/controls/file-drop/component/file-drop.component.spec.ts
deleted file mode 100644
index 4e2cbe8..0000000
--- a/src/app/shared/controls/file-drop/component/file-drop.component.spec.ts
+++ /dev/null
@@ -1,210 +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 {FileDropComponent} from "./file-drop.component";
-
-describe("FileDropComponent", () => {
-    let component: FileDropComponent;
-    let fixture: ComponentFixture<FileDropComponent>;
-
-    let file: File;
-    let anotherFile: File;
-    let thirdFile: File;
-    let fakeFileList: FakeFileList;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [FileDropComponent]
-        }).compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(FileDropComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    beforeEach(() => {
-        file = new File(["Some", "File"], "Some File");
-        anotherFile = new File(["Another", "File"], "Another File");
-        thirdFile = new File(["Third", "File"], "Third File");
-        fakeFileList = new FakeFileList([file]);
-    });
-
-    it("should create", () => {
-        expect(component).toBeTruthy();
-    });
-
-    describe("onInput", () => {
-        it("should add the files of the given filelist to the appValue array", () => {
-            expect(component.appValue).toEqual(undefined);
-            component.appDisabled = false;
-            component.onInput(fakeFileList as FileList);
-            expect(component.appValue).toEqual(getAttachmentListWithTags([file]));
-        });
-
-        it("should not add the files to appValue when the app is disabled", () => {
-            expect(component.appValue).toEqual(undefined);
-            component.appDisabled = true;
-            component.onInput(fakeFileList as FileList);
-            expect(component.appValue).toEqual(undefined);
-        });
-
-        it("should emit the appValueChanged", () => {
-            spyOn(component.appValueChange, "emit");
-            component.appDisabled = false;
-            component.onInput(fakeFileList as FileList);
-            expect(component.appValueChange.emit).toHaveBeenCalledWith(getAttachmentListWithTags([file]));
-        });
-    });
-
-    describe("onDelete", () => {
-        it("should not delete the file when disabled or the index is invalid", () => {
-            const fileArray = [file, anotherFile, thirdFile];
-            const attachmentList = getAttachmentListWithTags(fileArray);
-
-            component.appValue = attachmentList;
-            component.appDisabled = true;
-            component.onDelete(2);
-            expect(component.appValue).toEqual(attachmentList);
-
-            component.appDisabled = false;
-            component.onDelete(14);
-            expect(component.appValue).toEqual(attachmentList);
-        });
-
-        it("should delete the file found at the index in appValue", () => {
-            component.appValue = getAttachmentListWithTags([file, anotherFile, thirdFile]);
-            component.appDisabled = false;
-            component.onDelete(1);
-            expect(component.appValue).toEqual(getAttachmentListWithTags([file, thirdFile]));
-            component.onDelete(1);
-            expect(component.appValue).toEqual(getAttachmentListWithTags([file]));
-        });
-
-        it("should emit the deleted file for appValueDelete and the new appValue array for appValueChange", () => {
-            spyOn(component.appValueChange, "emit");
-            spyOn(component.appValueDelete, "emit");
-            component.appValue = getAttachmentListWithTags([file, anotherFile, thirdFile]);
-            component.appDisabled = false;
-            component.onDelete(1);
-            expect(component.appValueChange.emit).toHaveBeenCalledWith(getAttachmentListWithTags([file, thirdFile]));
-            expect(component.appValueDelete.emit).toHaveBeenCalledWith(anotherFile);
-        });
-    });
-
-    describe("onDrop", () => {
-        it("should not call onInput when appDisabled is set to true", () => {
-            const dataTransfer = {
-                files: fakeFileList
-            } as unknown as DataTransfer;
-            const dragEvent = {
-                dataTransfer,
-                preventDefault() {
-                }
-            } as DragEvent;
-
-            spyOn(component, "onInput");
-            component.appDisabled = true;
-            component.onDrop(dragEvent);
-            expect(component.onInput).not.toHaveBeenCalled();
-        });
-
-        it("should call onInput with the data supplied to onDrop", () => {
-            const dataTransfer = {
-                files: fakeFileList
-            } as unknown as DataTransfer;
-            const dragEvent = {
-                dataTransfer,
-                preventDefault() {
-                }
-            } as DragEvent;
-
-            spyOn(component, "onInput");
-            component.appDisabled = false;
-            component.onDrop(dragEvent);
-            expect(component.onInput).toHaveBeenCalledWith(fakeFileList);
-        });
-
-        it("should not call onInput when there are no files in the DataTransfer-Object", () => {
-            const dataTransfer = {
-                files: null
-            } as unknown as DataTransfer;
-            const dragEvent = {
-                dataTransfer,
-                preventDefault() {
-                }
-            } as DragEvent;
-
-            spyOn(component, "onInput");
-            component.appDisabled = false;
-            component.onDrop(dragEvent);
-            expect(component.onInput).not.toHaveBeenCalled();
-        });
-    });
-
-    describe("onDragOver", () => {
-        it("should call preventDefault", () => {
-            const dragEvent = {
-                preventDefault() {
-                }
-            } as DragEvent;
-
-            spyOn(dragEvent, "preventDefault");
-            component.appDisabled = false;
-            component.onDragOver(dragEvent);
-            expect(dragEvent.preventDefault).toHaveBeenCalled();
-        });
-    });
-
-    describe("openDialog", () => {
-        it("should call the click function for the inputElement if not disabled", () => {
-            spyOn(component.inputElement.nativeElement, "click");
-            component.appDisabled = true;
-            component.openDialog();
-            expect(component.inputElement.nativeElement.click).not.toHaveBeenCalled();
-
-            component.appDisabled = false;
-            component.openDialog();
-            expect(component.inputElement.nativeElement.click).toHaveBeenCalled();
-        });
-    });
-});
-
-class FakeFileList implements FileList {
-    public data: File[];
-    public length: number;
-
-    [index: number]: File;
-
-    constructor(data: File[]) {
-        this.data = data;
-        this.length = this.data.length;
-    }
-
-    public item(idx) {
-        return this.data[idx];
-    }
-}
-
-const getAttachmentWithTags = (attachment: File) => {
-    return {
-        attachment,
-        tags: []
-    };
-};
-
-const getAttachmentListWithTags = (attachments: any[]) => {
-    return attachments.map((_) => getAttachmentWithTags(_));
-};
diff --git a/src/app/shared/controls/file-drop/component/file-drop.component.ts b/src/app/shared/controls/file-drop/component/file-drop.component.ts
deleted file mode 100644
index 9682501..0000000
--- a/src/app/shared/controls/file-drop/component/file-drop.component.ts
+++ /dev/null
@@ -1,117 +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, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Input, Output, ViewChild} from "@angular/core";
-import {NG_VALUE_ACCESSOR} from "@angular/forms";
-import {IAttachmentWithTags} from "../../../../store";
-import {arrayJoin} from "../../../../util";
-import {AbstractControlValueAccessorComponent} from "../../common";
-
-@Component({
-    selector: "app-file-drop",
-    templateUrl: "./file-drop.component.html",
-    styleUrls: ["./file-drop.component.scss"],
-    providers: [
-        {
-            provide: NG_VALUE_ACCESSOR,
-            useExisting: forwardRef(() => FileDropComponent),
-            multi: true
-        }
-    ]
-})
-export class FileDropComponent extends AbstractControlValueAccessorComponent<IAttachmentWithTags<File>[]> {
-
-    @HostBinding("class.no-drop")
-    @Input()
-    public appDisabled = false;
-
-    /**
-     * List of tag ids which are added automatically to dropped files.
-     */
-    @Input()
-    public appAutoTagIds: string[];
-
-    @Output()
-    public appValueDelete = new EventEmitter<File>();
-
-    @ViewChild("inputElement")
-    public inputElement: ElementRef<HTMLInputElement>;
-
-    @HostListener("dragover", ["$event"])
-    public onDragOver(event: DragEvent) {
-        if (this.appDisabled) {
-            return;
-        }
-
-        event.preventDefault();
-    }
-
-    @HostListener("drop", ["$event"])
-    public onDrop(event: DragEvent) {
-        if (this.appDisabled) {
-            return;
-        }
-
-        if (event?.dataTransfer?.files?.length > 0) {
-            event.preventDefault();
-            this.onInput(event?.dataTransfer?.files);
-        }
-    }
-
-    public openDialog(): void {
-        if (!this.appDisabled && typeof this.inputElement?.nativeElement?.click === "function") {
-            this.inputElement.nativeElement.value = "";
-            this.inputElement.nativeElement.click();
-        }
-    }
-
-    public onInput(fileList: FileList) {
-        if (this.appDisabled) {
-            return;
-        }
-        const value = arrayJoin(
-            this.appValue,
-            fileListToFileArray(fileList)
-                .map((_) => ({attachment: _, tags: arrayJoin(this.appAutoTagIds)}))
-        );
-        this.writeValue(value, true);
-    }
-
-    public onDelete(index: number) {
-        if (this.appDisabled || !Array.isArray(this.appValue) || this.appValue[index]?.attachment == null) {
-            return;
-        }
-
-        const file = this.appValue[index].attachment;
-        const value = this.appValue.filter((_) => _?.attachment !== file);
-        this.writeValue(value, true);
-        this.appValueDelete.emit(file);
-    }
-
-    public writeValue(obj: IAttachmentWithTags<File>[], emit?: boolean): void {
-        super.writeValue(arrayJoin(obj).filter((_) => _?.attachment instanceof File), emit);
-    }
-
-}
-
-function fileListToFileArray(fileList: FileList): File[] {
-    try {
-        const result: File[] = [];
-        for (let i = 0; i < fileList.length; i++) {
-            result.push(fileList.item(i));
-        }
-        return result;
-    } catch (e) {
-        return [];
-    }
-}
diff --git a/src/app/shared/controls/file-select/component/file-select.component.html b/src/app/shared/controls/file-select/component/file-select.component.html
deleted file mode 100644
index cfb9458..0000000
--- a/src/app/shared/controls/file-select/component/file-select.component.html
+++ /dev/null
@@ -1,42 +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
- -------------------------------------------------------------------------------->
-
-<div *ngFor="let attachment of appAttachments" [class.attachment---disabled]="appDisabled" class="attachment">
-
-  <input #inputElement (keydown.enter)="inputElement.click()"
-         (ngModelChange)="$event ? select(attachment?.id) : deselect(attachment?.id)"
-         [class.cursor-pointer]="!appDisabled"
-         [disabled]="appDisabled"
-         [id]="appId + '-' + attachment?.id"
-         [ngModel]="isSelected(attachment?.id)"
-         class="attachments--input"
-         type="checkbox">
-
-  <label
-    [class.attachments--label---deselected]="!isSelected(attachment?.id)"
-    [class.cursor-pointer]="!appDisabled"
-    [for]="appId + '-' + attachment?.id"
-    class="attachments--label">
-    {{attachment?.name}}
-  </label>
-
-  <button
-    (click)="appOpenAttachment.emit(attachment?.id)"
-    (pointerdown)="$event.preventDefault();"
-    [disabled]="appDisabled"
-    class="openk-button openk-info openk-button-rounded attachments--button"
-    type="button">
-    <mat-icon>call_made</mat-icon>
-  </button>
-
-</div>
diff --git a/src/app/shared/controls/file-select/component/file-select.component.scss b/src/app/shared/controls/file-select/component/file-select.component.scss
deleted file mode 100644
index ebda584..0000000
--- a/src/app/shared/controls/file-select/component/file-select.component.scss
+++ /dev/null
@@ -1,68 +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 "openk.styles";
-
-:host {
-  display: flex;
-  flex-flow: column;
-  box-sizing: border-box;
-}
-
-.attachment {
-  display: flex;
-  flex-flow: row;
-  width: 100%;
-  align-items: center;
-  line-height: 1.25;
-  padding: 0 0.25em;
-  transition: background-color 100ms ease-in;
-
-  &:hover {
-    background-color: $openk-background-highlight;
-  }
-}
-
-.attachment---disabled {
-  opacity: 0.66;
-
-  &:hover {
-    background-color: initial;
-  }
-}
-
-.attachments--input {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 1em;
-  width: 1em;
-  height: 1em;
-}
-
-
-.attachments--label {
-  display: inline-flex;
-  flex: 1 1 100%;
-  padding: 0.05em 0.25em;
-}
-
-.attachments--label---deselected {
-  text-decoration: line-through;
-}
-
-.attachments--button {
-  font-size: 0.56em;
-  border: 0;
-  margin-left: 0.4em;
-}
diff --git a/src/app/shared/controls/file-select/component/file-select.component.spec.ts b/src/app/shared/controls/file-select/component/file-select.component.spec.ts
deleted file mode 100644
index be50be6..0000000
--- a/src/app/shared/controls/file-select/component/file-select.component.spec.ts
+++ /dev/null
@@ -1,139 +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} from "@angular/core";
-import {ComponentFixture, TestBed} from "@angular/core/testing";
-import {By} from "@angular/platform-browser";
-import {IAPIAttachmentModel} from "../../../../core/api";
-import {I18nModule} from "../../../../core/i18n";
-import {IAttachmentWithTags} from "../../../../store/attachments/model";
-import {FileSelectModule} from "../file-select.module";
-import {FileSelectComponent} from "./file-select.component";
-
-@Component({
-    selector: `app-host-component`,
-    template: `
-        <app-file-select
-            [appAttachments]="attachments"
-            [appValue]="appValue">
-        </app-file-select>
-    `
-})
-class TestHostComponent {
-
-    public attachments: IAPIAttachmentModel[] = [
-        {
-            id: 1,
-            name: "Attachment 1",
-            type: "string",
-            size: 1,
-            timestamp: "string",
-            tagIds: []
-        },
-        {
-            id: 2,
-            name: "Attachment 1",
-            type: "string",
-            size: 1,
-            timestamp: "string",
-            tagIds: []
-        }
-    ];
-
-    public appValue: IAttachmentWithTags<number>[] = [
-        {
-            attachment: 2,
-            tags: []
-        }
-    ];
-}
-
-describe("FileSelectComponent", () => {
-    let component: TestHostComponent;
-    let fixture: ComponentFixture<TestHostComponent>;
-    let childComponent: FileSelectComponent;
-
-    beforeEach(async () => {
-        await TestBed.configureTestingModule({
-            declarations: [
-                TestHostComponent
-            ],
-            imports: [
-                FileSelectModule,
-                I18nModule
-            ]
-        }).compileComponents();
-    });
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(TestHostComponent);
-        component = fixture.componentInstance;
-        childComponent = fixture.debugElement.query(By.directive(FileSelectComponent)).componentInstance;
-        fixture.detectChanges();
-    });
-
-    it("should create", () => {
-        expect(component).toBeTruthy();
-    });
-
-    it("should emit appOpenAttachment with the file id of the given file", () => {
-        spyOn(childComponent.appOpenAttachment, "emit").and.callThrough();
-        const button = fixture.debugElement.query(By.css(".openk-button"));
-        button.nativeElement.click();
-        expect(childComponent.appOpenAttachment.emit).toHaveBeenCalledWith(1);
-    });
-
-    it("should return true only if the file id is not in appValue", () => {
-        const isSelected = childComponent.isSelected(childComponent.appAttachments[0].id);
-        expect(isSelected).toBeTrue();
-        const isNotSelected = childComponent.isSelected(childComponent.appAttachments[1].id);
-        expect(isNotSelected).toBeFalse();
-    });
-
-    it("should remove the file id from app value on select", () => {
-        childComponent.appValue = getAttachmentListWithTags([2, 3]);
-        childComponent.select(2);
-        expect(childComponent.appValue).toEqual(getAttachmentListWithTags([3]));
-        childComponent.select(3);
-        expect(childComponent.appValue).toEqual([]);
-    });
-
-    it("should add the file id to app value on deselect", () => {
-        childComponent.appValue = [];
-        childComponent.deselect(2);
-        expect(childComponent.appValue).toEqual(getAttachmentListWithTags([2]));
-        childComponent.deselect(3);
-        expect(childComponent.appValue).toEqual(getAttachmentListWithTags([2, 3]));
-    });
-
-    it("should add and remove items from appValue when no input value was given", () => {
-        childComponent.appValue = undefined;
-        childComponent.select(2);
-        expect(childComponent.appValue).toEqual([]);
-        childComponent.appValue = undefined;
-        childComponent.deselect(3);
-        expect(childComponent.appValue).toEqual(getAttachmentListWithTags([3]));
-    });
-});
-
-const getAttachmentWithTags = (attachment: number) => {
-    return {
-        attachment,
-        tags: [],
-        remove: true
-    };
-};
-
-const getAttachmentListWithTags = (attachments: any[]) => {
-    return attachments.map((_) => getAttachmentWithTags(_));
-};
diff --git a/src/app/shared/controls/file-select/component/file-select.component.stories.ts b/src/app/shared/controls/file-select/component/file-select.component.stories.ts
deleted file mode 100644
index bb65083..0000000
--- a/src/app/shared/controls/file-select/component/file-select.component.stories.ts
+++ /dev/null
@@ -1,57 +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 {withKnobs} from "@storybook/addon-knobs";
-import {moduleMetadata, storiesOf} from "@storybook/angular";
-import {IAPIAttachmentModel} from "../../../../core/api";
-import {FileSelectModule} from "../file-select.module";
-
-
-const attachments: IAPIAttachmentModel[] = [
-    {
-        id: 1,
-        name: "Attachment 1",
-        type: "string",
-        size: 1,
-        timestamp: "string",
-        tagIds: []
-    },
-    {
-        id: 2,
-        name: "Attachment 1",
-        type: "string",
-        size: 1,
-        timestamp: "string",
-        tagIds: []
-    }
-];
-
-const appValue: number[] = [1];
-
-const deleteAttachment = (ids: number[]) => {
-    console.log(ids);
-};
-
-storiesOf("Shared/Controls", module)
-    .addDecorator(withKnobs)
-    .addDecorator(moduleMetadata({imports: [FileSelectModule]}))
-    .add("FileSelectComponent", () => ({
-        template: `
-            <app-file-select [appAttachments]="attachments" [appValue]="appValue" (appValueChange)="deleteAttachment($event)"></app-file-select>
-        `,
-        props: {
-            attachments,
-            appValue,
-            deleteAttachment
-        }
-    }));
diff --git a/src/app/shared/controls/file-select/component/file-select.component.ts b/src/app/shared/controls/file-select/component/file-select.component.ts
deleted file mode 100644
index 9074918..0000000
--- a/src/app/shared/controls/file-select/component/file-select.component.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 {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, Output} from "@angular/core";
-import {NG_VALUE_ACCESSOR} from "@angular/forms";
-import {IAPIAttachmentModel} from "../../../../core";
-import {IAttachmentWithTags} from "../../../../store";
-import {arrayJoin} from "../../../../util";
-import {AbstractControlValueAccessorComponent} from "../../common";
-
-@Component({
-    selector: "app-file-select",
-    templateUrl: "./file-select.component.html",
-    styleUrls: ["./file-select.component.scss"],
-    changeDetection: ChangeDetectionStrategy.OnPush,
-    providers: [
-        {
-            provide: NG_VALUE_ACCESSOR,
-            useExisting: forwardRef(() => FileSelectComponent),
-            multi: true
-        }
-    ]
-})
-export class FileSelectComponent extends AbstractControlValueAccessorComponent<IAttachmentWithTags<number>[]> {
-
-    private static id = 0;
-
-    @Input()
-    public appId = `FileSelectComponent-${FileSelectComponent.id}`;
-
-    @Input()
-    public appAttachments: IAPIAttachmentModel[];
-
-    @Output()
-    public appOpenAttachment = new EventEmitter<number>();
-
-    public constructor(public readonly changeDetectorRef: ChangeDetectorRef) {
-        super();
-    }
-
-    public isSelected(attachmentId: number): boolean {
-        return !arrayJoin(this.appValue).some((_) => _?.attachment === attachmentId);
-    }
-
-    public select(attachmentId: number) {
-        const value = arrayJoin(this.appValue).filter((_) => _?.attachment !== attachmentId);
-        this.writeValue(value, true);
-    }
-
-    public deselect(attachmentId: number) {
-        this.writeValue(arrayJoin(this.appValue, [{attachment: attachmentId, tags: [], remove: true}]), true);
-    }
-
-    public writeValue(obj: IAttachmentWithTags<number>[], emit?: boolean) {
-        super.writeValue(obj, emit);
-        this.changeDetectorRef.markForCheck();
-    }
-
-    public setDisabledState(isDisabled: boolean) {
-        super.setDisabledState(isDisabled);
-        this.changeDetectorRef.markForCheck();
-    }
-}
diff --git a/src/app/shared/controls/file-select/index.ts b/src/app/shared/controls/file-select/index.ts
deleted file mode 100644
index f751a69..0000000
--- a/src/app/shared/controls/file-select/index.ts
+++ /dev/null
@@ -1,15 +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 "./file-select.module";
-export * from "./component/file-select.component";
diff --git a/src/app/shared/controls/select/components/select-group/select-group.component.html b/src/app/shared/controls/select/components/select-group/select-group.component.html
index d7afd46..b5cc4ab 100644
--- a/src/app/shared/controls/select/components/select-group/select-group.component.html
+++ b/src/app/shared/controls/select/components/select-group/select-group.component.html
@@ -22,25 +22,24 @@
     <div
       *ngFor="let pair of appOptions | filterOptionsByGroup : group : appValue | pair : appPairSize ? appPairSize : (group?.options?.length < 4 ? 1 : 2); let optionIndex = index; trackBy: trackByIndex;"
       class="select-group--pair">
-
       <div *ngFor="let option of pair; let pairIndex = index; trackBy: trackByIndex"
            class="select-group-entry">
 
         <div class="select-group-entry--control">
           <ng-template #iconTemplate>
-            <mat-icon [class.select-group-entry__icon&#45;&#45;success]="option?.isSelected"
-                      class="select-group-entry--icon">
-              {{ option?.isSelected ? 'check_box' : 'crop_square'}}
+            <mat-icon class="select-group-entry--icon">
+              {{ option?.isSelected ? 'check_box' : (option?.isIndeterminate ? "indeterminate_check_box" : "crop_square")}}
             </mat-icon>
           </ng-template>
 
           <input #inputElement (keydown.enter)="inputElement.click()"
-                 (ngModelChange)="$event ? select(option?.value) : deselect(option?.value)"
+                 (ngModelChange)="toggleCheckbox(option?.value, option?.isSelected, option?.isIndeterminate, inputElement)"
                  *ngIf="!appHideControls; else iconTemplate"
                  [class.cursor-pointer]="!appHideControls && !appDisabled"
                  [disabled]="appDisabled"
                  [id]="appId + '.' + groupIndex + '.' + optionIndex + '.' + pairIndex"
-                 [ngModel]="option?.isSelected"
+                 [indeterminate]="option?.isIndeterminate"
+                 [ngModel]="option?.isSelected && !option?.isIndeterminate"
                  class="select-group-entry--input"
                  type="checkbox">
         </div>
diff --git a/src/app/shared/controls/select/components/select-group/select-group.component.spec.ts b/src/app/shared/controls/select/components/select-group/select-group.component.spec.ts
index 07ee05b..79cc423 100644
--- a/src/app/shared/controls/select/components/select-group/select-group.component.spec.ts
+++ b/src/app/shared/controls/select/components/select-group/select-group.component.spec.ts
@@ -47,16 +47,33 @@
     });
 
     it("should register to Angular forms", async () => {
-        component.appValue = [0, 1, 2];
+        component.appValue = {
+            selected: [0, 1, 2],
+            indeterminate: []
+        };
         fixture.detectChanges();
         await fixture.whenStable();
         expect(component.ngForm.value).toBeDefined();
-        expect(component.ngForm.value.selectGroup).toEqual([0, 1, 2]);
+        expect(component.ngForm.value.selectGroup).toEqual({
+            selected: [0, 1, 2],
+            indeterminate: []
+        });
 
-        component.ngForm.setValue({selectGroup: [17, 18, 19]});
+        component.ngForm.setValue({
+            selectGroup: {
+                selected: [3, 4],
+                indeterminate: [5, 6]
+            }
+        });
         fixture.detectChanges();
         await fixture.whenStable();
-        expect(component.component.appValue).toEqual([17, 18, 19]);
+
+        expect(component.component.appValue).toEqual(
+            {
+                selected: [3, 4],
+                indeterminate: [5, 6]
+            }
+        );
     });
 
     it("should only render inputs if controls are not hidden", async () => {
@@ -88,55 +105,121 @@
         fixture.detectChanges();
         await fixture.whenStable();
 
-        component.component.select(18);
-        component.component.select(19);
+        component.component.toggleCheckbox(18, false, false, undefined);
+        component.component.toggleCheckbox(19, false, false, undefined);
 
         fixture.detectChanges();
         await fixture.whenStable();
 
-        expect(component.component.appValue).toEqual([18, 19]);
-        expect(component.ngForm.value.selectGroup).toEqual([18, 19]);
+        expect(component.component.appValue).toEqual({
+            selected: [18, 19],
+            indeterminate: []
+        });
+        expect(component.ngForm.value.selectGroup).toEqual({
+            selected: [18, 19],
+            indeterminate: []
+        });
 
-        component.component.deselect(18);
+        component.component.toggleCheckbox(18, true, false, undefined);
 
         fixture.detectChanges();
         await fixture.whenStable();
 
-        expect(component.component.appValue).toEqual([19]);
-        expect(component.ngForm.value.selectGroup).toEqual([19]);
+        expect(component.component.appValue).toEqual({
+            selected: [19],
+            indeterminate: []
+        });
+        expect(component.ngForm.value.selectGroup).toEqual({
+            selected: [19],
+            indeterminate: []
+        });
     });
 
     it("should not select or deselect options if disabled", async () => {
         component.ngForm.control.disable();
-        component.ngForm.control.patchValue({selectGroup: [19]});
+        component.ngForm.control.patchValue({
+            selectGroup: {
+                selected: [19],
+                indeterminate: []
+            }
+        });
         component.createGroups(10, 6, 6, 6);
         component.createOptions(28);
         fixture.detectChanges();
         await fixture.whenStable();
 
-        component.component.select(null);
-        component.component.select(18);
+        component.component.toggleCheckbox(null, false, false, undefined);
+        component.component.toggleCheckbox(18, false, false, undefined);
 
         fixture.detectChanges();
         await fixture.whenStable();
 
-        expect(component.component.appValue).toEqual([19]);
-        expect(component.ngForm.value.selectGroup).toEqual([19]);
+        expect(component.component.appValue).toEqual({
+            selected: [19],
+            indeterminate: []
+        });
+        expect(component.ngForm.value.selectGroup).toEqual({
+            selected: [19],
+            indeterminate: []
+        });
 
-        component.component.deselect(null);
-        component.component.deselect(19);
+        component.component.toggleCheckbox(19, true, false, undefined);
+        component.component.toggleCheckbox(null, true, false, undefined);
 
         fixture.detectChanges();
         await fixture.whenStable();
 
-        expect(component.component.appValue).toEqual([19]);
-        expect(component.ngForm.value.selectGroup).toEqual([19]);
-
-        /*expect(component.component.appValue).not.toBeDefined();
-        expect(component.ngForm.value.selectGroup).not.toBeDefined();*/
+        expect(component.component.appValue).toEqual({
+            selected: [19],
+            indeterminate: []
+        });
+        expect(component.ngForm.value.selectGroup).toEqual({
+            selected: [19],
+            indeterminate: []
+        });
     });
 
-});
+    it("should set state to indeterminate from selected if appIndeterminate is set", async () => {
+        component.createGroups(10, 6, 6, 6);
+        component.createOptions(28);
+        component.component.appDisabled = false;
+        fixture.detectChanges();
+        await fixture.whenStable();
+
+        component.component.toggleCheckbox(18, false, false, undefined);
+
+        fixture.detectChanges();
+        await fixture.whenStable();
+
+        expect(component.component.appValue).toEqual({
+            selected: [18],
+            indeterminate: []
+        });
+
+        component.component.toggleCheckbox(18, true, false, undefined);
+
+        fixture.detectChanges();
+        await fixture.whenStable();
+
+        expect(component.component.appValue).toEqual({
+            selected: [],
+            indeterminate: []
+        });
+
+        component.component.appIndeterminate = true;
+
+        component.component.toggleCheckbox(18, true, false, undefined);
+
+        fixture.detectChanges();
+        await fixture.whenStable();
+
+        expect(component.component.appValue).toEqual({
+            selected: [],
+            indeterminate: [18]
+        });
+    });
+})
+;
 
 @Component({
     selector: "app-select-group-spec",
@@ -160,7 +243,10 @@
 
     public appOptions: ISelectOption[];
 
-    public appValue: number[];
+    public appValue: {
+        selected: number[],
+        indeterminate: number[]
+    };
 
     @ViewChild(SelectGroupComponent)
     public component: SelectGroupComponent;
diff --git a/src/app/shared/controls/select/components/select-group/select-group.component.stories.ts b/src/app/shared/controls/select/components/select-group/select-group.component.stories.ts
index 627217b..5249d57 100644
--- a/src/app/shared/controls/select/components/select-group/select-group.component.stories.ts
+++ b/src/app/shared/controls/select/components/select-group/select-group.component.stories.ts
@@ -29,7 +29,6 @@
 }
 
 function getGroups(...groupSizes: number[]): ISelectOptionGroup[] {
-    const result: ISelectOptionGroup[] = [];
     let optionId = 0;
     return groupSizes.map((size, index) => {
         return {
@@ -51,7 +50,8 @@
                 [appHideControls]="appHideControls"
                 [appOptions]="appOptions"
                 [appPairSize]="appPairSize"
-                [appValue]="appValue"
+                [appValue]="{selected: appSelected, indeterminate: appIndeterminateValues}"
+                [appIndeterminate]="appIndeterminate"
                 style="padding: 1em; width: calc(100% - 2em);">
             </app-select-group>
         `,
@@ -60,8 +60,10 @@
             appHideControls: boolean("appHideControls", false),
             appValueChange: action("appValueChange"),
             appPairSize: number("appPairSize", 2),
-            appValue: [...getValue(6, 0, 10), ...getValue(4, 16, 22)],
+            appSelected: [...getValue(6, 0, 10), ...getValue(4, 16, 22)],
+            appIndeterminateValues: [...getValue(5, 0, 26)],
             appGroups: getGroups(10, 6, 6, 6),
-            appOptions: getOptions(38)
+            appOptions: getOptions(38),
+            appIndeterminate: boolean("appIndeterminate", false)
         }
     }));
diff --git a/src/app/shared/controls/select/components/select-group/select-group.component.ts b/src/app/shared/controls/select/components/select-group/select-group.component.ts
index f0120fa..2320e0e 100644
--- a/src/app/shared/controls/select/components/select-group/select-group.component.ts
+++ b/src/app/shared/controls/select/components/select-group/select-group.component.ts
@@ -29,7 +29,8 @@
         }
     ]
 })
-export class SelectGroupComponent<TOptionValueType = any> extends AbstractControlValueAccessorComponent<TOptionValueType[]> {
+export class SelectGroupComponent<TOptionValueType = any>
+    extends AbstractControlValueAccessorComponent<{ selected: TOptionValueType[], indeterminate?: TOptionValueType[] }> {
 
     private static id = 0;
 
@@ -63,29 +64,50 @@
     @Input()
     public appPairSize;
 
+    @Input()
+    public appIndeterminate = false;
+
     /**
      * This function is used by the NgFor directive and controls when the elements are rendered anew.
      */
     public readonly trackByIndex = (index: number) => index;
 
-    public select(value: TOptionValueType) {
+    public toggleCheckbox(value: TOptionValueType, isSelected: boolean, isIndeterminate: boolean, inputElement: HTMLInputElement) {
+
         if (this.appDisabled || value == null) {
             return;
         }
-        this.writeValue(arrayJoin(this.appValue, [value]), true);
-    }
 
-    public deselect(value: TOptionValueType) {
-        if (this.appDisabled || value == null) {
-            return;
+        let newAppValue = {
+            selected: this.appValue.selected.filter((val) => val !== value),
+            indeterminate: this.appValue.indeterminate.filter((val) => val !== value)
+        };
+        if (isSelected) {
+            if (this.appIndeterminate && !isIndeterminate) {
+                newAppValue = {
+                    ...newAppValue,
+                    indeterminate: arrayJoin(newAppValue.indeterminate, [value])
+                };
+            }
+        } else if (!isIndeterminate || !this.appIndeterminate) {
+            newAppValue = {
+                ...newAppValue,
+                selected: arrayJoin(newAppValue.selected, [value])
+            };
+        } else if (isIndeterminate) {
+            if (inputElement != null) {
+                inputElement.checked = false;
+            }
         }
-        const obj = arrayJoin(this.appValue, []).filter((v) => v !== value);
-        this.writeValue(obj, true);
+        this.writeValue(newAppValue, true);
     }
 
-    public writeValue(obj: TOptionValueType[], emit?: boolean) {
+    public writeValue(obj: { selected: TOptionValueType[], indeterminate?: TOptionValueType[] }, emit?: boolean) {
         // All values are filtered and sorted before writing and emitting them.
-        super.writeValue(filterDistinctValues(obj).sort(), emit);
+        super.writeValue({
+            selected: filterDistinctValues(obj?.selected).sort(),
+            indeterminate: filterDistinctValues(obj?.indeterminate).sort()
+        }, emit);
     }
 
 }
diff --git a/src/app/shared/controls/select/components/select/select.component.html b/src/app/shared/controls/select/components/select/select.component.html
index 336b467..43a3a0c 100644
--- a/src/app/shared/controls/select/components/select/select.component.html
+++ b/src/app/shared/controls/select/components/select/select.component.html
@@ -15,25 +15,34 @@
         #toggleButtonRef
         (click)="toggle()"
         (keydown)="onKeyDown($event)"
+        (appClose)="appClose.emit()"
         [appConnectedPositions]="connectedPositions"
         [appDisabled]="appDisabled || !(appOptions?.length > 0)"
         [appDropDown]="optionsRef"
+        [appFixedWidth]="appMaxWidth"
         [class.select-toggle-opened-bottom]="dropDown?.position?.panelClass === 'bottom'"
         [class.select-toggle-opened-top]="dropDown?.position?.panelClass === 'top'"
         [disabled]="appDisabled"
         [id]="appId"
-        class="select-toggle openk-input"
+        [class.select-toggle---small]="appSmall"
+        [class.openk-input]="!appSmall"
+        class="select-toggle"
         type="button">
 
-  <span *ngIf="(appOptions | selected: appValue)?.value != null; else placeholderRef" class="select-toggle-text">
+  <span *ngIf="(appOptions | selected: appValue)?.value != null; else placeholderRef"
+        [class.select-toggle-text---small]="appSmall"
+        class="select-toggle-text">
       {{(appOptions | selected: appValue)?.label }}
   </span>
 
   <ng-template #placeholderRef>
-    <span class="select-toggle-text select-toggle-text-placeholder"> {{appPlaceholder}} </span>
+    <span [class.select-toggle-text---small]="appSmall" [class.select-toggle-text-placeholder]="!appSmall"
+          class="select-toggle-text">
+      {{appPlaceholder}} </span>
   </ng-template>
 
   <mat-icon [class.select-toggle-icon-opened]="dropDown.isOpen"
+            [class.select-toggle-icon---color-small]="appSmall"
             class="select-toggle-icon">
     play_arrow
   </mat-icon>
@@ -43,13 +52,15 @@
 <ng-template #optionsRef>
   <div (focusin)="toggleButtonRef.focus()"
        (keydown)="onKeyDown($event)"
-       [class.select-options-bottom]="dropDown?.position?.panelClass === 'bottom'"
+       [class.select-options---static-size]="!appSmall"
        [class.select-options-top]="dropDown?.position?.panelClass === 'top'"
+       [class.select-options-bottom]="dropDown?.position?.panelClass === 'bottom' && !appSmall"
        class="select-options"
        tabindex="0">
     <div (click)="onClickOnOption(option?.value)"
          *ngFor="let option of appOptions"
          [class.select-options-button-selected]="appValue != null && option?.value === appValue"
+         [class.select-options-button---small]="appSmall"
          class="select-options-button openk-button">
       <span class="select-options-button-label">
         {{option?.label}}
diff --git a/src/app/shared/controls/select/components/select/select.component.scss b/src/app/shared/controls/select/components/select/select.component.scss
index 5406349..305eef4 100644
--- a/src/app/shared/controls/select/components/select/select.component.scss
+++ b/src/app/shared/controls/select/components/select/select.component.scss
@@ -31,11 +31,28 @@
   }
 }
 
+.select-toggle---small {
+  padding: 0;
+  border: 0;
+  background: 0;
+
+  &:focus {
+    border: 0;
+    outline: 0;
+  }
+}
+
 .select-toggle-text {
   overflow-x: hidden;
   text-overflow: ellipsis;
-  padding-right: 0.5em;
   min-height: 1.5em;
+  padding-right: 0.5em;
+}
+
+.select-toggle-text---small {
+  color: get-color($openk-info-palette, A300);
+  padding-right: 0;
+  font-size: initial;
 }
 
 .select-toggle-text-placeholder {
@@ -51,11 +68,19 @@
   transform: rotate(90deg);
   transition: transform 100ms ease-in, color 100ms ease-in;
   color: get-color($openk-default-palette, 800);
+
+  &.select-toggle-icon-opened {
+    color: get-color($openk-info-palette, A300);
+  }
 }
 
 .select-toggle-icon-opened {
   transform: rotate(270deg);
+}
+
+.select-toggle-icon---color-small {
   color: get-color($openk-info-palette, A300);
+  font-size: initial;
 }
 
 .select-toggle-opened-bottom {
@@ -68,11 +93,9 @@
 
 .select-options {
   box-sizing: border-box;
-  width: calc(100% - 4px);
-  max-height: calc(7.5 * 2.5em);
+  width: 100%;
   overflow: auto;
-  display: flex;
-  flex-flow: column nowrap;
+  max-height: calc(7.5 * 2.5em);
   background: get-color($openk-default-palette);
   border: 1px solid get-color($openk-default-palette, 800);
   border-color: get-color($openk-info-palette, A300);
@@ -80,6 +103,12 @@
   box-shadow: 0 3px 3px rgba(0, 0, 0, 0.1);
 }
 
+.select-options---static-size {
+  display: flex;
+  flex-flow: column nowrap;
+  width: calc(100% - 4px);
+}
+
 .select-options-bottom {
   border-top: 0;
 }
@@ -109,6 +138,10 @@
   }
 }
 
+.select-options-button---small {
+  display: flex;
+}
+
 .select-options-button-label {
   text-overflow: ellipsis;
   overflow-x: hidden;
diff --git a/src/app/shared/controls/select/components/select/select.component.ts b/src/app/shared/controls/select/components/select/select.component.ts
index f4c96a8..6f06a09 100644
--- a/src/app/shared/controls/select/components/select/select.component.ts
+++ b/src/app/shared/controls/select/components/select/select.component.ts
@@ -12,7 +12,7 @@
  ********************************************************************************/
 
 import {ConnectedPosition} from "@angular/cdk/overlay";
-import {Component, forwardRef, Input, ViewChild} from "@angular/core";
+import {Component, ElementRef, EventEmitter, forwardRef, Input, Output, ViewChild} from "@angular/core";
 import {NG_VALUE_ACCESSOR} from "@angular/forms";
 import {EKeyboardKeys} from "../../../../../util/events";
 import {DropDownDirective} from "../../../../layout/drop-down";
@@ -53,9 +53,27 @@
     @Input()
     public appOptions: ISelectOption<T>[] = [];
 
+    /**
+     * Shows select with less padding if set.
+     */
+    @Input()
+    public appSmall = false;
+
+    @Input()
+    public appMaxWidth: string;
+
+    /**
+     * Outputs when the drop down is closed for parent components to react to.
+     */
+    @Output()
+    public appClose = new EventEmitter<void>();
+
     @ViewChild(DropDownDirective)
     public dropDown: DropDownDirective;
 
+    @ViewChild("toggleButtonRef", {static: true})
+    public toggleButton: ElementRef<HTMLElement>;
+
     public readonly connectedPositions: ConnectedPosition[] = [
         {
             originX: "start",
@@ -89,6 +107,9 @@
      */
     public toggle(openOrClose?: boolean) {
         this.dropDown.toggle(openOrClose);
+        if (this.dropDown.isOpen) {
+            this.toggleButton.nativeElement.focus();
+        }
     }
 
     public onClickOnOption(value: any) {
diff --git a/src/app/shared/controls/select/model/ISelectOption.ts b/src/app/shared/controls/select/model/ISelectOption.ts
index bc5ced3..301770c 100644
--- a/src/app/shared/controls/select/model/ISelectOption.ts
+++ b/src/app/shared/controls/select/model/ISelectOption.ts
@@ -18,4 +18,5 @@
 
 export interface IMultiSelectOption<T = any> extends ISelectOption<T> {
     isSelected: boolean;
+    isIndeterminate: boolean;
 }
diff --git a/src/app/shared/controls/select/pipes/filter-options-by-group.pipe.ts b/src/app/shared/controls/select/pipes/filter-options-by-group.pipe.ts
index fa48c08..6fb6ef0 100644
--- a/src/app/shared/controls/select/pipes/filter-options-by-group.pipe.ts
+++ b/src/app/shared/controls/select/pipes/filter-options-by-group.pipe.ts
@@ -12,6 +12,7 @@
  ********************************************************************************/
 
 import {Pipe, PipeTransform} from "@angular/core";
+import {arrayJoin} from "../../../../util/store";
 import {IMultiSelectOption, ISelectOption, ISelectOptionGroup} from "../model";
 
 @Pipe({
@@ -23,21 +24,31 @@
      * Filters a list for objects belonging to a group.
      * @param options List of options in which the results are searched
      * @param group Determines, which options are put in the result
-     * @param selected If an option value is part of this array, the isSelected property of the resulting option is set to true
+     * @param optionsState List of selected and indeterminate options to set isSelected and isIndeterminate accordingly.
      */
     public transform<T = any>(
         options: ISelectOption<T>[],
         group: ISelectOptionGroup<T>,
-        selected: T[]
+        optionsState: { selected: T[], indeterminate?: T[] }
     ): IMultiSelectOption[] {
         if (Array.isArray(options) && Array.isArray(group?.options)) {
-            selected = Array.isArray(selected) ? selected : [];
+
+            const selectedValues = Array.isArray(optionsState?.selected) ? arrayJoin(optionsState.selected) : [];
+            const indeterminateValues = Array.isArray(optionsState?.indeterminate) ? arrayJoin(optionsState.indeterminate) : [];
+
             return options
                 .filter((option) => {
                     return option != null && group.options.find((value) => option.value === value) != null;
                 })
                 .map((option) => {
-                    return {...option, isSelected: selected.find((value) => option.value === value) != null};
+                    const isSelected = selectedValues.find((value) => option.value === value) != null;
+                    const isIndeterminate = indeterminateValues.find((value) => option.value === value) != null;
+
+                    return {
+                        ...option,
+                        isSelected: isSelected && !isIndeterminate,
+                        isIndeterminate
+                    };
                 });
         }
 
diff --git a/src/app/shared/controls/text-field/text-field.component.spec.ts b/src/app/shared/controls/text-field/text-field.component.spec.ts
deleted file mode 100644
index 71da2ca..0000000
--- a/src/app/shared/controls/text-field/text-field.component.spec.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 {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {TextFieldComponent} from "./text-field.component";
-import {TextInputFieldModule} from "./text-field.module";
-
-describe("TextFieldComponent", () => {
-    let component: TextFieldComponent;
-    let fixture: ComponentFixture<TextFieldComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            imports: [
-                TextInputFieldModule
-            ]
-        }).compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(TextFieldComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it("should be created", () => {
-        expect(component).toBeTruthy();
-    });
-
-    it("should resize the textarea to always show all text", () => {
-        component.inputElement.nativeElement.style.height = "1px";
-        component.inputElement.nativeElement.value =
-            "A text that has to be of a certain length so it needs multiple lines to display. This should be enough.";
-        component.resize();
-        expect(component.inputElement.nativeElement.style.height).toEqual(component.inputElement.nativeElement.scrollHeight + "px");
-    });
-
-    it("should emit appFocusOut", () => {
-        spyOn(component.appFocusOut, "emit").and.callThrough();
-        component.onFocusOut();
-        expect(component.appFocusOut.emit).toHaveBeenCalled();
-    });
-
-    it("should call resize() and emit value on appInputValue", () => {
-        spyOn(component.appInputValue, "emit").and.callThrough();
-        spyOn(component, "resize").and.callThrough();
-        const value = "value";
-        component.onInput(value);
-        expect(component.resize).toHaveBeenCalled();
-        expect(component.appInputValue.emit).toHaveBeenCalledWith(value);
-    });
-
-});
diff --git a/src/app/shared/controls/text-field/text-field.component.ts b/src/app/shared/controls/text-field/text-field.component.ts
deleted file mode 100644
index 518e4e4..0000000
--- a/src/app/shared/controls/text-field/text-field.component.ts
+++ /dev/null
@@ -1,60 +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 {AfterContentChecked, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from "@angular/core";
-import {timer} from "rxjs";
-
-@Component({
-    selector: "app-text-field",
-    templateUrl: "./text-field.component.html",
-    styleUrls: ["./text-field.component.scss"]
-})
-export class TextFieldComponent implements AfterContentChecked {
-
-    @ViewChild("inputElement") inputElement: ElementRef;
-
-    @Input()
-    public appIsFocused: boolean;
-
-    @Input()
-    public appValue: string;
-
-    @Output()
-    public appInputValue = new EventEmitter<string>();
-
-    @Output()
-    public appFocusOut = new EventEmitter<void>();
-
-    public async ngAfterContentChecked() {
-        if (this.inputElement && this.appIsFocused) {
-            await timer(0).toPromise();
-            this.inputElement.nativeElement.focus();
-            this.resize();
-        }
-    }
-
-    public resize() {
-        this.inputElement.nativeElement.style.height = "1px";
-        this.inputElement.nativeElement.style.height = this.inputElement.nativeElement.scrollHeight + "px";
-    }
-
-    public onFocusOut() {
-        this.appFocusOut.emit();
-    }
-
-    public onInput(value: string) {
-        this.resize();
-        this.appInputValue.emit(value);
-    }
-
-}
-
diff --git a/src/app/shared/controls/text-field/text-field.component.html b/src/app/shared/file-preview/file-preview.component.html
similarity index 72%
rename from src/app/shared/controls/text-field/text-field.component.html
rename to src/app/shared/file-preview/file-preview.component.html
index 129e808..26357a6 100644
--- a/src/app/shared/controls/text-field/text-field.component.html
+++ b/src/app/shared/file-preview/file-preview.component.html
@@ -11,10 +11,6 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<textarea #inputElement
-          (focusout)="onFocusOut()"
-          (input)="onInput(inputElement.value)"
-          [value]="appValue"
-          class="openk-textarea text-input"
-          rows="1">
-</textarea>
+<object *ngIf="pdfUrl" [data]="pdfUrl" class="pdf">
+  {{appFile?.name}}
+</object>
diff --git a/src/app/shared/layout/page-header/component/page-header.component.scss b/src/app/shared/file-preview/file-preview.component.scss
similarity index 70%
rename from src/app/shared/layout/page-header/component/page-header.component.scss
rename to src/app/shared/file-preview/file-preview.component.scss
index 7ed6bcc..9a3cd89 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.scss
+++ b/src/app/shared/file-preview/file-preview.component.scss
@@ -11,26 +11,22 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+@import "openk.styles";
+
 :host {
   width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.pdf {
+  width: 100%;
+  height: 100%;
+
   display: flex;
-  flex-flow: row wrap;
+  justify-content: center;
   align-items: center;
-}
 
-.page-header-title {
-  margin-right: auto;
-  font-size: x-large;
-  font-weight: 600;
-}
-
-.page-header-actions {
-  display: flex;
-  flex-flow: row wrap;
-  margin-left: auto;
-  justify-content: flex-end;
-
-  .openk-button {
-    margin: 0.25em;
-  }
+  background-color: rgba(get-color($openk-default-palette, 500, contrast), 0.8);
+  color: get-color($openk-default-palette);
 }
diff --git a/src/app/shared/file-preview/file-preview.component.spec.ts b/src/app/shared/file-preview/file-preview.component.spec.ts
new file mode 100644
index 0000000..bdedae5
--- /dev/null
+++ b/src/app/shared/file-preview/file-preview.component.spec.ts
@@ -0,0 +1,99 @@
+/********************************************************************************
+ * 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 {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser";
+import {URL_TOKEN} from "../../core/dom";
+import {createFileMock, UrlMock} from "../../test";
+import {FilePreviewComponent} from "./file-preview.component";
+import {FilePreviewModule} from "./file-preview.module";
+
+export class MockSanitizer {
+    public bypassSecurityTrustResourceUrl(url: string): SafeResourceUrl {
+        return ("safe" + url) as SafeResourceUrl;
+    }
+}
+
+describe("FilePreviewComponent", () => {
+    let component: FilePreviewComponent;
+    let fixture: ComponentFixture<FilePreviewComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                {
+                    provide: URL_TOKEN,
+                    useClass: UrlMock
+                },
+                {
+                    provide: DomSanitizer,
+                    useClass: MockSanitizer
+                }
+            ],
+            imports: [FilePreviewModule]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(FilePreviewComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should return a safe resource url for the input file", () => {
+        component.appFile = null;
+        let url = component.getSafeUrl();
+        expect(url).toBeFalsy();
+
+        component.appFile = createFileMock("test.pdf");
+        url = component.getSafeUrl();
+        expect(component.objectUrl).toEqual(new UrlMock().createObjectURL(component.appFile));
+        expect(url).toEqual(new MockSanitizer().bypassSecurityTrustResourceUrl(component.objectUrl + "#view=Fit"));
+    });
+
+    it("should call revokeUrl to clear up memory", () => {
+        spyOn(component.url, "revokeObjectURL").and.callThrough();
+        component.ngOnDestroy();
+        expect(component.url.revokeObjectURL).not.toHaveBeenCalled();
+        component.appFile = createFileMock("test.pdf");
+        component.getSafeUrl();
+        component.ngOnDestroy();
+        expect(component.url.revokeObjectURL).toHaveBeenCalled();
+    });
+
+    it("should get new resource url to display on file input change", () => {
+        spyOn(component.url, "revokeObjectURL").and.callThrough();
+        spyOn(component.url, "createObjectURL").and.callThrough();
+        spyOn(component.sanitizer, "bypassSecurityTrustResourceUrl").and.callThrough();
+        component.ngOnChanges({});
+        expect(component.url.revokeObjectURL).not.toHaveBeenCalled();
+        expect(component.url.createObjectURL).not.toHaveBeenCalled();
+        expect(component.sanitizer.bypassSecurityTrustResourceUrl).not.toHaveBeenCalled();
+
+        component.appFile = createFileMock("test.pdf");
+        component.objectUrl = "test";
+        component.ngOnChanges({appFile: new SimpleChange(null, component.appFile, false)});
+        expect(component.url.revokeObjectURL).toHaveBeenCalled();
+        expect(component.url.createObjectURL).toHaveBeenCalled();
+        expect(component.sanitizer.bypassSecurityTrustResourceUrl).toHaveBeenCalled();
+
+        const expectedSafeUrl = new MockSanitizer().bypassSecurityTrustResourceUrl(
+            new UrlMock().createObjectURL(component.appFile) + "#view=Fit");
+        expect(component.pdfUrl).toEqual(expectedSafeUrl);
+    });
+});
diff --git a/src/app/shared/file-preview/file-preview.component.ts b/src/app/shared/file-preview/file-preview.component.ts
new file mode 100644
index 0000000..849b077
--- /dev/null
+++ b/src/app/shared/file-preview/file-preview.component.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 {Component, Inject, Input, OnChanges, OnDestroy, SimpleChanges} from "@angular/core";
+import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser";
+import {URL_TOKEN} from "../../core/dom";
+
+@Component({
+    selector: "app-file-preview",
+    templateUrl: "./file-preview.component.html",
+    styleUrls: ["./file-preview.component.scss"]
+})
+export class FilePreviewComponent implements OnChanges, OnDestroy {
+
+    @Input()
+    public appFile: File;
+
+    public pdfUrl: SafeResourceUrl;
+
+    public objectUrl: string;
+
+    public constructor(
+        public sanitizer: DomSanitizer,
+        @Inject(URL_TOKEN) public url: typeof URL
+    ) {
+
+    }
+
+    public ngOnChanges(changes: SimpleChanges) {
+        if (changes.appFile) {
+            if (this.objectUrl) {
+                this.url.revokeObjectURL(this.objectUrl);
+                this.objectUrl = null;
+                this.pdfUrl = null;
+            }
+            this.pdfUrl = this.getSafeUrl();
+        }
+    }
+
+    public ngOnDestroy() {
+        if (this.objectUrl) {
+            this.url.revokeObjectURL(this.objectUrl);
+        }
+    }
+
+    public getSafeUrl() {
+        this.objectUrl = this.appFile ? this.url.createObjectURL(this.appFile) : null;
+        return this.objectUrl ? this.sanitizer.bypassSecurityTrustResourceUrl(this.objectUrl + "#view=Fit") : null;
+    }
+
+}
diff --git a/src/app/shared/controls/file-select/file-select.module.ts b/src/app/shared/file-preview/file-preview.module.ts
similarity index 77%
copy from src/app/shared/controls/file-select/file-select.module.ts
copy to src/app/shared/file-preview/file-preview.module.ts
index 3dced60..d314c5a 100644
--- a/src/app/shared/controls/file-select/file-select.module.ts
+++ b/src/app/shared/file-preview/file-preview.module.ts
@@ -13,23 +13,23 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {FormsModule} from "@angular/forms";
 import {MatIconModule} from "@angular/material/icon";
-import {FileSelectComponent} from "./component/file-select.component";
+import {CollapsibleModule} from "../layout/collapsible";
+import {FilePreviewComponent} from "./file-preview.component";
 
 @NgModule({
     imports: [
         CommonModule,
-        FormsModule,
+        CollapsibleModule,
         MatIconModule
     ],
     declarations: [
-        FileSelectComponent
+        FilePreviewComponent
     ],
     exports: [
-        FileSelectComponent
+        FilePreviewComponent
     ]
 })
-export class FileSelectModule {
+export class FilePreviewModule {
 
 }
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/shared/file-preview/index.ts
similarity index 93%
rename from src/app/shared/controls/text-field/index.ts
rename to src/app/shared/file-preview/index.ts
index f223969..6c9c7c9 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/shared/file-preview/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./file-preview.module";
diff --git a/src/app/shared/controls/file-select/file-select.module.ts b/src/app/shared/layout/action-button/action-button.module.ts
similarity index 78%
rename from src/app/shared/controls/file-select/file-select.module.ts
rename to src/app/shared/layout/action-button/action-button.module.ts
index 3dced60..0a3ca7b 100644
--- a/src/app/shared/controls/file-select/file-select.module.ts
+++ b/src/app/shared/layout/action-button/action-button.module.ts
@@ -13,23 +13,23 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {FormsModule} from "@angular/forms";
 import {MatIconModule} from "@angular/material/icon";
-import {FileSelectComponent} from "./component/file-select.component";
+import {RouterModule} from "@angular/router";
+import {ActionButtonComponent} from "./components";
 
 @NgModule({
     imports: [
         CommonModule,
-        FormsModule,
+        RouterModule,
         MatIconModule
     ],
     declarations: [
-        FileSelectComponent
+        ActionButtonComponent
     ],
     exports: [
-        FileSelectComponent
+        ActionButtonComponent
     ]
 })
-export class FileSelectModule {
+export class ActionButtonModule {
 
 }
diff --git a/src/app/shared/layout/action-button/components/action-button.component.html b/src/app/shared/layout/action-button/components/action-button.component.html
new file mode 100644
index 0000000..603230e
--- /dev/null
+++ b/src/app/shared/layout/action-button/components/action-button.component.html
@@ -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
+ -------------------------------------------------------------------------------->
+
+<ng-template #contenRef>
+  <ng-content></ng-content>
+</ng-template>
+
+<button (click)="appClick.emit($event)"
+        *ngIf="appRouterLink == null || appDisabled ; else anchorRef"
+        [disabled]="appDisabled"
+        [type]="appType"
+        class="action-button openk-button">
+  <mat-icon *ngIf="appIcon">{{appIcon}}</mat-icon>
+  <ng-container [ngTemplateOutlet]="contenRef"></ng-container>
+</button>
+
+<ng-template #anchorRef>
+  <a (click)="appClick.emit($event)"
+     [queryParams]="appStatementId != null ? { id: appStatementId } : undefined"
+     [routerLink]="appRouterLink"
+     [type]="appType"
+     class="action-button openk-button">
+    <mat-icon *ngIf="appIcon">{{appIcon}}</mat-icon>
+    <ng-container [ngTemplateOutlet]="contenRef"></ng-container>
+  </a>
+</ng-template>
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/shared/layout/action-button/components/action-button.component.scss
similarity index 69%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/shared/layout/action-button/components/action-button.component.scss
index 563859b..3aa7130 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/shared/layout/action-button/components/action-button.component.scss
@@ -11,9 +11,20 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
+:host {
+  display: inline-block;
+  justify-content: flex-start;
+  text-align: left;
+  white-space: initial;
+  line-height: 1;
+}
+
+.action-button {
   box-sizing: border-box;
-  min-height: 2.5em;
+  width: 100%;
+  height: 100%;
+  justify-content: inherit;
+  text-align: inherit;
+  white-space: inherit;
+  line-height: inherit;
 }
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/shared/layout/action-button/components/action-button.component.spec.ts
similarity index 65%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/shared/layout/action-button/components/action-button.component.spec.ts
index 20e1e36..753f99c 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/shared/layout/action-button/components/action-button.component.spec.ts
@@ -12,23 +12,21 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {ActionButtonModule} from "../action-button.module";
+import {ActionButtonComponent} from "./action-button.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("ActionButtonComponent", () => {
+    let component: ActionButtonComponent;
+    let fixture: ComponentFixture<ActionButtonComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [ActionButtonModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(ActionButtonComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/shared/layout/action-button/components/action-button.component.stories.ts b/src/app/shared/layout/action-button/components/action-button.component.stories.ts
new file mode 100644
index 0000000..c961ef8
--- /dev/null
+++ b/src/app/shared/layout/action-button/components/action-button.component.stories.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 {RouterTestingModule} from "@angular/router/testing";
+import {action} from "@storybook/addon-actions";
+import {boolean, number, text, withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {ActionButtonModule} from "../action-button.module";
+
+storiesOf("Shared / Layout", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({
+        imports: [
+            RouterTestingModule,
+            ActionButtonModule
+        ]
+    }))
+    .add("ActionButtonComponent", () => ({
+        template: `
+            <app-action-button
+                style="margin: 1em; width: auto; max-width: 15em;"
+                [appDisabled]="appDisabled"
+                [appIcon]="appIcon"
+                [appRouterLink]="asAnchor ? appRouterLink : null"
+                [appStatementId]="appStatementId"
+                [ngClass]="ngClass"
+                (appClick)="appClick($event)">
+                {{content}}
+            </app-action-button>
+        `,
+        props: {
+            content: text("content", "Button"),
+            asAnchor: boolean("asAnchor", false),
+
+            appDisabled: boolean("appDisabled", false),
+            appIcon: text("appLoadingMessage", "redo"),
+            appRouterLink: text("appRouterLink", "/details"),
+            appStatementId: number("appStatementId", 19),
+            appClick: action("appClick"),
+            ngClass: text("ngClass", "openk-info")
+        }
+    }));
diff --git a/src/app/shared/layout/action-button/components/action-button.component.ts b/src/app/shared/layout/action-button/components/action-button.component.ts
new file mode 100644
index 0000000..a01c386
--- /dev/null
+++ b/src/app/shared/layout/action-button/components/action-button.component.ts
@@ -0,0 +1,41 @@
+/********************************************************************************
+ * 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-action-button",
+    templateUrl: "./action-button.component.html",
+    styleUrls: ["./action-button.component.scss"]
+})
+export class ActionButtonComponent {
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appIcon: string;
+
+    @Input()
+    public appType: string;
+
+    @Input()
+    public appRouterLink: string;
+
+    @Input()
+    public appStatementId: number;
+
+    @Output()
+    public appClick = new EventEmitter<MouseEvent>();
+
+}
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/shared/layout/action-button/components/index.ts
similarity index 92%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/shared/layout/action-button/components/index.ts
index f223969..ac0aff9 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/shared/layout/action-button/components/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./action-button.component";
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/shared/layout/action-button/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/shared/layout/action-button/index.ts
index f223969..0b7c475 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/shared/layout/action-button/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./action-button.module";
diff --git a/src/app/shared/layout/collapsible/collapsible.component.html b/src/app/shared/layout/collapsible/collapsible.component.html
index 96bf684..6c5152f 100644
--- a/src/app/shared/layout/collapsible/collapsible.component.html
+++ b/src/app/shared/layout/collapsible/collapsible.component.html
@@ -15,8 +15,10 @@
      #header [class.collapsible-header---no-color]="appSimpleCollapsible" class="collapsible-header">
 
   <button (click)="toggle()"
+          [class.not-allowed]="appDisabled"
           type="button"
           [class.collapsible-header--toggle---small]="appSimpleCollapsible"
+          [disabled]="appDisabled"
           class="collapsible-header--toggle cursor-pointer">
     <mat-icon [class.collapsible-header--icon---collapsed]="isCollapsed$ | async"
               class="collapsible-header--icon">
diff --git a/src/app/shared/layout/collapsible/collapsible.component.spec.ts b/src/app/shared/layout/collapsible/collapsible.component.spec.ts
index c667a8a..9a61b98 100644
--- a/src/app/shared/layout/collapsible/collapsible.component.spec.ts
+++ b/src/app/shared/layout/collapsible/collapsible.component.spec.ts
@@ -20,6 +20,10 @@
     let component: CollapsibleComponent;
     let fixture: ComponentFixture<CollapsibleComponent>;
 
+    function getHeight() {
+        return component.elementRef.nativeElement.style.height;
+    }
+
     beforeEach(async(() => {
         TestBed.configureTestingModule({
             imports: [CollapsibleModule]
@@ -53,17 +57,17 @@
     it("should adjust the height after collapsing or expanding", fakeAsync(() => {
         expect(component).toBeTruthy();
         component.getScrollHeight = () => 100;
-        expect(component.height).not.toBeDefined();
+        expect(getHeight()).toBe("");
 
         component.appCollapsed = true;
-        expect(component.height).toBe("100px");
+        expect(getHeight()).toBe("100px");
         tick(20);
-        expect(component.height).toBe("0px");
+        expect(getHeight()).toBe("0px");
 
         component.appCollapsed = false;
-        expect(component.height).toBe("100px");
+        expect(getHeight()).toBe("100px");
         tick(200);
-        expect(component.height).toBe("initial");
+        expect(getHeight()).toBe("initial");
     }));
 
     it("should return the scroll height of it's native element", () => {
diff --git a/src/app/shared/layout/collapsible/collapsible.component.ts b/src/app/shared/layout/collapsible/collapsible.component.ts
index fdefdfd..74d7a8c 100644
--- a/src/app/shared/layout/collapsible/collapsible.component.ts
+++ b/src/app/shared/layout/collapsible/collapsible.component.ts
@@ -60,16 +60,13 @@
     @Input()
     public appSimpleCollapsible = false;
 
+    @Input()
+    public appDisabled: boolean;
+
     @Output()
     public readonly appCollapsedChange = new EventEmitter<boolean>();
 
     /**
-     * Sets the height of the component's native element (is overwritten while animating)
-     */
-    @HostBinding("style.height")
-    public height: string;
-
-    /**
      * This observables returns true if and only if the container is collapsed
      */
     public isCollapsed$ = defer(() => this.isCollapsedSubject.asObservable());
@@ -79,10 +76,10 @@
      * Note that the animation works only, if the native element has it's height set to an absolute value.
      */
     private collapse$ = defer(() => {
-        this.height = this.getScrollHeight() + "px";
+        this.setHeight(this.getScrollHeight() + "px");
         return timer(20); // This timeout is necessary so that the height is set on the element.
     }).pipe(
-        tap(() => this.height = "0px"),
+        tap(() => this.setHeight("0px")),
         switchMap(() => timer(this.appTransitionTime))
     );
 
@@ -91,16 +88,18 @@
      * Note that the animation works only, if the native element has it's height set to an absolute value.
      */
     private expand$ = defer(() => {
-        this.height = this.getScrollHeight() + "px";
+        this.setHeight(this.getScrollHeight() + "px");
         return timer(this.appTransitionTime);
-    }).pipe(tap(() => this.height = "initial"));
+    }).pipe(tap(() => this.setHeight("initial")));
 
     @ViewChild("bodyElement")
     private bodyElementRef: ElementRef<HTMLElement>;
 
     private isCollapsedSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
 
-    public constructor(public readonly elementRef: ElementRef<HTMLElement>) {
+    public constructor(
+        public readonly elementRef: ElementRef<HTMLElement>
+    ) {
 
     }
 
@@ -122,7 +121,7 @@
 
     public ngOnInit() {
         if (this.isCollapsedSubject.getValue()) {
-            this.height = "0";
+            this.setHeight("0");
         }
 
         this.isCollapsed$.pipe(
@@ -146,6 +145,17 @@
     }
 
     /**
+     * Sets the height of the component's native element (is overwritten while animating)
+     */
+    public setHeight(height: string) {
+        try {
+            this.elementRef.nativeElement.style.height = height;
+        } catch (e) {
+            return;
+        }
+    }
+
+    /**
      * Computes the scroll height of the component's native element
      */
     public getScrollHeight(): number {
diff --git a/src/app/shared/layout/drop-down/drop-down.directive.ts b/src/app/shared/layout/drop-down/drop-down.directive.ts
index 6fdcaf5..23c1850 100644
--- a/src/app/shared/layout/drop-down/drop-down.directive.ts
+++ b/src/app/shared/layout/drop-down/drop-down.directive.ts
@@ -64,6 +64,9 @@
     @Input()
     public appDropDown: TemplateRef<C>;
 
+    @Input()
+    public appFixedWidth: string;
+
     @Output()
     public readonly appOpen = new EventEmitter<Event | null>();
 
@@ -181,7 +184,8 @@
         const origin = this.elementRef.nativeElement;
         const rect = origin.getBoundingClientRect();
         return new OverlayConfig({
-            width: rect.width,
+            width: this.appFixedWidth ? "unset" : rect.width,
+            maxWidth: this.appFixedWidth ? this.appFixedWidth : undefined,
             positionStrategy: this.getPositionStrategy(origin),
             scrollStrategy: this.overlay.scrollStrategies.close()
         });
diff --git a/src/app/shared/controls/file-drop/component/file-drop.component.html b/src/app/shared/layout/file-drop/component/file-drop.component.html
similarity index 64%
rename from src/app/shared/controls/file-drop/component/file-drop.component.html
rename to src/app/shared/layout/file-drop/component/file-drop.component.html
index ba860d6..c5a267c 100644
--- a/src/app/shared/controls/file-drop/component/file-drop.component.html
+++ b/src/app/shared/layout/file-drop/component/file-drop.component.html
@@ -11,24 +11,21 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<span (dragover)="onDragOver($event)"
-      *ngFor="let item of appValue; let index = index;"
-      class="attachments-file">
+<ng-content></ng-content>
 
-  <button (click)="onDelete(index)"
-          [disabled]="appDisabled"
-          class="openk-button openk-button-rounded"
-          type="button">
-    <mat-icon>clear</mat-icon>
-  </button>
-
-  {{item?.attachment?.name}}
-
-</span>
+<label *ngIf="appPlaceholder"
+       [class.cursor-pointer]="!appDisabled"
+       [for]="appId"
+       class="file-drop-placeholder">
+  <span class="file-drop-placeholder--label">
+    {{appPlaceholder}}
+  </span>
+</label>
 
 <input #inputElement
+       [id]="appId"
        (input)="onInput(inputElement.files)"
        [disabled]="appDisabled"
-       id="new-statement-attachments" multiple
+       multiple
        style="display: none;"
        type="file">
diff --git a/src/app/shared/controls/file-drop/component/file-drop.component.scss b/src/app/shared/layout/file-drop/component/file-drop.component.scss
similarity index 60%
rename from src/app/shared/controls/file-drop/component/file-drop.component.scss
rename to src/app/shared/layout/file-drop/component/file-drop.component.scss
index 02f7922..63c48e0 100644
--- a/src/app/shared/controls/file-drop/component/file-drop.component.scss
+++ b/src/app/shared/layout/file-drop/component/file-drop.component.scss
@@ -11,11 +11,59 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-@import "openk.styles";
+@import "src/styles/openk.styles";
 
 :host {
   @include rounded-border-mixin();
 
+  position: relative;
+
+  display: flex;
+  flex-flow: column;
+  align-items: flex-start;
+  box-sizing: border-box;
+
+  line-height: 1;
+
+  border: 1px dashed $openk-form-border;
+  background: get-color($openk-default-palette);
+
+  min-height: 6em;
+}
+
+.file-drop-placeholder {
+  display: none;
+  width: 100%;
+  flex: 1 1 100%;
+  height: 100%;
+  justify-content: center;
+  align-items: center;
+
+  color: $openk-form-border;
+
+  font-size: smaller;
+
+  &:first-child {
+    display: flex;
+  }
+}
+
+
+* + .file-drop-placeholder {
+  display: none;
+}
+
+.file-drop-placeholder--label {
+  font-style: italic;
+  text-align: center;
+  max-width: 14em;
+  padding: 0.5em;
+}
+
+/*
+:host {
+  @include rounded-border-mixin();
+
   background: get-color($openk-default-palette);
   display: inline-flex;
   border: 1px dashed get-color($openk-default-palette, A700);
@@ -43,3 +91,4 @@
     margin-right: 0.25em;
   }
 }
+*/
diff --git a/src/app/shared/layout/file-drop/component/file-drop.component.spec.ts b/src/app/shared/layout/file-drop/component/file-drop.component.spec.ts
new file mode 100644
index 0000000..4f6b107
--- /dev/null
+++ b/src/app/shared/layout/file-drop/component/file-drop.component.spec.ts
@@ -0,0 +1,118 @@
+/********************************************************************************
+ * 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 {FileDropComponent} from "./file-drop.component";
+
+describe("FileDropComponent", () => {
+    let component: FileDropComponent;
+    let fixture: ComponentFixture<FileDropComponent>;
+    let fileListMock: FileListMock;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [FileDropComponent]
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(FileDropComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    beforeEach(() => {
+        fileListMock = new FileListMock([
+            new File(["File", "A"], "FileA.txt"),
+            new File(["File", "B"], "FileB.txt"),
+            new File(["File", "C"], "FileC.txt")
+        ]);
+    });
+
+    it("should create", () => {
+        expect(component).toBeTruthy();
+    });
+
+    it("should prevent default browser file drops", () => {
+        const dragEvent = new DragEvent("dragover");
+        const prevenDefaultSpy = spyOn(dragEvent, "preventDefault");
+        component.onDocumentDrag(dragEvent);
+        expect(prevenDefaultSpy).toHaveBeenCalled();
+    });
+
+    it("should not prevent default browser file drops if disabled", () => {
+        component.appDisabled = true;
+        const dragEvent = new DragEvent("dragover");
+        const prevenDefaultSpy = spyOn(dragEvent, "preventDefault");
+        component.onDocumentDrag(dragEvent);
+        expect(prevenDefaultSpy).not.toHaveBeenCalled();
+    });
+
+    it("should call onInput when files are dropped", () => {
+        const onInputSpy = spyOn(component, "onInput");
+
+        component.onDrop(null);
+        expect(onInputSpy).not.toHaveBeenCalled();
+        component.onDrop(new DragEvent("drop"));
+        expect(onInputSpy).not.toHaveBeenCalled();
+        component.onDrop({dataTransfer: {files: fileListMock.toFileList()}} as DragEvent);
+        expect(onInputSpy).toHaveBeenCalledWith(fileListMock.toFileList());
+    });
+
+    it("should open file select dialog", () => {
+        const clickSpy = spyOn(component.inputElement.nativeElement, "click");
+        component.appDisabled = true;
+        component.openDialog();
+        expect(clickSpy).not.toHaveBeenCalled();
+
+        component.appDisabled = false;
+        component.openDialog();
+        expect(clickSpy).toHaveBeenCalled();
+    });
+
+    it("should emit file array on input", () => {
+        const fileDropEmitSpy = spyOn(component.appFileDrop, "emit");
+        component.appDisabled = true;
+        component.onInput(fileListMock.toFileList());
+        expect(fileDropEmitSpy).not.toHaveBeenCalled();
+
+        component.appDisabled = false;
+        component.onInput(fileListMock.toFileList());
+        expect(fileDropEmitSpy).toHaveBeenCalledWith(fileListMock.array);
+        component.onInput(null);
+        expect(fileDropEmitSpy).toHaveBeenCalledWith([]);
+    });
+
+});
+
+
+class FileListMock {
+
+    constructor(public array: File[]) {
+
+    }
+
+    public get length(): number {
+        return this.array.length;
+    }
+
+    public item(idx) {
+        return this.array[idx];
+    }
+
+    public toFileList() {
+        return this as any as FileList;
+    }
+
+
+}
diff --git a/src/app/shared/controls/file-drop/component/file-drop.component.stories.ts b/src/app/shared/layout/file-drop/component/file-drop.component.stories.ts
similarity index 62%
rename from src/app/shared/controls/file-drop/component/file-drop.component.stories.ts
rename to src/app/shared/layout/file-drop/component/file-drop.component.stories.ts
index fab1fe0..f65555b 100644
--- a/src/app/shared/controls/file-drop/component/file-drop.component.stories.ts
+++ b/src/app/shared/layout/file-drop/component/file-drop.component.stories.ts
@@ -12,11 +12,11 @@
  ********************************************************************************/
 
 import {action} from "@storybook/addon-actions";
-import {boolean, number, withKnobs} from "@storybook/addon-knobs";
+import {boolean, text, withKnobs} from "@storybook/addon-knobs";
 import {moduleMetadata, storiesOf} from "@storybook/angular";
 import {FileDropModule} from "../file-drop.module";
 
-storiesOf("Shared/Controls", module)
+storiesOf("Shared/Layout", module)
     .addDecorator(withKnobs)
     .addDecorator(moduleMetadata({imports: [FileDropModule]}))
     .add("FileDropComponent", () => ({
@@ -25,9 +25,11 @@
                 #fileDrop
                 style="margin: 1em;"
                 [appDisabled]="appDisabled"
-                (appValueChange)="appValueChange($event)"
-                (appValueDelete)="appValueDelete($event)"
-                >
+                (appFileDrop)="appFileDrop($event)"
+                [appPlaceholder]="appPlaceholder">
+                <div style="margin: auto" *ngIf="content">
+                    {{content}}
+                </div>
             </app-file-drop>
             <div>
                 <button (click)="fileDrop.openDialog();" class="openk-button" style="margin: 0 1em;">
@@ -36,21 +38,9 @@
             </div>
         `,
         props: {
-            getFiles: (length) => getFiles(length),
+            content: text("Content", undefined),
+            appPlaceholder: text("appPlaceholder", "Drop files here."),
             appDisabled: boolean("appDisabled", false),
-            numberOfFiles: number("Number of files", 2, {min: 0, max: 30, step: 1, range: true}),
-            appValueChange: action("appValueChange"),
-            appValueDelete: action("appValueDelete")
+            appFileDrop: action("appFileDrop")
         }
     }));
-
-function getFiles(length): File[] {
-    return Array(length)
-        .fill(0)
-        .map((_, id) => {
-            const fileBits = Array((id + 1) * 1024)
-                .fill(0)
-                .map(() => "" + Math.floor(Math.random() * 8));
-            return new File(fileBits, `New File (${id}).txt`);
-        });
-}
diff --git a/src/app/shared/layout/file-drop/component/file-drop.component.ts b/src/app/shared/layout/file-drop/component/file-drop.component.ts
new file mode 100644
index 0000000..4111f6b
--- /dev/null
+++ b/src/app/shared/layout/file-drop/component/file-drop.component.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 {Component, ElementRef, EventEmitter, HostListener, Input, Output, ViewChild} from "@angular/core";
+
+@Component({
+    selector: "app-file-drop",
+    templateUrl: "./file-drop.component.html",
+    styleUrls: ["./file-drop.component.scss"]
+})
+export class FileDropComponent {
+
+    private static id = 0;
+
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appId = `FileDropComponent-${FileDropComponent.id++}`;
+
+    @Input()
+    public appPlaceholder: string;
+
+    @Output()
+    public appFileDrop = new EventEmitter<File[]>();
+
+    @ViewChild("inputElement", {static: true})
+    public inputElement: ElementRef<HTMLInputElement>;
+
+    @HostListener("document:dragover", ["$event"])
+    @HostListener("document:drop", ["$event"])
+    public onDocumentDrag(event: Event) {
+        if (this.appDisabled) {
+            return;
+        }
+        // Disable default file drop to browser window:
+        event.preventDefault();
+    }
+
+    @HostListener("drop", ["$event"])
+    public onDrop(event: DragEvent) {
+        const files = event?.dataTransfer?.files;
+        if (files != null) {
+            this.onInput(files);
+        }
+    }
+
+    public openDialog(): void {
+        if (this.appDisabled) {
+            return;
+        }
+        this.inputElement.nativeElement.value = "";
+        this.inputElement.nativeElement.click();
+    }
+
+    public onInput(fileList: FileList) {
+        if (this.appDisabled) {
+            return;
+        }
+        this.appFileDrop.emit(fileListToFileArray(fileList));
+    }
+
+}
+
+function fileListToFileArray(fileList: FileList): File[] {
+    try {
+        const result: File[] = [];
+        for (let i = 0; i < fileList.length; i++) {
+            result.push(fileList.item(i));
+        }
+        return result;
+    } catch (e) {
+        return [];
+    }
+}
diff --git a/src/app/shared/controls/file-drop/file-drop.module.ts b/src/app/shared/layout/file-drop/file-drop.module.ts
similarity index 100%
rename from src/app/shared/controls/file-drop/file-drop.module.ts
rename to src/app/shared/layout/file-drop/file-drop.module.ts
diff --git a/src/app/shared/controls/file-drop/index.ts b/src/app/shared/layout/file-drop/index.ts
similarity index 100%
rename from src/app/shared/controls/file-drop/index.ts
rename to src/app/shared/layout/file-drop/index.ts
diff --git a/src/app/shared/layout/page-header/component/IPageHeaderActions.ts b/src/app/shared/layout/page-header/component/IPageHeaderActions.ts
deleted file mode 100644
index 174a250..0000000
--- a/src/app/shared/layout/page-header/component/IPageHeaderActions.ts
+++ /dev/null
@@ -1,19 +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 interface IPageHeaderAction {
-    name: string;
-    icon: string;
-    routerLink: string;
-    queryParams?: any;
-}
diff --git a/src/app/shared/layout/page-header/component/page-header.component.html b/src/app/shared/layout/page-header/component/page-header.component.html
deleted file mode 100644
index e100f46..0000000
--- a/src/app/shared/layout/page-header/component/page-header.component.html
+++ /dev/null
@@ -1,28 +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
- -------------------------------------------------------------------------------->
-
-<span class="page-header-title">
-  {{appTitle | translate}}
-</span>
-
-<div *ngIf="appActions?.length > 0" class="page-header-actions">
-
-  <a *ngFor="let action of appActions"
-     [routerLink]="action?.routerLink"
-     [queryParams]="action?.queryParams"
-     class="openk-button openk-info">
-    <mat-icon>{{action?.icon}}</mat-icon>
-    {{action.name | translate}}
-  </a>
-
-</div>
diff --git a/src/app/shared/layout/page-header/index.ts b/src/app/shared/layout/page-header/index.ts
deleted file mode 100644
index 30a8ac4..0000000
--- a/src/app/shared/layout/page-header/index.ts
+++ /dev/null
@@ -1,15 +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 "./component/IPageHeaderActions";
-export * from "./page-header.module";
diff --git a/src/app/shared/layout/page-header/page-header.module.ts b/src/app/shared/layout/page-header/page-header.module.ts
deleted file mode 100644
index 218ea03..0000000
--- a/src/app/shared/layout/page-header/page-header.module.ts
+++ /dev/null
@@ -1,36 +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 {CommonModule} from "@angular/common";
-import {NgModule} from "@angular/core";
-import {MatIconModule} from "@angular/material/icon";
-import {RouterModule} from "@angular/router";
-import {TranslateModule} from "@ngx-translate/core";
-import {PageHeaderComponent} from "./component/page-header.component";
-
-@NgModule({
-    imports: [
-        CommonModule,
-        TranslateModule,
-        MatIconModule,
-        RouterModule
-    ],
-    exports: [
-        PageHeaderComponent
-    ],
-    declarations: [
-        PageHeaderComponent
-    ]
-})
-export class PageHeaderModule {
-}
diff --git a/src/app/features/details/selectors/index.ts b/src/app/shared/layout/side-menu/TSideMenuContentPosition.ts
similarity index 88%
copy from src/app/features/details/selectors/index.ts
copy to src/app/shared/layout/side-menu/TSideMenuContentPosition.ts
index aeb78c3..bcaba13 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/shared/layout/side-menu/TSideMenuContentPosition.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export type TSideMenuContentPosition = "top" | "center" | "bottom";
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/shared/layout/side-menu/components/index.ts
similarity index 88%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/shared/layout/side-menu/components/index.ts
index f223969..b6fb216 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/shared/layout/side-menu/components/index.ts
@@ -11,4 +11,5 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./side-menu-container";
+export * from "./side-menu-status";
diff --git a/src/app/features/details/selectors/index.ts b/src/app/shared/layout/side-menu/components/side-menu-container/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/shared/layout/side-menu/components/side-menu-container/index.ts
index aeb78c3..3e8a333 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/shared/layout/side-menu/components/side-menu-container/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./side-menu-container.component";
diff --git a/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.html b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.html
new file mode 100644
index 0000000..efe06cd
--- /dev/null
+++ b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.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
+ -------------------------------------------------------------------------------->
+
+<ng-template #sideMenuTemplateRef>
+  <div *ngIf="hasContent$ | async"
+       [class.side-menu---collapsed]="!isOpen"
+       [class.side-menu---left]="left$ | async"
+       class="side-menu">
+
+
+    <div class="side-menu--content">
+
+      <ng-container *ngIf="(title$ | async)?.trim().length > 0">
+        <div class="side-menu--content--title">
+          {{title$ | async}}
+        </div>
+      </ng-container>
+
+      <ng-container *ngFor="let directive of (content$ | async); let first = first;">
+        <ng-container *ngIf="directive?.templateRef"
+                      [ngTemplateOutlet]="directive?.templateRef">
+        </ng-container>
+        <div class="side-menu--content--spacing"></div>
+      </ng-container>
+
+    </div>
+
+    <div (pointerdown)="isOpen = !isOpen"
+         [class.side-menu--toggle-bar---collapsed]="!isOpen"
+         [class.side-menu--toggle-bar---left]="left$ | async"
+         class="side-menu--toggle-bar">
+    </div>
+
+  </div>
+</ng-template>
+
+
+<ng-container
+  *ngIf="(left$ | async) === true"
+  [ngTemplateOutlet]="sideMenuTemplateRef">
+</ng-container>
+
+<div class="main-content main-content---without-scroll">
+  <div class="main-content">
+    <ng-content></ng-content>
+  </div>
+</div>
+
+<ng-container
+  *ngIf="(left$ | async) !== true"
+  [ngTemplateOutlet]="sideMenuTemplateRef">
+</ng-container>
diff --git a/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.scss b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.scss
new file mode 100644
index 0000000..b187757
--- /dev/null
+++ b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.scss
@@ -0,0 +1,142 @@
+/********************************************************************************
+ * 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 "openk.styles";
+
+:host {
+  display: flex;
+  flex-flow: row;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+}
+
+.main-content {
+  flex: 1 1 100%;
+  width: 100%;
+  height: 100%;
+
+  display: flex;
+  flex-flow: column;
+  overflow-x: auto;
+  overflow-y: scroll;
+}
+
+.main-content---without-scroll {
+  position: relative;
+  overflow: hidden;
+}
+
+.side-menu {
+  --side-menu-width: 17em;
+  --side-menu-width-collapsed: 1em;
+  --side-menu-border-width-highlighted: 3px;
+
+  position: relative;
+  display: flex;
+
+  flex: 1 1 var(--side-menu-width-collapsed);
+  width: var(--side-menu-width);
+  box-sizing: border-box;
+  border-left: 1px solid $openk-form-border;
+  height: 100%;
+
+  background-color: $openk-background-highlight;
+
+  transition: width ease-out 60ms;
+}
+
+.side-menu---collapsed {
+  width: var(--side-menu-width-collapsed);
+}
+
+.side-menu---left {
+  border-left: initial;
+  border-right: 1px solid $openk-form-border;
+  justify-content: flex-end;
+}
+
+
+.side-menu--content {
+  height: 100%;
+  min-width: var(--side-menu-width);
+  overflow: auto;
+
+  display: flex;
+  flex-flow: column nowrap;
+  justify-content: flex-start;
+  align-items: center;
+  padding: 1em;
+  box-sizing: border-box;
+}
+
+.side-menu--content--title {
+  font-weight: 600;
+  width: 100%;
+  line-height: 1.25;
+  margin-bottom: 0.5em;
+  font-size: larger;
+}
+
+.side-menu--content--spacing {
+  flex: 1 1 100%;
+  width: 100%;
+  min-height: 1em;
+
+  & + & {
+    min-height: initial;
+  }
+
+  &:last-of-type {
+    display: none;
+  }
+}
+
+.side-menu--toggle-bar {
+  position: absolute;
+  left: -1px;
+  top: 0;
+
+  width: 0.5em;
+  height: 100%;
+  box-sizing: border-box;
+  border-left: var(--side-menu-border-width-highlighted) solid transparent;
+
+  transition: background-color ease-in-out 150ms, border-color ease-in-out 150ms;
+
+  &:hover {
+    cursor: e-resize;
+    background-color: rgba(get-color($openk-info-palette), 0.01);
+    border-color: get-color($openk-info-palette);
+  }
+
+  &.side-menu--toggle-bar---collapsed {
+    width: 1em;
+    cursor: w-resize;
+  }
+}
+
+.side-menu--toggle-bar---left {
+  left: initial;
+  right: -1px;
+  border-left: initial;
+  border-right: var(--side-menu-border-width-highlighted) solid transparent;
+
+  &:hover {
+    cursor: w-resize;
+  }
+
+  &.side-menu--toggle-bar---collapsed {
+    cursor: e-resize;
+  }
+}
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.spec.ts
similarity index 65%
rename from src/app/shared/layout/page-header/component/page-header.component.spec.ts
rename to src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.spec.ts
index 20e1e36..0118ae3 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.spec.ts
@@ -12,23 +12,21 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {SideMenuModule} from "../../side-menu.module";
+import {SideMenuContainerComponent} from "./side-menu-container.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("SideMenuContainerComponent", () => {
+    let component: SideMenuContainerComponent;
+    let fixture: ComponentFixture<SideMenuContainerComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [SideMenuModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(SideMenuContainerComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.stories.ts b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.stories.ts
new file mode 100644
index 0000000..9bb5071
--- /dev/null
+++ b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.stories.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, Input} from "@angular/core";
+import {boolean, text, withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {SideMenuModule} from "../../side-menu.module";
+
+@Component({
+    selector: "app-side-menu-container-story",
+    template: `
+        <app-side-menu-container>
+
+            <div>
+                Content...
+            </div>
+
+        </app-side-menu-container>
+
+        <div *appSideMenu="'top'; title: appTitle; left: appLeft;">
+            Top
+        </div>
+
+        <div *appSideMenu="'center'">
+            Center
+        </div>
+
+        <div *appSideMenu="'bottom'">
+            Bottom
+        </div>
+    `
+})
+class SideMenuContainerStoryComponent {
+
+    @Input()
+    public appTitle: string;
+
+    @Input()
+    public appLeft: boolean;
+
+}
+
+storiesOf("Shared / Layout / Side Menu", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({
+        imports: [
+            SideMenuModule
+        ],
+        declarations: [
+            SideMenuContainerStoryComponent
+        ]
+    }))
+    .add("SideMenuContainerComponent", () => ({
+        component: SideMenuContainerStoryComponent,
+        props: {
+            appLeft: boolean("Left", false),
+            appTitle: text("Title", "Title")
+        }
+    }));
diff --git a/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.ts b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.ts
new file mode 100644
index 0000000..b6dbac3
--- /dev/null
+++ b/src/app/shared/layout/side-menu/components/side-menu-container/side-menu-container.component.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 {Component} from "@angular/core";
+import {SideMenuRegistrationService} from "../../services";
+
+@Component({
+    selector: "app-side-menu-container",
+    templateUrl: "./side-menu-container.component.html",
+    styleUrls: ["./side-menu-container.component.scss"]
+})
+export class SideMenuContainerComponent {
+
+    public content$ = this.registrationService.content$;
+
+    public left$ = this.registrationService.left$;
+
+    public hasContent$ = this.registrationService.hasContent$;
+
+    public title$ = this.registrationService.title$;
+
+    public isOpen = true;
+
+    constructor(public registrationService: SideMenuRegistrationService) {
+
+    }
+
+}
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/shared/layout/side-menu/components/side-menu-status/index.ts
similarity index 92%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/shared/layout/side-menu/components/side-menu-status/index.ts
index f223969..becd062 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/shared/layout/side-menu/components/side-menu-status/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./side-menu-status.component";
diff --git a/src/app/shared/controls/text-field/text-field.component.html b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.html
similarity index 65%
copy from src/app/shared/controls/text-field/text-field.component.html
copy to src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.html
index 129e808..d5ab4a9 100644
--- a/src/app/shared/controls/text-field/text-field.component.html
+++ b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.html
@@ -11,10 +11,14 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<textarea #inputElement
-          (focusout)="onFocusOut()"
-          (input)="onInput(inputElement.value)"
-          [value]="appValue"
-          class="openk-textarea text-input"
-          rows="1">
-</textarea>
+<app-progress-spinner
+  [class.progress-spinner---hidden]="!appLoading">
+</app-progress-spinner>
+
+<div *ngIf="appLoading && appLoadingMessage?.trim().length > 0" class="loading-message">
+  {{appLoadingMessage}}
+</div>
+
+<ng-container *ngIf="!appLoading">
+  <ng-content></ng-content>
+</ng-container>
diff --git a/src/app/shared/layout/page-header/component/page-header.component.scss b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.scss
similarity index 68%
copy from src/app/shared/layout/page-header/component/page-header.component.scss
copy to src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.scss
index 7ed6bcc..f54bba3 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.scss
+++ b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.scss
@@ -13,24 +13,27 @@
 
 :host {
   width: 100%;
+  font-size: smaller;
+
   display: flex;
-  flex-flow: row wrap;
+  flex-flow: column;
+  justify-content: center;
   align-items: center;
-}
+  text-align: center;
 
-.page-header-title {
-  margin-right: auto;
-  font-size: x-large;
-  font-weight: 600;
-}
-
-.page-header-actions {
-  display: flex;
-  flex-flow: row wrap;
-  margin-left: auto;
-  justify-content: flex-end;
-
-  .openk-button {
-    margin: 0.25em;
+  & > *:not(:first-child) {
+    margin-top: 0.5em;
   }
 }
+
+.progress-spinner---hidden {
+  display: block;
+  font-size: 0;
+  height: 0;
+  width: 0;
+  overflow: hidden;
+}
+
+.loading-message {
+  font-style: italic;
+}
diff --git a/src/app/shared/layout/page-header/component/page-header.component.spec.ts b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.spec.ts
similarity index 65%
copy from src/app/shared/layout/page-header/component/page-header.component.spec.ts
copy to src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.spec.ts
index 20e1e36..2019003 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.spec.ts
+++ b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.spec.ts
@@ -12,23 +12,21 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
-import {RouterTestingModule} from "@angular/router/testing";
-import {I18nModule} from "../../../../core/i18n";
-import {PageHeaderModule} from "../page-header.module";
-import {PageHeaderComponent} from "./page-header.component";
+import {SideMenuModule} from "../../side-menu.module";
+import {SideMenuStatusComponent} from "./side-menu-status.component";
 
-describe("PageHeaderComponent", () => {
-    let component: PageHeaderComponent;
-    let fixture: ComponentFixture<PageHeaderComponent>;
+describe("SideMenuStatusComponent", () => {
+    let component: SideMenuStatusComponent;
+    let fixture: ComponentFixture<SideMenuStatusComponent>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [PageHeaderModule, RouterTestingModule, I18nModule]
+            imports: [SideMenuModule]
         }).compileComponents();
     }));
 
     beforeEach(() => {
-        fixture = TestBed.createComponent(PageHeaderComponent);
+        fixture = TestBed.createComponent(SideMenuStatusComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.stories.ts b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.stories.ts
new file mode 100644
index 0000000..8bf08fa
--- /dev/null
+++ b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.stories.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 {boolean, text, withKnobs} from "@storybook/addon-knobs";
+import {moduleMetadata, storiesOf} from "@storybook/angular";
+import {SideMenuModule} from "../../side-menu.module";
+
+storiesOf("Shared / Layout / Side Menu", module)
+    .addDecorator(withKnobs)
+    .addDecorator(moduleMetadata({
+        imports: [
+            SideMenuModule
+        ]
+    }))
+    .add("SideMenuStatusComponent", () => ({
+        template: `
+            <app-side-menu-status style="max-width: 15em; margin: 1em;"
+                [appLoading]="appLoading"
+                [appLoadingMessage]="appLoadingMessage">
+                {{content}}
+            </app-side-menu-status>
+        `,
+        props: {
+            appLoading: boolean("appLoading", false),
+            appLoadingMessage: text("appLoadingMessage", "Loading..."),
+            content: text("content", "Content...")
+        }
+    }));
diff --git a/src/app/shared/layout/page-header/component/page-header.component.ts b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.ts
similarity index 68%
rename from src/app/shared/layout/page-header/component/page-header.component.ts
rename to src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.ts
index 66f87db..3153bc2 100644
--- a/src/app/shared/layout/page-header/component/page-header.component.ts
+++ b/src/app/shared/layout/side-menu/components/side-menu-status/side-menu-status.component.ts
@@ -12,19 +12,18 @@
  ********************************************************************************/
 
 import {Component, Input} from "@angular/core";
-import {IPageHeaderAction} from "./IPageHeaderActions";
 
 @Component({
-    selector: "app-page-header",
-    templateUrl: "./page-header.component.html",
-    styleUrls: ["./page-header.component.scss"]
+    selector: "app-side-menu-status",
+    templateUrl: "./side-menu-status.component.html",
+    styleUrls: ["./side-menu-status.component.scss"]
 })
-export class PageHeaderComponent {
+export class SideMenuStatusComponent {
 
     @Input()
-    public appTitle: string;
+    public appLoading: boolean;
 
     @Input()
-    public appActions: IPageHeaderAction[];
+    public appLoadingMessage;
 
 }
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/shared/layout/side-menu/directives/index.ts
similarity index 93%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/shared/layout/side-menu/directives/index.ts
index f223969..221b6e9 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/shared/layout/side-menu/directives/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./side-menu.directive";
diff --git a/src/app/shared/layout/side-menu/directives/side-menu.directive.spec.ts b/src/app/shared/layout/side-menu/directives/side-menu.directive.spec.ts
new file mode 100644
index 0000000..16704a6
--- /dev/null
+++ b/src/app/shared/layout/side-menu/directives/side-menu.directive.spec.ts
@@ -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
+ ********************************************************************************/
+
+import {Component, SimpleChange, SimpleChanges, ViewChild} from "@angular/core";
+import {async, TestBed} from "@angular/core/testing";
+import {SideMenuRegistrationService} from "../services";
+import {SideMenuModule} from "../side-menu.module";
+import {SideMenuDirective} from "./side-menu.directive";
+
+describe("SideMenuDirective", () => {
+    let directive: SideMenuDirective;
+    let registration: SideMenuRegistrationService;
+    let resetSpyCalls = () => null;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [SideMenuDirectiveSpecComponent],
+            imports: [SideMenuModule]
+        }).compileComponents();
+    }));
+
+    beforeEach(async () => {
+        const fixture = TestBed.createComponent(SideMenuDirectiveSpecComponent);
+        directive = fixture.componentInstance.directive;
+        registration = fixture.componentInstance.registration;
+        fixture.detectChanges();
+        await fixture.whenStable();
+        const spies = [spyOn(registration, "register"), spyOn(registration, "deregister")];
+        resetSpyCalls = () => spies.forEach((_) => _.calls.reset());
+    });
+
+    it("should create", () => {
+        expect(directive).toBeTruthy();
+    });
+
+    it("should re-register on changes", () => {
+        resetSpyCalls();
+        expect(registration.register).not.toHaveBeenCalled();
+        ["appSideMenu", "appSideMenuLeft", "appSideMenuTitle"]
+            .map<SimpleChanges>((_) => ({[_]: new SimpleChange(0, 1, false)}))
+            .forEach((changes) => {
+                directive.ngOnChanges(changes);
+                expect(registration.register).toHaveBeenCalledWith(directive);
+            });
+    });
+
+    it("should deregister on destroy", () => {
+        resetSpyCalls();
+        expect(registration.register).not.toHaveBeenCalled();
+        directive.ngOnDestroy();
+        expect(registration.deregister).toHaveBeenCalledWith(directive);
+    });
+});
+
+@Component({
+    selector: "app-side-menu-directive-spec",
+    template: `
+        <div *appSideMenu="'top'">
+        </div>
+    `
+})
+class SideMenuDirectiveSpecComponent {
+
+    @ViewChild(SideMenuDirective, {static: true})
+    public directive: SideMenuDirective;
+
+    public constructor(public readonly registration: SideMenuRegistrationService) {
+
+    }
+
+}
diff --git a/src/app/shared/layout/side-menu/directives/side-menu.directive.ts b/src/app/shared/layout/side-menu/directives/side-menu.directive.ts
new file mode 100644
index 0000000..f7842f7
--- /dev/null
+++ b/src/app/shared/layout/side-menu/directives/side-menu.directive.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 {Directive, Input, OnChanges, OnDestroy, SimpleChanges, TemplateRef} from "@angular/core";
+import {SideMenuRegistrationService} from "../services";
+import {TSideMenuContentPosition} from "../TSideMenuContentPosition";
+
+@Directive({
+    selector: "[appSideMenu]"
+})
+export class SideMenuDirective implements OnDestroy, OnChanges {
+
+    @Input()
+    public appSideMenu: TSideMenuContentPosition = "bottom";
+
+    @Input()
+    public appSideMenuLeft: boolean;
+
+    @Input()
+    public appSideMenuTitle: string;
+
+    public test: boolean;
+
+    public constructor(
+        public readonly templateRef: TemplateRef<any>,
+        public readonly sideMenuRegistrationService: SideMenuRegistrationService
+    ) {
+
+    }
+
+    public ngOnChanges(changes: SimpleChanges) {
+        const keys: Array<keyof SideMenuDirective> = ["appSideMenu", "appSideMenuLeft", "appSideMenuTitle"];
+        if (keys.filter((_) => changes[_] != null).length > 0) {
+            this.sideMenuRegistrationService.register(this);
+        }
+    }
+
+    public ngOnDestroy() {
+        this.sideMenuRegistrationService.deregister(this);
+    }
+
+}
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/shared/layout/side-menu/index.ts
similarity index 76%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/shared/layout/side-menu/index.ts
index 563859b..51a1a49 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/shared/layout/side-menu/index.ts
@@ -11,9 +11,9 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
-}
+export * from "./components";
+export * from "./directives";
+export * from "./services";
+
+export * from "./side-menu.module";
+export * from "./TSideMenuContentPosition";
diff --git a/src/app/features/details/selectors/index.ts b/src/app/shared/layout/side-menu/services/index.ts
similarity index 91%
copy from src/app/features/details/selectors/index.ts
copy to src/app/shared/layout/side-menu/services/index.ts
index aeb78c3..80760b9 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/shared/layout/side-menu/services/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+export * from "./side-menu-registration.service";
diff --git a/src/app/shared/layout/side-menu/services/side-menu-registration.service.spec.ts b/src/app/shared/layout/side-menu/services/side-menu-registration.service.spec.ts
new file mode 100644
index 0000000..5cdac56
--- /dev/null
+++ b/src/app/shared/layout/side-menu/services/side-menu-registration.service.spec.ts
@@ -0,0 +1,89 @@
+/********************************************************************************
+ * 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, TestBed} from "@angular/core/testing";
+import {take} from "rxjs/operators";
+import {SideMenuDirective} from "../directives";
+import {SideMenuModule} from "../side-menu.module";
+import {SideMenuRegistrationService} from "./side-menu-registration.service";
+
+describe("SideMenuRegistrationService", () => {
+    let directive: SideMenuDirective;
+    let registration: SideMenuRegistrationService;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [SideMenuDirectiveSpecComponent],
+            imports: [SideMenuModule]
+        }).compileComponents();
+    }));
+
+    beforeEach(async () => {
+        const fixture = TestBed.createComponent(SideMenuDirectiveSpecComponent);
+        directive = fixture.componentInstance.directive;
+        registration = fixture.componentInstance.registration;
+        fixture.detectChanges();
+        await fixture.whenStable();
+    });
+
+    it("should return content of side menu", async () => {
+        const getContent$ = registration.content$.pipe(take(1));
+        await expectAsync(getContent$.toPromise()).toBeResolvedTo([undefined, undefined, directive]);
+        registration.deregister(directive);
+        await expectAsync(getContent$.toPromise()).toBeResolvedTo([undefined, undefined, undefined]);
+    });
+
+    it("should return if content is available", async () => {
+        const getHasContent$ = registration.hasContent$.pipe(take(1));
+        await expectAsync(getHasContent$.toPromise()).toBeResolvedTo(true);
+        registration.deregister(directive);
+        await expectAsync(getHasContent$.toPromise()).toBeResolvedTo(false);
+    });
+
+    it("should return if the side menu should be placed on the left side", async () => {
+        const getLeft$ = registration.left$.pipe(take(1));
+        await expectAsync(getLeft$.toPromise()).toBeResolvedTo(false);
+        directive.appSideMenuLeft = true;
+        registration.register(directive);
+        await expectAsync(getLeft$.toPromise()).toBeResolvedTo(true);
+        registration.deregister(directive);
+        await expectAsync(getLeft$.toPromise()).toBeResolvedTo(false);
+    });
+
+    it("should return the title of the side menu", async () => {
+        const getTitle$ = registration.title$.pipe(take(1));
+        await expectAsync(getTitle$.toPromise()).toBeResolvedTo("Side Menu Spec");
+        registration.deregister(directive);
+        await expectAsync(getTitle$.toPromise()).toBeResolvedTo(undefined);
+    });
+
+});
+
+@Component({
+    selector: "app-side-menu-directive-spec",
+    template: `
+        <div *appSideMenu="'bottom'; title: 'Side Menu Spec'">
+        </div>
+    `
+})
+class SideMenuDirectiveSpecComponent {
+
+    @ViewChild(SideMenuDirective, {static: true})
+    public directive: SideMenuDirective;
+
+    public constructor(public readonly registration: SideMenuRegistrationService) {
+
+    }
+
+}
diff --git a/src/app/shared/layout/side-menu/services/side-menu-registration.service.ts b/src/app/shared/layout/side-menu/services/side-menu-registration.service.ts
new file mode 100644
index 0000000..4e75543
--- /dev/null
+++ b/src/app/shared/layout/side-menu/services/side-menu-registration.service.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 {BehaviorSubject, defer} from "rxjs";
+import {distinctUntilChanged, map} from "rxjs/operators";
+import {filterDistinctValues} from "../../../../util/store";
+import {SideMenuDirective} from "../directives";
+import {TSideMenuContentPosition} from "../TSideMenuContentPosition";
+
+@Injectable({providedIn: "root"})
+export class SideMenuRegistrationService {
+
+    public content$ = defer(() => this.directive$).pipe(
+        map(() => {
+            const positions: TSideMenuContentPosition[] = ["top", "center", "bottom"];
+            return positions.map((_) => this.getContentInPosition(_));
+        })
+    );
+
+    public hasContent$ = defer(() => this.directive$).pipe(
+        map((directives) => directives.length > 0),
+        distinctUntilChanged()
+    );
+
+    public left$ = defer(() => this.directive$).pipe(
+        map(() => this.getLeft()),
+        distinctUntilChanged()
+    );
+
+    public title$ = defer(() => this.directive$).pipe(
+        map(() => this.getTitle()),
+        distinctUntilChanged()
+    );
+
+    private readonly directivesSubject = new BehaviorSubject<SideMenuDirective[]>([]);
+
+    private get directive$() {
+        return this.directivesSubject.asObservable();
+    }
+
+    public register(directive: SideMenuDirective) {
+        this.directivesSubject.next(filterDistinctValues([...this.directivesSubject.getValue(), directive]));
+    }
+
+    public deregister(directive: SideMenuDirective) {
+        this.directivesSubject.next(this.directivesSubject.getValue().filter((_) => _ !== directive));
+    }
+
+    private getLeft(): boolean {
+        return this.directivesSubject.getValue()
+            .reduce((left, _) => left || _.appSideMenuLeft, false) === true;
+    }
+
+    private getTitle(): string {
+        return this.directivesSubject.getValue().reverse()
+            .map((_) => _.appSideMenuTitle)
+            .filter((_) => _)[0];
+    }
+
+    private getContentInPosition(position: TSideMenuContentPosition) {
+        return this.directivesSubject.getValue().reverse()
+            .find((_) => _.appSideMenu === position);
+    }
+
+}
diff --git a/src/app/shared/controls/file-select/file-select.module.ts b/src/app/shared/layout/side-menu/side-menu.module.ts
similarity index 63%
copy from src/app/shared/controls/file-select/file-select.module.ts
copy to src/app/shared/layout/side-menu/side-menu.module.ts
index 3dced60..681028c 100644
--- a/src/app/shared/controls/file-select/file-select.module.ts
+++ b/src/app/shared/layout/side-menu/side-menu.module.ts
@@ -13,23 +13,26 @@
 
 import {CommonModule} from "@angular/common";
 import {NgModule} from "@angular/core";
-import {FormsModule} from "@angular/forms";
-import {MatIconModule} from "@angular/material/icon";
-import {FileSelectComponent} from "./component/file-select.component";
+import {ProgressSpinnerModule} from "../../progress-spinner";
+import {SideMenuContainerComponent, SideMenuStatusComponent} from "./components";
+import {SideMenuDirective} from "./directives";
 
 @NgModule({
     imports: [
         CommonModule,
-        FormsModule,
-        MatIconModule
+        ProgressSpinnerModule
     ],
     declarations: [
-        FileSelectComponent
+        SideMenuContainerComponent,
+        SideMenuDirective,
+        SideMenuStatusComponent
     ],
     exports: [
-        FileSelectComponent
+        SideMenuContainerComponent,
+        SideMenuDirective,
+        SideMenuStatusComponent
     ]
 })
-export class FileSelectModule {
+export class SideMenuModule {
 
 }
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 666c8dc..4f2817e 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
@@ -19,7 +19,7 @@
 })
 export class GetFormArrayPipe implements PipeTransform {
 
-    public transform(group: FormGroup, key: string, getControls?: boolean): FormArray {
+    public transform(group: FormGroup, key: string): FormArray {
         const control = group?.get(key);
         return control instanceof FormArray ? control : undefined;
     }
diff --git a/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.spec.ts b/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.spec.ts
index 6c9bddc..8ec1af8 100644
--- a/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.spec.ts
+++ b/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.spec.ts
@@ -25,9 +25,9 @@
         ];
         const result = pipe.transform(array);
         expect(result).toEqual([
-            {value: "option 1", label: "option 1"},
-            {value: "option 2", label: "option 2"},
-            {value: "option 3", label: "option 3"}
+            {value: "0", label: "option 1"},
+            {value: "1", label: "option 2"},
+            {value: "2", label: "option 3"}
         ]);
     });
 
diff --git a/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.ts b/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.ts
index a8a8a89..1a782ea 100644
--- a/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.ts
+++ b/src/app/shared/pipes/strings-to-options/strings-to-options.pipe.ts
@@ -21,7 +21,7 @@
 export class StringsToOptionsPipe implements PipeTransform {
 
     public transform(optionStrings: string[]): ISelectOption[] {
-        return arrayJoin(optionStrings).map((_) => ({label: _, value: _}));
+        return arrayJoin(optionStrings).map((_, index) => ({label: _, value: index.toString()}));
     }
 
 }
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 b72785b..e1d8824 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
@@ -19,6 +19,7 @@
     (appAdd)="appAdd.emit($event)"
     [appConnectedTo]="appConnectedTo"
     [appDuplicateOnDrag]="true"
+    [appDisabled]="appDisabled"
     [appListData]="standardBlocks">
   </app-text-block-list>
 
@@ -26,6 +27,8 @@
     *ngFor="let group of appGroups"
     [appSimpleCollapsible]="true"
     [appTitle]="group?.groupName"
+    [appDisabled]="appDisabled"
+    [class.disable]="appDisabled"
     class="text-block-group">
 
     <app-text-block-list
@@ -34,8 +37,9 @@
       [appDuplicateOnDrag]="true"
       [appListData]="group?.textBlocks.slice()"
       [appSelectedIds]="appSelectedIds"
+      [appDisabled]="appDisabled"
+      [appReplacements]="appReplacements"
       [appShortMode]="appShortMode">
-
     </app-text-block-list>
 
   </app-collapsible>
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 64f4243..9fe7502 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
@@ -35,6 +35,12 @@
     @Input()
     public appConnectedTo: CdkDropList | string | Array<CdkDropList | string>;
 
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appReplacements: { [key: string]: string };
+
     @Output()
     public appAdd = new EventEmitter<IExtendedTextBlockModel>();
 
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 0affc85..e8a9426 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
@@ -25,12 +25,14 @@
     *ngFor="let block of appListData"
     [appShortMode]="appShortMode"
     [appShowNewLine]="false"
-    [appTextBlockData]="block | getBlockDataFromBlockModel"
+    [appTextBlockData]="block | getBlockDataFromBlockModel: appReplacements"
     [appTitle]="block.id"
     [cdkDragData]="block"
     [cdkDragDisabled]="(block?.id | findElementInArray : appSelectedIds) != null"
     [class.text-block-list-entry---selected]="(block?.id | findElementInArray : appSelectedIds) != null"
+    [appDisabled]="appDisabled"
     cdkDrag
+    [class.disable]="appDisabled"
     class="text-block-list-entry grab">
   </app-text-block>
 
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 db9f66b..257454d 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
@@ -38,6 +38,12 @@
     @Input()
     public appSelectedIds: string[];
 
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appReplacements: { [key: string]: string };
+
     @Output()
     public appAdd = new EventEmitter<IAPITextBlockModel>();
 
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 63c28b3..7b0eb34 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
@@ -11,22 +11,25 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<div (mousedown)="editText ? $event.stopPropagation() : null" [class.text-block---error]="appErrors?.length > 0"
-     class="text-block openk-drag-element">
+<div #textBlock (mousedown)="(editText || appDisabled || editReplacement) ? $event.stopPropagation() : null"
+     [class.disabled]="appDisabled"
+     [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')"
-              class="openk-mat-icon-button title-bar--buttons-btn" mat-icon-button>
+              [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' || appType === 'text') && appBlockText"
-              class="openk-mat-icon-button title-bar--buttons-btn" 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"
-              class="openk-mat-icon-button" mat-icon-button>
+              [disabled]="appDisabled" class="openk-mat-icon-button" mat-icon-button>
         <mat-icon class="">keyboard_return</mat-icon>
       </button>
     </div>
@@ -37,52 +40,51 @@
     </div>
 
     <div>
-      <button (click)="appButtonPress.emit()" class="openk-mat-icon-button" mat-icon-button>
-        <mat-icon class="">{{appShowClose ? "remove_circle_outline" : "add_circle_outline"}}</mat-icon>
+      <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>
 
-  <div *ngIf="!editText" [class.fade]="appShortMode">
+  <div *ngIf="!editText" [class.fade]="appShortMode" class="text-block--text">
 
     <ng-container *ngFor="let block of appTextBlockData, trackBy: trackByIndex">
 
-      <span *ngIf="block?.type === 'text'">
-        {{block?.value}}
-      </span>
+      <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}}</span>
 
-      <ng-container *ngIf="block?.type === 'newline'">
-        <br>
-      </ng-container>
+      <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"
+                            [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}}
-      </span>
+            class="highlight-text"><!--
+        -->{{block?.placeholder ? block.placeholder : block?.value}}
+        <mat-icon *ngIf="!block?.placeholder" class="error--icon">warning</mat-icon><!--
+      --></span>
 
     </ng-container>
 
   </div>
 
   <div [class.hidden]="!editText">
-    <app-text-field (appFocusOut)="editText = false" (appInputValue)="onInput($event)" [appIsFocused]="editText"
-                    [appValue]="appBlockText"></app-text-field>
+    <textarea #inputElement (focusout)="onFocusOut($event)" (input)="onInput(inputElement.value)"
+              [disabled]="appDisabled" [value]="appBlockText" appAutoTextFieldResize class="openk-textarea text-input">
+    </textarea>
   </div>
 
   <button (click)="addNewLineAfter()" *ngIf="appShowNewLine && appTextBlockData?.length > 0"
-          class="openk-mat-icon-button" mat-icon-button>
+          [disabled]="appDisabled" class="openk-mat-icon-button" mat-icon-button>
     <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 b31fb4b..31362c5 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
@@ -71,7 +71,12 @@
 }
 
 .highlight-text {
-  background-color: $openk-background-highlight;
+  color: get-color($openk-info-palette, A300);
+}
+
+.text-block--text {
+  white-space: break-spaces;
+  word-break: break-word;
 }
 
 .fade {
@@ -93,9 +98,25 @@
 }
 
 .error {
-  background-color: get-color($openk-danger-palette, 200);
+  color: get-color($openk-danger-palette, A200);
+  display: inline-flex;
+}
+
+.error--icon {
+  height: initial;
+  width: initial;
+  font-size: 1em;
+  margin: auto;
 }
 
 .hidden {
   display: none;
 }
+
+.text-input {
+  resize: none;
+  width: 100%;
+  box-sizing: border-box;
+  min-height: 2.5em;
+  white-space: break-spaces;
+}
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 5b21620..ff8ce19 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
@@ -82,4 +82,19 @@
         expect(component.editText).toBeFalse();
     });
 
+    it("should set editText to false when input field loses focus", () => {
+        component.editText = true;
+        component.onFocusOut(new Event("focusout"));
+        expect(component.editText).toBeFalse();
+    });
+
+    it("should not lose focus when revert button is clicked", () => {
+        component.editText = true;
+        const event = {relatedTarget: {name: "revertButton"}, preventDefault: () => null} as unknown as FocusEvent;
+        spyOn(event, "preventDefault").and.callThrough();
+        component.onFocusOut(event);
+        expect(event.preventDefault).toHaveBeenCalled();
+        expect(component.editText).toBeTrue();
+    });
+
 });
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 57f3e3a..6d5fe6a 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 {ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from "@angular/core";
+import {AfterViewChecked, ChangeDetectorRef, 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";
@@ -19,10 +19,9 @@
 @Component({
     selector: "app-text-block",
     templateUrl: "./text-block.component.html",
-    styleUrls: ["./text-block.component.scss"],
-    changeDetection: ChangeDetectionStrategy.OnPush
+    styleUrls: ["./text-block.component.scss"]
 })
-export class TextBlockComponent {
+export class TextBlockComponent implements AfterViewChecked {
 
     @Input()
     public appErrors: ITextblockError[] = [];
@@ -46,7 +45,10 @@
     public appShowClose = false;
 
     @Input()
-    public appBlockText;
+    public appBlockText: string;
+
+    @Input()
+    public appDisabled: boolean;
 
     @Output()
     public appTextInput = new EventEmitter<void>();
@@ -65,8 +67,22 @@
 
     @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() {
@@ -80,6 +96,8 @@
     public convertToTextInput() {
         this.appTextInput.emit();
         this.editText = true;
+        this.changeDetectorRef.detectChanges();
+        this.inputElement.nativeElement.focus();
     }
 
     public onInput(value: string) {
@@ -90,4 +108,12 @@
         this.appChangeText.emit(this.appType === "text" ? "" : undefined);
         this.editText = false;
     }
+
+    public onFocusOut(event) {
+        if (event.relatedTarget?.name === "revertButton") {
+            event.preventDefault();
+        } else {
+            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 ca1e3d7..61ad4c6 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
@@ -11,25 +11,58 @@
  * SPDX-License-Identifier: EPL-2.0
  -------------------------------------------------------------------------------->
 
-<div (click)="onClick()" *ngIf="!appEditable" class="editable-text">
-  {{!!appValue && appValue !== "" ? appValue : appPlaceholder}}
-  <mat-icon *ngIf="appType === 'input'" class="editable-text--icon">edit</mat-icon>
-  <mat-icon *ngIf="appType === 'select'" class="editable-text--icon">arrow_drop_down</mat-icon>
-  <mat-icon *ngIf="appType === 'date'" class="editable-text--icon">today</mat-icon>
+  <div #editBox (click)="onClick()" *ngIf="!appEditable && appType !== 'select'"
+       [class.editable-text---disabled]="appDisabled"
+       class="editable-text"><!--
+  -->{{appValue != null && appValue !== "" ? (appType !== "select" ? appValue : appOptions[appValue]) : appPlaceholder}}
+    <mat-icon *ngIf="appType === 'input'" class="editable-text--icon">edit</mat-icon>
+    <mat-icon *ngIf="appType === 'select'" class="editable-text--icon">arrow_drop_down</mat-icon>
+    <mat-icon *ngIf="appType === 'date'" class="editable-text--icon">today</mat-icon><!--
+--></div>
+
+
+<span *ngIf="appType === 'input'" [class.hidden]="!appEditable"
+      class="input-field">
+
+  <span class="input-field--placeholder openk-textarea">
+    {{appValue}}
+  </span>
+
+  <input #inputElement
+         (focusout)="onFocusOut()"
+         (input)="inputValue(inputElement.value)"
+         (keydown.enter)="onFocusOut();"
+         [disabled]="appDisabled"
+         class="openk-textarea input-field--control"
+         value="{{appValue}}"/>
+</span>
+
+
+<div *ngIf="appType === 'date'"
+     [class.hidden]="!appEditable"
+     class="date-field">
+  <app-date-control #dateElement
+                    (appValueSet)="onFocusOut(); appValueChange.emit($event);"
+                    [appDisabled]="appDisabled"
+                    [appDisplayFormat]="'DD.MM.YYYY'"
+                    [appInternalFormat]="'DD.MM.YYYY'"
+                    [appSmall]="true"
+                    [appValue]="appValue">
+  </app-date-control>
 </div>
 
-<input #inputElement (focusout)="onFocusOut()" (input)="inputValue(inputElement.value)"
-       *ngIf="appType === 'input'" [class.hidden]="!appEditable"
-       value="{{appValue}}"/>
-
-<app-date-control #dateElement (appValueChange)="inputValue($event)" *ngIf="appType === 'date'"
-                  [appValue]="appValue" [class.hidden]="!appEditable"
-                  class="openk-info"></app-date-control>
-
-<app-select #selectElement (appValueChange)="inputValue($event)" *ngIf="appType === 'select'"
-            [appOptions]="appOptions | stringsToOptions" [appValue]="appValue"
-            [class.hidden]="!appEditable"></app-select>
-
-
-
+  <div *ngIf="appType === 'select'" class="select">
+    <app-select #selectElement
+                (appClose)="onFocusOut()"
+                (appValueChange)="appValueChange.emit($event)"
+                *ngIf="appType === 'select'"
+                [appDisabled]="appDisabled"
+                [appOptions]="appOptions | stringsToOptions"
+                [appPlaceholder]="appPlaceholder"
+                [appSmall]="true"
+                [appValue]="appValue"
+                [appMaxWidth]="appMaxWidth"
+                class="select">
+    </app-select>
+  </div>
 
diff --git a/src/app/shared/text-block/components/text-replacement/text-replacement.component.scss b/src/app/shared/text-block/components/text-replacement/text-replacement.component.scss
index a77d583..e559d86 100644
--- a/src/app/shared/text-block/components/text-replacement/text-replacement.component.scss
+++ b/src/app/shared/text-block/components/text-replacement/text-replacement.component.scss
@@ -13,9 +13,14 @@
 
 @import "../../../../../styles/openk.styles";
 
+:host {
+  display: inline-flex;
+  max-width: 100%;
+}
+
 .editable-text {
   display: inline-flex;
-  background-color: $openk-background-highlight;
+  color: get-color($openk-info-palette, A300);
   cursor: pointer;
 }
 
@@ -27,5 +32,47 @@
 }
 
 .hidden {
-  display: none;
+  display: none !important;
+}
+
+.editable-text---disabled {
+  pointer-events: none;
+}
+
+.input-field {
+  display: inline-block;
+  position: relative;
+  max-width: 100%;
+  min-width: 1.5em;
+}
+
+.input-field--placeholder {
+  padding: 0 0.4em;
+  box-sizing: border-box;
+  opacity: 0;
+  max-width: 100%;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.input-field--control {
+  resize: none;
+  max-width: 100%;
+  padding: 0 0.4em;
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(calc(-50%));
+  min-width: 100%;
+  box-sizing: border-box;
+}
+
+
+.date-field {
+  width: 6em;
+}
+
+.select {
+  width: 100%;
+  white-space: initial;
 }
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 4a91bc3..174cbe9 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
@@ -12,6 +12,8 @@
  ********************************************************************************/
 
 import {async, ComponentFixture, TestBed} from "@angular/core/testing";
+import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
+import {timer} from "rxjs";
 import {TextBlockModule} from "../../text-block.module";
 import {TextReplacementComponent} from "./text-replacement.component";
 
@@ -22,7 +24,8 @@
     beforeEach(async(() => {
         TestBed.configureTestingModule({
             imports: [
-                TextBlockModule
+                TextBlockModule,
+                BrowserAnimationsModule
             ]
         }).compileComponents();
     }));
@@ -45,8 +48,44 @@
 
     it("should set appEditable to true on click on the element", () => {
         component.appEditable = false;
+        spyOn(component.appEdit, "emit").and.callThrough();
         component.onClick();
         expect(component.appEditable).toBeTrue();
+        expect(component.appEdit.emit).toHaveBeenCalled();
+    });
+
+    it("should focus input field on click", async () => {
+        component.appEditable = false;
+        component.appType = "input";
+        fixture.detectChanges();
+        spyOn(component.inputElement.nativeElement, "focus").and.callThrough();
+        component.onClick();
+        fixture.detectChanges();
+        await timer(0).toPromise();
+        expect(component.appEditable).toBeTrue();
+        expect(component.inputElement.nativeElement.focus).toHaveBeenCalled();
+    });
+
+    it("should toggle datepicker on click", async () => {
+        component.appEditable = false;
+        component.appType = "date";
+        fixture.detectChanges();
+        spyOn(component.dateElement, "toggle").and.callThrough();
+        component.onClick();
+        await timer(0).toPromise();
+        expect(component.appEditable).toBeTrue();
+        expect(component.dateElement.toggle).toHaveBeenCalled();
+    });
+
+    it("should toggle app select on click", async () => {
+        component.appEditable = false;
+        component.appType = "select";
+        fixture.detectChanges();
+        spyOn(component.selectElement, "toggle").and.callThrough();
+        component.onClick();
+        await timer(0).toPromise();
+        expect(component.appEditable).toBeTrue();
+        expect(component.selectElement.toggle).toHaveBeenCalled();
     });
 
     it("should set appEditable to false when input element loses focus (or date, or select)", () => {
@@ -54,4 +93,18 @@
         component.onFocusOut();
         expect(component.appEditable).toBeFalse();
     });
+
+    it("should resize the input width to show all characters", async () => {
+        component.appValue = "test value";
+        component.appEditable = true;
+        component.appType = "input";
+        fixture.detectChanges();
+        await fixture.whenStable();
+        const widthBeforeResize = component.inputElement.nativeElement.style.width;
+        component.appValue = "another test value";
+        component.resizeInput();
+        const widthAfterResize = component.inputElement.nativeElement.style.width;
+        expect(widthAfterResize).toBeGreaterThan(widthBeforeResize);
+    });
+
 });
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 bbbe7be..248d383 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
@@ -12,6 +12,9 @@
  ********************************************************************************/
 
 import {ChangeDetectorRef, 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";
 
 @Component({
     selector: "app-text-replacement",
@@ -35,11 +38,22 @@
     @Input()
     public appOptions: string[] = [];
 
+    @Input()
+    public appDisabled: boolean;
+
+    @Input()
+    public appMaxWidth: string;
+
+    @Output()
+    public appEdit = new EventEmitter<boolean>();
+
     @ViewChild("inputElement") inputElement: ElementRef;
 
-    @ViewChild("dateElement") dateElement: ElementRef;
+    @ViewChild("dateElement") dateElement: DateControlComponent;
 
-    @ViewChild("selectElement") selectElement: ElementRef;
+    @ViewChild("selectElement") selectElement: SelectComponent;
+
+    @ViewChild("editBox") editBox: ElementRef;
 
     @Output()
     public appValueChange = new EventEmitter<string>();
@@ -49,19 +63,40 @@
 
     public async onClick() {
         this.appEditable = true;
+        this.appEdit.emit(this.appEditable);
         this.changeDetectorRef.detectChanges();
+        this.resizeInput();
         switch (this.appType) {
             case "input":
+                await timer(0).toPromise();
                 this.inputElement.nativeElement.focus();
                 break;
+            case "date":
+                await timer(0).toPromise();
+                this.dateElement.toggle(true);
+                this.dateElement.inputElement.nativeElement.focus();
+                break;
+            case "select":
+                await timer(0).toPromise();
+                this.selectElement.toggle();
+                break;
         }
     }
 
     public onFocusOut() {
         this.appEditable = false;
+        this.appEdit.emit(this.appEditable);
+    }
+
+    public resizeInput() {
+        if (this.appType === "input" && this.inputElement) {
+            this.inputElement.nativeElement.style.width = "1px";
+            this.inputElement.nativeElement.style.width = this.inputElement.nativeElement.scrollWidth + "px";
+        }
     }
 
     public inputValue(value: string) {
+        this.resizeInput();
         this.appValueChange.emit(value);
     }
 }
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 8acb262..9a7311f 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
@@ -18,12 +18,12 @@
 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"},
-    {separator: /(<t:[A-Za-z0-9_\\-]+>)/g, type: "text-fill"}
+    {separator: /(<s:[A-Za-z0-9_\\-]+>)/g, type: "select"}
 ];
 
 export const alwaysReplace: IReplacement[] = [
-    {separator: /(\n)/, type: "newline"}
+    {separator: /(\n)/, type: "newline"},
+    {separator: /(<t:[A-Za-z0-9_\\-]+>)/g, type: "text-fill"}
 ];
 
 export function textToBlockDataArray(
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 12aaaa3..5eb6dee 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
@@ -47,7 +47,7 @@
                 value: "freetext"
             },
             {
-                type: "highlight-text",
+                type: "text-fill",
                 value: "replacementtext"
             },
             {
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 a07e312..c2c1494 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
@@ -20,7 +20,8 @@
 })
 export class GetBlockDataFromBlockModelPipe implements PipeTransform {
 
-    public transform(blockModel: IAPITextBlockModel): Array<{ value: string, type: string, placeholder?: string, options?: string[] }> {
-        return blockModel ? textToBlockDataArray(blockModel, undefined, undefined, undefined, false) : [];
+    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) : [];
     }
 }
diff --git a/src/app/shared/text-block/text-block.module.ts b/src/app/shared/text-block/text-block.module.ts
index 19c1730..0b1f592 100644
--- a/src/app/shared/text-block/text-block.module.ts
+++ b/src/app/shared/text-block/text-block.module.ts
@@ -17,9 +17,9 @@
 import {MatButtonModule} from "@angular/material/button";
 import {MatIconModule} from "@angular/material/icon";
 import {TranslateModule} from "@ngx-translate/core";
+import {CommonControlsModule} from "../controls/common";
 import {DateControlModule} from "../controls/date-control";
 import {SelectModule} from "../controls/select";
-import {TextInputFieldModule} from "../controls/text-field";
 import {CollapsibleModule} from "../layout/collapsible";
 import {GlobalClassToggleModule} from "../layout/global-class-toggle";
 import {SharedPipesModule} from "../pipes";
@@ -42,7 +42,7 @@
         GlobalClassToggleModule,
         TranslateModule,
         MatButtonModule,
-        TextInputFieldModule
+        CommonControlsModule
     ],
     declarations: [
         TextBlockComponent,
diff --git a/src/app/store/attachments/actions/attachments.actions.ts b/src/app/store/attachments/actions/attachments.actions.ts
index 49cca63..9512731 100644
--- a/src/app/store/attachments/actions/attachments.actions.ts
+++ b/src/app/store/attachments/actions/attachments.actions.ts
@@ -12,16 +12,25 @@
  ********************************************************************************/
 
 import {createAction, props} from "@ngrx/store";
-import {IAPIAttachmentModel} from "../../../core/api";
-import {IAttachmentError, IAttachmentFormValue, IAttachmentWithTags} from "../model";
+import {IAPIAttachmentModel, IAPIAttachmentTag} from "../../../core/api";
+import {IAttachmentControlValue, IAttachmentFormValue} from "../model";
 
 export const fetchAttachmentsAction = createAction(
     "[Details/Edit] Fetch attachments",
     props<{ statementId: number }>()
 );
 
+export const fetchAttachmentTagsAction = createAction(
+    "[Details/Edit] Fetch attachments"
+);
+
+export const setAttachmentTagsAction = createAction(
+    "[API] Set attachment tags",
+    props<{ tags: IAPIAttachmentTag[] }>()
+);
+
 export const submitAttachmentFormAction = createAction(
-    "[Edit/Details] Add or remove attachments",
+    "[Details/Edit] Add or remove attachments",
     props<{
         statementId: number,
         taskId: string,
@@ -29,21 +38,6 @@
     }>()
 );
 
-export const addAttachmentErrorAction = createAction(
-    "[API] Add attachment error",
-    props<{
-        statementId: number,
-        taskId: string,
-        addError: IAttachmentError<IAttachmentWithTags<File>>[],
-        removeError: IAttachmentError<number>[]
-    }>()
-);
-
-export const clearFileCacheAction = createAction(
-    "[Edit/API] Clear file cache",
-    props<{ statementId: number; }>()
-);
-
 export const setAttachmentsAction = createAction(
     "[API] Set attachments",
     props<{ statementId: number, entities: IAPIAttachmentModel[] }>()
@@ -58,3 +52,25 @@
     "[API] Delete attachments",
     props<{ statementId: number, entityIds: number[] }>()
 );
+
+
+export const updateAttachmentTagsAction = createAction(
+    "[Edit] Update attachment tags",
+    props<{ items: IAttachmentControlValue[] }>()
+);
+
+export const setAttachmentCacheAction = createAction(
+    "[Edit] Set attachment cache",
+    props<{ statementId: number, items: IAttachmentControlValue[] }>()
+);
+
+export const clearAttachmentCacheAction = createAction(
+    "[Edit] Clear attachment cache",
+    props<{ statementId: number; }>()
+);
+
+
+export const startAttachmentDownloadAction = createAction(
+    "[Edit/Details] Start attachment download",
+    props<{ statementId: number; attachmentId: number }>()
+);
diff --git a/src/app/store/attachments/attachments-reducers.token.ts b/src/app/store/attachments/attachments-reducers.token.ts
index 4c82830..535f485 100644
--- a/src/app/store/attachments/attachments-reducers.token.ts
+++ b/src/app/store/attachments/attachments-reducers.token.ts
@@ -14,7 +14,7 @@
 import {InjectionToken} from "@angular/core";
 import {ActionReducerMap} from "@ngrx/store";
 import {IAttachmentsStoreState} from "./model";
-import {attachmentEntitiesReducer, statementAttachmentsReducer, statementFileCacheReducer} from "./reducers";
+import {attachmentEntitiesReducer, attachmentStatementCacheReducer, attachmentTagsReducer, statementAttachmentsReducer} from "./reducers";
 
 export const ATTACHMENTS_NAME = "attachments";
 
@@ -22,7 +22,8 @@
     providedIn: "root",
     factory: () => ({
         entities: attachmentEntitiesReducer,
-        statementFileCache: statementFileCacheReducer,
-        statementAttachments: statementAttachmentsReducer
+        statementCache: attachmentStatementCacheReducer,
+        statementAttachments: statementAttachmentsReducer,
+        tags: attachmentTagsReducer
     })
 });
diff --git a/src/app/store/attachments/attachments-store.module.ts b/src/app/store/attachments/attachments-store.module.ts
index e857630..895c2f9 100644
--- a/src/app/store/attachments/attachments-store.module.ts
+++ b/src/app/store/attachments/attachments-store.module.ts
@@ -16,11 +16,13 @@
 import {StoreModule} from "@ngrx/store";
 import {ATTACHMENTS_NAME, ATTACHMENTS_REDUCER} from "./attachments-reducers.token";
 import {FetchAttachmentsEffect, SubmitAttachmentsEffect} from "./effects";
+import {AttachmentDownloadEffect} from "./effects/download";
 
 @NgModule({
     imports: [
         StoreModule.forFeature(ATTACHMENTS_NAME, ATTACHMENTS_REDUCER),
         EffectsModule.forFeature([
+            AttachmentDownloadEffect,
             FetchAttachmentsEffect,
             SubmitAttachmentsEffect
         ])
diff --git a/src/app/store/attachments/effects/download/attachment-download.effect.spec.ts b/src/app/store/attachments/effects/download/attachment-download.effect.spec.ts
new file mode 100644
index 0000000..b03e4ad
--- /dev/null
+++ b/src/app/store/attachments/effects/download/attachment-download.effect.spec.ts
@@ -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 {TestBed} from "@angular/core/testing";
+import {RouterTestingModule} from "@angular/router/testing";
+import {provideMockActions} from "@ngrx/effects/testing";
+import {Action} from "@ngrx/store";
+import {Observable, of, Subscription} from "rxjs";
+import {SPA_BACKEND_ROUTE} from "../../../../core/external-routes";
+import {startAttachmentDownloadAction} from "../../actions";
+import {AttachmentDownloadEffect} from "./attachment-download.effect";
+
+
+describe("AttachmentDownloadEffect", () => {
+
+    const token = "" + Math.floor(10000 * Math.random());
+
+    let actions$: Observable<Action>;
+    let effect: AttachmentDownloadEffect;
+    let subscription: Subscription;
+
+    beforeEach(async () => {
+        TestBed.configureTestingModule({
+            imports: [
+                RouterTestingModule
+            ],
+            providers: [
+                provideMockActions(() => actions$),
+                {
+                    provide: SPA_BACKEND_ROUTE,
+                    useValue: "/statementpaBE"
+                }
+            ]
+        });
+        effect = TestBed.inject(AttachmentDownloadEffect);
+    });
+
+    afterEach(() => {
+        if (subscription != null) {
+            subscription.unsubscribe();
+        }
+    });
+
+    it("should start downloads of attachments", async () => {
+        const statementId = 19;
+        const attachmentId = 1919;
+        const downloadSpy = spyOn(effect.downloadService, "startDownload");
+        effect.authService = {token} as any;
+
+        actions$ = of(startAttachmentDownloadAction({statementId, attachmentId}));
+        subscription = effect.startAttachmentDownload$.subscribe();
+        await Promise.resolve();
+        expect(downloadSpy).toHaveBeenCalledWith(`/statementpaBE/statements/${statementId}/attachments/${attachmentId}/file`, token);
+    });
+
+});
diff --git a/src/app/store/attachments/effects/download/attachment-download.effect.ts b/src/app/store/attachments/effects/download/attachment-download.effect.ts
new file mode 100644
index 0000000..f1d0d7c
--- /dev/null
+++ b/src/app/store/attachments/effects/download/attachment-download.effect.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 {Inject, Injectable} from "@angular/core";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {filter, mergeMap} from "rxjs/operators";
+import {AuthService, DownloadService, SPA_BACKEND_ROUTE} from "../../../../core";
+import {urlJoin} from "../../../../util/http";
+import {startAttachmentDownloadAction} from "../../actions";
+
+@Injectable({providedIn: "root"})
+export class AttachmentDownloadEffect {
+
+    public startAttachmentDownload$ = createEffect(() => this.actions.pipe(
+        ofType(startAttachmentDownloadAction),
+        filter((action) => action.statementId != null && action.attachmentId != null),
+        mergeMap(async (action) => this.startAttachmentDownload(action.statementId, action.attachmentId))
+    ), {dispatch: false});
+
+    public constructor(
+        public actions: Actions,
+        public downloadService: DownloadService,
+        public authService: AuthService,
+        @Inject(SPA_BACKEND_ROUTE) public spaBackendRoute: string
+    ) {
+
+    }
+
+    public startAttachmentDownload(statementId: number, attachmentId: number) {
+        const endPoint = `/statements/${statementId}/attachments/${attachmentId}/file`;
+        return this.downloadService.startDownload(urlJoin(this.spaBackendRoute, endPoint), this.authService.token);
+    }
+
+}
diff --git a/src/app/shared/controls/text-field/index.ts b/src/app/store/attachments/effects/download/index.ts
similarity index 92%
copy from src/app/shared/controls/text-field/index.ts
copy to src/app/store/attachments/effects/download/index.ts
index f223969..009a316 100644
--- a/src/app/shared/controls/text-field/index.ts
+++ b/src/app/store/attachments/effects/download/index.ts
@@ -11,4 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./text-field.module";
+export * from "./attachment-download.effect";
diff --git a/src/app/store/attachments/effects/fetch/fetch-attachments.effect.spec.ts b/src/app/store/attachments/effects/fetch/fetch-attachments.effect.spec.ts
index 95aca3c..ed71802 100644
--- a/src/app/store/attachments/effects/fetch/fetch-attachments.effect.spec.ts
+++ b/src/app/store/attachments/effects/fetch/fetch-attachments.effect.spec.ts
@@ -16,8 +16,9 @@
 import {provideMockActions} from "@ngrx/effects/testing";
 import {Action} from "@ngrx/store";
 import {Observable, of, Subscription} from "rxjs";
-import {IAPIAttachmentModel, SPA_BACKEND_ROUTE} from "../../../../core";
-import {fetchAttachmentsAction, setAttachmentsAction} from "../../actions";
+import {IAPIAttachmentModel, IAPIAttachmentTag, SPA_BACKEND_ROUTE} from "../../../../core";
+import {createAttachmentTagList} from "../../../../test";
+import {fetchAttachmentsAction, fetchAttachmentTagsAction, setAttachmentsAction, setAttachmentTagsAction} from "../../actions";
 import {FetchAttachmentsEffect} from "./fetch-attachments.effect";
 
 describe("FetchAttachmentsEffect", () => {
@@ -51,9 +52,9 @@
         }
     });
 
-    it("should fetch the attachments for the statement and dispatch setAttachmentsAction with the result", () => {
+    it("should fetch attachments for a statement", () => {
         const statementId = 19;
-
+        const tagList = createAttachmentTagList();
         const fetchAttachmentResult: IAPIAttachmentModel[] = [
             {
                 id: 1,
@@ -65,20 +66,37 @@
             }
         ];
 
-        const expectedResult = setAttachmentsAction({statementId, entities: fetchAttachmentResult});
+        const expectedResults = [
+            setAttachmentsAction({statementId, entities: fetchAttachmentResult}),
+            setAttachmentTagsAction({tags: tagList})
+        ];
         const results: Action[] = [];
 
         actions$ = of(fetchAttachmentsAction({statementId}));
         subscription = effect.fetch$.subscribe((action) => results.push(action));
 
         expectFetchAttachments(statementId, fetchAttachmentResult);
-        expect(results).toEqual([expectedResult]);
+        expectFetchAttachmentTags(tagList);
+        expect(results).toEqual(expectedResults);
+        httpTestingController.verify();
+    });
+
+    it("should fetch a list of attachment tags", () => {
+        const tagList = createAttachmentTagList();
+        const expectedResults = [
+            setAttachmentTagsAction({tags: tagList})
+        ];
+        const results: Action[] = [];
+
+        actions$ = of(fetchAttachmentTagsAction());
+        subscription = effect.fetchTags$.subscribe((action) => results.push(action));
+        expectFetchAttachmentTags(tagList);
+        expect(results).toEqual(expectedResults);
         httpTestingController.verify();
     });
 
     it("should not dispatch any further actions when supplied with insufficient data", () => {
         const statementId = null;
-
         const results: Action[] = [];
 
         actions$ = of(fetchAttachmentsAction({statementId}));
@@ -95,5 +113,12 @@
         request.flush(returnValue);
     }
 
+    function expectFetchAttachmentTags(returnValue: IAPIAttachmentTag[]) {
+        const url = `/tags`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("GET");
+        request.flush(returnValue);
+    }
+
 });
 
diff --git a/src/app/store/attachments/effects/fetch/fetch-attachments.effect.ts b/src/app/store/attachments/effects/fetch/fetch-attachments.effect.ts
index 6a4b345..f4c7276 100644
--- a/src/app/store/attachments/effects/fetch/fetch-attachments.effect.ts
+++ b/src/app/store/attachments/effects/fetch/fetch-attachments.effect.ts
@@ -14,10 +14,11 @@
 import {Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {Action} from "@ngrx/store";
-import {Observable} from "rxjs";
-import {filter, map, switchMap} from "rxjs/operators";
+import {merge, Observable} from "rxjs";
+import {filter, map, retry, switchMap} from "rxjs/operators";
 import {AttachmentsApiService} from "../../../../core/api/attachments";
-import {fetchAttachmentsAction, setAttachmentsAction} from "../../actions";
+import {completeInitializationAction} from "../../../root/actions";
+import {fetchAttachmentsAction, fetchAttachmentTagsAction, setAttachmentsAction, setAttachmentTagsAction} from "../../actions";
 
 @Injectable({providedIn: "root"})
 export class FetchAttachmentsEffect {
@@ -25,16 +26,36 @@
     public fetch$ = createEffect(() => this.actions.pipe(
         ofType(fetchAttachmentsAction),
         filter((action) => action.statementId != null),
-        switchMap((action) => this.fetchAttachments(action.statementId))
+        switchMap((action) => this.fetch(action.statementId))
+    ));
+
+    public fetchTags$ = createEffect(() => this.actions.pipe(
+        ofType(fetchAttachmentTagsAction, completeInitializationAction),
+        switchMap(() => this.fetchAttachmentTags())
     ));
 
     public constructor(public actions: Actions, private attachmentsApiService: AttachmentsApiService) {
 
     }
 
+    public fetch(statementId: number) {
+        return merge(
+            this.fetchAttachments(statementId),
+            this.fetchAttachmentTags()
+        );
+    }
+
     public fetchAttachments(statementId: number): Observable<Action> {
         return this.attachmentsApiService.getAttachments(statementId).pipe(
-            map((entities) => setAttachmentsAction({statementId, entities}))
+            map((entities) => setAttachmentsAction({statementId, entities})),
+            retry(2)
+        );
+    }
+
+    public fetchAttachmentTags(): Observable<Action> {
+        return this.attachmentsApiService.getTagList().pipe(
+            map((tags) => setAttachmentTagsAction({tags})),
+            retry(2)
         );
     }
 
diff --git a/src/app/store/attachments/effects/submit/submit-attachments.effect.spec.ts b/src/app/store/attachments/effects/submit/submit-attachments.effect.spec.ts
index ab6e7de..ab634c8 100644
--- a/src/app/store/attachments/effects/submit/submit-attachments.effect.spec.ts
+++ b/src/app/store/attachments/effects/submit/submit-attachments.effect.spec.ts
@@ -15,21 +15,24 @@
 import {TestBed} from "@angular/core/testing";
 import {provideMockActions} from "@ngrx/effects/testing";
 import {Action} from "@ngrx/store";
-import {Observable, of, Subscription} from "rxjs";
+import {EMPTY, Observable, of, Subscription} from "rxjs";
 import {IAPIAttachmentModel, SPA_BACKEND_ROUTE} from "../../../../core";
+import {createFileMock} from "../../../../test";
 import {
     addAttachmentEntityAction,
-    addAttachmentErrorAction,
-    clearFileCacheAction,
     deleteAttachmentsAction,
-    fetchAttachmentsAction,
-    submitAttachmentFormAction
+    setAttachmentCacheAction,
+    submitAttachmentFormAction,
+    updateAttachmentTagsAction
 } from "../../actions";
-import {IAttachmentWithTags} from "../../model";
+import {IAttachmentControlValue, IAttachmentError} from "../../model";
 import {SubmitAttachmentsEffect} from "./submit-attachments.effect";
 
 describe("SubmitAttachmentsEffect", () => {
 
+    const statementId = 19;
+    const taskId = "1919";
+
     let actions$: Observable<Action>;
     let httpTestingController: HttpTestingController;
     let effect: SubmitAttachmentsEffect;
@@ -59,152 +62,181 @@
         }
     });
 
-    it("should not clear file cache and refresh attachments", () => {
-        const statementId = 19;
-        const taskId = "asd12";
-        const expectedResult = [
-            clearFileCacheAction({statementId}),
-            fetchAttachmentsAction({statementId})
-        ];
-        const results: Action[] = [];
-
+    it("should call submit method on submit action", async () => {
+        const submitSpy = spyOn(effect, "submit").and.returnValue(EMPTY);
         actions$ = of(submitAttachmentFormAction({statementId, taskId, value: {add: [], edit: []}}));
-        subscription = effect.submit$.subscribe((action) => results.push(action));
-
-        expect(results).toEqual(expectedResult);
+        subscription = effect.submit$.subscribe();
+        expect(submitSpy).toHaveBeenCalledWith(statementId, taskId, {add: [], edit: []});
     });
 
-    it("should clear file cache when no errors occurred", () => {
-        const statementId = 19;
-        const taskId = "asd12";
-        const add: IAttachmentWithTags<File>[] = [new File(["Some", "File"], "Some File")]
-            .map((_) => ({attachment: _, tags: []}));
-        const remove: number[] = [1];
-        const edit: IAttachmentWithTags<number>[] = remove
-            .map((_) => ({attachment: _, tags: [], remove: true}));
+    it("should call add and remove method on submit", async () => {
+        const addSpy = spyOn(effect, "addAttachments").and.returnValue(EMPTY);
+        const editSpy = spyOn(effect, "editAttachments").and.returnValue(EMPTY);
+        const fetchSpy = spyOn(effect.fetchAttachmentsEffect, "fetchAttachments").and.returnValue(EMPTY);
 
-        const attachmentModelResponse: IAPIAttachmentModel = {
-            id: 1,
-            name: "fileName",
-            type: "file",
-            size: 150,
-            timestamp: "2007-08-31T16:47+00:00",
-            tagIds: []
+        subscription = effect.submit(statementId, taskId, {add: [], edit: []}).subscribe();
+        expect(addSpy).toHaveBeenCalledWith(statementId, taskId, [], []);
+        expect(editSpy).toHaveBeenCalledWith(statementId, taskId, [], []);
+        expect(fetchSpy).toHaveBeenCalledWith(statementId);
+    });
+
+    it("should throw an error if a single request fails on submit", async () => {
+        const attachmentError: IAttachmentError = {statementId, attachment: null, error: new Error("Test")};
+        effect.addAttachments = (
+            _: number,
+            __: string,
+            ___: IAttachmentControlValue[],
+            errors: IAttachmentError[] = []
+        ) => {
+            errors.push(attachmentError);
+            return EMPTY;
         };
+        spyOn(effect, "editAttachments").and.returnValue(EMPTY);
+        spyOn(effect.fetchAttachmentsEffect, "fetchAttachments").and.returnValue(EMPTY);
 
-        const expectedResult: Action[] = [
-            deleteAttachmentsAction({statementId, entityIds: remove}),
-            addAttachmentEntityAction({statementId, entity: attachmentModelResponse}),
-            clearFileCacheAction({statementId}),
-            fetchAttachmentsAction({statementId})
+        await expectAsync(effect.submit(statementId, taskId, {add: [], edit: []}).toPromise())
+            .toBeRejectedWith([attachmentError]);
+    });
+
+    it("should add attachments", async () => {
+        const errors: IAttachmentError[] = [];
+        const values: IAttachmentControlValue[] = [
+            {
+                name: "18.pdf",
+                file: createFileMock("18.pdf"),
+                tagIds: ["tag18", "tag1818"]
+            },
+            {
+                name: "19.pdf",
+                file: createFileMock("19.pdf"),
+                tagIds: ["tag19", "tag1919"]
+            },
+            {
+                tagIds: []
+            },
+            null
         ];
+        const add$ = effect.addAttachments(statementId, taskId, values, errors);
         const results: Action[] = [];
 
-        actions$ = of(submitAttachmentFormAction({statementId, taskId, value: {add, edit}}));
-        subscription = effect.submit$.subscribe((action) => results.push(action));
-
-        expectDeleteRequest(statementId, taskId, 1);
-        expectAddRequest(statementId, taskId, attachmentModelResponse);
-        expect(results).toEqual(expectedResult);
+        subscription = add$.subscribe((_) => results.push(_));
+        expectAddRequest({id: 18} as any, values[0].tagIds, true);
+        expectAddRequest({id: 19} as any, values[1].tagIds);
+        expect(results).toEqual([
+            setAttachmentCacheAction({statementId, items: values}),
+            addAttachmentEntityAction({statementId, entity: {id: 19} as any}),
+            setAttachmentCacheAction({statementId, items: [values[0]]})
+        ]);
+        expect(errors.length).toBe(1);
+        expect(errors[0].statementId).toBe(statementId);
+        expect(errors[0].attachment).toBe(values[0]);
+        expect(errors[0].error).toBeDefined();
         httpTestingController.verify();
     });
 
-    it("should dispatch addAttachmentErrorAction if an error removing an attachment occurs", () => {
-        const statementId = 19;
-        const taskId = "asd12";
-        const add: IAttachmentWithTags<File>[] = [new File(["Some", "File"], "Some File")]
-            .map((_) => ({attachment: _, tags: [], remove: true}));
-        const remove: number[] = [1];
-        const edit: IAttachmentWithTags<number>[] = remove
-            .map((_) => ({attachment: _, tags: [], remove: true}));
 
-        const attachmentModelResponse: IAPIAttachmentModel = {
-            id: 1,
-            name: "fileName",
-            type: "file",
-            size: 150,
-            timestamp: "2007-08-31T16:47+00:00",
-            tagIds: []
-        };
-
-        const expectedResult: Action[] = [
-            addAttachmentEntityAction({statementId, entity: attachmentModelResponse}),
-            addAttachmentErrorAction({
-                statementId, taskId, addError: [],
-                removeError: [{attachment: 1, error: undefined}]
-            }),
-            fetchAttachmentsAction({statementId})
+    it("should remove attachments", async () => {
+        const errors: IAttachmentError[] = [];
+        const values: IAttachmentControlValue[] = [
+            {
+                id: 18,
+                name: "18.pdf",
+                tagIds: ["tag18", "tag1818"],
+                isSelected: false
+            },
+            {
+                id: 19,
+                name: "19.pdf",
+                tagIds: ["tag19", "tag1919"],
+                isSelected: false
+            },
+            {
+                name: "",
+                tagIds: []
+            },
+            null
         ];
+        const add$ = effect.editAttachments(statementId, taskId, values, errors);
         const results: Action[] = [];
 
-        actions$ = of(submitAttachmentFormAction({statementId, taskId, value: {edit, add}}));
-        subscription = effect.submit$.subscribe((action) => results.push(action));
-
-        expectDeleteRequest(statementId, taskId, 1, true);
-        expectAddRequest(statementId, taskId, attachmentModelResponse);
-
-        expect(results.length).toEqual(expectedResult.length);
-        expect(results[0]).toEqual(expectedResult[0]);
+        subscription = add$.subscribe((_) => results.push(_));
+        expectDeleteRequest(18, true);
+        expectDeleteRequest(19, false);
+        expect(results).toEqual([
+            updateAttachmentTagsAction({items: values}),
+            deleteAttachmentsAction({statementId, entityIds: [19]})
+        ]);
+        expect(errors.length).toBe(1);
+        expect(errors[0].statementId).toBe(statementId);
+        expect(errors[0].attachment).toBe(values[0]);
+        expect(errors[0].error).toBeDefined();
         httpTestingController.verify();
     });
 
-    it("should dispatch addAttachmentErrorAction if an error adding an attachment occurs", () => {
-        const statementId = 19;
-        const taskId = "asd12";
-        const add: IAttachmentWithTags<File>[] = [new File(["Some", "File"], "Some File")]
-            .map((_) => ({attachment: _, tags: [], remove: true}));
-        const remove = [1];
-        const edit: IAttachmentWithTags<number>[] = remove
-            .map((_) => ({attachment: _, tags: [], remove: true}));
-
-        const attachmentModelResponse: IAPIAttachmentModel = {
-            id: 1,
-            name: "fileName",
-            type: "file",
-            size: 150,
-            timestamp: "2007-08-31T16:47+00:00",
-            tagIds: []
-        };
-
-        const expectedResult: Action[] = [
-            deleteAttachmentsAction({statementId, entityIds: remove}),
-            addAttachmentErrorAction({
-                statementId, taskId, addError: [{attachment: {attachment: add[0].attachment, tags: []}, error: undefined}],
-                removeError: []
-            }),
-            fetchAttachmentsAction({statementId})
+    it("should edit attachment tags", async () => {
+        const errors: IAttachmentError[] = [];
+        const values: IAttachmentControlValue[] = [
+            {
+                id: 18,
+                name: "18.pdf",
+                tagIds: ["tag18", "tag1818"],
+                isSelected: true
+            },
+            {
+                id: 19,
+                name: "19.pdf",
+                tagIds: ["tag19", "tag1919"],
+                isSelected: true
+            },
+            {
+                tagIds: []
+            }
         ];
+        const add$ = effect.editAttachments(statementId, taskId, values, errors);
         const results: Action[] = [];
 
-        actions$ = of(submitAttachmentFormAction({statementId, taskId, value: {add, edit}}));
-        subscription = effect.submit$.subscribe((action) => results.push(action));
-
-        expectDeleteRequest(statementId, taskId, 1);
-        expectAddRequest(statementId, taskId, attachmentModelResponse, true);
-
-        expect(results.length).toEqual(expectedResult.length);
-        expect(results[0]).toEqual(expectedResult[0]);
+        subscription = add$.subscribe((_) => results.push(_));
+        expectPostAttachmentTags(values[0].id, values[0].tagIds, true);
+        expectPostAttachmentTags(values[1].id, values[1].tagIds, false);
+        expect(results).toEqual([updateAttachmentTagsAction({items: values})]);
+        expect(errors.length).toBe(1);
+        expect(errors[0].statementId).toBe(statementId);
+        expect(errors[0].attachment).toBe(values[0]);
+        expect(errors[0].error).toBeDefined();
         httpTestingController.verify();
     });
 
-    function expectAddRequest(statementId: number, taskId: string, returnValue: IAPIAttachmentModel, error = false) {
+    function expectAddRequest(returnValue: IAPIAttachmentModel, tagIds: string[], error = false) {
         const url = `/process/statements/${statementId}/task/${taskId}/attachments`;
-        const request = httpTestingController.expectOne(url);
+        const params = tagIds.reduce((_, tagId, i) => _ + (i > 0 ? "&" : "") + "tagId=" + tagId, "");
+        const request = httpTestingController.expectOne(url + (params.length > 0 ? "?" : "") + params);
         expect(request.request.method).toBe("POST");
         if (error) {
-            request.error(new ErrorEvent("test-error"));
+            request.error(new ErrorEvent("test error"));
         } else {
             request.flush(returnValue);
         }
 
     }
 
-    function expectDeleteRequest(statementId: number, taskId: string, attachmentId: number, error = false) {
+    function expectPostAttachmentTags(attachmentId: number, tagIds: string[], error = false) {
+        const url = `/process/statements/${statementId}/task/${taskId}/attachments/${attachmentId}/tags`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("POST");
+        expect(request.request.body).toEqual(tagIds);
+        if (error) {
+            request.error(new ErrorEvent("test error"));
+        } else {
+            request.flush("204");
+        }
+    }
+
+    function expectDeleteRequest(attachmentId: number, error = false) {
         const url = `/process/statements/${statementId}/task/${taskId}/attachments/${attachmentId}`;
         const request = httpTestingController.expectOne(url);
         expect(request.request.method).toBe("DELETE");
         if (error) {
-            request.error(new ErrorEvent("test-error"));
+            request.error(new ErrorEvent("test error"));
         } else {
             request.flush("204");
         }
diff --git a/src/app/store/attachments/effects/submit/submit-attachments.effect.ts b/src/app/store/attachments/effects/submit/submit-attachments.effect.ts
index a090dcf..39d8366 100644
--- a/src/app/store/attachments/effects/submit/submit-attachments.effect.ts
+++ b/src/app/store/attachments/effects/submit/submit-attachments.effect.ts
@@ -14,20 +14,19 @@
 import {Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {Action} from "@ngrx/store";
-import {concat, defer, EMPTY, Observable, of, throwError} from "rxjs";
-import {catchError, map, switchMap} from "rxjs/operators";
+import {concat, EMPTY, Observable, of, throwError} from "rxjs";
+import {catchError, filter, ignoreElements, map, mergeMap, startWith, switchMap} from "rxjs/operators";
 import {AttachmentsApiService} from "../../../../core/api/attachments";
-import {ignoreError} from "../../../../util/rxjs";
-import {arrayJoin} from "../../../../util/store";
+import {arrayJoin, endWithObservable, ignoreError} from "../../../../util";
 import {
     addAttachmentEntityAction,
-    addAttachmentErrorAction,
-    clearFileCacheAction,
     deleteAttachmentsAction,
-    fetchAttachmentsAction,
+    setAttachmentCacheAction,
     submitAttachmentFormAction,
+    updateAttachmentTagsAction
 } from "../../actions";
-import {IAttachmentError, IAttachmentFormValue, IAttachmentWithTags} from "../../model";
+import {IAttachmentControlValue, IAttachmentError, IAttachmentFormValue} from "../../model";
+import {FetchAttachmentsEffect} from "../fetch";
 
 @Injectable({providedIn: "root"})
 export class SubmitAttachmentsEffect {
@@ -41,7 +40,11 @@
         })
     ));
 
-    public constructor(public readonly actions: Actions, public readonly attachmentsApiService: AttachmentsApiService) {
+    public constructor(
+        public readonly actions: Actions,
+        public readonly attachmentsApiService: AttachmentsApiService,
+        public readonly fetchAttachmentsEffect: FetchAttachmentsEffect
+    ) {
 
     }
 
@@ -50,67 +53,84 @@
         taskId: string,
         value: IAttachmentFormValue
     ): Observable<Action> {
-        const add = arrayJoin(value?.add);
-        const remove = arrayJoin(value?.edit)
-            .filter((_) => _.remove)
-            .map((_) => _.attachment);
-
-        const removeError: IAttachmentError<number>[] = [];
-        const addError: IAttachmentError<IAttachmentWithTags<File>>[] = [];
-
-        let isSuccess = false;
+        const errors: IAttachmentError[] = [];
         return concat(
-            this.removeAttachments(statementId, taskId, remove, removeError),
-            this.addAttachments(statementId, taskId, add, addError),
-            defer(() => {
-                isSuccess = addError.length === 0 && removeError.length === 0;
-                return isSuccess ?
-                    of(clearFileCacheAction({statementId})) :
-                    of(addAttachmentErrorAction({statementId, taskId, addError, removeError}));
-            }),
-            of(fetchAttachmentsAction({statementId})),
-            defer(() => isSuccess ? EMPTY : throwError([...removeError, ...addError]))
+            this.editAttachments(statementId, taskId, value?.edit, errors),
+            this.addAttachments(statementId, taskId, value?.add, errors),
+            this.fetchAttachmentsEffect.fetchAttachments(statementId)
+        ).pipe(
+            endWithObservable(() => errors.length > 0 ? throwError(errors) : EMPTY)
         );
     }
 
-    public removeAttachments(
+    public editAttachments(
         statementId: number,
         taskId: string,
-        remove: number[],
-        errors: IAttachmentError<number>[] = []
+        edit: IAttachmentControlValue[],
+        errors: IAttachmentError[] = []
     ): Observable<Action> {
-        const remove$s = arrayJoin(remove).map((attachmentId) => {
-            return this.attachmentsApiService.deleteAttachment(statementId, taskId, attachmentId).pipe(
-                map(() => deleteAttachmentsAction({statementId, entityIds: [attachmentId]})),
-                catchError((error) => {
-                    errors.push({attachment: attachmentId, error});
-                    return EMPTY;
-                })
-            );
-        });
-
-        return concat(...remove$s);
+        return of(...arrayJoin(edit)).pipe(
+            filter((item) => item?.id != null),
+            mergeMap((item) => {
+                return this.editSingleAttachment(statementId, taskId, item.id, item.tagIds, !item.isSelected).pipe(
+                    catchError((error) => {
+                        errors.push({statementId, attachment: item, error});
+                        return EMPTY;
+                    })
+                );
+            }, 10),
+            startWith(updateAttachmentTagsAction({items: edit}))
+        );
     }
 
     public addAttachments(
         statementId: number,
         taskId: string,
-        add: IAttachmentWithTags<File>[],
-        errors: IAttachmentError<IAttachmentWithTags<File>>[] = []
+        add: IAttachmentControlValue[],
+        errors: IAttachmentError[] = []
     ): Observable<Action> {
-        const add$s = arrayJoin(add)
-            .filter((item) => item?.attachment instanceof File)
-            .map((item) => {
-                return this.attachmentsApiService.postAttachment(statementId, taskId, item.attachment, ...arrayJoin(item.tags)).pipe(
-                    map((entity) => addAttachmentEntityAction({statementId, entity})),
+        const items: IAttachmentControlValue[] = [];
+        return of(...arrayJoin(add)).pipe(
+            filter((item) => item?.file instanceof File),
+            mergeMap((item) => {
+                return this.addSingleAttachment(statementId, taskId, item.file, item.tagIds).pipe(
                     catchError((error) => {
-                        errors.push({attachment: item, error});
+                        items.push(item);
+                        errors.push({statementId, attachment: item, error});
                         return EMPTY;
                     })
                 );
-            });
+            }, 2),
+            startWith(setAttachmentCacheAction({statementId, items: add})),
+            endWithObservable(() => of(setAttachmentCacheAction({statementId, items})))
+        );
+    }
 
-        return concat(...add$s);
+    private editSingleAttachment(
+        statementId: number,
+        taskId: string,
+        id: number,
+        tagIds: string[],
+        remove?: boolean
+    ) {
+        return remove ?
+            this.attachmentsApiService.deleteAttachment(statementId, taskId, id).pipe(
+                map(() => deleteAttachmentsAction({statementId, entityIds: [id]}))
+            ) :
+            this.attachmentsApiService.postAttachmentTags(statementId, taskId, id, ...arrayJoin(tagIds)).pipe(
+                ignoreElements(),
+            );
+    }
+
+    private addSingleAttachment(
+        statementId: number,
+        taskId: string,
+        file: File,
+        tagIds: string[]
+    ): Observable<Action> {
+        return this.attachmentsApiService.postAttachment(statementId, taskId, file, ...arrayJoin(tagIds)).pipe(
+            map((entity) => addAttachmentEntityAction({statementId, entity}))
+        );
     }
 
 }
diff --git a/src/app/store/attachments/model/IAttachmentWithTags.ts b/src/app/store/attachments/model/IAttachmentControlValue.ts
similarity index 79%
rename from src/app/store/attachments/model/IAttachmentWithTags.ts
rename to src/app/store/attachments/model/IAttachmentControlValue.ts
index b25a992..bb4fca5 100644
--- a/src/app/store/attachments/model/IAttachmentWithTags.ts
+++ b/src/app/store/attachments/model/IAttachmentControlValue.ts
@@ -11,12 +11,16 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export interface IAttachmentWithTags<T> {
+export interface IAttachmentControlValue {
 
-    attachment: T;
+    tagIds: string[];
 
-    tags: string[];
+    name?: string;
 
-    remove?: boolean;
+    id?: number;
+
+    file?: File;
+
+    isSelected?: boolean;
 
 }
diff --git a/src/app/store/attachments/model/IAttachmentError.ts b/src/app/store/attachments/model/IAttachmentError.ts
index c320020..60084e6 100644
--- a/src/app/store/attachments/model/IAttachmentError.ts
+++ b/src/app/store/attachments/model/IAttachmentError.ts
@@ -11,7 +11,10 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export interface IAttachmentError<T> {
-    attachment: T;
+import {IAttachmentControlValue} from "./IAttachmentControlValue";
+
+export interface IAttachmentError {
+    statementId: number;
+    attachment: IAttachmentControlValue;
     error: any;
 }
diff --git a/src/app/store/attachments/model/IAttachmentFormValue.ts b/src/app/store/attachments/model/IAttachmentFormValue.ts
index e908199..74ec430 100644
--- a/src/app/store/attachments/model/IAttachmentFormValue.ts
+++ b/src/app/store/attachments/model/IAttachmentFormValue.ts
@@ -11,12 +11,21 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {IAttachmentWithTags} from "./IAttachmentWithTags";
+import {FormArray} from "@angular/forms";
+import {createFormGroup} from "../../../util/forms";
+import {IAttachmentControlValue} from "./IAttachmentControlValue";
 
 export interface IAttachmentFormValue {
 
-    add?: IAttachmentWithTags<File>[];
+    add?: IAttachmentControlValue[];
 
-    edit?: IAttachmentWithTags<number>[];
+    edit?: IAttachmentControlValue[];
 
 }
+
+export function createAttachmentForm() {
+    return createFormGroup<IAttachmentFormValue>({
+        add: new FormArray([]),
+        edit: new FormArray([])
+    });
+}
diff --git a/src/app/store/attachments/model/IAttachmentsStoreState.ts b/src/app/store/attachments/model/IAttachmentsStoreState.ts
index 75431d0..d947c1d 100644
--- a/src/app/store/attachments/model/IAttachmentsStoreState.ts
+++ b/src/app/store/attachments/model/IAttachmentsStoreState.ts
@@ -11,16 +11,18 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {IAPIAttachmentModel} from "../../../core/api";
+import {IAPIAttachmentModel, IAPIAttachmentTag} from "../../../core/api";
 import {TStoreEntities} from "../../../util/store";
-import {IAttachmentWithTags} from "./IAttachmentWithTags";
+import {IAttachmentControlValue} from "./IAttachmentControlValue";
 
 export interface IAttachmentsStoreState {
 
     entities: TStoreEntities<IAPIAttachmentModel>;
 
-    statementFileCache?: TStoreEntities<IAttachmentWithTags<File>[]>;
-
     statementAttachments: TStoreEntities<number[]>;
 
+    statementCache?: TStoreEntities<IAttachmentControlValue[]>;
+
+    tags?: IAPIAttachmentTag[];
+
 }
diff --git a/src/app/store/attachments/model/index.ts b/src/app/store/attachments/model/index.ts
index eb2d219..5647428 100644
--- a/src/app/store/attachments/model/index.ts
+++ b/src/app/store/attachments/model/index.ts
@@ -14,4 +14,4 @@
 export * from "./IAttachmentFormValue";
 export * from "./IAttachmentError";
 export * from "./IAttachmentsStoreState";
-export * from "./IAttachmentWithTags";
+export * from "./IAttachmentControlValue";
diff --git a/src/app/store/attachments/reducers/entities/attachment-entities.reducer.spec.ts b/src/app/store/attachments/reducers/entities/attachment-entities.reducer.spec.ts
index 8555e0c..f4a1383 100644
--- a/src/app/store/attachments/reducers/entities/attachment-entities.reducer.spec.ts
+++ b/src/app/store/attachments/reducers/entities/attachment-entities.reducer.spec.ts
@@ -12,15 +12,16 @@
  ********************************************************************************/
 
 import {Action} from "@ngrx/store";
+import {IAPIAttachmentModel} from "../../../../core/api/attachments";
 import {createAttachmentModelMock} from "../../../../test";
-import {addAttachmentEntityAction, deleteAttachmentsAction, setAttachmentsAction} from "../../actions";
+import {addAttachmentEntityAction, deleteAttachmentsAction, setAttachmentsAction, updateAttachmentTagsAction} from "../../actions";
 import {IAttachmentsStoreState} from "../../model";
 import {attachmentEntitiesReducer} from "./attachment-entities.reducer";
 
 describe("attachmentEntitiesReducer", () => {
 
     const initialState: IAttachmentsStoreState["entities"] = {
-        17: createAttachmentModelMock(17),
+        17: createAttachmentModelMock(17, "tag0"),
         18: createAttachmentModelMock(18)
     };
 
@@ -61,5 +62,31 @@
         });
     });
 
+    it("should update the tag list of attachments", () => {
+        const action = updateAttachmentTagsAction({
+            items: [
+                {
+                    id: 17,
+                    tagIds: ["tag19"]
+                },
+                {
+                    id: 19,
+                    tagIds: ["tag19"]
+                }
+            ]
+        });
+        const state = attachmentEntitiesReducer(initialState, action);
+        expect(state).toEqual({
+            ...initialState,
+            17: {
+                ...initialState[17],
+                tagIds: ["tag19"]
+            },
+            19: {
+                id: 19,
+                tagIds: ["tag19"]
+            } as IAPIAttachmentModel
+        });
+    });
 
 });
diff --git a/src/app/store/attachments/reducers/entities/attachment-entities.reducer.ts b/src/app/store/attachments/reducers/entities/attachment-entities.reducer.ts
index 058bb92..570f1e9 100644
--- a/src/app/store/attachments/reducers/entities/attachment-entities.reducer.ts
+++ b/src/app/store/attachments/reducers/entities/attachment-entities.reducer.ts
@@ -13,8 +13,8 @@
 
 import {createReducer, on} from "@ngrx/store";
 import {IAPIAttachmentModel} from "../../../../core/api";
-import {deleteEntities, setEntitiesObject, TStoreEntities, updateEntitiesObject} from "../../../../util/store";
-import {addAttachmentEntityAction, deleteAttachmentsAction, setAttachmentsAction} from "../../actions";
+import {arrayJoin, deleteEntities, setEntitiesObject, TStoreEntities, updateEntitiesObject} from "../../../../util/store";
+import {addAttachmentEntityAction, deleteAttachmentsAction, setAttachmentsAction, updateAttachmentTagsAction} from "../../actions";
 
 export const attachmentEntitiesReducer = createReducer<TStoreEntities<IAPIAttachmentModel>>(
     {},
@@ -26,5 +26,8 @@
     }),
     on(deleteAttachmentsAction, (state, payload) => {
         return deleteEntities(state, payload.entityIds);
+    }),
+    on(updateAttachmentTagsAction, (state, payload) => {
+        return updateEntitiesObject(state, arrayJoin(payload.items).map((_) => ({id: _.id, tagIds: _.tagIds})), (_) => _.id);
     })
 );
diff --git a/src/app/store/attachments/reducers/index.ts b/src/app/store/attachments/reducers/index.ts
index 526049c..259ed59 100644
--- a/src/app/store/attachments/reducers/index.ts
+++ b/src/app/store/attachments/reducers/index.ts
@@ -12,5 +12,6 @@
  ********************************************************************************/
 
 export * from "./entities/attachment-entities.reducer";
-export * from "./statement-file-cache/statement-file-cache.reducer";
+export * from "./statement-file-cache/attachment-statement-cache.reducer";
 export * from "./statement-attachments/statement-attachments.reducer";
+export * from "./tags/attachment-tags.reducer";
diff --git a/src/app/store/attachments/reducers/statement-file-cache/attachment-statement-cache.reducer.spec.ts b/src/app/store/attachments/reducers/statement-file-cache/attachment-statement-cache.reducer.spec.ts
new file mode 100644
index 0000000..8790495
--- /dev/null
+++ b/src/app/store/attachments/reducers/statement-file-cache/attachment-statement-cache.reducer.spec.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 {createAttachmentFileMock} from "../../../../test";
+import {clearAttachmentCacheAction, setAttachmentCacheAction} from "../../actions";
+import {IAttachmentsStoreState} from "../../model";
+import {attachmentStatementCacheReducer} from "./attachment-statement-cache.reducer";
+
+describe("statementFileCacheReducer", () => {
+
+    const initialState: IAttachmentsStoreState["statementCache"] = {
+        1919: [createAttachmentFileMock("test.pdf")]
+    };
+
+    it("should set the attachment cache", () => {
+        const action = setAttachmentCacheAction({
+            statementId: 1919,
+            items: [createAttachmentFileMock("File19.pdf"), createAttachmentFileMock("File20.pdf")]
+        });
+        const state = attachmentStatementCacheReducer(initialState, action);
+        expect(state).toEqual({
+            1919: action.items
+        });
+    });
+
+    it("should clear file cache", () => {
+        const action = clearAttachmentCacheAction({
+            statementId: 1919
+        });
+        const state = attachmentStatementCacheReducer(initialState, action);
+        expect(state).toEqual({});
+    });
+
+});
diff --git a/src/app/store/attachments/reducers/statement-file-cache/statement-file-cache.reducer.ts b/src/app/store/attachments/reducers/statement-file-cache/attachment-statement-cache.reducer.ts
similarity index 64%
rename from src/app/store/attachments/reducers/statement-file-cache/statement-file-cache.reducer.ts
rename to src/app/store/attachments/reducers/statement-file-cache/attachment-statement-cache.reducer.ts
index 9f52122..4b35a9a 100644
--- a/src/app/store/attachments/reducers/statement-file-cache/statement-file-cache.reducer.ts
+++ b/src/app/store/attachments/reducers/statement-file-cache/attachment-statement-cache.reducer.ts
@@ -13,17 +13,15 @@
 
 import {createReducer, on} from "@ngrx/store";
 import {arrayJoin, deleteEntities, setEntitiesObject} from "../../../../util/store";
-import {addAttachmentErrorAction, clearFileCacheAction} from "../../actions";
+import {clearAttachmentCacheAction, setAttachmentCacheAction} from "../../actions";
 import {IAttachmentsStoreState} from "../../model";
 
-export const statementFileCacheReducer = createReducer<IAttachmentsStoreState["statementFileCache"]>(
+export const attachmentStatementCacheReducer = createReducer<IAttachmentsStoreState["statementCache"]>(
     undefined,
-    on(clearFileCacheAction, (state, payload) => {
+    on(clearAttachmentCacheAction, (state, payload) => {
         return deleteEntities(state, [payload.statementId]);
     }),
-    on(addAttachmentErrorAction, (state, payload) => {
-        const files = arrayJoin(payload.addError)
-            .map((error) => error.attachment);
-        return setEntitiesObject(state, [files], () => payload.statementId);
+    on(setAttachmentCacheAction, (state, payload) => {
+        return setEntitiesObject(state, [arrayJoin(payload.items)], () => payload.statementId);
     })
 );
diff --git a/src/app/store/attachments/reducers/statement-file-cache/statement-file-cache.reducer.spec.ts b/src/app/store/attachments/reducers/statement-file-cache/statement-file-cache.reducer.spec.ts
deleted file mode 100644
index 153f725..0000000
--- a/src/app/store/attachments/reducers/statement-file-cache/statement-file-cache.reducer.spec.ts
+++ /dev/null
@@ -1,58 +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 {Action} from "@ngrx/store";
-import {createAttachmentFileMock} from "../../../../test";
-import {addAttachmentErrorAction, clearFileCacheAction} from "../../actions";
-import {IAttachmentsStoreState} from "../../model";
-import {statementFileCacheReducer} from "./statement-file-cache.reducer";
-
-describe("statementFileCacheReducer", () => {
-
-    const file = createAttachmentFileMock("test.pdf");
-
-    const initialState: IAttachmentsStoreState["statementFileCache"] = {
-        1919: [file]
-    };
-
-    it("should set file cache on add attachment errors", () => {
-        const action = addAttachmentErrorAction({
-            statementId: 1919,
-            taskId: "1919",
-            addError: [
-                {
-                    attachment: createAttachmentFileMock("File 0.pdf"),
-                    error: new Error("")
-                },
-                {
-                    attachment: createAttachmentFileMock("File 0.pdf"),
-                    error: new Error("")
-                }
-            ],
-            removeError: []
-        });
-        const state = statementFileCacheReducer(initialState, action);
-        expect(state).toEqual({
-            1919: action.addError.map((_) => _.attachment)
-        });
-    });
-
-    it("should clear file cache", () => {
-        const action: Action = clearFileCacheAction({
-            statementId: 1919
-        });
-        const state = statementFileCacheReducer(initialState, action);
-        expect(state).toEqual({});
-    });
-
-});
diff --git a/src/app/store/attachments/reducers/tags/attachment-tags.reducer.spec.ts b/src/app/store/attachments/reducers/tags/attachment-tags.reducer.spec.ts
new file mode 100644
index 0000000..1782cb1
--- /dev/null
+++ b/src/app/store/attachments/reducers/tags/attachment-tags.reducer.spec.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 {Action} from "@ngrx/store";
+import {AUTO_SELECTED_TAGS, IAPIAttachmentTag} from "../../../../core/api/attachments";
+import {setAttachmentTagsAction} from "../../actions";
+import {attachmentTagsReducer} from "./attachment-tags.reducer";
+
+describe("attachmentTagsReducer", () => {
+
+    const initialState: IAPIAttachmentTag[] = [];
+
+    it("should set a list of attachment tags", () => {
+        const tags: IAPIAttachmentTag[] = AUTO_SELECTED_TAGS.map((_) => ({id: _, label: _}));
+        const action: Action = setAttachmentTagsAction({tags});
+        const state = attachmentTagsReducer(initialState, action);
+        expect(state).toEqual(tags);
+    });
+
+});
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/store/attachments/reducers/tags/attachment-tags.reducer.ts
similarity index 61%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/store/attachments/reducers/tags/attachment-tags.reducer.ts
index 563859b..4f487bb 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/store/attachments/reducers/tags/attachment-tags.reducer.ts
@@ -11,9 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
-}
+import {createReducer, on} from "@ngrx/store";
+import {IAPIAttachmentTag} from "../../../../core/api/attachments";
+import {setAttachmentTagsAction} from "../../actions";
+
+export const attachmentTagsReducer = createReducer<IAPIAttachmentTag[]>(
+    undefined,
+    on(setAttachmentTagsAction, (state, payload) => {
+        return payload.tags;
+    })
+);
diff --git a/src/app/store/attachments/selectors/attachments.selectors.spec.ts b/src/app/store/attachments/selectors/attachments.selectors.spec.ts
index 6f8a265..5d7dab3 100644
--- a/src/app/store/attachments/selectors/attachments.selectors.spec.ts
+++ b/src/app/store/attachments/selectors/attachments.selectors.spec.ts
@@ -11,24 +11,27 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {IAPIAttachmentTag} from "../../../core/api/attachments";
 import {createAttachmentFileMock, createAttachmentModelMock} from "../../../test";
 import {IAttachmentsStoreState} from "../model";
-import {attachmentsEntitiesSelector, getStatementAttachmentsSelector, getStatementFileCacheSelector} from "./attachments.selectors";
+import {
+    getAttachmentControlValueSelector,
+    getFilteredAttachmentTagsSelector,
+    getStatementAttachmentsSelector
+} from "./attachments.selectors";
 
 describe("attachmentsSelectors", () => {
 
-    const statementId = 1919;
-
     const state: IAttachmentsStoreState = {
         entities: {
-            17: createAttachmentModelMock(17),
-            18: createAttachmentModelMock(18),
-            19: createAttachmentModelMock(19)
+            17: createAttachmentModelMock(17, "tag17"),
+            18: createAttachmentModelMock(18, "tag18"),
+            19: createAttachmentModelMock(19, "tag19")
         },
         statementAttachments: {
             1919: [17, 19]
         },
-        statementFileCache: {
+        statementCache: {
             1919: [
                 createAttachmentFileMock("Test1.pdf"),
                 createAttachmentFileMock("Test2.pdf")
@@ -36,24 +39,31 @@
         }
     };
 
-    it("attachmentsEntitiesSelector", () => {
-        const projector = attachmentsEntitiesSelector.projector;
-        expect(projector(null)).toEqual({});
-        expect(projector({...state, entities: null})).toEqual({});
-        expect(projector(state)).toBe(state.entities);
+    it("getFilteredAttachmentTagsSelector", () => {
+        const tags: IAPIAttachmentTag[] = Array(100).fill(0)
+            .map((_, id) => ({id: "id" + id, label: "Tag" + id}));
+        const projector = getFilteredAttachmentTagsSelector.projector;
+        expect(projector(tags, {without: null})).toEqual(tags);
+        expect(projector(tags, {without: ["id18", "id19", "id199"]}))
+            .toEqual(tags.filter((_) => _.id !== "id18" && _.id !== "id19"));
     });
 
     it("getStatementAttachmentsSelector", () => {
         const projector = getStatementAttachmentsSelector.projector;
-        expect(projector(null, state.entities, statementId)).toEqual([]);
-        expect(projector({...state, statementAttachments: null}, state.entities, statementId)).toEqual([]);
-        expect(projector(state, state.entities, statementId)).toEqual([state.entities[17], state.entities[19]]);
+        expect(projector(null, null, null)).toEqual([]);
+        expect(projector([17, 19], state.entities, null)).toEqual([state.entities[17], state.entities[19]]);
+        expect(projector([17, 18, 19], state.entities, {
+            restrictedTagIds: ["tag17", "tag18"]
+        })).toEqual([state.entities[17], state.entities[18]]);
+        expect(projector([17, 18, 19], state.entities, {
+            forbiddenTagIds: ["tag17", "tag18"]
+        })).toEqual([state.entities[19]]);
     });
 
-    it("getStatementFileCacheSelector", () => {
-        const projector = getStatementFileCacheSelector.projector;
-        expect(projector(null, statementId)).toEqual([]);
-        expect(projector({...state, statementFileCache: null}, statementId)).toEqual([]);
-        expect(projector(state, statementId)).toEqual(state.statementFileCache[statementId]);
+    it("getAttachmentControlValueSelector", () => {
+        const projector = getAttachmentControlValueSelector.projector;
+        expect(projector([{id: 19, name: "name", tagIds: []}], null))
+            .toEqual([{id: 19, name: "name", tagIds: [], isSelected: true}]);
     });
+
 });
diff --git a/src/app/store/attachments/selectors/attachments.selectors.ts b/src/app/store/attachments/selectors/attachments.selectors.ts
index af299a8..3035e85 100644
--- a/src/app/store/attachments/selectors/attachments.selectors.ts
+++ b/src/app/store/attachments/selectors/attachments.selectors.ts
@@ -12,33 +12,68 @@
  ********************************************************************************/
 
 import {createFeatureSelector, createSelector} from "@ngrx/store";
-import {IAPIAttachmentModel} from "../../../core/api/attachments";
-import {arrayJoin, TStoreEntities} from "../../../util/store";
+import {IAPIAttachmentModel, IAPIAttachmentTag} from "../../../core/api/attachments";
+import {
+    arrayJoin,
+    filterDistinctValues,
+    selectArrayProjector,
+    selectEntityWithIdProjector,
+    selectPropertyProjector,
+    TStoreEntities
+} from "../../../util/store";
 import {queryParamsIdSelector} from "../../root/selectors";
 import {ATTACHMENTS_NAME} from "../attachments-reducers.token";
-import {IAttachmentsStoreState, IAttachmentWithTags} from "../model";
+import {IAttachmentControlValue, IAttachmentsStoreState} from "../model";
 
 export const attachmentsStoreStateSelector = createFeatureSelector<IAttachmentsStoreState>(ATTACHMENTS_NAME);
 
-export const attachmentsEntitiesSelector = createSelector(
+const attachmentsEntitiesSelector = createSelector(
     attachmentsStoreStateSelector,
-    (state) => state?.entities == null ? {} : state.entities
+    selectPropertyProjector("entities", {})
+);
+
+const getStatementAttachmentIdEntities = createSelector(
+    attachmentsStoreStateSelector,
+    selectPropertyProjector("statementAttachments", {})
+);
+
+const getStatementAttachmentCacheEntitiesSelector = createSelector(
+    attachmentsStoreStateSelector,
+    selectPropertyProjector("statementCache", {})
+);
+
+
+export const getAttachmentTagsSelector = createSelector(
+    attachmentsStoreStateSelector,
+    selectArrayProjector("tags", [])
+);
+
+export const getFilteredAttachmentTagsSelector = createSelector(
+    getAttachmentTagsSelector,
+    (tags: IAPIAttachmentTag[], props: { without: string[] }) => {
+        return arrayJoin(tags).filter((_) => filterDistinctValues(props.without).indexOf(_.id) === -1);
+    }
+);
+
+
+export const getStatementAttachmentIds = createSelector(
+    getStatementAttachmentIdEntities,
+    queryParamsIdSelector,
+    selectEntityWithIdProjector()
 );
 
 export const getStatementAttachmentsSelector = createSelector(
-    attachmentsStoreStateSelector,
+    getStatementAttachmentIds,
     attachmentsEntitiesSelector,
-    queryParamsIdSelector,
     (
-        state: IAttachmentsStoreState,
-        entities: TStoreEntities<IAPIAttachmentModel>,
-        statementId: number,
+        attachmentIds: number[],
+        attachmentEntities: TStoreEntities<IAPIAttachmentModel>,
         props: { restrictedTagIds?: string[], forbiddenTagIds?: string[] }
     ): IAPIAttachmentModel[] => {
         const restrictedTagIds = arrayJoin(props?.restrictedTagIds);
         const forbiddenTagIds = arrayJoin(props?.forbiddenTagIds);
-        return state?.statementAttachments == null ? [] : arrayJoin(state.statementAttachments[statementId])
-            .map((attachmentId) => entities[attachmentId])
+        return arrayJoin(attachmentIds)
+            .map((attachmentId) => attachmentEntities[attachmentId])
             .filter((attachment) => {
                 return restrictedTagIds.length === 0 || attachment.tagIds.some((_) => restrictedTagIds.indexOf(_) > -1);
             })
@@ -48,10 +83,20 @@
     }
 );
 
-export const getStatementFileCacheSelector = createSelector(
-    attachmentsStoreStateSelector,
+export const getStatementAttachmentCacheSelector = createSelector(
+    getStatementAttachmentCacheEntitiesSelector,
     queryParamsIdSelector,
-    (state, id): IAttachmentWithTags<File>[] => {
-        return state?.statementFileCache == null ? [] : arrayJoin(state.statementFileCache[id]);
+    selectEntityWithIdProjector()
+);
+
+export const getAttachmentControlValueSelector = createSelector(
+    getStatementAttachmentsSelector,
+    (attachments: IAPIAttachmentModel[], props: { restrictedTagIds?: string[], forbiddenTagIds?: string[] }): IAttachmentControlValue[] => {
+        return attachments.map<IAttachmentControlValue>((_) => {
+            return {
+                ..._,
+                isSelected: true
+            };
+        });
     }
 );
diff --git a/src/app/store/process/actions/process.actions.ts b/src/app/store/process/actions/process.actions.ts
index b7dd725..0508382 100644
--- a/src/app/store/process/actions/process.actions.ts
+++ b/src/app/store/process/actions/process.actions.ts
@@ -12,20 +12,32 @@
  ********************************************************************************/
 
 import {Action, createAction, props} from "@ngrx/store";
-import {IAPIProcessObject, IAPIProcessTask, IAPIStatementHistory} from "../../../core";
+import {EAPIProcessTaskDefinitionKey, IAPIProcessObject, IAPIProcessTask, IAPIStatementHistory, TCompleteTaskVariable} from "../../../core";
 
 export const claimTaskAction = createAction(
     "[Details] Claim task",
     props<{ statementId: number, taskId: string, options?: { negative?: boolean } }>()
 );
 
+
+export const claimAndCompleteTask = createAction(
+    "[Details] Claim and complete Task",
+    props<{ statementId: number, taskId: string, variables: TCompleteTaskVariable, claimNext?: boolean | EAPIProcessTaskDefinitionKey }>()
+);
+
+export const unclaimAllTasksAction = createAction(
+    "[Details/Edit] Unclaim all tasks",
+    props<{ statementId: number, assignee: string }>()
+);
+
+
 export const completeTaskAction = createAction(
     "[Edit] Complete task",
     props<{
         statementId: number;
         taskId: string,
         variables: IAPIProcessObject,
-        claimNext?: boolean,
+        claimNext?: boolean | EAPIProcessTaskDefinitionKey,
         endWith?: Action[]
     }>()
 );
@@ -38,7 +50,7 @@
 
 export const deleteTaskAction = createAction(
     "[API] Delete task",
-    props<{ statementId: number; taskId: string }>()
+    props<{ statementId: number; taskId?: string }>()
 );
 
 export const updateTaskAction = createAction(
@@ -55,3 +67,8 @@
     "[API] Set bpmn diagram",
     props<{ statementId: number, diagram: string }>()
 );
+
+export const setProcessLoadingAction = createAction(
+    "[API] Set process loading",
+    props<{ statementId: number, loading: boolean }>()
+);
diff --git a/src/app/store/process/effects/claim-task.effect.spec.ts b/src/app/store/process/effects/claim-task.effect.spec.ts
deleted file mode 100644
index 85a5421..0000000
--- a/src/app/store/process/effects/claim-task.effect.spec.ts
+++ /dev/null
@@ -1,132 +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 {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
-import {TestBed} from "@angular/core/testing";
-import {Router} from "@angular/router";
-import {provideMockActions} from "@ngrx/effects/testing";
-import {Action} from "@ngrx/store";
-import {Observable, of, Subscription, timer} from "rxjs";
-import {SPA_BACKEND_ROUTE} from "../../../core";
-import {EAPIProcessTaskDefinitionKey, IAPIProcessTask} from "../../../core/api/process";
-import {claimTaskAction, deleteTaskAction, updateTaskAction} from "../actions";
-import {ClaimTaskEffect} from "./claim-task.effect";
-
-describe("ClaimTaskEffect", () => {
-
-    let actions$: Observable<Action>;
-    let httpTestingController: HttpTestingController;
-    let effect: ClaimTaskEffect;
-    let subscription: Subscription;
-    const routerSpy = {navigate: jasmine.createSpy("navigate")};
-
-    beforeEach(async () => {
-        TestBed.configureTestingModule({
-            imports: [
-                HttpClientTestingModule
-            ],
-            providers: [
-                ClaimTaskEffect,
-                provideMockActions(() => actions$),
-                {
-                    provide: SPA_BACKEND_ROUTE,
-                    useValue: "/"
-                },
-                {provide: Router, useValue: routerSpy}
-            ]
-        });
-        effect = TestBed.inject(ClaimTaskEffect);
-        httpTestingController = TestBed.inject(HttpTestingController);
-    });
-
-    afterEach(() => {
-        if (subscription != null) {
-            subscription.unsubscribe();
-        }
-    });
-
-    it("should claim the task and dispatch the updateTaskAction", async () => {
-
-        const statementId = 19;
-        const taskId = "taskId";
-
-        const fetchResult: IAPIProcessTask = {
-            statementId: 19,
-            taskId: "taskId",
-            taskDefinitionKey: EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA,
-            processDefinitionKey: "string",
-            assignee: "string",
-            requiredVariables: {
-                var1: "string"
-            }
-        };
-
-        const expectedResult = [
-            updateTaskAction({task: fetchResult})
-        ];
-        const results: Action[] = [];
-
-        actions$ = of(claimTaskAction({statementId, taskId}));
-        subscription = effect.editTask$.subscribe((action) => results.push(action));
-
-        expectEditTask(statementId, taskId, fetchResult);
-        await timer(500).toPromise();
-        expect(routerSpy.navigate).toHaveBeenCalledWith(["/edit"], {queryParams: {id: statementId, taskId}});
-        expect(results).toEqual(expectedResult);
-        httpTestingController.verify();
-    });
-
-    it("should dispatch deleteTaskAction on error", async () => {
-
-        const statementId = 19;
-        const taskId = "taskId";
-
-        const fetchResult: IAPIProcessTask = {
-            statementId: 19,
-            taskId: "taskId",
-            taskDefinitionKey: EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA,
-            processDefinitionKey: "string",
-            assignee: "string",
-            requiredVariables: {
-                var1: "string"
-            }
-        };
-
-        const expectedResult = [
-            deleteTaskAction({statementId, taskId})
-        ];
-        const results: Action[] = [];
-
-        actions$ = of(claimTaskAction({statementId, taskId}));
-        subscription = effect.editTask$.subscribe((action) => results.push(action));
-
-        expectEditTask(statementId, taskId, fetchResult, true);
-        await timer(500).toPromise();
-        expect(routerSpy.navigate).toHaveBeenCalledWith(["/edit"], {queryParams: {id: statementId, taskId}});
-        expect(results).toEqual(expectedResult);
-        httpTestingController.verify();
-    });
-
-    function expectEditTask(statementId: number, taskId: string, returnValue: IAPIProcessTask, error = false) {
-        const url = `/process/statements/${statementId}/task/${taskId}/claim`;
-        const request = httpTestingController.expectOne(url);
-        expect(request.request.method).toBe("POST");
-        if (error) {
-            request.error(new ErrorEvent("test-error"));
-        } else {
-            request.flush(returnValue);
-        }
-    }
-
-});
-
diff --git a/src/app/store/process/effects/claim-task.effect.ts b/src/app/store/process/effects/claim-task.effect.ts
deleted file mode 100644
index 86cb700..0000000
--- a/src/app/store/process/effects/claim-task.effect.ts
+++ /dev/null
@@ -1,52 +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 {Injectable} from "@angular/core";
-import {Router} from "@angular/router";
-import {Actions, createEffect, ofType} from "@ngrx/effects";
-import {Action} from "@ngrx/store";
-import {Observable} from "rxjs";
-import {catchError, filter, map, switchMap} from "rxjs/operators";
-import {ProcessApiService} from "../../../core/api/process";
-import {claimTaskAction, deleteTaskAction, updateTaskAction} from "../actions";
-
-@Injectable({providedIn: "root"})
-export class ClaimTaskEffect {
-
-    public readonly editTask$ = createEffect(() => this.actions$.pipe(
-        ofType(claimTaskAction),
-        filter((action) => typeof action.statementId === "number" && typeof action.taskId === "string"),
-        switchMap((action) => this.editTask(action.statementId, action.taskId, action.options))
-    ));
-
-    public constructor(
-        private readonly actions$: Actions,
-        private readonly processApiService: ProcessApiService,
-        private readonly router: Router
-    ) {
-
-    }
-
-    public editTask(statementId: number, taskId: string, queryParams?: any): Observable<Action> {
-        queryParams = {...queryParams, id: statementId, taskId};
-        return this.processApiService.claimStatementTask(statementId, taskId).pipe(
-            map((task) => updateTaskAction({task})),
-            catchError(async () => deleteTaskAction({statementId, taskId})),
-            switchMap(async (action) => {
-                await this.router.navigate(["/edit"], {queryParams});
-                return action;
-            }),
-        );
-    }
-
-}
diff --git a/src/app/store/process/effects/complete-task.effect.spec.ts b/src/app/store/process/effects/complete-task.effect.spec.ts
deleted file mode 100644
index beb7779..0000000
--- a/src/app/store/process/effects/complete-task.effect.spec.ts
+++ /dev/null
@@ -1,173 +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 {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
-import {TestBed} from "@angular/core/testing";
-import {Router} from "@angular/router";
-import {provideMockActions} from "@ngrx/effects/testing";
-import {Action} from "@ngrx/store";
-import {Observable, of, Subscription, timer} from "rxjs";
-import {SPA_BACKEND_ROUTE} from "../../../core";
-import {EAPIProcessTaskDefinitionKey, IAPIProcessTask} from "../../../core/api/process";
-import {completeTaskAction, deleteTaskAction} from "../actions";
-import {CompleteTaskEffect} from "./complete-task.effect";
-
-describe("CompleteTaskEffect", () => {
-
-    let actions$: Observable<Action>;
-    let httpTestingController: HttpTestingController;
-    let effect: CompleteTaskEffect;
-    let subscription: Subscription;
-    const routerSpy = {navigate: jasmine.createSpy("navigate")};
-
-    beforeEach(async () => {
-        TestBed.configureTestingModule({
-            imports: [
-                HttpClientTestingModule
-            ],
-            providers: [
-                CompleteTaskEffect,
-                provideMockActions(() => actions$),
-                {
-                    provide: SPA_BACKEND_ROUTE,
-                    useValue: "/"
-                },
-                {provide: Router, useValue: routerSpy}
-            ]
-        });
-        effect = TestBed.inject(CompleteTaskEffect);
-        httpTestingController = TestBed.inject(HttpTestingController);
-    });
-
-    afterEach(() => {
-        if (subscription != null) {
-            subscription.unsubscribe();
-        }
-    });
-
-    it("should claim the task and dispatch the updateTaskAction", async () => {
-
-        const statementId = 19;
-        const taskId = "taskId";
-
-        const expectedResult = [deleteTaskAction({statementId, taskId})];
-        const results: Action[] = [];
-
-        actions$ = of(completeTaskAction({statementId, taskId, variables: {}}));
-        subscription = effect.completeTask$.subscribe((action) => results.push(action));
-
-        expectCompleteTask(statementId, taskId);
-        await timer(500).toPromise();
-
-        expect(results).toEqual(expectedResult);
-        httpTestingController.verify();
-    });
-
-    it("should complete the task and afterwards claim it", async () => {
-
-        const statementId = 19;
-        const taskId = "taskId";
-
-        const expectedResult = [deleteTaskAction({statementId, taskId})];
-        const results: Action[] = [];
-
-        actions$ = of(completeTaskAction({statementId, taskId, variables: {}}));
-        subscription = effect.completeTask$.subscribe((action) => results.push(action));
-
-        expectCompleteTask(statementId, taskId);
-
-        await timer(500).toPromise();
-
-        expect(results).toEqual(expectedResult);
-        httpTestingController.verify();
-    });
-
-    it("should claim the next task", async () => {
-
-        const statementId = 19;
-        const taskId = "taskId";
-
-        const fetchResult: IAPIProcessTask[] = [
-            {
-                statementId: 19,
-                taskId: "taskId",
-                taskDefinitionKey: EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA,
-                processDefinitionKey: "string",
-                assignee: "string",
-                requiredVariables: {
-                    var1: "string"
-                }
-            }
-        ];
-
-        const expectedResult = [deleteTaskAction({statementId, taskId})];
-        const results: Action[] = [];
-
-        actions$ = of(completeTaskAction({statementId, taskId, variables: {}, claimNext: true}));
-        subscription = effect.completeTask$.subscribe((action) => results.push(action));
-
-        expectCompleteTask(statementId, taskId);
-        expectGetStatement(statementId, fetchResult);
-        expectClaimTask(statementId, taskId, fetchResult[0]);
-
-        await timer(500).toPromise();
-
-        expect(results).toEqual(expectedResult);
-        httpTestingController.verify();
-    });
-
-    it("should follow up with the actions supplied in endWith", async () => {
-
-        const statementId = 19;
-        const taskId = "taskId";
-
-        const expectedResult = [
-            deleteTaskAction({statementId, taskId}),
-            deleteTaskAction({statementId, taskId})
-        ];
-        const results: Action[] = [];
-
-        actions$ = of(completeTaskAction({statementId, taskId, variables: {}, endWith: [deleteTaskAction({statementId, taskId})]}));
-        subscription = effect.completeTask$.subscribe((action) => results.push(action));
-
-        expectCompleteTask(statementId, taskId);
-
-        await timer(500).toPromise();
-
-        expect(results).toEqual(expectedResult);
-        httpTestingController.verify();
-    });
-
-    function expectCompleteTask(statementId: number, taskId: string) {
-        const url = `/process/statements/${statementId}/task/${taskId}/complete`;
-        const request = httpTestingController.expectOne(url);
-        expect(request.request.method).toBe("POST");
-        request.flush({});
-    }
-
-    function expectGetStatement(statementId: number, returnValue: IAPIProcessTask[]) {
-        const url = `/process/statements/${statementId}/task`;
-        const request = httpTestingController.expectOne(url);
-        expect(request.request.method).toBe("GET");
-        request.flush(returnValue);
-    }
-
-    function expectClaimTask(statementId: number, taskId: string, returnValue: IAPIProcessTask) {
-        const url = `/process/statements/${statementId}/task/${taskId}/claim`;
-        const request = httpTestingController.expectOne(url);
-        expect(request.request.method).toBe("POST");
-        request.flush(returnValue);
-    }
-
-});
-
diff --git a/src/app/store/process/effects/complete-task.effect.ts b/src/app/store/process/effects/complete-task.effect.ts
deleted file mode 100644
index 2784052..0000000
--- a/src/app/store/process/effects/complete-task.effect.ts
+++ /dev/null
@@ -1,92 +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 {Injectable} from "@angular/core";
-import {Params, Router} from "@angular/router";
-import {Actions, createEffect, ofType} from "@ngrx/effects";
-import {Action} from "@ngrx/store";
-import {concat, defer, EMPTY, Observable, of} from "rxjs";
-import {exhaustMap, filter, map, switchMap} from "rxjs/operators";
-import {EAPIProcessTaskDefinitionKey} from "../../../core/api";
-import {IAPIProcessObject, IAPIProcessTask, ProcessApiService} from "../../../core/api/process";
-import {ignoreError} from "../../../util/rxjs";
-import {arrayJoin} from "../../../util/store";
-import {completeTaskAction, deleteTaskAction} from "../actions";
-
-@Injectable({providedIn: "root"})
-export class CompleteTaskEffect {
-
-    public readonly completeTask$ = createEffect(() => this.actions$.pipe(
-        ofType(completeTaskAction),
-        filter((action) => typeof action.statementId === "number" && typeof action.taskId === "string"),
-        exhaustMap((action) => this.completeTask(action.statementId, action.taskId, action.variables, action.claimNext, action.endWith))
-    ));
-
-    public constructor(
-        private readonly actions$: Actions,
-        private readonly processApiService: ProcessApiService,
-        private readonly router: Router
-    ) {
-
-    }
-
-    public completeTask(
-        statementId: number,
-        taskId: string,
-        variables: IAPIProcessObject,
-        claimNext?: boolean | EAPIProcessTaskDefinitionKey,
-        endWithActions?: Action[]
-    ): Observable<Action> {
-        return concat(
-            this.processApiService.completeStatementTask(statementId, taskId, variables).pipe(
-                switchMap(() => {
-                    endWithActions = arrayJoin([deleteTaskAction({statementId, taskId})], endWithActions);
-                    return claimNext == null ? of<IAPIProcessTask>(null) : this.claimNextTask(statementId);
-                }),
-                switchMap((task) => {
-                    return this.navigateToStatement(statementId, task?.taskId);
-                }),
-                switchMap(() => EMPTY),
-                ignoreError()
-            ),
-            defer(() => of(...endWithActions))
-        );
-    }
-
-    public claimNextTask(statementId: number, key?: EAPIProcessTaskDefinitionKey): Observable<IAPIProcessTask> {
-        return this.processApiService.getStatementTasks(statementId).pipe(
-            map((taskList) => {
-                return taskList
-                    .filter((task) => task?.taskId != null)
-                    .filter((task) => key == null || task.taskDefinitionKey === key)[0];
-            }),
-            switchMap((task) => {
-                return task == null ? of(task) : this.processApiService.claimStatementTask(statementId, task.taskId);
-            })
-        );
-    }
-
-    public navigateToStatement(statementId: number, taskId?: string, queryParams: Params = {}): Observable<boolean> {
-        return defer(() => {
-            const route = statementId == null ? "/" : taskId == null ? "/details" : "/edit";
-            if (statementId != null) {
-                queryParams.id = statementId;
-                if (taskId != null) {
-                    queryParams.taskId = taskId;
-                }
-            }
-            return this.router.navigate([route], {queryParams});
-        });
-    }
-
-}
diff --git a/src/app/store/process/effects/index.ts b/src/app/store/process/effects/index.ts
index 7ea19bf..bb63de8 100644
--- a/src/app/store/process/effects/index.ts
+++ b/src/app/store/process/effects/index.ts
@@ -11,5 +11,4 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./complete-task.effect";
-export * from "./claim-task.effect";
+export * from "./process-task.effect";
diff --git a/src/app/store/process/effects/process-task.effect.spec.ts b/src/app/store/process/effects/process-task.effect.spec.ts
new file mode 100644
index 0000000..14f1b90
--- /dev/null
+++ b/src/app/store/process/effects/process-task.effect.spec.ts
@@ -0,0 +1,216 @@
+/********************************************************************************
+ * 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 {Router} from "@angular/router";
+import {RouterTestingModule} from "@angular/router/testing";
+import {provideMockActions} from "@ngrx/effects/testing";
+import {Action} from "@ngrx/store";
+import {EMPTY, Observable, of, Subscription} from "rxjs";
+import {SPA_BACKEND_ROUTE, TCompleteTaskVariable} from "../../../core";
+import {IAPIProcessTask} from "../../../core/api/process";
+import {
+    claimAndCompleteTask,
+    claimTaskAction,
+    completeTaskAction,
+    deleteTaskAction,
+    setProcessLoadingAction,
+    setTasksAction,
+    unclaimAllTasksAction
+} from "../actions";
+import {ProcessTaskEffect} from "./process-task.effect";
+
+describe("ProcessTaskEffect", () => {
+
+    let actions$: Observable<Action>;
+    let httpTestingController: HttpTestingController;
+    let effect: ProcessTaskEffect;
+    let subscription: Subscription;
+    let router: Router;
+
+    beforeEach(async () => {
+        TestBed.configureTestingModule({
+            imports: [
+                HttpClientTestingModule,
+                RouterTestingModule
+            ],
+            providers: [
+                provideMockActions(() => actions$),
+                {
+                    provide: SPA_BACKEND_ROUTE,
+                    useValue: "/"
+                }
+            ]
+        });
+        effect = TestBed.inject(ProcessTaskEffect);
+        httpTestingController = TestBed.inject(HttpTestingController);
+        router = TestBed.inject(Router);
+    });
+
+    afterEach(() => {
+        if (subscription != null) {
+            subscription.unsubscribe();
+        }
+    });
+
+    it("should claim a task", async () => {
+        const statementId = 19;
+        const taskId = "191919";
+        const task: IAPIProcessTask = {...{} as IAPIProcessTask, taskId};
+        actions$ = of(claimTaskAction({statementId, taskId}));
+        const navigateSpy = spyOn(effect, "navigateTo").and.returnValue(EMPTY);
+
+        const expectedResult = [
+            setProcessLoadingAction({statementId, loading: true}),
+            deleteTaskAction({statementId}),
+            setTasksAction({statementId, tasks: [task]}),
+            setProcessLoadingAction({statementId, loading: false})
+        ];
+        const results: Action[] = [];
+
+        subscription = effect.claim$.subscribe((action) => results.push(action));
+
+        expectClaimTask(statementId, taskId, task);
+        expectGetStatementTasks(statementId, [task]);
+
+        expect(results).toEqual(expectedResult);
+        expect(navigateSpy).toHaveBeenCalledWith(statementId, taskId);
+
+        httpTestingController.verify();
+    });
+
+    it("should claim and complete a task", async () => {
+        const statementId = 19;
+        const taskId = "191919";
+        const task: IAPIProcessTask = {...{} as IAPIProcessTask, taskId};
+        const variables = {};
+        actions$ = of(claimAndCompleteTask({statementId, taskId, variables, claimNext: true}));
+        const completeTaskSpy = spyOn(effect, "completeTask").and.returnValue(EMPTY);
+
+        const expectedResult = [
+            setProcessLoadingAction({statementId, loading: true}),
+            setProcessLoadingAction({statementId, loading: false})
+        ];
+        const results: Action[] = [];
+
+        subscription = effect.claimAndComplete$.subscribe((action) => results.push(action));
+
+        expectClaimTask(statementId, taskId, task);
+
+        expect(results).toEqual(expectedResult);
+        expect(completeTaskSpy).toHaveBeenCalledWith(statementId, taskId, variables, true);
+
+        httpTestingController.verify();
+    });
+
+    it("should complete a task", async () => {
+        const statementId = 19;
+        const taskId = "191919";
+        const nextTaskId = "161616";
+        const variables = {};
+        const nextTask: IAPIProcessTask = {...{} as IAPIProcessTask, taskId: nextTaskId};
+        actions$ = of(completeTaskAction({statementId, taskId, variables, claimNext: true}));
+        const navigateSpy = spyOn(effect, "navigateTo").and.returnValue(EMPTY);
+
+        const expectedResult = [
+            setProcessLoadingAction({statementId, loading: true}),
+            deleteTaskAction({statementId}),
+            setTasksAction({statementId, tasks: [nextTask]}),
+            setProcessLoadingAction({statementId, loading: false})
+        ];
+        const results: Action[] = [];
+
+        subscription = effect.completeTask$.subscribe((action) => results.push(action));
+
+        expectCompleteTask(statementId, taskId, variables);
+        expectGetStatementTasks(statementId, [nextTask]);
+        expectClaimTask(statementId, nextTaskId, nextTask);
+        expectGetStatementTasks(statementId, [nextTask]);
+
+        expect(results).toEqual(expectedResult);
+        expect(navigateSpy).toHaveBeenCalledWith(statementId, nextTaskId);
+
+        httpTestingController.verify();
+    });
+
+    it("should unclaim all tasks for a statement", async () => {
+        const statementId = 19;
+        const assignee = "assignee";
+        const tasks: IAPIProcessTask[] = [
+            {...{} as IAPIProcessTask, taskId: "19", assignee},
+            {...{} as IAPIProcessTask, taskId: "20"}
+        ];
+
+        actions$ = of(unclaimAllTasksAction({statementId, assignee}));
+
+        const expectedResult = [
+            deleteTaskAction({statementId}),
+            setTasksAction({statementId, tasks}),
+        ];
+        const results: Action[] = [];
+
+        subscription = effect.unclaimAll$.subscribe((action) => results.push(action));
+
+        expectGetStatementTasks(statementId, tasks);
+        expectUnclaimTask(statementId, tasks[0].taskId);
+        expectGetStatementTasks(statementId, tasks);
+
+        expect(results).toEqual(expectedResult);
+
+        httpTestingController.verify();
+    });
+
+    it("should navigate to details or edit page", async () => {
+        const statementId = 19;
+        const taskId = "19191919";
+        const navigateToSpy = spyOn(router, "navigate").and.returnValue(Promise.resolve(true));
+
+        await effect.navigateTo(statementId).toPromise();
+        expect(navigateToSpy).toHaveBeenCalledWith(["details"], {queryParams: {id: statementId}});
+
+        await effect.navigateTo(statementId, taskId).toPromise();
+        expect(navigateToSpy).toHaveBeenCalledWith(["edit"], {queryParams: {id: statementId, taskId}});
+    });
+
+    function expectCompleteTask(statementId: number, taskId: string, body: TCompleteTaskVariable) {
+        const url = `/process/statements/${statementId}/task/${taskId}/complete`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("POST");
+        expect(request.request.body).toEqual(body);
+        request.flush({});
+    }
+
+    function expectGetStatementTasks(statementId: number, returnValue: IAPIProcessTask[]) {
+        const url = `/process/statements/${statementId}/task`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("GET");
+        request.flush(returnValue);
+    }
+
+    function expectClaimTask(statementId: number, taskId: string, returnValue: IAPIProcessTask) {
+        const url = `/process/statements/${statementId}/task/${taskId}/claim`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("POST");
+        request.flush(returnValue);
+    }
+
+    function expectUnclaimTask(statementId: number, taskId: string) {
+        const url = `/process/statements/${statementId}/task/${taskId}/unclaim`;
+        const request = httpTestingController.expectOne(url);
+        expect(request.request.method).toBe("POST");
+        request.flush({statementId, taskId});
+    }
+
+});
diff --git a/src/app/store/process/effects/process-task.effect.ts b/src/app/store/process/effects/process-task.effect.ts
new file mode 100644
index 0000000..458e130
--- /dev/null
+++ b/src/app/store/process/effects/process-task.effect.ts
@@ -0,0 +1,155 @@
+/********************************************************************************
+ * 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 {Router} from "@angular/router";
+import {Actions, createEffect, ofType} from "@ngrx/effects";
+import {Action} from "@ngrx/store";
+import {EMPTY, from, Observable, of} from "rxjs";
+import {catchError, endWith, exhaustMap, filter, ignoreElements, map, mergeMap, retry, startWith, switchMap, tap} from "rxjs/operators";
+import {EAPIProcessTaskDefinitionKey, ProcessApiService, TCompleteTaskVariable} from "../../../core/api/process";
+import {emitOnComplete, endWithObservable, ignoreError} from "../../../util/rxjs";
+import {arrayJoin} from "../../../util/store";
+import {
+    claimAndCompleteTask,
+    claimTaskAction,
+    completeTaskAction,
+    deleteTaskAction,
+    setProcessLoadingAction,
+    setTasksAction,
+    unclaimAllTasksAction
+} from "../actions";
+
+@Injectable({providedIn: "root"})
+export class ProcessTaskEffect {
+
+    public claim$ = createEffect(() => this.actions.pipe(
+        ofType(claimTaskAction),
+        switchMap((action) => this.claimTask(action.statementId, action.taskId).pipe(
+            startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
+            endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
+        ))
+    ));
+
+    public claimAndComplete$ = createEffect(() => this.actions.pipe(
+        ofType(claimAndCompleteTask),
+        switchMap((action) => this.claimAndCompleteTask(action.statementId, action.taskId, action.variables, action.claimNext).pipe(
+            startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
+            endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
+        ))
+    ));
+
+    public completeTask$ = createEffect(() => this.actions.pipe(
+        ofType(completeTaskAction),
+        exhaustMap((action) => this.completeTask(action.statementId, action.taskId, action.variables, action.claimNext).pipe(
+            startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
+            endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
+        ))
+    ));
+
+    public unclaimAll$ = createEffect(() => this.actions.pipe(
+        ofType(unclaimAllTasksAction),
+        switchMap((action) => this.unclaim(action.statementId, action.assignee))
+    ));
+
+    public constructor(
+        private readonly actions: Actions,
+        private readonly processApiService: ProcessApiService,
+        private readonly router: Router
+    ) {
+
+    }
+
+    public claimTask(statementId: number, taskId: string): Observable<Action> {
+        let nextTaskId: string = null;
+        return this.processApiService.claimStatementTask(statementId, taskId).pipe(
+            tap((_) => nextTaskId = _.taskId),
+            ignoreElements(),
+            ignoreError(),
+            endWithObservable(() => this.fetchTasks(statementId)),
+            endWithObservable(() => nextTaskId == null ? EMPTY : this.navigateTo(statementId, nextTaskId))
+        );
+    }
+
+    public claimAndCompleteTask(
+        statementId: number,
+        taskId: string,
+        variable: TCompleteTaskVariable,
+        claimNext?: boolean | EAPIProcessTaskDefinitionKey
+    ): Observable<Action> {
+        return this.processApiService.claimStatementTask(statementId, taskId).pipe(
+            switchMap(() => this.completeTask(statementId, taskId, variable, claimNext)),
+            catchError(() => this.fetchTasks(statementId))
+        );
+    }
+
+    public completeTask(
+        statementId: number,
+        taskId: string,
+        variable: TCompleteTaskVariable,
+        claimNext?: boolean | EAPIProcessTaskDefinitionKey
+    ): Observable<Action> {
+        let nextTaskId: string = null;
+        return this.processApiService.completeStatementTask(statementId, taskId, variable).pipe(
+            switchMap(() => claimNext == null ? EMPTY : this.claimNext(statementId, claimNext).pipe(
+                tap((_) => nextTaskId = _)
+            )),
+            ignoreError(),
+            ignoreElements(),
+            endWithObservable(() => this.fetchTasks(statementId)),
+            endWithObservable(() => this.navigateTo(statementId, nextTaskId))
+        );
+    }
+
+    public claimNext(statementId: number, claimNext: boolean | EAPIProcessTaskDefinitionKey): Observable<string> {
+        return this.processApiService.getStatementTasks(statementId).pipe(
+            switchMap((tasks) => {
+                const nextTaskId = arrayJoin(tasks)
+                    .find((_) => claimNext === true || _.taskDefinitionKey === claimNext)?.taskId;
+                return nextTaskId == null ? EMPTY : this.processApiService.claimStatementTask(statementId, nextTaskId).pipe(
+                    map(() => nextTaskId)
+                );
+            })
+        );
+    }
+
+    public unclaim(statementId: number, assignee: string): Observable<Action> {
+        return this.processApiService.getStatementTasks(statementId).pipe(
+            switchMap((tasks) => of(...tasks)),
+            filter((task) => task?.assignee === assignee),
+            mergeMap((task) => this.processApiService.unclaimStatementTask(statementId, task.taskId).pipe(ignoreError())),
+            ignoreElements(),
+            ignoreError(),
+            endWithObservable(() => this.fetchTasks(statementId))
+        );
+    }
+
+
+    public fetchTasks(statementId: number): Observable<Action> {
+        return this.processApiService.getStatementTasks(statementId).pipe(
+            retry(2),
+            map((tasks) => setTasksAction({statementId, tasks})),
+            ignoreError(),
+            startWith(deleteTaskAction({statementId})),
+            emitOnComplete()
+        );
+    }
+
+    public navigateTo(statementId: number, taskId?: string) {
+        return from(taskId == null ?
+            this.router.navigate(["details"], {queryParams: {id: statementId}}) :
+            this.router.navigate(["edit"], {queryParams: {id: statementId, taskId}})
+        ).pipe(ignoreElements());
+    }
+
+}
diff --git a/src/app/store/process/model/IProcessStoreState.ts b/src/app/store/process/model/IProcessStoreState.ts
index 9a0d895..ce5cdb1 100644
--- a/src/app/store/process/model/IProcessStoreState.ts
+++ b/src/app/store/process/model/IProcessStoreState.ts
@@ -24,4 +24,6 @@
 
     tasks: TStoreEntities<IAPIProcessTask>;
 
+    loading?: boolean;
+
 }
diff --git a/src/app/store/process/process-reducers.token.ts b/src/app/store/process/process-reducers.token.ts
index a71ec4d..cdb2dee 100644
--- a/src/app/store/process/process-reducers.token.ts
+++ b/src/app/store/process/process-reducers.token.ts
@@ -14,7 +14,7 @@
 import {InjectionToken} from "@angular/core";
 import {ActionReducerMap} from "@ngrx/store";
 import {IProcessStoreState} from "./model";
-import {diagramReducer, historyReducer, statementTaskReducer, tasksReducer} from "./reducers";
+import {diagramReducer, historyReducer, processLoadingReducer, statementTaskReducer, tasksReducer} from "./reducers";
 
 export const PROCESS_FEATURE_NAME = "process";
 
@@ -24,6 +24,7 @@
         diagram: diagramReducer,
         history: historyReducer,
         statementTasks: statementTaskReducer,
-        tasks: tasksReducer
+        tasks: tasksReducer,
+        loading: processLoadingReducer
     })
 });
diff --git a/src/app/store/process/process-store.module.ts b/src/app/store/process/process-store.module.ts
index 5ee81a7..5dc581a 100644
--- a/src/app/store/process/process-store.module.ts
+++ b/src/app/store/process/process-store.module.ts
@@ -14,15 +14,14 @@
 import {NgModule} from "@angular/core";
 import {EffectsModule} from "@ngrx/effects";
 import {StoreModule} from "@ngrx/store";
-import {ClaimTaskEffect, CompleteTaskEffect} from "./effects";
+import {ProcessTaskEffect} from "./effects";
 import {PROCESS_FEATURE_NAME, PROCESS_REDUCER} from "./process-reducers.token";
 
 @NgModule({
     imports: [
         StoreModule.forFeature(PROCESS_FEATURE_NAME, PROCESS_REDUCER),
         EffectsModule.forFeature([
-            ClaimTaskEffect,
-            CompleteTaskEffect
+            ProcessTaskEffect
         ])
     ]
 })
diff --git a/src/app/store/process/reducers/index.ts b/src/app/store/process/reducers/index.ts
index b15bd7e..580a3e5 100644
--- a/src/app/store/process/reducers/index.ts
+++ b/src/app/store/process/reducers/index.ts
@@ -14,5 +14,6 @@
 
 export * from "./diagram.reducer";
 export * from "./history.reducer";
+export * from "./process-loading.reducer";
 export * from "./statement-tasks.reducer";
 export * from "./tasks.reducer";
diff --git a/src/app/store/process/reducers/process-loading.reducer.spec.ts b/src/app/store/process/reducers/process-loading.reducer.spec.ts
new file mode 100644
index 0000000..a6ebabb
--- /dev/null
+++ b/src/app/store/process/reducers/process-loading.reducer.spec.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 {setProcessLoadingAction} from "../actions";
+import {processLoadingReducer} from "./process-loading.reducer";
+
+describe("processLoadingReducer", () => {
+
+    it("should toggle loading state", () => {
+        let initialState = false;
+        let action = setProcessLoadingAction({statementId: 19, loading: true});
+        expect(processLoadingReducer(initialState, action)).toBe(true);
+
+        initialState = true;
+        action = setProcessLoadingAction({statementId: 19, loading: false});
+        expect(processLoadingReducer(initialState, action)).toBe(false);
+    });
+
+});
diff --git a/src/app/features/details/selectors/index.ts b/src/app/store/process/reducers/process-loading.reducer.ts
similarity index 67%
copy from src/app/features/details/selectors/index.ts
copy to src/app/store/process/reducers/process-loading.reducer.ts
index aeb78c3..cdb0e50 100644
--- a/src/app/features/details/selectors/index.ts
+++ b/src/app/store/process/reducers/process-loading.reducer.ts
@@ -11,4 +11,12 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-export * from "./statement-details-button.selector";
+import {createReducer, on} from "@ngrx/store";
+import {setProcessLoadingAction} from "../actions";
+
+export const processLoadingReducer = createReducer<boolean>(
+    undefined,
+    on(setProcessLoadingAction, (state, payload) => {
+        return payload.loading;
+    })
+);
diff --git a/src/app/store/process/reducers/statement-tasks.reducer.spec.ts b/src/app/store/process/reducers/statement-tasks.reducer.spec.ts
index 7cba67d..775052a 100644
--- a/src/app/store/process/reducers/statement-tasks.reducer.spec.ts
+++ b/src/app/store/process/reducers/statement-tasks.reducer.spec.ts
@@ -67,12 +67,6 @@
         let state = statementTaskReducer(initialState, action);
         expect(state).toEqual(initialState);
 
-        actionPayload.statementId = 1;
-        actionPayload.taskId = {} as string;
-        action = deleteTaskAction(actionPayload);
-        state = statementTaskReducer(initialState, action);
-        expect(state).toEqual(initialState);
-
         initialState = {1: ["taskId"], 2: ["taskId2"]};
         actionPayload.statementId = 1;
         actionPayload.taskId = "taskId";
@@ -100,6 +94,13 @@
         action = deleteTaskAction(actionPayload);
         state = statementTaskReducer(initialState, action);
         expect(state).toEqual({...initialState, 3: undefined});
+
+        initialState = {1: ["taskId1", "taskId2"], 2: ["taskId2"]};
+        actionPayload.statementId = 1;
+        actionPayload.taskId = undefined;
+        action = deleteTaskAction(actionPayload);
+        state = statementTaskReducer(initialState, action);
+        expect(state).toEqual({...initialState, 1: undefined});
     });
 
 });
diff --git a/src/app/store/process/reducers/statement-tasks.reducer.ts b/src/app/store/process/reducers/statement-tasks.reducer.ts
index 703360e..d8a9ce8 100644
--- a/src/app/store/process/reducers/statement-tasks.reducer.ts
+++ b/src/app/store/process/reducers/statement-tasks.reducer.ts
@@ -12,7 +12,7 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
-import {filterDistinctValues, TStoreEntities} from "../../../util/store";
+import {arrayJoin, filterDistinctValues, TStoreEntities} from "../../../util/store";
 import {deleteTaskAction, setTasksAction} from "../actions";
 
 export const statementTaskReducer = createReducer<TStoreEntities<string[]>>(
@@ -34,12 +34,13 @@
         };
     }),
     on(deleteTaskAction, (state, payload) => {
-        if (typeof payload?.statementId !== "number" || typeof payload?.taskId !== "string") {
+        if (typeof payload?.statementId !== "number") {
             return state;
         }
 
-        const {statementId, taskId} = payload;
-        const tasks = Array.isArray(state[statementId]) ? state[statementId].filter((t) => t !== taskId) : [];
+        const statementId = payload.statementId;
+        const taskId = payload.taskId;
+        const tasks = taskId == null ? [] : arrayJoin(state[statementId]).filter((t) => t !== taskId);
 
         return {
             ...state,
diff --git a/src/app/store/process/reducers/tasks.reducer.ts b/src/app/store/process/reducers/tasks.reducer.ts
index e967051..589c9c1 100644
--- a/src/app/store/process/reducers/tasks.reducer.ts
+++ b/src/app/store/process/reducers/tasks.reducer.ts
@@ -37,11 +37,19 @@
         };
     }),
     on(deleteTaskAction, (state, payload) => {
-        if (typeof payload?.statementId !== "number" || typeof payload?.taskId !== "string") {
+        if (typeof payload?.statementId !== "number") {
             return state;
         }
 
-        return deleteEntities(state, [payload.taskId]);
+        if (payload.taskId != null) {
+            return deleteEntities(state, [payload.taskId]);
+        }
+
+        const taskIdsToDelete = entitiesToArray(state)
+            .filter((task) => task?.statementId === payload.statementId)
+            .map((task) => task.taskId);
+
+        return deleteEntities(state, taskIdsToDelete);
     }),
     on(updateTaskAction, (state, payload) => {
         const taskId = payload?.task?.taskId;
diff --git a/src/app/store/process/selectors/process.selectors.ts b/src/app/store/process/selectors/process.selectors.ts
index 11b7d34..2d037f4 100644
--- a/src/app/store/process/selectors/process.selectors.ts
+++ b/src/app/store/process/selectors/process.selectors.ts
@@ -12,7 +12,7 @@
  ********************************************************************************/
 
 import {createFeatureSelector, createSelector} from "@ngrx/store";
-import {arrayJoin} from "../../../util/store";
+import {arrayJoin, selectEntityWithIdProjector, selectPropertyProjector} from "../../../util/store";
 import {queryParamsIdSelector, queryParamsTaskIdSelector} from "../../root/selectors";
 import {IProcessStoreState} from "../model";
 import {PROCESS_FEATURE_NAME} from "../process-reducers.token";
@@ -48,27 +48,23 @@
 export const processDiagramSelector = createSelector(
     processStateSelector,
     queryParamsIdSelector,
-    (processState, statementId) => {
-        return processState?.diagram != null ? processState.diagram[statementId] : undefined;
-    }
+    selectEntityWithIdProjector(undefined, "diagram")
 );
 
 export const historySelector = createSelector(
     processStateSelector,
     queryParamsIdSelector,
-    (processState, statementId) => {
-        return processState?.history != null ? processState.history[statementId] : undefined;
-    }
+    selectEntityWithIdProjector(undefined, "history")
 );
 
 export const processNameSelector = createSelector(
     historySelector,
-    (history) => history?.processName
+    selectPropertyProjector("processName")
 );
 
 export const processVersionSelector = createSelector(
     historySelector,
-    (history) => history?.processVersion
+    selectPropertyProjector("processVersion")
 );
 
 export const currentActivityIds = createSelector(
@@ -76,3 +72,8 @@
     (history) => arrayJoin(history?.currentProcessActivities)
         .map((activity) => activity.activityId)
 );
+
+export const processLoadingSelector = createSelector(
+    processStateSelector,
+    selectPropertyProjector("loading")
+);
diff --git a/src/app/store/root/actions/root.actions.ts b/src/app/store/root/actions/root.actions.ts
index abce748..f70ba86 100644
--- a/src/app/store/root/actions/root.actions.ts
+++ b/src/app/store/root/actions/root.actions.ts
@@ -44,7 +44,7 @@
 
 export const setUserAction = createAction(
     "[API] Set user",
-    props<IAPIUserInfo>()
+    props<{ user: IAPIUserInfo }>()
 );
 
 export const clearUserAction = createAction(
@@ -65,15 +65,11 @@
     props<{ queryParams: TStoreEntities<any> }>()
 );
 
+
 export const openContactDataBaseAction = createAction(
     "[Details/Edit] Open contact data base in new tab"
 );
 
-export const openAttachmentAction = createAction(
-    "[Details/Edit] Open attachment in new tab",
-    props<{ statementId: number, attachmentId: number }>()
-);
-
 export const openFileAction = createAction(
     "[API] Open file in new tab",
     props<{ file: File }>()
diff --git a/src/app/store/root/effects/initialization.effect.ts b/src/app/store/root/effects/initialization.effect.ts
index 18e3803..6e01c94 100644
--- a/src/app/store/root/effects/initialization.effect.ts
+++ b/src/app/store/root/effects/initialization.effect.ts
@@ -64,7 +64,7 @@
         }
 
         return this.coreApiService.getUserInfo().pipe(
-            switchMap((userInfo) => of(setUserAction(userInfo), keepSessionAliveAction())),
+            switchMap((user) => of(setUserAction({user}), keepSessionAliveAction())),
             catchError((err) => {
                 if (isHttpErrorWithStatus(err, EHttpStatusCodes.UNAUTHORIZED)) {
                     return of(openExitPageAction({code: EExitCode.UNAUTHORIZED}));
diff --git a/src/app/store/root/effects/open-new-tab.effect.ts b/src/app/store/root/effects/open-new-tab.effect.ts
index 621fde4..de4a5fd 100644
--- a/src/app/store/root/effects/open-new-tab.effect.ts
+++ b/src/app/store/root/effects/open-new-tab.effect.ts
@@ -14,11 +14,8 @@
 import {Inject, Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {filter, mergeMap, switchMap} from "rxjs/operators";
-import {AuthService} from "../../../core/auth";
-import {URL_TOKEN, WINDOW} from "../../../core/dom";
-import {CONTACT_DATA_BASE_ROUTE, SPA_BACKEND_ROUTE} from "../../../core/external-routes";
-import {urlJoin} from "../../../util/http";
-import {openAttachmentAction, openContactDataBaseAction, openFileAction} from "../actions";
+import {AuthService, CONTACT_DATA_BASE_ROUTE, SPA_BACKEND_ROUTE, URL_TOKEN, WINDOW} from "../../../core";
+import {openContactDataBaseAction, openFileAction} from "../actions";
 
 @Injectable({providedIn: "root"})
 export class OpenNewTabEffect {
@@ -29,13 +26,6 @@
         switchMap(() => this.open(this.contactDataBaseRoute, true))
     ), {dispatch: false});
 
-    public openAttachment$ = createEffect(() => this.actions.pipe(
-        ofType(openAttachmentAction),
-        filter(() => this.authenticationService.token != null),
-        filter((action) => action.statementId != null && action.attachmentId != null),
-        switchMap((action) => this.openAttachment(action.statementId, action.attachmentId))
-    ), {dispatch: false});
-
     public openFile$ = createEffect(() => this.actions.pipe(
         ofType(openFileAction),
         filter((action) => action.file instanceof File),
@@ -53,11 +43,6 @@
 
     }
 
-    public async openAttachment(statementId: number, attachmentId: number) {
-        const endPoint = `/statements/${statementId}/attachments/${attachmentId}/file`;
-        return this.open(urlJoin(this.spaBackendRoute, endPoint), true);
-    }
-
     public async openFile(file: File) {
         const objectUrl = this.url.createObjectURL(file);
         const tab = await this.open(objectUrl, false, file.name);
@@ -74,5 +59,4 @@
         return tab;
     }
 
-
 }
diff --git a/src/app/store/root/model/IRootStoreState.ts b/src/app/store/root/model/IRootStoreState.ts
index 31f34b2..a913c60 100644
--- a/src/app/store/root/model/IRootStoreState.ts
+++ b/src/app/store/root/model/IRootStoreState.ts
@@ -12,8 +12,8 @@
  ********************************************************************************/
 
 import {Params} from "@angular/router";
+import {IAPIUserInfo} from "../../../core/api/core";
 import {EExitCode} from "./EExitCode";
-import {ESPAUserRoles} from "./ESPAUserRoles";
 
 export interface IRootStoreState {
     /**
@@ -31,11 +31,7 @@
      */
     versionBackEnd?: string;
 
-    user?: {
-        firstName?: string;
-        lastName?: string;
-        roles: ESPAUserRoles[];
-    };
+    user?: IAPIUserInfo;
 
     /**
      * Fatal error which traps the user on the error page
diff --git a/src/app/store/root/model/index.ts b/src/app/store/root/model/index.ts
index f54ce4c..10b28aa 100644
--- a/src/app/store/root/model/index.ts
+++ b/src/app/store/root/model/index.ts
@@ -12,5 +12,4 @@
  ********************************************************************************/
 
 export * from "./EExitCode";
-export * from "./ESPAUserRoles";
 export * from "./IRootStoreState";
diff --git a/src/app/store/root/reducers/user.reducer.spec.ts b/src/app/store/root/reducers/user.reducer.spec.ts
index ae22fd1..2915d43 100644
--- a/src/app/store/root/reducers/user.reducer.spec.ts
+++ b/src/app/store/root/reducers/user.reducer.spec.ts
@@ -12,50 +12,32 @@
  ********************************************************************************/
 
 import {Action} from "@ngrx/store";
+import {EAPIUserRoles, IAPIUserInfo} from "../../../core";
 import {clearUserAction, setUserAction} from "../actions";
-import {ESPAUserRoles} from "../model";
 import {userReducer} from "./user.reducer";
 
 describe("userReducer", () => {
 
     it("should set the user information to the state", () => {
-        let initialState;
+        const user: IAPIUserInfo = {
+            firstName: "firstName",
+            lastName: "lastName",
+            roles: [
+                EAPIUserRoles.DIVISION_MEMBER,
+                EAPIUserRoles.ROLE_SPA_ACCESS
+            ],
+            userName: "userName"
+        };
         let action: Action = setUserAction(null);
-        let state = userReducer(initialState, action);
-        expect(state).toEqual({firstName: undefined, lastName: undefined, roles: []});
-
-        action = setUserAction({
-            firstName: "firstName",
-            lastName: "lastName",
-            roles: [
-                ESPAUserRoles.DIVISION_MEMBER,
-                ESPAUserRoles.ROLE_SPA_ACCESS
-            ]
-        });
-        state = userReducer(initialState, action);
-        expect(state).toEqual({
-            firstName: "firstName",
-            lastName: "lastName",
-            roles: [
-                ESPAUserRoles.DIVISION_MEMBER,
-                ESPAUserRoles.ROLE_SPA_ACCESS
-            ]
-        });
-
-        action = clearUserAction();
-        state = userReducer(initialState, action);
+        let state = userReducer(undefined, action);
         expect(state).toEqual(undefined);
 
-        initialState = {
-            firstName: "firstName",
-            lastName: "lastName",
-            roles: [
-                ESPAUserRoles.DIVISION_MEMBER,
-                ESPAUserRoles.ROLE_SPA_ACCESS
-            ]
-        };
+        action = setUserAction({user});
+        state = userReducer(undefined, action);
+        expect(state).toEqual(user);
+
         action = clearUserAction();
-        state = userReducer(initialState, action);
+        state = userReducer(user, action);
         expect(state).toEqual(undefined);
     });
 
diff --git a/src/app/store/root/reducers/user.reducer.ts b/src/app/store/root/reducers/user.reducer.ts
index 583e285..114d6ec 100644
--- a/src/app/store/root/reducers/user.reducer.ts
+++ b/src/app/store/root/reducers/user.reducer.ts
@@ -12,16 +12,13 @@
  ********************************************************************************/
 
 import {createReducer, on} from "@ngrx/store";
+import {IAPIUserInfo} from "../../../core/api/core";
 import {clearUserAction, setUserAction} from "../actions";
 
-export const userReducer = createReducer(
+export const userReducer = createReducer<IAPIUserInfo>(
     undefined,
     on(setUserAction, (state, payload) => {
-        return payload == null ? {} : {
-            firstName: payload.firstName,
-            lastName: payload.lastName,
-            roles: Array.isArray(payload.roles) ? [...payload.roles] : []
-        };
+        return payload.user;
     }),
     on(clearUserAction, () => undefined)
 );
diff --git a/src/app/store/root/selectors/user.selectors.ts b/src/app/store/root/selectors/user.selectors.ts
index 1c63bab..b9d93af 100644
--- a/src/app/store/root/selectors/user.selectors.ts
+++ b/src/app/store/root/selectors/user.selectors.ts
@@ -12,13 +12,13 @@
  ********************************************************************************/
 
 import {createSelector} from "@ngrx/store";
-import {filterDistinctValues} from "../../../util/store";
-import {ESPAUserRoles} from "../model";
+import {EAPIUserRoles} from "../../../core/api/core";
+import {selectArrayProjector, selectPropertyProjector} from "../../../util/store";
 import {rootStateSelector} from "./root.selectors";
 
 export const userSelector = createSelector(
     rootStateSelector,
-    (state) => state?.user
+    selectPropertyProjector("user")
 );
 
 export const isLoggedInSelector = createSelector(
@@ -36,7 +36,7 @@
     (user) => typeof user?.lastName === "string" ? user.lastName.trim() : ""
 );
 
-export const userNameSelector = createSelector(
+export const userFullNameSelector = createSelector(
     userNameFirstNameSelector,
     userNameLastNameSelector,
     (firstName, lastName) => (firstName + " " + lastName).trim()
@@ -45,10 +45,15 @@
 
 export const userRolesSelector = createSelector(
     userSelector,
-    (state) => filterDistinctValues(state?.roles)
+    selectArrayProjector("roles", [])
 );
 
 export const isOfficialInChargeSelector = createSelector(
     userRolesSelector,
-    (roles): boolean => roles.find((role) => role === ESPAUserRoles.SPA_OFFICIAL_IN_CHARGE) != null
+    (roles): boolean => roles.find((role) => role === EAPIUserRoles.SPA_OFFICIAL_IN_CHARGE) != null
+);
+
+export const userNameSelector = createSelector(
+    userSelector,
+    selectPropertyProjector("userName")
 );
diff --git a/src/app/store/statements/actions/submit.actions.ts b/src/app/store/statements/actions/submit.actions.ts
index d296bb3..3975538 100644
--- a/src/app/store/statements/actions/submit.actions.ts
+++ b/src/app/store/statements/actions/submit.actions.ts
@@ -12,6 +12,7 @@
  ********************************************************************************/
 
 import {createAction, props} from "@ngrx/store";
+import {EAPIProcessTaskDefinitionKey, TCompleteTaskVariable} from "../../../core/api/process";
 import {IAPITextArrangementItemModel} from "../../../core/api/text";
 import {IStatementEditorFormValue, IStatementInformationFormValue, IWorkflowFormValue} from "../model";
 
@@ -59,5 +60,22 @@
 
 export const submitStatementEditorFormAction = createAction(
     "[Edit] Submit statement editor form",
-    props<{ statementId: number, taskId: string, value: IStatementEditorFormValue }>()
+    props<{
+        statementId: number,
+        taskId: string,
+        value: IStatementEditorFormValue,
+        options?: {
+            completeTask?: TCompleteTaskVariable,
+            claimNext?: boolean | EAPIProcessTaskDefinitionKey,
+            compile?: boolean,
+            contribute?: boolean,
+            file?: File
+        }
+    }>()
+);
+
+
+export const sendStatementViaMailAction = createAction(
+    "[Details] Resend statement via email",
+    props<{ statementId: number, taskId: string }>()
 );
diff --git a/src/app/store/statements/effects/compile-statement-arrangement/compile-statement-arrangement.effect.ts b/src/app/store/statements/effects/compile-statement-arrangement/compile-statement-arrangement.effect.ts
index ce2efeb..8b86f09 100644
--- a/src/app/store/statements/effects/compile-statement-arrangement/compile-statement-arrangement.effect.ts
+++ b/src/app/store/statements/effects/compile-statement-arrangement/compile-statement-arrangement.effect.ts
@@ -14,17 +14,12 @@
 import {Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {Action} from "@ngrx/store";
-import {Observable, of} from "rxjs";
+import {Observable, ObservableInput, of} from "rxjs";
 import {endWith, filter, startWith, switchMap} from "rxjs/operators";
 import {IAPITextArrangementItemModel, IAPITextArrangementValidationModel, TextApiService} from "../../../../core";
 import {catchHttpError, EHttpStatusCodes, ignoreError} from "../../../../util";
 import {openFileAction} from "../../../root/actions";
-import {
-    compileStatementArrangementAction,
-    setStatementErrorAction,
-    setStatementLoadingAction,
-    updateStatementEntityAction
-} from "../../actions";
+import {compileStatementArrangementAction, setStatementErrorAction, setStatementLoadingAction} from "../../actions";
 
 @Injectable({providedIn: "root"})
 export class CompileStatementArrangementEffect {
@@ -43,20 +38,23 @@
         return this.textApiService.compileArrangement(statementId, taskId, arrangement).pipe(
             switchMap((file) => {
                 return of(
-                    updateStatementEntityAction({statementId, entity: {file}}),
                     setStatementErrorAction({statementId, error: {arrangement: null}}),
                     openFileAction({file})
                 );
             }),
-            catchHttpError(async (response) => {
-                const errorMessage = response.error instanceof Blob ? await response.error.text() : response.error;
-                const body: IAPITextArrangementValidationModel = JSON.parse(errorMessage);
-                return setStatementErrorAction({statementId, error: {arrangement: body.errors}});
-            }, EHttpStatusCodes.FAILED_DEPENDENCY),
+            this.catchArrangementError(statementId),
             ignoreError(),
             startWith(setStatementLoadingAction({loading: {submittingStatementEditorForm: true}})),
             endWith(setStatementLoadingAction({loading: {submittingStatementEditorForm: false}}))
         );
     }
 
+    public catchArrangementError(statementId: number) {
+        return catchHttpError<any, ObservableInput<Action>>(async (response) => {
+            const errorMessage = response.error instanceof Blob ? await response.error.text() : response.error;
+            const body: IAPITextArrangementValidationModel = JSON.parse(errorMessage);
+            return setStatementErrorAction({statementId, error: {arrangement: body.errors}});
+        }, EHttpStatusCodes.FAILED_DEPENDENCY);
+    }
+
 }
diff --git a/src/app/store/statements/effects/fetch-statement-details/fetch-statement-details.effect.ts b/src/app/store/statements/effects/fetch-statement-details/fetch-statement-details.effect.ts
index 3e93c9e..c14d357 100644
--- a/src/app/store/statements/effects/fetch-statement-details/fetch-statement-details.effect.ts
+++ b/src/app/store/statements/effects/fetch-statement-details/fetch-statement-details.effect.ts
@@ -20,7 +20,8 @@
 import {ignoreError} from "../../../../util/rxjs";
 import {arrayJoin} from "../../../../util/store";
 import {fetchAttachmentsAction} from "../../../attachments/actions";
-import {setDiagramAction, setHistoryAction, setTasksAction} from "../../../process/actions";
+import {setDiagramAction, setHistoryAction} from "../../../process/actions";
+import {ProcessTaskEffect} from "../../../process/effects";
 import {
     fetchCommentsAction,
     fetchStatementDetailsAction,
@@ -42,7 +43,8 @@
         private readonly actions: Actions,
         private readonly statementsApiService: StatementsApiService,
         private readonly processApiService: ProcessApiService,
-        private readonly settingsApiService: SettingsApiService
+        private readonly settingsApiService: SettingsApiService,
+        private readonly taskEffect: ProcessTaskEffect
     ) {
 
     }
@@ -52,8 +54,9 @@
             retry(2),
             switchMap((info) => {
                 return merge<Action>(
-                    this.fetchTasks(statementId),
+                    this.taskEffect.fetchTasks(statementId),
                     this.fetchWorkflowData(statementId),
+                    this.fetchContributions(statementId),
                     this.fetchParents(statementId),
                     this.fetchHistory(statementId),
                     this.fetchDiagram(statementId),
@@ -94,17 +97,17 @@
         );
     }
 
-    public fetchConfiguration(statementId: number) {
-        return this.settingsApiService.getDepartmentsConfiguration(statementId).pipe(
-            map((departments) => updateStatementConfigurationAction({statementId, entity: {departments}})),
+    public fetchContributions(statementId: number) {
+        return this.statementsApiService.getContributions(statementId).pipe(
             retry(2),
+            map((contributions) => updateStatementEntityAction({statementId, entity: {contributions}})),
             ignoreError()
         );
     }
 
-    public fetchTasks(statementId: number) {
-        return this.processApiService.getStatementTasks(statementId).pipe(
-            map((tasks) => setTasksAction({statementId, tasks})),
+    public fetchConfiguration(statementId: number) {
+        return this.settingsApiService.getDepartmentsConfiguration(statementId).pipe(
+            map((departments) => updateStatementConfigurationAction({statementId, entity: {departments}})),
             retry(2),
             ignoreError()
         );
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 02e771a..8570828 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
@@ -20,7 +20,7 @@
 import {EAPIProcessTaskDefinitionKey, ProcessApiService, StatementsApiService} from "../../../../core";
 import {ignoreError} from "../../../../util/rxjs";
 import {SubmitAttachmentsEffect} from "../../../attachments/effects/submit";
-import {CompleteTaskEffect} from "../../../process/effects";
+import {ProcessTaskEffect} from "../../../process/effects";
 import {setStatementLoadingAction, submitStatementInformationFormAction} from "../../actions";
 import {IStatementInformationFormValue} from "../../model";
 
@@ -42,7 +42,7 @@
         private readonly actions: Actions,
         private readonly router: Router,
         private readonly submitAttachmentsEffect: SubmitAttachmentsEffect,
-        private readonly completeTaskEffect: CompleteTaskEffect,
+        private readonly taskEffect: ProcessTaskEffect,
         private readonly processApiService: ProcessApiService,
         private readonly statementsApiService: StatementsApiService,
     ) {
@@ -83,9 +83,9 @@
         let taskId: string;
         return this.createStatement(value).pipe(
             map((info) => statementId = info.id),
-            switchMap(() => this.completeTaskEffect.claimNextTask(statementId, EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA)),
-            switchMap((task) => {
-                taskId = task?.taskId;
+            switchMap(() => this.taskEffect.claimNext(statementId, EAPIProcessTaskDefinitionKey.ADD_BASIC_INFO_DATA)),
+            switchMap((_) => {
+                taskId = _;
                 const submitAttachments$ = this.submitAttachmentsEffect.submit(statementId, taskId, value?.attachments).pipe(
                     catchError(() => {
                         responsible = undefined;
@@ -102,7 +102,7 @@
                 if (statementId == null) {
                     return EMPTY;
                 }
-                return this.completeTaskEffect.navigateToStatement(statementId).pipe(switchMap(() => EMPTY));
+                return this.taskEffect.navigateTo(statementId).pipe(switchMap(() => EMPTY));
             }),
             ignoreError(),
             startWith(setStatementLoadingAction({loading: {submittingStatementInformation: true}})),
@@ -112,14 +112,11 @@
 
     public finalizeSubmit(statementId: number, taskId: string, responsible?: boolean): Observable<Action> {
         return defer(() => {
-            if (taskId != null && responsible) {
-                return this.completeTaskEffect
-                    .completeTask(statementId, taskId, {responsible: {type: "Boolean", value: true}}, true);
+            if (taskId != null && responsible != null) {
+                return this.taskEffect
+                    .completeTask(statementId, taskId, {responsible: {type: "Boolean", value: responsible}}, true);
             } else {
-                const queryParams = taskId != null && responsible === false ? {negative: true} : {};
-                return this.completeTaskEffect.navigateToStatement(statementId, taskId, queryParams).pipe(
-                    switchMap(() => EMPTY)
-                );
+                return this.taskEffect.navigateTo(statementId, taskId);
             }
         }).pipe(
             ignoreError()
diff --git a/src/app/store/statements/effects/submit-statement-editor-form/submit-statement-editor-form.effect.ts b/src/app/store/statements/effects/submit-statement-editor-form/submit-statement-editor-form.effect.ts
index 4c9b4b0..7cb78a2 100644
--- a/src/app/store/statements/effects/submit-statement-editor-form/submit-statement-editor-form.effect.ts
+++ b/src/app/store/statements/effects/submit-statement-editor-form/submit-statement-editor-form.effect.ts
@@ -14,13 +14,22 @@
 import {Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {Action} from "@ngrx/store";
-import {concat, Observable} from "rxjs";
-import {endWith, filter, map, retry, startWith, switchMap} from "rxjs/operators";
-import {IAPITextArrangementItemModel, TextApiService} from "../../../../core";
-import {ignoreError} from "../../../../util";
+import {concat, EMPTY, Observable, throwError} from "rxjs";
+import {endWith, filter, ignoreElements, map, retry, startWith, switchMap, tap} from "rxjs/operators";
+import {
+    EAPIProcessTaskDefinitionKey,
+    EAPIStaticAttachmentTagIds,
+    IAPITextArrangementItemModel,
+    StatementsApiService,
+    TCompleteTaskVariable,
+    TextApiService
+} from "../../../../core";
+import {arrayJoin, endWithObservable, ignoreError} from "../../../../util";
 import {SubmitAttachmentsEffect} from "../../../attachments/effects";
+import {ProcessTaskEffect} from "../../../process/effects";
 import {setStatementLoadingAction, submitStatementEditorFormAction, updateStatementEntityAction} from "../../actions";
-import {IStatementEditorFormValue} from "../../model";
+import {IDepartmentOptionValue, IStatementEditorFormValue} from "../../model";
+import {CompileStatementArrangementEffect} from "../compile-statement-arrangement";
 
 @Injectable({providedIn: "root"})
 export class SubmitStatementEditorFormEffect {
@@ -28,21 +37,44 @@
     public submit$ = createEffect(() => this.actions.pipe(
         ofType(submitStatementEditorFormAction),
         filter((action) => action.statementId != null && action.taskId != null),
-        switchMap((action) => this.submit(action.statementId, action.taskId, action.value))
+        switchMap((action) => this.submit(action.statementId, action.taskId, action.value, action.options))
     ));
 
     public constructor(
         private readonly actions: Actions,
         private readonly submitAttachmentsEffect: SubmitAttachmentsEffect,
-        private readonly textApiService: TextApiService
+        private readonly compileStatementArrangementEffect: CompileStatementArrangementEffect,
+        private readonly textApiService: TextApiService,
+        private readonly taskEffect: ProcessTaskEffect,
+        private readonly statementsApiService: StatementsApiService
     ) {
 
     }
 
-    public submit(statementId: number, taskId: string, value: IStatementEditorFormValue): Observable<Action> {
-        return concat(
+    public submit(
+        statementId: number,
+        taskId: string,
+        value: IStatementEditorFormValue,
+        options: {
+            completeTask?: TCompleteTaskVariable,
+            claimNext?: boolean | EAPIProcessTaskDefinitionKey,
+            compile?: boolean,
+            contribute?: boolean,
+            file?: File
+        }
+    ): Observable<Action> {
+        options = options == null ? {} : options;
+        return concat<Action>(
+            value.contributions ? this.submitContributions(statementId, taskId, value.contributions.selected) : EMPTY,
             this.submitArrangement(statementId, taskId, value.arrangement),
-            this.submitAttachmentsEffect.submit(statementId, taskId, value.attachments)
+            this.submitAttachmentsEffect.submit(statementId, taskId, value.attachments),
+            options.compile ? this.compile(statementId, taskId, value.arrangement) : EMPTY,
+            options.file instanceof File ? this.submitStatementFile(statementId, taskId, options.file) : EMPTY,
+            options.contribute ?
+                this.contribute(statementId, taskId) :
+                options.completeTask != null ?
+                    this.taskEffect.completeTask(statementId, taskId, options.completeTask, options.claimNext) :
+                    EMPTY
         ).pipe(
             ignoreError(),
             startWith(setStatementLoadingAction({loading: {submittingStatementEditorForm: true}})),
@@ -57,4 +89,44 @@
         );
     }
 
+    public submitContributions(statementId: number, taskId: string, contributions: IDepartmentOptionValue[]): Observable<Action> {
+        const departmentGroups = contributions.reduce((current, value) => {
+            return {
+                ...current,
+                [value.groupName]: arrayJoin(current[value.groupName], [value.name])
+            };
+        }, {});
+        return this.statementsApiService.postContributions(statementId, taskId, departmentGroups).pipe(
+            map(() => updateStatementEntityAction({statementId, entity: {contributions: departmentGroups}})),
+            retry(2)
+        );
+    }
+
+    public contribute(statementId: number, taskId: string): Observable<Action> {
+        return this.statementsApiService.contribute(statementId, taskId)
+            .pipe(switchMap(() => this.taskEffect.navigateTo(statementId)));
+    }
+
+    private submitStatementFile(statementId: number, taskId: string, file: File) {
+        return this.submitAttachmentsEffect.submit(statementId, taskId, {
+            add: [{
+                tagIds: [EAPIStaticAttachmentTagIds.STATEMENT, EAPIStaticAttachmentTagIds.OUTBOX],
+                name: file.name,
+                file
+            }]
+        }).pipe(
+            ignoreElements()
+        );
+    }
+
+    private compile(statementId: number, taskId: string, arrangement: IAPITextArrangementItemModel[]) {
+        let err: any;
+        return this.textApiService.compileArrangement(statementId, taskId, arrangement).pipe(
+            map((file) => updateStatementEntityAction({statementId, entity: {file}})),
+            tap({error: (_) => err = _}),
+            this.compileStatementArrangementEffect.catchArrangementError(statementId),
+            endWithObservable(() => err == null ? EMPTY : throwError(err))
+        );
+    }
+
 }
diff --git a/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.spec.ts b/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.spec.ts
index 5ba56c9..b5e48ae 100644
--- a/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.spec.ts
+++ b/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.spec.ts
@@ -18,8 +18,8 @@
 import {Action} from "@ngrx/store";
 import {EMPTY, Observable, of, Subscription} from "rxjs";
 import {IAPIWorkflowData, SPA_BACKEND_ROUTE} from "../../../../core";
-import {CompleteTaskEffect} from "../../../process/effects";
-import {submitWorkflowDataFormAction, updateStatementEntityAction} from "../../actions";
+import {ProcessTaskEffect} from "../../../process/effects";
+import {setStatementLoadingAction, submitWorkflowDataFormAction, updateStatementEntityAction} from "../../actions";
 import {IWorkflowFormValue} from "../../model";
 import {SubmitWorkflowFormEffect} from "./submit-workflow-form.effect";
 
@@ -71,8 +71,10 @@
         subscription.unsubscribe();
 
         expect(results).toEqual([
+            setStatementLoadingAction({loading: {submittingWorkflowData: true}}),
             updateStatementEntityAction({statementId, entity: {workflow: expectedData}}),
-            updateStatementEntityAction({statementId, entity: {parentIds}})
+            updateStatementEntityAction({statementId, entity: {parentIds}}),
+            setStatementLoadingAction({loading: {submittingWorkflowData: false}})
         ]);
 
         httpTestingController.verify();
@@ -85,7 +87,7 @@
         const parentIds = [18, 19];
         const data = createFormValue(parentIds);
         const expectedData = createData();
-        const completeTaskSpy = spyOn(TestBed.inject(CompleteTaskEffect), "completeTask").and.returnValue(EMPTY);
+        const completeTaskSpy = spyOn(TestBed.inject(ProcessTaskEffect), "completeTask").and.returnValue(EMPTY);
 
         actions$ = of(submitWorkflowDataFormAction({statementId, taskId, data, completeTask: true}));
         subscription = effect.submit$.subscribe((action) => results.push(action));
@@ -94,8 +96,10 @@
         expectPostParentIdsRequest(statementId, taskId, parentIds);
 
         expect(results).toEqual([
+            setStatementLoadingAction({loading: {submittingWorkflowData: true}}),
             updateStatementEntityAction({statementId, entity: {workflow: expectedData}}),
-            updateStatementEntityAction({statementId, entity: {parentIds}})
+            updateStatementEntityAction({statementId, entity: {parentIds}}),
+            setStatementLoadingAction({loading: {submittingWorkflowData: false}})
         ]);
         expect(completeTaskSpy).toHaveBeenCalledWith(statementId, taskId, {}, true);
 
@@ -131,20 +135,24 @@
 
     function createFormValue(parentIds: number[]): IWorkflowFormValue {
         return {
-            departments: [
-                {
-                    groupName: "Group A",
-                    name: "Department 1"
-                },
-                {
-                    groupName: "Group A",
-                    name: "Department 2"
-                },
-                {
-                    groupName: "Group B",
-                    name: "Department 1"
-                }
-            ],
+            departments: {
+                selected: [
+                    {
+                        groupName: "Group A",
+                        name: "Department 1"
+                    },
+                    {
+                        groupName: "Group A",
+                        name: "Department 2"
+                    },
+                    {
+                        groupName: "Group B",
+                        name: "Department 1"
+                    }
+                ],
+                indeterminate: []
+            }
+            ,
             geographicPosition: "",
             parentIds
         };
@@ -153,10 +161,11 @@
     function createData(): IAPIWorkflowData {
         return {
             geoPosition: "",
-            selectedDepartments: {
+            mandatoryDepartments: {
                 "Group A": ["Department 1", "Department 2"],
                 "Group B": ["Department 1"]
-            }
+            },
+            optionalDepartments: {}
         };
     }
 
diff --git a/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.ts b/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.ts
index 7a06174..ec019a9 100644
--- a/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.ts
+++ b/src/app/store/statements/effects/submit-workflow-form/submit-workflow-form.effect.ts
@@ -14,13 +14,14 @@
 import {Injectable} from "@angular/core";
 import {Actions, createEffect, ofType} from "@ngrx/effects";
 import {Action} from "@ngrx/store";
-import {concat, EMPTY, merge, Observable, of} from "rxjs";
-import {exhaustMap, filter, map, retry, switchMap, toArray} from "rxjs/operators";
+import {concat, EMPTY, merge, Observable} from "rxjs";
+import {endWith, exhaustMap, filter, map, retry, startWith} from "rxjs/operators";
 import {IAPIDepartmentGroups} from "../../../../core/api/settings";
 import {IAPIWorkflowData, StatementsApiService} from "../../../../core/api/statements";
+import {emitOnComplete} from "../../../../util/rxjs";
 import {arrayJoin} from "../../../../util/store";
-import {CompleteTaskEffect} from "../../../process/effects";
-import {submitWorkflowDataFormAction, updateStatementEntityAction} from "../../actions";
+import {ProcessTaskEffect} from "../../../process/effects";
+import {setStatementLoadingAction, submitWorkflowDataFormAction, updateStatementEntityAction} from "../../actions";
 import {IWorkflowFormValue} from "../../model";
 
 @Injectable({providedIn: "root"})
@@ -36,7 +37,7 @@
     public constructor(
         private readonly actions: Actions,
         private readonly statementsApiService: StatementsApiService,
-        private readonly completeTaskEffect: CompleteTaskEffect
+        private readonly taskEffect: ProcessTaskEffect
     ) {
 
     }
@@ -50,12 +51,12 @@
         return concat(
             merge(
                 this.postWorkflowData(statementId, taskId, data),
-                this.postParentIds(statementId, taskId, data),
-            ).pipe(
-                toArray(),
-                switchMap((results) => of(...results))
-            ),
-            completeTask ? this.completeTaskEffect.completeTask(statementId, taskId, {}, true) : EMPTY
+                this.postParentIds(statementId, taskId, data)
+            ).pipe(emitOnComplete()),
+            completeTask ? this.taskEffect.completeTask(statementId, taskId, {}, true) : EMPTY
+        ).pipe(
+            startWith(setStatementLoadingAction({loading: {submittingWorkflowData: true}})),
+            endWith(setStatementLoadingAction({loading: {submittingWorkflowData: false}}))
         );
     }
 
@@ -65,7 +66,14 @@
         data: IWorkflowFormValue
     ): Observable<Action> {
         const body: IAPIWorkflowData = {
-            selectedDepartments: data.departments
+            mandatoryDepartments: data.departments.selected
+                .reduce<IAPIDepartmentGroups>((current, value) => {
+                    return {
+                        ...current,
+                        [value.groupName]: arrayJoin(current[value.groupName], [value.name])
+                    };
+                }, {}),
+            optionalDepartments: data.departments.indeterminate
                 .reduce<IAPIDepartmentGroups>((current, value) => {
                     return {
                         ...current,
diff --git a/src/app/store/statements/model/IStatementEntity.ts b/src/app/store/statements/model/IStatementEntity.ts
index 3ac3430..70f662a 100644
--- a/src/app/store/statements/model/IStatementEntity.ts
+++ b/src/app/store/statements/model/IStatementEntity.ts
@@ -11,6 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {IAPIDepartmentGroups} from "../../../core/api/settings";
 import {IAPICommentModel, IAPIStatementModel, IAPIWorkflowData} from "../../../core/api/statements";
 import {IAPITextArrangementItemModel} from "../../../core/api/text";
 
@@ -20,6 +21,8 @@
 
     workflow?: IAPIWorkflowData;
 
+    contributions?: IAPIDepartmentGroups;
+
     parentIds?: number[];
 
     comments?: IAPICommentModel[];
diff --git a/src/app/store/statements/model/statement-editor-form/IStatementEditorFormValue.ts b/src/app/store/statements/model/statement-editor-form/IStatementEditorFormValue.ts
index aa071d2..e03b3b6 100644
--- a/src/app/store/statements/model/statement-editor-form/IStatementEditorFormValue.ts
+++ b/src/app/store/statements/model/statement-editor-form/IStatementEditorFormValue.ts
@@ -11,8 +11,11 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {FormArray, FormControl} from "@angular/forms";
+import {IDepartmentOptionValue} from "..";
 import {IAPITextArrangementItemModel} from "../../../../core";
-import {IAttachmentFormValue} from "../../../attachments/model";
+import {createFormGroup} from "../../../../util/forms";
+import {createAttachmentForm, IAttachmentFormValue} from "../../../attachments/model";
 
 export interface IStatementEditorFormValue {
 
@@ -20,4 +23,14 @@
 
     attachments?: IAttachmentFormValue;
 
+    contributions: { selected: IDepartmentOptionValue[], indeterminate: IDepartmentOptionValue[] };
+
+}
+
+export function createStatementEditorForm() {
+    return createFormGroup<IStatementEditorFormValue>({
+        arrangement: new FormArray([]),
+        attachments: createAttachmentForm(),
+        contributions: new FormControl({selected: [], indeterminate: []})
+    });
 }
diff --git a/src/app/store/statements/model/statement-info-form/IStatementInformationFormValue.ts b/src/app/store/statements/model/statement-info-form/IStatementInformationFormValue.ts
index 1821844..304c3ce 100644
--- a/src/app/store/statements/model/statement-info-form/IStatementInformationFormValue.ts
+++ b/src/app/store/statements/model/statement-info-form/IStatementInformationFormValue.ts
@@ -11,11 +11,26 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {FormControl, Validators} from "@angular/forms";
 import {IAPIPartialStatementModel} from "../../../../core/api/statements";
-import {IAttachmentFormValue} from "../../../attachments/model";
+import {createFormGroup} from "../../../../util/forms";
+import {createAttachmentForm, IAttachmentFormValue} from "../../../attachments/model";
 
 export interface IStatementInformationFormValue extends IAPIPartialStatementModel {
 
     attachments?: IAttachmentFormValue;
 
 }
+
+export function createStatementInformationForm() {
+    return createFormGroup<IStatementInformationFormValue>({
+        title: new FormControl(undefined, [Validators.required]),
+        dueDate: new FormControl(undefined, [Validators.required]),
+        receiptDate: new FormControl(undefined, [Validators.required]),
+        typeId: new FormControl(undefined, [Validators.required]),
+        city: new FormControl(undefined, [Validators.required]),
+        district: new FormControl(undefined, [Validators.required]),
+        contactId: new FormControl(undefined, [Validators.required]),
+        attachments: createAttachmentForm()
+    });
+}
diff --git a/src/app/store/statements/model/workflow-form/IWorkflowFormValue.ts b/src/app/store/statements/model/workflow-form/IWorkflowFormValue.ts
index d18c6e6..09b83a0 100644
--- a/src/app/store/statements/model/workflow-form/IWorkflowFormValue.ts
+++ b/src/app/store/statements/model/workflow-form/IWorkflowFormValue.ts
@@ -11,14 +11,27 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+import {FormControl} from "@angular/forms";
+import {createFormGroup} from "../../../../util/forms";
 import {IDepartmentOptionValue} from "./IDepartmentOptionValue";
 
 export interface IWorkflowFormValue {
 
-    departments: IDepartmentOptionValue[];
+    departments: {
+        selected: IDepartmentOptionValue[],
+        indeterminate: IDepartmentOptionValue[]
+    };
 
     geographicPosition: string;
 
     parentIds: number[];
 
 }
+
+export function createWorkflowForm() {
+    return createFormGroup<IWorkflowFormValue>({
+        departments: new FormControl([]),
+        geographicPosition: new FormControl(""),
+        parentIds: new FormControl([])
+    });
+}
diff --git a/src/app/store/statements/selectors/statement.selectors.ts b/src/app/store/statements/selectors/statement.selectors.ts
index dd22a06..ad4d2d9 100644
--- a/src/app/store/statements/selectors/statement.selectors.ts
+++ b/src/app/store/statements/selectors/statement.selectors.ts
@@ -32,11 +32,21 @@
     selectPropertyProjector("info")
 );
 
+export const statementTitleSelector = createSelector(
+    statementInfoSelector,
+    selectPropertyProjector("title")
+);
+
 export const statementWorkflowSelector = createSelector(
     statementSelector,
     selectPropertyProjector("workflow")
 );
 
+export const statementContributionsSelector = createSelector(
+    statementSelector,
+    selectPropertyProjector("contributions")
+);
+
 export const statementParentIdsSelector = createSelector(
     statementSelector,
     selectArrayProjector("parentIds", [])
diff --git a/src/app/store/statements/selectors/statements-store-state.selectors.spec.ts b/src/app/store/statements/selectors/statements-store-state.selectors.spec.ts
index 93323e6..f0b32b9 100644
--- a/src/app/store/statements/selectors/statements-store-state.selectors.spec.ts
+++ b/src/app/store/statements/selectors/statements-store-state.selectors.spec.ts
@@ -22,7 +22,7 @@
     statementSelector,
     statementWorkflowSelector
 } from "./statement.selectors";
-import {getStatementLoadingSelector, getStatementSearchSelector} from "./statements-store-state.selectors";
+import {getStatementSearchSelector} from "./statements-store-state.selectors";
 
 describe("statementSelectors", () => {
 
@@ -74,12 +74,6 @@
         expect(getStatementSearchSelector.projector({search})).toBe(search);
     });
 
-    it("getStatementLoadingSelector", () => {
-        const loading = {} as any;
-        expect(getStatementLoadingSelector.projector(undefined)).not.toBeDefined();
-        expect(getStatementLoadingSelector.projector({loading})).toBe(loading);
-    });
-
     it("getStatementSectorsSelector", () => {
         let settings: Partial<ISettingsStoreState> = {};
         let configuration: Partial<IStatementConfigurationEntity> = {};
diff --git a/src/app/store/statements/selectors/statements-store-state.selectors.ts b/src/app/store/statements/selectors/statements-store-state.selectors.ts
index ad2bf14..adc3fbb 100644
--- a/src/app/store/statements/selectors/statements-store-state.selectors.ts
+++ b/src/app/store/statements/selectors/statements-store-state.selectors.ts
@@ -13,6 +13,7 @@
 
 import {createFeatureSelector, createSelector} from "@ngrx/store";
 import {selectEntityWithIdProjector, selectPropertyProjector} from "../../../util";
+import {processLoadingSelector} from "../../process/selectors";
 import {queryParamsIdSelector} from "../../root/selectors";
 import {IStatementsStoreState} from "../model";
 import {STATEMENTS_NAME} from "../statements-reducers.token";
@@ -26,7 +27,7 @@
 
 export const getStatementLoadingSelector = createSelector(
     statementsStoreStateSelector,
-    selectPropertyProjector("loading")
+    selectPropertyProjector("loading", {})
 );
 
 export const getStatementErrorSelector = createSelector(
@@ -34,3 +35,11 @@
     queryParamsIdSelector,
     selectEntityWithIdProjector({}, "error")
 );
+
+export const statementLoadingSelector = createSelector(
+    getStatementLoadingSelector,
+    processLoadingSelector,
+    (statementLoading, processLoading): boolean => {
+        return processLoading || Object.values(statementLoading == null ? {} : statementLoading).reduce((_, v) => _ || v, false);
+    }
+);
diff --git a/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.spec.ts b/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.spec.ts
index 1e6f044..5d24fab 100644
--- a/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.spec.ts
+++ b/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.spec.ts
@@ -34,9 +34,10 @@
         const allDepartments = {} as any;
         const selectedDepartments = {} as any;
         expect(departmentGroupsObjectSelector.projector(null, null)).not.toBeDefined();
-        expect(departmentGroupsObjectSelector.projector(null, {selectedDepartments})).toEqual(selectedDepartments);
-        expect(departmentGroupsObjectSelector.projector({}, {selectedDepartments})).toEqual(selectedDepartments);
-        expect(departmentGroupsObjectSelector.projector({allDepartments}, {selectedDepartments})).toEqual(allDepartments);
+        expect(departmentGroupsObjectSelector.projector(null, {mandatoryDepartments: selectedDepartments})).toEqual(selectedDepartments);
+        expect(departmentGroupsObjectSelector.projector({}, {mandatoryDepartments: selectedDepartments})).toEqual(selectedDepartments);
+        expect(departmentGroupsObjectSelector.projector(
+            {allDepartments}, {mandatoryDepartments: selectedDepartments})).toEqual(allDepartments);
     });
 
     it("selectedDepartmentGroupsObjectSelector", () => {
@@ -110,26 +111,35 @@
     });
 
     it("workflowFormValueSelector", () => {
-        const departments = getValues(19, "Group");
+        const departments = {
+            selected: getValues(19, "Group"),
+            indeterminate: getValues(19, "Group")
+        };
         const geoPosition = "geoPosition";
         const parentIds = [18, 19];
 
         let result = workflowFormValueSelector.projector(undefined, undefined, undefined);
-        expect(result.departments).toEqual([]);
+        expect(result.departments).toEqual({
+            selected: [],
+            indeterminate: []
+        });
         expect(result.geographicPosition).not.toBeDefined();
         expect(result.parentIds).toEqual([]);
 
-        result = workflowFormValueSelector.projector(undefined, departments, undefined);
+        result = workflowFormValueSelector.projector(undefined, departments.selected, departments.indeterminate, undefined);
         expect(result.departments).toEqual(departments);
         expect(result.geographicPosition).not.toBeDefined();
         expect(result.parentIds).toEqual([]);
 
-        result = workflowFormValueSelector.projector(createWorkflowDataMock({geoPosition: undefined}), departments, undefined);
+        result = workflowFormValueSelector.projector(
+            createWorkflowDataMock({geoPosition: undefined}), departments.selected, departments.indeterminate, undefined);
         expect(result.departments).toEqual(departments);
         expect(result.geographicPosition).not.toBeDefined();
         expect(result.parentIds).toEqual([]);
 
-        result = workflowFormValueSelector.projector(createWorkflowDataMock({geoPosition}), departments, parentIds);
+        result = workflowFormValueSelector.projector(
+            createWorkflowDataMock({geoPosition}), departments.selected, departments.indeterminate, parentIds);
+        console.log(result);
         expect(result.departments).toEqual(departments);
         expect(result.geographicPosition).toBe(geoPosition);
         expect(result.parentIds).toEqual(parentIds);
diff --git a/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.ts b/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.ts
index 1c792c9..3a0567e 100644
--- a/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.ts
+++ b/src/app/store/statements/selectors/workflow-form/workflow-form.selectors.ts
@@ -12,17 +12,18 @@
  ********************************************************************************/
 
 import {createSelector} from "@ngrx/store";
+import {IAPIDepartmentGroups} from "../../../../core/api/settings";
 import {ISelectOption, ISelectOptionGroup} from "../../../../shared/controls/select";
 import {arrayJoin, objectToArray} from "../../../../util/store";
 import {IDepartmentOptionValue, IWorkflowFormValue} from "../../model";
 import {getStatementDepartmentConfigurationSelector} from "../statement-configuration.selectors";
-import {statementParentIdsSelector, statementWorkflowSelector} from "../statement.selectors";
+import {statementContributionsSelector, statementParentIdsSelector, statementWorkflowSelector} from "../statement.selectors";
 
 export const departmentGroupsObjectSelector = createSelector(
     getStatementDepartmentConfigurationSelector,
     statementWorkflowSelector,
     (configuration, workflow) => {
-        return configuration?.allDepartments != null ? configuration.allDepartments : workflow?.selectedDepartments;
+        return configuration?.allDepartments != null ? configuration.allDepartments : workflow?.mandatoryDepartments;
     }
 );
 
@@ -30,22 +31,21 @@
     getStatementDepartmentConfigurationSelector,
     statementWorkflowSelector,
     (configuration, workflow) => {
-        return workflow?.selectedDepartments == null ? configuration?.suggestedDepartments : workflow.selectedDepartments;
+        return workflow?.mandatoryDepartments == null ? configuration?.suggestedDepartments : workflow.mandatoryDepartments;
+    }
+);
+
+export const optionalDepartmentGroupsObjectSelector = createSelector(
+    statementWorkflowSelector,
+    (workflow) => {
+        return workflow?.optionalDepartments == null ? {} : workflow.optionalDepartments;
     }
 );
 
 export const departmentGroupsSelector = createSelector(
     departmentGroupsObjectSelector,
     (departmentGroupsObject): ISelectOptionGroup<IDepartmentOptionValue>[] => {
-        return objectToArray(departmentGroupsObject)
-            .filter((obj) => obj.key != null)
-            .sort((a, b) => a.key.localeCompare(b.key))
-            .map<ISelectOptionGroup>((obj) => {
-                return {
-                    label: obj.key,
-                    options: [...obj.value].sort().map((name) => ({groupName: obj.key, name}))
-                };
-            });
+        return groupsToOptionsObject(departmentGroupsObject);
     }
 );
 
@@ -61,27 +61,84 @@
     selectedDepartmentGroupsObjectSelector,
     departmentGroupsSelector,
     (selectedGroupObject, groups): IDepartmentOptionValue[] => {
-        const groupOptions = (Array.isArray(groups) && selectedGroupObject != null ? groups : [])
-            .filter((group) => Array.isArray(selectedGroupObject[group.label]))
-            .map<IDepartmentOptionValue[]>((group) => {
-                return group.options.filter((option) => {
-                    return selectedGroupObject[group.label].find((name) => name === option.name) != null;
-                });
-            });
+        const groupOptions = groupsToOptions(groups, selectedGroupObject);
         return arrayJoin(...groupOptions);
     }
 );
 
+export const optionalDepartmentSelector = createSelector(
+    optionalDepartmentGroupsObjectSelector,
+    departmentGroupsSelector,
+    (selectedGroupObject, groups): IDepartmentOptionValue[] => {
+        const groupOptions = groupsToOptions(groups, selectedGroupObject);
+        return arrayJoin(...groupOptions);
+    }
+);
+
+export const getContributionsSelector = createSelector(
+    statementContributionsSelector,
+    departmentGroupsSelector,
+    (contributions, groups) => {
+        const groupOptions = groupsToOptions(groups, contributions);
+        return {selected: arrayJoin(...groupOptions), indeterminate: []};
+    }
+);
 
 export const workflowFormValueSelector = createSelector(
     statementWorkflowSelector,
     selectedDepartmentSelector,
+    optionalDepartmentSelector,
     statementParentIdsSelector,
-    (workflow, departments, parentIds): IWorkflowFormValue => {
+    (workflow, selectedDepartments, optionalDepartments, parentIds): IWorkflowFormValue => {
         return {
-            departments: arrayJoin(departments),
+            departments: {selected: arrayJoin(selectedDepartments), indeterminate: arrayJoin(optionalDepartments)},
             geographicPosition: workflow?.geoPosition,
             parentIds: arrayJoin(parentIds)
         };
     }
 );
+
+export const requiredContributionsGroupsSelector = createSelector(
+    selectedDepartmentGroupsObjectSelector,
+    optionalDepartmentGroupsObjectSelector,
+    departmentGroupsSelector,
+    (selectedDepartments, optionalDepartments, departmentGroups): ISelectOptionGroup<IDepartmentOptionValue>[] => {
+        return departmentGroups.filter((group) => {
+            return selectedDepartments[group.label] != null || optionalDepartments[group.label] != null;
+        });
+    }
+);
+
+export const requiredContributionsOptionsSelector = createSelector(
+    departmentOptionsSelector,
+    selectedDepartmentGroupsObjectSelector,
+    optionalDepartmentGroupsObjectSelector,
+    (options, selectedGroups, optionalGroups): ISelectOption<IDepartmentOptionValue>[] => {
+        return options.filter((option) => {
+            return arrayJoin(selectedGroups[option.value.groupName]).some((_) => option.value.name === _)
+                || arrayJoin(optionalGroups[option.value.groupName]).some((_) => option.value.name === _);
+        });
+    }
+);
+
+export function groupsToOptionsObject(departmentGroups: IAPIDepartmentGroups) {
+    return objectToArray(departmentGroups)
+        .filter((obj) => obj.key != null)
+        .sort((a, b) => a.key.localeCompare(b.key))
+        .map<ISelectOptionGroup>((obj) => {
+            return {
+                label: obj.key,
+                options: [...obj.value].sort().map((name) => ({groupName: obj.key, name}))
+            };
+        });
+}
+
+export function groupsToOptions(groups: ISelectOptionGroup<IDepartmentOptionValue>[], selectedGroupObject: IAPIDepartmentGroups) {
+    return (Array.isArray(groups) && selectedGroupObject != null ? groups : [])
+        .filter((group) => Array.isArray(selectedGroupObject[group.label]))
+        .map<IDepartmentOptionValue[]>((group) => {
+            return group.options.filter((option) => {
+                return selectedGroupObject[group.label].find((name) => name === option.name) != null;
+            });
+        });
+}
diff --git a/src/app/test/create-attachment-file-mock.spec.ts b/src/app/test/create-attachment-file-mock.spec.ts
index e594761..66f71c6 100644
--- a/src/app/test/create-attachment-file-mock.spec.ts
+++ b/src/app/test/create-attachment-file-mock.spec.ts
@@ -11,12 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {IAttachmentWithTags} from "../store/attachments/model";
+import {IAttachmentControlValue} from "../store/attachments/model";
 import {createFileMock} from "./create-file-mock.spec";
 
-export function createAttachmentFileMock(name: string): IAttachmentWithTags<File> {
+export function createAttachmentFileMock(name: string): IAttachmentControlValue {
     return {
-        attachment: createFileMock(name),
-        tags: []
+        name,
+        file: createFileMock(name),
+        tagIds: []
     };
 }
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/test/create-attachment-tag-mock.spec.ts
similarity index 69%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/test/create-attachment-tag-mock.spec.ts
index 563859b..ed9b2b4 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/test/create-attachment-tag-mock.spec.ts
@@ -10,10 +10,10 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
+import {IAPIAttachmentTag} from "../core";
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+
+export function createAttachmentTagList(): IAPIAttachmentTag[] {
+    return ["Übersicht", "Anschreiben", "Plan", "Gutachten", "Erläuterungsbericht"]
+        .map((_, id) => ({label: _, id: "" + id}));
 }
diff --git a/src/app/test/create-statement-text-configuration-mock.spec.ts b/src/app/test/create-statement-text-configuration-mock.spec.ts
index e0c7615..6897e88 100644
--- a/src/app/test/create-statement-text-configuration-mock.spec.ts
+++ b/src/app/test/create-statement-text-configuration-mock.spec.ts
@@ -15,14 +15,14 @@
 
 export const blockTexts: string[] = [
     "Lorem ipsum dolor sit amet, <f:name> \n",
-    "consetetur sadipscing elitr, sed diam nonumy eirmod <d:datum> tempor invidunt ut labore et dolore magna aliquyam erat <s:options>, sed diam voluptua. \n\n At vero eos et accusam et justo duo dolores et ea rebum.",
+    "consetetur sadipscing elitr, sed diam nonumy eirmod <d:datum> tempor invidunt ut labore et dolore magna aliquyam erat <s:options>, sed diam voluptua. \n\nAt vero eos et accusam et justo duo dolores et ea rebum.",
     `Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
-     At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. \n Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
-      sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
-       Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
-        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.`,
+At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. \nLorem ipsum dolor sit amet, consetetur sadipscing elitr,
+sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
+Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.`,
     "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, <t:Ansprech_Partner>, <t:Firma> no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
-    "Stet clita kasd gubergren, <f:Bearbeiter> \n no \n \n \n sea takimata sanctus est Lorem ipsum dolor sit amet."
+    "Stet clita kasd gubergren, <f:Bearbeiter> \nno \n \n \nsea takimata sanctus est Lorem ipsum dolor sit amet. <t:Firma>."
 ];
 
 export function createTextBlockModelMock(id: string, text: string): IAPITextBlockModel {
@@ -46,7 +46,7 @@
         configuration: {
             selects: {
                 DropDown: ["Option 1", "Option 2", "Option 3"],
-                options: ["null", "eins", "zwei"]
+                options: ["null", "eins", "zwei", "drei", "siebenundzwanzig"]
             },
             groups: Array(3).fill(0)
                 .map((_, i) => createTextBlockModelGroup(i, 5))
diff --git a/src/app/test/create-workflow-data-mock.spec.ts b/src/app/test/create-workflow-data-mock.spec.ts
index f8b96e5..9bce910 100644
--- a/src/app/test/create-workflow-data-mock.spec.ts
+++ b/src/app/test/create-workflow-data-mock.spec.ts
@@ -15,7 +15,8 @@
 
 export function createWorkflowDataMock(partialData: Partial<IAPIWorkflowData> = {}): IAPIWorkflowData {
     return {
-        selectedDepartments: {},
+        mandatoryDepartments: {},
+        optionalDepartments: {},
         geoPosition: "",
         ...partialData
     };
diff --git a/src/app/test/index.ts b/src/app/test/index.ts
index 3031ba7..dd3e7d0 100644
--- a/src/app/test/index.ts
+++ b/src/app/test/index.ts
@@ -11,11 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+export * from "./create-attachment-file-mock.spec";
 export * from "./create-attachment-model-mock.spec";
+export * from "./create-attachment-tag-mock.spec";
 export * from "./create-file-mock.spec";
 export * from "./create-pagination-response-mock.spec";
 export * from "./create-select-options.spec";
 export * from "./create-statement-model-mock.spec";
 export * from "./create-statement-text-configuration-mock.spec";
 export * from "./create-workflow-data-mock.spec";
-export * from "./create-attachment-file-mock.spec";
+export * from "./url-mock.spec";
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/app/test/url-mock.spec.ts
similarity index 74%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/app/test/url-mock.spec.ts
index 563859b..79ee666 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/app/test/url-mock.spec.ts
@@ -11,9 +11,13 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+export class UrlMock {
+
+    public createObjectURL(object: any): string {
+        return "objectUrl" + object.toString();
+    }
+
+    public revokeObjectURL(url: string) {
+        return;
+    }
 }
diff --git a/src/app/util/rxjs/rxjs.util.spec.ts b/src/app/util/rxjs/rxjs.util.spec.ts
index 4dda810..dace0ee 100644
--- a/src/app/util/rxjs/rxjs.util.spec.ts
+++ b/src/app/util/rxjs/rxjs.util.spec.ts
@@ -12,9 +12,9 @@
  ********************************************************************************/
 
 import {fakeAsync, tick} from "@angular/core/testing";
-import {of, throwError} from "rxjs";
-import {map} from "rxjs/operators";
-import {ignoreError, retryAfter} from "./rxjs.util";
+import {concat, of, throwError, timer} from "rxjs";
+import {map, toArray} from "rxjs/operators";
+import {emitOnComplete, endWithObservable, ignoreError, retryAfter} from "./rxjs.util";
 
 describe("retryAfter", () => {
 
@@ -116,3 +116,37 @@
     });
 
 });
+
+describe("endWithObservable", () => {
+
+    it("should end an observable with another observable", async () => {
+        const observable$ = of(0).pipe(
+            endWithObservable(() => of(1)),
+            toArray()
+        );
+        const result = await observable$.toPromise();
+        expect(result).toEqual([0, 1]);
+    });
+
+});
+
+describe("emitOnComplete", () => {
+
+    it("should emit all values on completion", fakeAsync(() => {
+        const observable$ = concat(
+            of("start"),
+            timer(1000)
+        ).pipe(emitOnComplete());
+
+        const result: any[] = [];
+        const subscription = observable$.subscribe((_) => result.push(_));
+
+        tick(999);
+        expect(subscription.closed).toBeFalse();
+        expect(result).toEqual([]);
+        tick(1);
+        expect(subscription.closed).toBeTrue();
+        expect(result).toEqual(["start", 0]);
+    }));
+
+});
diff --git a/src/app/util/rxjs/rxjs.util.ts b/src/app/util/rxjs/rxjs.util.ts
index 98d5898..643500b 100644
--- a/src/app/util/rxjs/rxjs.util.ts
+++ b/src/app/util/rxjs/rxjs.util.ts
@@ -11,8 +11,8 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-import {EMPTY, OperatorFunction, pipe, throwError, timer} from "rxjs";
-import {catchError, concatMap, map, retryWhen} from "rxjs/operators";
+import {EMPTY, ObservableInput, of, OperatorFunction, pipe, throwError, timer} from "rxjs";
+import {catchError, concatMap, endWith, map, retryWhen, switchMap, toArray} from "rxjs/operators";
 
 /**
  * If an error occurs, the observable is repeated after a given amount of time.
@@ -44,3 +44,21 @@
         return EMPTY;
     });
 }
+
+/**
+ * The current observables ends with the subscription to another one provided by the fiven factory function.
+ */
+export function endWithObservable<T, Z = T>(endWithFactory: () => ObservableInput<Z>): OperatorFunction<T, T | Z> {
+    const TOKEN: T = {} as any;
+    return pipe(
+        endWith<T, T>(TOKEN),
+        concatMap<T, ObservableInput<T | Z>>((_) => _ === TOKEN ? endWithFactory() : of(_))
+    );
+}
+
+export function emitOnComplete<T>() {
+    return pipe(
+        toArray<T>(),
+        switchMap((_) => of<T>(..._))
+    );
+}
diff --git a/src/assets/i18n/de.i18.json b/src/assets/i18n/de.i18.json
index 2edc9a3..bfab544 100644
--- a/src/assets/i18n/de.i18.json
+++ b/src/assets/i18n/de.i18.json
@@ -3,6 +3,8 @@
     "title": "Stellungnahmen öffentlicher Belange",
     "version": "Version {{version}}",
     "momentJS": "de",
+    "loading": "Daten werden geladen...",
+    "submitting": "Daten werden übertragen...",
     "actions": {
       "logout": "Abmelden",
       "backToDashboard": "Zurück zur Übersicht",
@@ -44,16 +46,21 @@
     }
   },
   "details": {
-    "button": {
-      "createDraftForNegativeAnswer": "Negativantwort erstellen",
-      "addBasicInfoData": "Basisinformationen bearbeiten",
-      "addWorkflowData": "Workflowinformationen bearbeiten",
-      "createDraft": "Entwurf erstellen",
-      "enrichDraft": "Entwurf bearbeiten",
-      "checkForCompleteness": "Auf Vollständigkeit prüfen",
-      "finalizeStatement": "Stellungnahme finalisieren",
-      "approveStatement": "Stellungnahme prüfen",
-      "sendStatement": "Stellungnahme versenden"
+    "sideMenu": {
+      "title": "Detailansicht",
+      "backToDashboard": "Zurück zur Übersicht",
+      "backToInfoData": "Informationsdaten ergänzen",
+      "createNegativeStatement": "Negativmeldung verfassen",
+      "editInfoData": "Informationsdaten bearbeiten",
+      "editWorkflowData": "Workflowdaten bearbeiten",
+      "createDraft": "Entwurf anlegen",
+      "editDraft": "Entwurf bearbeiten",
+      "checkDraft": "Entwurf prüfen",
+      "completeDraft": "Entwurf finalisieren",
+      "sendEmail": "Email erneut versenden",
+      "completeIssue": "Vorgang manuell abschließen",
+      "disapprove": "Nachbearbeitung anstoßen",
+      "approve": "Stellungnahme genehmigen"
     }
   },
   "edit": {
@@ -75,9 +82,10 @@
     }
   },
   "attachments": {
-    "edit": "Anhänge übernehmen:",
-    "add": "Anhänge hinzufügen:",
-    "selectFile": "Datei auswählen"
+    "edit": "Dokumente übernehmen:",
+    "add": "Dokumente hinzufügen:",
+    "selectFile": "Datei auswählen",
+    "fileDropPlaceholder": "Dialog öffnen oder Dateien via Drag and Drop hinzufügen."
   },
   "comments": {
     "title": "Kommentare",
@@ -97,6 +105,15 @@
     "addNew": "Neuen Kontakt hinzufügen"
   },
   "statementInformationForm": {
+    "sideMenu": {
+      "title": "Informationsdaten bearbeiten",
+      "titleNew": "Stellungnahme anlegen",
+      "backToInbox": "Zurück zum Posteingang",
+      "backToDetails": "Zurück zur Detailansicht",
+      "submit": "Speichern",
+      "submitAndReject": "Negativantwort erstellen",
+      "submitAndComplete": "Informationsdaten festlegen"
+    },
     "title": "Informationsdatensatz bearbeiten",
     "titleNew": "Stellungnahme anlegen",
     "container": {
@@ -111,22 +128,43 @@
       "city": "Ort:",
       "district": "Ortsteil:",
       "typeId": "Art des Vorgangs:"
-    },
-    "submit": "Speichern",
-    "submitAndReject": "Negativantwort erstellen",
-    "submitAndComplete": "Informationsdaten festlegen"
+    }
   },
   "workflowDataForm": {
-    "title": "Workflowdatensatz bearbeiten",
+    "sideMenu": {
+      "title": "Workflowdatensatz bearbeiten",
+      "backToDetails": "Zurück zur Detailansicht",
+      "submit": "Speichern",
+      "submitAndComplete": "Workflowdaten festlegen"
+    },
     "container": {
       "general": "Allgemeine Informationen",
       "inboxAttachments": "Eingangsdokumente",
       "geographicPosition": "Geographische Position",
       "departments": "Betroffene Fachbereiche",
       "linkedIssues": "Verknüpfte Vorgänge"
+    }
+  },
+  "statementEditorForm": {
+    "sideMenu": {
+      "title": "Stellungnahme bearbeiten",
+      "backToDetails": "Zurück zur Detailansicht",
+      "save": "Speichern",
+      "validate": "Eingabe validieren",
+      "compile": "PDF generieren",
+      "continue": "Bearbeitung fortsetzen",
+      "releaseForDepartments": "Für Fachbereiche freigeben",
+      "contribute": "Bearbeitung abschließen",
+      "backToInfoData": "Informationsdaten ergänzen",
+      "backToDepartments": "Nachbearbeitung anstoßen",
+      "finalize": "Stellungnahme finalisieren",
+      "releaseForApproval": "Zur Genehmigung freigeben"
     },
-    "submit": "Speichern",
-    "submitAndComplete": "Workflowdaten festlegen"
+    "container": {
+      "contributionStatus": "Bearbeitungsstatus der Fachbereiche",
+      "draft": "Entwurf der Stellungnahme",
+      "attachments": "Anhänge"
+    }
   },
   "shared": {
     "linkedStatements": {
diff --git a/src/theme/misc/_cursor.theme.scss b/src/theme/misc/_cursor.theme.scss
index da746aa..a914399 100644
--- a/src/theme/misc/_cursor.theme.scss
+++ b/src/theme/misc/_cursor.theme.scss
@@ -30,3 +30,7 @@
     cursor: grabbing !important;
   }
 }
+
+.not-allowed {
+  cursor: not-allowed !important;
+}
diff --git a/src/theme/misc/misc.theme.scss b/src/theme/misc/misc.theme.scss
index 7f8166d..d4ade00 100644
--- a/src/theme/misc/misc.theme.scss
+++ b/src/theme/misc/misc.theme.scss
@@ -33,3 +33,8 @@
   }
 }
 
+.disable {
+  opacity: 0.6;
+  cursor: not-allowed !important;
+}
+
diff --git a/src/theme/user-controls/_button.theme.scss b/src/theme/user-controls/_button.theme.scss
index 2f8ceec..835deb3 100644
--- a/src/theme/user-controls/_button.theme.scss
+++ b/src/theme/user-controls/_button.theme.scss
@@ -35,7 +35,6 @@
   &.openk-button-focus,
   &:focus {
     filter: brightness(100% + 2 * $lightness);
-    box-shadow: 0 0 4px 1px rgba($border, 0.33);
   }
 
   &.openk-button-active,
@@ -122,47 +121,75 @@
   }
 }
 
-.openk-primary.openk-button {
-  @include openk-button-mixin(
-      get-color($openk-primary-palette, 500, contrast),
-      get-color($openk-primary-palette, 500),
-      get-color($openk-primary-palette, 700),
-      12%
-  );
+.openk-primary {
+
+  .openk-button,
+  &.openk-button {
+    @include openk-button-mixin(
+        get-color($openk-primary-palette, 500, contrast),
+        get-color($openk-primary-palette, 500),
+        get-color($openk-primary-palette, 700),
+        12%
+    );
+  }
 }
 
-.openk-info.openk-button {
-  @include openk-button-mixin(
-      get-color($openk-info-palette, 500, contrast),
-      get-color($openk-info-palette, 500),
-      get-color($openk-info-palette, 600),
-      -4%
-  );
+.openk-info {
+
+  .openk-button,
+  &.openk-button {
+    @include openk-button-mixin(
+        get-color($openk-info-palette, 500, contrast),
+        get-color($openk-info-palette, 500),
+        get-color($openk-info-palette, 600),
+        -4%
+    );
+  }
 }
 
-.openk-success.openk-button {
-  @include openk-button-mixin(
-      get-color($openk-success-palette, 500, contrast),
-      get-color($openk-success-palette, 500),
-      get-color($openk-success-palette, 800),
-      -3%
-  );
+.openk-success {
+
+  .openk-button,
+  &.openk-button {
+    @include openk-button-mixin(
+        get-color($openk-success-palette, 500, contrast),
+        get-color($openk-success-palette, 500),
+        get-color($openk-success-palette, 800),
+        -3%
+    );
+  }
 }
 
-.openk-warning.openk-button {
-  @include openk-button-mixin(
-      get-color($openk-warning-palette, 500, contrast),
-      get-color($openk-warning-palette, 500),
-      get-color($openk-warning-palette, 600),
-      -3%
-  );
+.openk-warning {
+
+  .openk-button,
+  &.openk-button {
+    @include openk-button-mixin(
+        get-color($openk-warning-palette, 500, contrast),
+        get-color($openk-warning-palette, 500),
+        get-color($openk-warning-palette, 600),
+        -3%
+    );
+  }
 }
 
-.openk-danger.openk-button {
-  @include openk-button-mixin(
-      get-color($openk-danger-palette, 500, contrast),
-      get-color($openk-danger-palette, 500),
-      get-color($openk-danger-palette, 600),
-      12%
-  );
+.openk-danger {
+
+  .openk-button,
+  &.openk-button {
+    @include openk-button-mixin(
+        get-color($openk-danger-palette, 500, contrast),
+        get-color($openk-danger-palette, 500),
+        get-color($openk-danger-palette, 600),
+        12%
+    );
+  }
 }
+
+.openk-chip {
+  border-radius: 1em;
+  font-size: x-small;
+  font-style: italic;
+  padding: 0.25em 0.5em;
+}
+
diff --git a/src/app/shared/controls/text-field/text-field.component.scss b/src/theme/user-controls/_checkbox.theme.scss
similarity index 80%
copy from src/app/shared/controls/text-field/text-field.component.scss
copy to src/theme/user-controls/_checkbox.theme.scss
index 563859b..77a06de 100644
--- a/src/app/shared/controls/text-field/text-field.component.scss
+++ b/src/theme/user-controls/_checkbox.theme.scss
@@ -11,9 +11,11 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
-.text-input {
-  resize: none;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 2.5em;
+.openk-checkbox {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 1em;
+  width: 1em;
+  height: 1em;
 }
diff --git a/src/theme/user-controls/user-controls.theme.scss b/src/theme/user-controls/user-controls.theme.scss
index 4cf9a29..b939e05 100644
--- a/src/theme/user-controls/user-controls.theme.scss
+++ b/src/theme/user-controls/user-controls.theme.scss
@@ -12,4 +12,5 @@
  ********************************************************************************/
 
 @import "button.theme";
+@import "checkbox.theme";
 @import "input.theme";
diff --git a/webpack.config.js b/webpack.config.js
index 6882933..7b1c12b 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -11,22 +11,14 @@
  * SPDX-License-Identifier: EPL-2.0
  ********************************************************************************/
 
+const path = require("path");
 const LicenseWebpackPlugin = require("license-webpack-plugin").LicenseWebpackPlugin;
 
-function getAdditionalModules() {
-  return [
-    "material-design-icons",
-    "source-sans-pro",
-    "@angular/platform-browser-dynamic"
-  ].map((repositoryName) => {
-    try {
-      return require(repositoryName + "/package.json");
-    } catch (e) {
-      console.warn(e);
-      return null;
-    }
-  }).filter((_) => _ != null);
-}
+const additionalModules = [
+  "material-design-icons",
+  "source-sans-pro",
+  "@angular/platform-browser-dynamic"
+];
 
 function renderLicenses(modules) {
   modules = modules
@@ -37,8 +29,6 @@
       };
     });
 
-  modules.push(...getAdditionalModules());
-
   return modules
     .sort((a, b) => a.name.localeCompare(b.name))
     .reduce((_, module) => {
@@ -57,7 +47,9 @@
       renderLicenses: (modules) => {
         return renderLicenses(modules);
       },
-      outputFilename: "3rd.party.licenses.txt"
+      perChunkOutput: false,
+      additionalModules: additionalModules
+        .map((name) => ({name, directory: path.join(__dirname, "node_modules", name)}))
     })
   ]
 };