From 69dbda6c5e1d43e9b15ded7fe6f3096de009dc35 Mon Sep 17 00:00:00 2001
From: Martin Mechtel <mechtelm@user.hu-berlin.de>
Date: Mon, 8 Oct 2018 15:43:25 +0200
Subject: [PATCH] release 0.7

---
 package-lock.json                            |  10 +-
 src/app/admin/admin.module.ts                |   4 +-
 src/app/admin/backend.service.ts             | 141 ++++++++++++--
 src/app/admin/monitor/monitor.component.css  |  20 +-
 src/app/admin/monitor/monitor.component.html |  81 ++++++--
 src/app/admin/monitor/monitor.component.ts   | 123 ++++++++++--
 src/app/admin/results/results.component.css  |  23 +--
 src/app/admin/results/results.component.html | 120 ++++++------
 src/app/admin/results/results.component.ts   | 189 +++++++++++++++----
 src/environments/environment.build.ts        |   2 +-
 src/environments/environment.ts              |   2 +-
 11 files changed, 545 insertions(+), 170 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 0a01e0da..efc0b6e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3815,7 +3815,7 @@
     },
     "file-saver": {
       "version": "1.3.8",
-      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
+      "resolved": "http://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
       "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg=="
     },
     "file-uri-to-path": {
@@ -4066,12 +4066,14 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -4091,7 +4093,8 @@
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
@@ -4239,6 +4242,7 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts
index 05d3e654..1351c1c2 100644
--- a/src/app/admin/admin.module.ts
+++ b/src/app/admin/admin.module.ts
@@ -1,3 +1,4 @@
+import { FlexLayoutModule } from '@angular/flex-layout';
 import { LoginDialogComponent } from './login-dialog/login-dialog.component';
 import { BackendService } from './backend.service';
 import { IqbFilesModule } from '../iqb-files';
@@ -48,7 +49,8 @@ import {MatGridListModule} from '@angular/material/grid-list';
     MatSnackBarModule,
     MatGridListModule,
     IqbCommonModule,
-    BrowserAnimationsModule
+    BrowserAnimationsModule,
+    FlexLayoutModule
   ],
   exports: [
     AdminComponent
diff --git a/src/app/admin/backend.service.ts b/src/app/admin/backend.service.ts
index 0b2903e7..3d35b089 100644
--- a/src/app/admin/backend.service.ts
+++ b/src/app/admin/backend.service.ts
@@ -127,41 +127,87 @@ export class BackendService {
         );
   }
 
-/*******************************/
+  /*******************************/
+  getBookletsStarted(adminToken: string, workspaceId: number, groups: string[]): Observable<BookletsStarted[]>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
+    return this.http
+      .post<BookletsStarted[]>(this._serverUrl + 'getBookletsStarted.php', {at: adminToken, ws: workspaceId, g: groups}, httpOptions);
+  }
+
+  /*******************************/
+  lockBooklets(adminToken: string, workspaceId: number, groups: string[]): Observable<boolean>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
+    return this.http
+      .post<boolean>(this._serverUrl + 'lockBooklets.php', {at: adminToken, ws: workspaceId, g: groups}, httpOptions);
+  }
 
-  showStats(adminToken: string, workspaceId: number, responseOnly: boolean): Observable<GroupResponse[]>{
+  unlockBooklets(adminToken: string, workspaceId: number, groups: string[]): Observable<boolean>{
     const httpOptions = {
       headers: new HttpHeaders({
         'Content-Type':  'application/json'
       })
     };
     return this.http
-      .post<GroupResponse[]>(this._serverUrl + 'getTestStats.php', {at: adminToken, ws: workspaceId, rso: responseOnly}, httpOptions);
+      .post<boolean>(this._serverUrl + 'unlockBooklets.php', {at: adminToken, ws: workspaceId, g: groups}, httpOptions);
   }
 
-  downloadCSVResponses(adminToken: string, workspaceId: number, groups: GroupResponse[]): Observable<string>{
-    const customHeaders = new HttpHeaders().set('Content-Type', 'application/json');
-    const httpOptions = {headers: customHeaders, responseType: 'text' as 'json'};
+  getMonitorData(adminToken: string, workspaceId: number): Observable<MonitorData[]>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
+    return this.http
+      .post<MonitorData[]>(this._serverUrl + 'getMonitorData.php', {at: adminToken, ws: workspaceId}, httpOptions);
+  }
 
+  getResultData(adminToken: string, workspaceId: number): Observable<ResultData[]>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
     return this.http
-      .post<string>(this._serverUrl + 'getCSVResponses.php', {at: adminToken, ws: workspaceId, groups: groups }, httpOptions);
+      .post<ResultData[]>(this._serverUrl + 'getResultData.php', {at: adminToken, ws: workspaceId}, httpOptions);
   }
 
-  downloadCSVResponses2(adminToken: string, workspaceId: number, groups: GroupResponse[]): void{
-    let url = this._serverUrl;
-    url += 'getCSVResponses.php?';
-    url += 'at=';
-    url += adminToken;
-    url += '&ws=';
-    url += workspaceId;
-    groups.forEach(group => {
-      url += '&groups[]=';
-      url += group;
-    });
-    window.open(url);
+  getResponses(adminToken: string, workspaceId: number, groups: string[]): Observable<UnitResponse[]>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
+    return this.http
+      .post<UnitResponse[]>(this._serverUrl + 'getResponses.php', {at: adminToken, ws: workspaceId, g: groups}, httpOptions);
   }
 
+  getLogs(adminToken: string, workspaceId: number, groups: string[]): Observable<LogData[]>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
+    return this.http
+      .post<LogData[]>(this._serverUrl + 'getLogs.php', {at: adminToken, ws: workspaceId, g: groups}, httpOptions);
+  }
 
+  getReviews(adminToken: string, workspaceId: number, groups: string[]): Observable<ReviewData[]>{
+    const httpOptions = {
+      headers: new HttpHeaders({
+        'Content-Type':  'application/json'
+      })
+    };
+    return this.http
+      .post<ReviewData[]>(this._serverUrl + 'getReviews.php', {at: adminToken, ws: workspaceId, g: groups}, httpOptions);
+  }
 
   // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
   private handleError(errorObj: HttpErrorResponse): Observable<ServerError> {
@@ -221,10 +267,65 @@ export interface CheckWorkspaceResponseData {
 }
 
 
-
 export interface GroupResponse {
   name: string;
   testsTotal: number;
   testsStarted: number;
   responsesGiven: number;
 }
+
+export interface BookletsStarted {
+  groupname: string;
+  loginname: string;
+  code: string;
+  bookletname: string;
+  locked: boolean;
+}
+
+export interface UnitResponse {
+  groupname: string;
+  loginname: string;
+  code: string;
+  bookletname: string;
+  unitname: string;
+  responses: string;
+}
+
+export interface MonitorData {
+  groupname: string;
+  loginsPrepared: number;
+  personsPrepared: number;
+  bookletsPrepared: number;
+  bookletsStarted: number;
+  bookletsLocked: number;
+}
+
+export interface ResultData {
+  groupname: string;
+  bookletsStarted: number;
+  num_units_min: number;
+  num_units_max: number;
+  num_units_mean: number;
+}
+
+export interface LogData {
+  groupname: string;
+  loginname: string;
+  code: string;
+  bookletname: string;
+  unitname: string;
+  logtime: Date;
+  logentry: string;
+}
+
+export interface ReviewData {
+  groupname: string;
+  loginname: string;
+  code: string;
+  bookletname: string;
+  unitname: string;
+  priority: number;
+  categories: string;
+  reviewtime: Date;
+  entry: string;
+}
diff --git a/src/app/admin/monitor/monitor.component.css b/src/app/admin/monitor/monitor.component.css
index 6dba0d90..0e471019 100644
--- a/src/app/admin/monitor/monitor.component.css
+++ b/src/app/admin/monitor/monitor.component.css
@@ -1,12 +1,24 @@
-.mat-card {
+.columnhost {
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: flex-start;
+  justify-content: left;
+}
+/* .mat-card {
   max-width: 50%;
   margin-top: 20px;
 }
 
 .title {
   margin-top: 2550px;
-}
+} */
 
-table {
+/* table {
   width: 100%;
-}
\ No newline at end of file
+} */
+
+.cellstyle1 {
+  padding: 5px;
+}
diff --git a/src/app/admin/monitor/monitor.component.html b/src/app/admin/monitor/monitor.component.html
index d2d21f9d..d823a3a7 100644
--- a/src/app/admin/monitor/monitor.component.html
+++ b/src/app/admin/monitor/monitor.component.html
@@ -1,29 +1,70 @@
-<table mat-table [dataSource]="groupStats" matSort  class="mat-elevation-z8">
+<div class="columnhost" fxLayout="column">
+  <div class="spinner-container" *ngIf="dataLoading">
+    <mat-spinner></mat-spinner>
+  </div>
+  <div fxLayout="row">
+    <button mat-raised-button (click)="downloadCSV()" [disabled]="!tableselectionCheckbox.hasValue()"
+        matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
+      <mat-icon>cloud_download</mat-icon>
+    </button>
+    <button mat-raised-button (click)="lock()" [disabled]="!tableselectionCheckbox.hasValue()"
+        matTooltip="Sperre gestartete Testhefte für markierte Gruppen" matTooltipPosition="above">
+      <mat-icon>lock</mat-icon>
+    </button>
 
-  <ng-container matColumnDef="name">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header>Group Name</th>
-    <td mat-cell *matCellDef="let element">{{element.name}}</td>
-  </ng-container>
+    <button mat-raised-button (click)="unlock()" [disabled]="!tableselectionCheckbox.hasValue()"
+        matTooltip="Gesperrte Testhefte für markierte Gruppen freigeben" matTooltipPosition="above">
+      <mat-icon>lock_open</mat-icon>
+    </button>
+  </div>
 
-  <ng-container matColumnDef="testsTotal">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header>Number of tests from XML</th>
-    <td mat-cell *matCellDef="let element">{{element.testsTotal}} </td>
-  </ng-container>
+  <mat-table [dataSource]="monitorDataSource" matSort>
+    <ng-container matColumnDef="selectCheckbox">
+      <mat-header-cell *matHeaderCellDef fxFlex="70px">
+        <mat-checkbox (change)="$event ? masterToggle() : null"
+                      [checked]="tableselectionCheckbox.hasValue() && isAllSelected()"
+                      [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()">
+        </mat-checkbox>
+      </mat-header-cell>
+      <mat-cell *matCellDef="let row" fxFlex="70px">
+        <mat-checkbox (click)="$event.stopPropagation()"
+                      (change)="$event ? tableselectionCheckbox.toggle(row) : null"
+                      [checked]="tableselectionCheckbox.isSelected(row)">
+        </mat-checkbox>
+      </mat-cell>
+    </ng-container>
 
-  <ng-container matColumnDef="testsStarted">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header> Booklets started / Number of test takers</th>
-    <td mat-cell *matCellDef="let element"> {{element.testsStarted}} </td>
-  </ng-container>
+    <ng-container matColumnDef="groupname">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxFlex="300px">Login-Gruppe</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxFlex="300px">{{element.groupname}}</mat-cell>
+    </ng-container>
 
-  <ng-container matColumnDef="responsesGiven">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header> Responses / Total responses planned </th>
-    <td mat-cell *matCellDef="let element"> {{element.responsesGiven}} </td>
-  </ng-container>
+    <ng-container matColumnDef="loginsPrepared">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Logins</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.loginsPrepared}} </mat-cell>
+    </ng-container>
 
-  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
-  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
-</table>
+    <ng-container matColumnDef="personsPrepared">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Personen</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.personsPrepared}} </mat-cell>
+    </ng-container>
 
+    <ng-container matColumnDef="bookletsPrepared">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Testhefte insges.</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.bookletsPrepared}} </mat-cell>
+    </ng-container>
 
+    <ng-container matColumnDef="bookletsStarted">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Testhefte gestartet</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.bookletsStarted}} </mat-cell>
+    </ng-container>
 
+    <ng-container matColumnDef="bookletsLocked">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">gesperrte Testhefte</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.bookletsLocked }} </mat-cell>
+    </ng-container>
 
+    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
+    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
+  </mat-table>
+</div>
diff --git a/src/app/admin/monitor/monitor.component.ts b/src/app/admin/monitor/monitor.component.ts
index 4361c9f2..ef4630b3 100644
--- a/src/app/admin/monitor/monitor.component.ts
+++ b/src/app/admin/monitor/monitor.component.ts
@@ -1,7 +1,9 @@
-import { BackendService, GroupResponse } from './../backend.service';
+import { BackendService, MonitorData, BookletsStarted } from './../backend.service';
 import { MainDatastoreService } from './../maindatastore.service';
 import { Component, OnInit, ViewChild } from '@angular/core';
 import { MatSnackBar, MatSort, MatTableDataSource } from '@angular/material';
+import { SelectionModel } from '@angular/cdk/collections';
+import { saveAs } from 'file-saver';
 
 
 @Component({
@@ -9,10 +11,13 @@ import { MatSnackBar, MatSort, MatTableDataSource } from '@angular/material';
   styleUrls: ['./monitor.component.css']
 })
 export class MonitorComponent implements OnInit {
-  displayedColumns: string[] = ['name', 'testsTotal', 'testsStarted', 'responsesGiven'];
-  private groupStats = new MatTableDataSource<GroupResponse>([]);
-  private isAdmin = false;
 
+  displayedColumns: string[] = ['selectCheckbox', 'groupname', 'loginsPrepared',
+          'personsPrepared', 'bookletsPrepared', 'bookletsStarted', 'bookletsLocked'];
+  private monitorDataSource = new MatTableDataSource<MonitorData>([]);
+  private isAdmin = false;
+  private tableselectionCheckbox = new SelectionModel<MonitorData>(true, []);
+  private dataLoading = false;
   @ViewChild(MatSort) sort: MatSort;
 
   constructor(
@@ -25,18 +30,114 @@ export class MonitorComponent implements OnInit {
   }
 
   ngOnInit() {
-    this.mds.adminToken$.subscribe(at => this.updateStats());
-    this.mds.workspaceId$.subscribe(ws => this.updateStats());
+    this.mds.adminToken$.subscribe(at => this.updateTable());
+    this.mds.workspaceId$.subscribe(ws => this.updateTable());
   }
 
-  updateStats() {
-    this.bs.showStats(this.mds.adminToken$.getValue(), this.mds.workspaceId$.getValue(), false).subscribe(
-      (responseData: GroupResponse[]) => {
-        this.groupStats = new MatTableDataSource<GroupResponse>(responseData);
-        this.groupStats.sort = this.sort;
+  updateTable() {
+    this.dataLoading = true;
+    this.tableselectionCheckbox.clear;
+    this.bs.getMonitorData(this.mds.adminToken$.getValue(), this.mds.workspaceId$.getValue()).subscribe(
+      (monitorData: MonitorData[]) => {
+        this.dataLoading = false;
+        this.monitorDataSource = new MatTableDataSource<MonitorData>(monitorData);
+        this.monitorDataSource.sort = this.sort;
       }
     )
 
   }
 
+  isAllSelected() {
+    const numSelected = this.tableselectionCheckbox.selected.length;
+    const numRows = this.monitorDataSource.data.length;
+    return numSelected === numRows;
+  }
+
+  masterToggle() {
+    this.isAllSelected() ?
+        this.tableselectionCheckbox.clear() :
+        this.monitorDataSource.data.forEach(row => this.tableselectionCheckbox.select(row));
+  }
+
+  downloadCSV() {
+    if (this.tableselectionCheckbox.selected.length > 0) {
+      this.dataLoading = true;
+      const selectedGroups: string[] = [];
+      this.tableselectionCheckbox.selected.forEach(element => {
+        selectedGroups.push(element.groupname);
+      });
+      this.bs.getBookletsStarted(
+            this.mds.adminToken$.getValue(),
+            this.mds.workspaceId$.getValue(),
+            selectedGroups).subscribe(bData => {
+
+          const bookletList = bData as BookletsStarted[];
+          if (bookletList.length > 0) {
+            const columnDelimiter = ';';
+            const lineDelimiter = '\n';
+
+            let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter +
+                'bookletname' + columnDelimiter + 'locked' + lineDelimiter;
+            bookletList.forEach((b: BookletsStarted) => {
+               myCsvData += '"' + b.groupname + '"' + columnDelimiter + '"' + b.loginname + '"' + columnDelimiter + '"' + b.code + '"' + columnDelimiter +
+                '"' + b.bookletname + '"' + columnDelimiter + '"' + (b.locked ? 'X' : '-') + '"' + lineDelimiter;
+            });
+            var blob = new Blob([myCsvData], {type: "text/csv;charset=utf-8"});
+            saveAs(blob, "iqb-testcenter-bookletsStarted.csv");
+          } else {
+            this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000});
+          }
+
+          this.tableselectionCheckbox.clear();
+          this.dataLoading = false;
+        }
+      );
+    }
+  }
+
+  lock() {
+    if (this.tableselectionCheckbox.selected.length > 0) {
+      this.dataLoading = true;
+      const selectedGroups: string[] = [];
+      this.tableselectionCheckbox.selected.forEach(element => {
+        selectedGroups.push(element.groupname);
+      });
+      this.bs.lockBooklets(
+            this.mds.adminToken$.getValue(),
+            this.mds.workspaceId$.getValue(),
+            selectedGroups).subscribe(success => {
+              const ok = success as boolean;
+              if (ok) {
+                this.snackBar.open('Testhefte wurden gesperrt.', 'Sperrung', {duration: 1000});
+              } else {
+                this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Fehler', {duration: 3000});
+              }
+            });
+            this.dataLoading = false;
+            this.updateTable();
+          }
+  }
+
+  unlock() {
+    if (this.tableselectionCheckbox.selected.length > 0) {
+      this.dataLoading = true;
+      const selectedGroups: string[] = [];
+      this.tableselectionCheckbox.selected.forEach(element => {
+        selectedGroups.push(element.groupname);
+      });
+      this.bs.unlockBooklets(
+            this.mds.adminToken$.getValue(),
+            this.mds.workspaceId$.getValue(),
+            selectedGroups).subscribe(success => {
+              const ok = success as boolean;
+              if (ok) {
+                this.snackBar.open('Testhefte wurden freigegeben.', 'Sperrung', {duration: 1000});
+              } else {
+                this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Fehler', {duration: 3000});
+              }
+            });
+            this.dataLoading = false;
+            this.updateTable();
+          }
+  }
 }
diff --git a/src/app/admin/results/results.component.css b/src/app/admin/results/results.component.css
index 3a8a1b11..3c577b39 100644
--- a/src/app/admin/results/results.component.css
+++ b/src/app/admin/results/results.component.css
@@ -1,17 +1,12 @@
-.mat-card {
-  max-width: 50%;
-  margin-top: 20px;
-}
-
-.title {
-  margin-top: 2550px;
-}
-
-table {
+.columnhost {
   width: 100%;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: flex-start;
+  justify-content: left;
 }
 
-button {
-  margin-top: 10px;
-  padding: 10px;
-}
\ No newline at end of file
+.mat-icon {
+  margin-right: 5px;
+}
diff --git a/src/app/admin/results/results.component.html b/src/app/admin/results/results.component.html
index 1686cf97..b07954ed 100644
--- a/src/app/admin/results/results.component.html
+++ b/src/app/admin/results/results.component.html
@@ -1,56 +1,64 @@
-
-<table mat-table [dataSource]="groupStats" matSort  class="mat-elevation-z8">
-  
-  <ng-container matColumnDef="selectCheckbox">
-    <mat-header-cell *matHeaderCellDef fxFlex="70px">
-      <mat-checkbox (change)="$event ? masterToggle() : null"
-                    [checked]="tableselectionCheckbox.hasValue() && isAllSelected()"
-                    [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()">
-      </mat-checkbox>
-    </mat-header-cell>
-    <mat-cell *matCellDef="let row" fxFlex="70px">
-      <mat-checkbox (click)="$event.stopPropagation()"
-                    (change)="$event ? tableselectionCheckbox.toggle(row) : null"
-                    [checked]="tableselectionCheckbox.isSelected(row)">
-      </mat-checkbox>
-    </mat-cell>
-  </ng-container>
-  
-  <ng-container matColumnDef="name">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header>Group Name</th>
-    <td mat-cell *matCellDef="let element">{{element.name}}</td>
-  </ng-container>
-
-  <ng-container matColumnDef="testsTotal">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header>Number of tests from XML</th>
-    <td mat-cell *matCellDef="let element">{{element.testsTotal}} </td>
-  </ng-container>
-
-  <ng-container matColumnDef="testsStarted">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header> Booklets started / Number of test takers</th>
-    <td mat-cell *matCellDef="let element"> {{element.testsStarted}} </td>
-  </ng-container>
-
-  <ng-container matColumnDef="responsesGiven">
-    <th mat-header-cell *matHeaderCellDef mat-sort-header> Responses / Total responses planned </th>
-    <td mat-cell *matCellDef="let element"> {{element.responsesGiven}} </td>
-  </ng-container>
-
-  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
-  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
-</table>
-<!-- <p>number of selected rows: {{  tableselectionCheckbox.selected.length }}</p> -->
-
-<button mat-button [disabled]="tableselectionCheckbox.selected.length === 0" (click)= "downloadOnClick()">Save Data to CSV</button>
-
-
-
-
-
-
-
-
-
-
-
-
+<div class="columnhost" fxLayout="column">
+  <div class="spinner-container" *ngIf="dataLoading">
+    <mat-spinner></mat-spinner>
+  </div>
+  <div fxLayout="row" fxLayoutGap="10px">
+    <button mat-raised-button (click)="downloadResponsesCSV()" [disabled]="!tableselectionCheckbox.hasValue()"
+        matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
+      <mat-icon>cloud_download</mat-icon>Antworten
+    </button>
+    <button mat-raised-button (click)="downloadLogsCSV()" [disabled]="!tableselectionCheckbox.hasValue()"
+        matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
+      <mat-icon>cloud_download</mat-icon>Logs
+    </button>
+    <button mat-raised-button (click)="downloadReviewsCSV()" [disabled]="!tableselectionCheckbox.hasValue()"
+        matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
+      <mat-icon>cloud_download</mat-icon>Kommentare
+    </button>
+  </div>
+
+  <mat-table [dataSource]="resultDataSource" matSort>
+    <ng-container matColumnDef="selectCheckbox">
+      <mat-header-cell *matHeaderCellDef fxFlex="70px">
+        <mat-checkbox (change)="$event ? masterToggle() : null"
+                      [checked]="tableselectionCheckbox.hasValue() && isAllSelected()"
+                      [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()">
+        </mat-checkbox>
+      </mat-header-cell>
+      <mat-cell *matCellDef="let row" fxFlex="70px">
+        <mat-checkbox (click)="$event.stopPropagation()"
+                      (change)="$event ? tableselectionCheckbox.toggle(row) : null"
+                      [checked]="tableselectionCheckbox.isSelected(row)">
+        </mat-checkbox>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="groupname">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxFlex="300px">Login-Gruppe</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxFlex="300px">{{element.groupname}}</mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="bookletsStarted">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Testhefte gestartet</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.bookletsStarted}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="num_units_min">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Aufgaben min</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_min}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="num_units_max">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Aufgaben max</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_max}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="num_units_mean">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Aufgaben Mittelwert</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_mean | number:'1.1-1'}} </mat-cell>
+    </ng-container>
+
+    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
+    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
+  </mat-table>
+</div>
diff --git a/src/app/admin/results/results.component.ts b/src/app/admin/results/results.component.ts
index 42f3f5f3..48b4b072 100644
--- a/src/app/admin/results/results.component.ts
+++ b/src/app/admin/results/results.component.ts
@@ -1,9 +1,9 @@
 import { Component, OnInit, ViewChild } from '@angular/core';
-import { BackendService, GroupResponse } from './../backend.service';
+import { BackendService, UnitResponse, ResultData, ReviewData, LogData } from './../backend.service';
 import { MainDatastoreService } from './../maindatastore.service';
 import { MatSnackBar, MatSort, MatTableDataSource } from '@angular/material';
 import { SelectionModel } from '@angular/cdk/collections';
-// import { saveAs } from 'file-saver';
+import { saveAs } from 'file-saver';
 
 
 @Component({
@@ -11,11 +11,12 @@ import { SelectionModel } from '@angular/cdk/collections';
   styleUrls: ['./results.component.css']
 })
 export class ResultsComponent implements OnInit {
-  displayedColumns: string[] = ['selectCheckbox', 'name', 'testsTotal', 'testsStarted', 'responsesGiven'];
-  private groupStats = new MatTableDataSource<GroupResponse>([]);
+  displayedColumns: string[] = ['selectCheckbox', 'groupname', 'bookletsStarted', 'num_units_min', 'num_units_max', 'num_units_mean'];
+  private resultDataSource = new MatTableDataSource<ResultData>([]);
   private isAdmin = false;
-  private downloadButton = Object;
-  private tableselectionCheckbox = new SelectionModel <GroupResponse>(true, []);
+  // prepared for selection if needed sometime
+  private tableselectionCheckbox = new SelectionModel<ResultData>(true, []);
+  private dataLoading = false;
 
   @ViewChild(MatSort) sort: MatSort;
 
@@ -29,57 +30,167 @@ export class ResultsComponent implements OnInit {
   }
 
   ngOnInit() {
-    this.mds.adminToken$.subscribe(at => this.updateStats());
-    this.mds.workspaceId$.subscribe(ws => this.updateStats());
+    this.mds.adminToken$.subscribe(at => this.updateTable());
+    this.mds.workspaceId$.subscribe(ws => this.updateTable());
     // console.log(saveAs);
   }
 
-  updateStats() {
-    this.bs.showStats(this.mds.adminToken$.getValue(), this.mds.workspaceId$.getValue(), true).subscribe(
-      (responseData: GroupResponse[]) => {
-        this.groupStats = new MatTableDataSource<GroupResponse>(responseData);
-        this.groupStats.sort = this.sort;
+  updateTable() {
+    this.dataLoading = true;
+    this.tableselectionCheckbox.clear();
+    this.bs.getResultData(this.mds.adminToken$.getValue(), this.mds.workspaceId$.getValue()).subscribe(
+      (resultData: ResultData[]) => {
+        this.dataLoading = false;
+        this.resultDataSource = new MatTableDataSource<ResultData>(resultData);
+        this.resultDataSource.sort = this.sort;
       }
     )
   }
 
   isAllSelected() {
     const numSelected = this.tableselectionCheckbox.selected.length;
-    const numRows = this.groupStats.data.length;
+    const numRows = this.resultDataSource.data.length;
     return numSelected === numRows;
   }
 
   masterToggle() {
     this.isAllSelected() ?
         this.tableselectionCheckbox.clear() :
-        this.groupStats.data.forEach(row => this.tableselectionCheckbox.select(row));
+        this.resultDataSource.data.forEach(row => this.tableselectionCheckbox.select(row));
   }
 
-  downloadOnClick() {
-    // let observableDlResponse = this.bs.downloadCSVResponses(this.mds.adminToken$.getValue(), this.mds.workspaceId$.getValue(), this.tableselectionCheckbox.selected);
-
-
-    // observableDlResponse.subscribe(
-    //   (responseData: string) => {
-    //     let savedFileContent = responseData;
-
-    //     // this.groupStats = new MatTableDataSource<GroupResponse>(responseData);
-    //     // this.groupStats.sort = this.sort;
-
-    //     //
-    //     const saveFile = new File([savedFileContent], 'responses.csv', { type: 'text/plain;charset=utf-8' });
-    //     saveAs(saveFile);
-    //   }
-    // )
-
-    // create an array with groupIds that you get with tableselectionCheckbox
-    let processedSelection = [];
-    this.tableselectionCheckbox.selected.forEach(element => {
-      processedSelection.push(element.name);
-    });
-
-    this.bs.downloadCSVResponses2(this.mds.adminToken$.getValue(), this.mds.workspaceId$.getValue(), processedSelection);
+  // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
+  downloadResponsesCSV() {
+    if (this.tableselectionCheckbox.selected.length > 0) {
+      this.dataLoading = true;
+      const selectedGroups: string[] = [];
+      this.tableselectionCheckbox.selected.forEach(element => {
+        selectedGroups.push(element.groupname);
+      });
+      this.bs.getResponses(
+            this.mds.adminToken$.getValue(),
+            this.mds.workspaceId$.getValue(),
+            selectedGroups).subscribe(
+      (responseData: UnitResponse[]) => {
+        if (responseData.length > 0) {
+          const columnDelimiter = ';';
+          const lineDelimiter = '\n';
+          let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter +
+              'bookletname' + columnDelimiter + 'unitname' + columnDelimiter + 'responses' + lineDelimiter;
+          responseData.forEach((resp: UnitResponse) => {
+            if ((resp.responses !== null) && (resp.responses.length > 0)) {
+             myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + columnDelimiter + '"' + resp.code + '"' + columnDelimiter +
+              '"' + resp.bookletname + '"' + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter + resp.responses.replace(/\\"/g, '""') + lineDelimiter;
+            }
+          });
+          var blob = new Blob([myCsvData], {type: "text/csv;charset=utf-8"});
+          saveAs(blob, "iqb-testcenter-responses.csv");
+        } else {
+          this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000});
+        }
+        this.tableselectionCheckbox.clear();
+        this.dataLoading = false;
+    })
+    }
+  }
 
+  // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
+  downloadReviewsCSV() {
+    if (this.tableselectionCheckbox.selected.length > 0) {
+      this.dataLoading = true;
+      const selectedGroups: string[] = [];
+      this.tableselectionCheckbox.selected.forEach(element => {
+        selectedGroups.push(element.groupname);
+      });
+      this.bs.getReviews(
+            this.mds.adminToken$.getValue(),
+            this.mds.workspaceId$.getValue(),
+            selectedGroups).subscribe(
+      (responseData: ReviewData[]) => {
+        if (responseData.length > 0) {
+          // collect categories
+          const allCategories: string[] = [];
+          responseData.forEach((resp: ReviewData) => {
+            resp.categories.split(' ').forEach(s => {
+              const s_trimmed = s.trim();
+              if (s_trimmed.length > 0) {
+                if (!allCategories.includes(s_trimmed)) {
+                  allCategories.push(s_trimmed);
+                }
+              }
+            })
+          });
+
+          const columnDelimiter = ';';
+          const lineDelimiter = '\n';
+          let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter +
+              'bookletname' + columnDelimiter + 'unitname' + columnDelimiter +
+              'priority' + columnDelimiter;
+          allCategories.forEach(s => {
+            myCsvData += 'category: ' + s + columnDelimiter;
+          });
+          myCsvData += 'reviewtime' + columnDelimiter + 'entry' + lineDelimiter;
+
+          responseData.forEach((resp: ReviewData) => {
+            if ((resp.entry !== null) && (resp.entry.length > 0)) {
+              myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + columnDelimiter + '"' + resp.code + '"' + columnDelimiter +
+                '"' + resp.bookletname + '"' + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter  + '"' +  resp.priority  + '"' + columnDelimiter;
+              const resp_categories = resp.categories.split(' ');
+              allCategories.forEach(s => {
+                if (resp_categories.includes(s)) {
+                  myCsvData += '"X"' + columnDelimiter;
+                } else {
+                  myCsvData += columnDelimiter;
+                }
+              });
+              myCsvData += '"' + resp.reviewtime + '"' + columnDelimiter  + '"' +  resp.entry  + '"' + lineDelimiter;
+            }
+          });
+          var blob = new Blob([myCsvData], {type: "text/csv;charset=utf-8"});
+          saveAs(blob, "iqb-testcenter-reviews.csv");
+        } else {
+          this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000});
+        }
+        this.tableselectionCheckbox.clear();
+        this.dataLoading = false;
+      })
+    }
   }
 
+  // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
+  downloadLogsCSV() {
+    if (this.tableselectionCheckbox.selected.length > 0) {
+      this.dataLoading = true;
+      const selectedGroups: string[] = [];
+      this.tableselectionCheckbox.selected.forEach(element => {
+        selectedGroups.push(element.groupname);
+      });
+      this.bs.getLogs(
+            this.mds.adminToken$.getValue(),
+            this.mds.workspaceId$.getValue(),
+            selectedGroups).subscribe(
+      (responseData: LogData[]) => {
+        if (responseData.length > 0) {
+          const columnDelimiter = ';';
+          const lineDelimiter = '\n';
+          let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter +
+              'bookletname' + columnDelimiter + 'unitname' + columnDelimiter +
+              'logtime' + columnDelimiter + 'logentry' + lineDelimiter;
+          responseData.forEach((resp: LogData) => {
+            if ((resp.logentry !== null) && (resp.logentry.length > 0)) {
+             myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + columnDelimiter + '"' + resp.code + '"' + columnDelimiter +
+              '"' + resp.bookletname + '"' + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter  + '"' +
+              resp.logtime + '"' + columnDelimiter  + resp.logentry.replace(/\\"/g, '""')  + lineDelimiter;
+            }
+          });
+          var blob = new Blob([myCsvData], {type: "text/csv;charset=utf-8"});
+          saveAs(blob, "iqb-testcenter-logs.csv");
+        } else {
+          this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000});
+        }
+        this.tableselectionCheckbox.clear();
+        this.dataLoading = false;
+      })
+    }
+  }
 }
diff --git a/src/environments/environment.build.ts b/src/environments/environment.build.ts
index fd6970bd..7c7fd335 100644
--- a/src/environments/environment.build.ts
+++ b/src/environments/environment.build.ts
@@ -5,5 +5,5 @@ export const environment = {
   testcenterUrl: '/',
   appName: 'IQB-Testcenter Verwaltung',
   appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen',
-  appVersion: '0.5 - 27.9.2018'
+  appVersion: '0.7 - 8.10.2018'
 };
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 9a83fe20..6b893b1a 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -5,7 +5,7 @@
 
 export const environment = {
   production: false,
-  testcenterUrl: 'https://ocba.iqb.hu-berlin.de/',
+  testcenterUrl: 'https://www.iqb-testcenter.de/',
   appName: 'IQB-Testcenter Verwaltung',
   appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen',
   appVersion: '0.5 (dev)'
-- 
GitLab