From bb72f9e78b8488b214f211aaadde010ce8454ce2 Mon Sep 17 00:00:00 2001 From: mechtelm <nicht@mehr.fragen> Date: Mon, 8 Apr 2019 15:25:42 +0200 Subject: [PATCH] workspace admin seems ok --- src/app/app-routing.module.ts | 2 +- src/app/maindata.service.ts | 30 ++-- src/app/start/start.component.ts | 6 +- src/app/workspace/backend.service.ts | 93 +++---------- src/app/workspace/files/files.component.css | 5 + src/app/workspace/files/files.component.html | 8 +- src/app/workspace/files/files.component.ts | 129 +++++++++--------- .../workspace/monitor/monitor.component.css | 5 + .../workspace/monitor/monitor.component.html | 9 +- .../workspace/monitor/monitor.component.ts | 72 ++++++---- .../workspace/results/results.component.css | 13 +- .../workspace/results/results.component.html | 9 +- .../workspace/results/results.component.ts | 43 ++++-- src/app/workspace/workspace-routing.module.ts | 4 +- src/app/workspace/workspace.component.html | 12 +- src/app/workspace/workspace.component.ts | 22 +-- src/app/workspace/workspace.interfaces.ts | 4 + src/app/workspace/workspacedata.service.ts | 47 ++++++- 18 files changed, 283 insertions(+), 230 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index e4c0b4dd..97b8c11a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -15,7 +15,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})], exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/src/app/maindata.service.ts b/src/app/maindata.service.ts index 70561232..acce73d4 100644 --- a/src/app/maindata.service.ts +++ b/src/app/maindata.service.ts @@ -46,6 +46,8 @@ export class MainDataService { myLoginData.name = logindata.name; myLoginData.workspaces = logindata.workspaces; myLoginData.is_superadmin = logindata.is_superadmin; + console.log('fü33 ' + myLoginData.name); + } } this.loginData$.next(myLoginData); @@ -60,12 +62,14 @@ export class MainDataService { // $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ getWorkspaceName(ws: number): string { let myreturn = ''; - const myLoginData = this.loginData$.getValue(); - if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) { - for (let i = 0; i < myLoginData.workspaces.length; i++) { - if (myLoginData.workspaces[i].id == ws) { - myreturn = myLoginData.workspaces[i].name; - break; + if (ws > 0) { + const myLoginData = this.loginData$.getValue(); + if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) { + for (let i = 0; i < myLoginData.workspaces.length; i++) { + if (myLoginData.workspaces[i].id == ws) { + myreturn = myLoginData.workspaces[i].name; + break; + } } } } @@ -75,12 +79,14 @@ export class MainDataService { // $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ getWorkspaceRole(ws: number): string { let myreturn = ''; - const myLoginData = this.loginData$.getValue(); - if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) { - for (let i = 0; i < myLoginData.workspaces.length; i++) { - if (myLoginData.workspaces[i].id == ws) { - myreturn = myLoginData.workspaces[i].role; - break; + if (ws > 0) { + const myLoginData = this.loginData$.getValue(); + if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) { + for (let i = 0; i < myLoginData.workspaces.length; i++) { + if (myLoginData.workspaces[i].id == ws) { + myreturn = myLoginData.workspaces[i].role; + break; + } } } } diff --git a/src/app/start/start.component.ts b/src/app/start/start.component.ts index 08b7fb9c..2192559d 100644 --- a/src/app/start/start.component.ts +++ b/src/app/start/start.component.ts @@ -49,7 +49,11 @@ export class StartComponent implements OnInit, OnDestroy { } buttonGotoWorkspace(ws: WorkspaceData) { - this.router.navigateByUrl('/ws/' + ws.id.toString()); + if (ws.role === 'MO') { + this.router.navigateByUrl('/ws/' + ws.id.toString() + '/monitor'); + } else { + this.router.navigateByUrl('/ws/' + ws.id.toString() + '/files'); + } } // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % diff --git a/src/app/workspace/backend.service.ts b/src/app/workspace/backend.service.ts index e56ca36f..9d62ebf5 100644 --- a/src/app/workspace/backend.service.ts +++ b/src/app/workspace/backend.service.ts @@ -14,7 +14,7 @@ export class BackendService { @Inject('SERVER_URL') private serverUrl: string, private http: HttpClient) { this.serverUrlSlim = this.serverUrl + 'php/ws.php/' - this.serverUrl = this.serverUrl + 'php_admin/'; + this.serverUrl = this.serverUrl + 'php/'; } @@ -28,15 +28,9 @@ export class BackendService { } // ******************************************************************* - // Fehlerbehandlung beim Aufrufer deleteFiles(filesToDelete: Array<string>): Observable<string | ServerError> { - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; return this.http - .post<string>(this.serverUrl + 'deleteFiles.php', {f: filesToDelete}, httpOptions) + .post<string>(this.serverUrlSlim + 'delete', {f: filesToDelete}) .pipe( catchError(ErrorHandler.handle) ); @@ -44,13 +38,8 @@ export class BackendService { // ******************************************************************* checkWorkspace(): Observable<CheckWorkspaceResponseData | ServerError> { - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; return this.http - .post<CheckWorkspaceResponseData>(this.serverUrl + 'checkWorkspace.php', {}, httpOptions) + .post<CheckWorkspaceResponseData>(this.serverUrl + 'checkWorkspace.php', {}) .pipe( catchError(ErrorHandler.handle) ); @@ -58,13 +47,8 @@ export class BackendService { /*******************************/ getBookletsStarted(groups: string[]): Observable<BookletsStarted[] | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; return this.http - .post<BookletsStarted[]>(this.serverUrl + 'getBookletsStarted.php', {g: groups}, httpOptions) + .post<BookletsStarted[]>(this.serverUrl + 'getBookletsStarted.php', {g: groups}) .pipe( catchError(ErrorHandler.handle) ); @@ -72,13 +56,8 @@ export class BackendService { /*******************************/ lockBooklets(groups: string[]): Observable<boolean | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; return this.http - .post<boolean>(this.serverUrl + 'lockBooklets.php', {g: groups}, httpOptions) + .post<boolean>(this.serverUrlSlim + 'lock', {g: groups}) .pipe( catchError(ErrorHandler.handle) ); @@ -86,14 +65,9 @@ export class BackendService { /*******************************/ unlockBooklets(groups: string[]): Observable<boolean | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; return this.http - .post<boolean>(this.serverUrl + 'unlockBooklets.php', {g: groups}, httpOptions) - .pipe( + .post<boolean>(this.serverUrlSlim + 'unlock', {g: groups}) + .pipe( catchError(ErrorHandler.handle) ); } @@ -113,70 +87,45 @@ export class BackendService { } /*******************************/ - getResultData(): Observable<ResultData[] | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; + getResultData(): Observable<ResultData[]>{ return this.http - .post<ResultData[]>(this.serverUrl + 'getResultData.php', {}, httpOptions) + .post<ResultData[]>(this.serverUrl + 'getResultData.php', {}) .pipe( - catchError(ErrorHandler.handle) + catchError(err => []) ); } /*******************************/ - getResponses(groups: string[]): Observable<UnitResponse[] | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; + getResponses(groups: string[]): Observable<UnitResponse[]>{ return this.http - .post<UnitResponse[]>(this.serverUrl + 'getResponses.php', {g: groups}, httpOptions) + .post<UnitResponse[]>(this.serverUrl + 'getResponses.php', {g: groups}) .pipe( - catchError(ErrorHandler.handle) + catchError(err => []) ); -} + } /*******************************/ - getLogs(groups: string[]): Observable<LogData[] | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; + getLogs(groups: string[]): Observable<LogData[]>{ return this.http - .post<LogData[]>(this.serverUrl + 'getLogs.php', {g: groups}, httpOptions) + .post<LogData[]>(this.serverUrl + 'getLogs.php', {g: groups}) .pipe( - catchError(ErrorHandler.handle) + catchError(err => []) ); } /*******************************/ - getReviews(groups: string[]): Observable<ReviewData[] | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; + getReviews(groups: string[]): Observable<ReviewData[]>{ return this.http - .post<ReviewData[]>(this.serverUrl + 'getReviews.php', {g: groups}, httpOptions) + .post<ReviewData[]>(this.serverUrl + 'getReviews.php', {g: groups}) .pipe( - catchError(ErrorHandler.handle) + catchError(err => []) ); } /*******************************/ deleteData(groups: string[]): Observable<boolean | ServerError>{ - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }; return this.http - .post<boolean>(this.serverUrl + 'deleteData.php', {g: groups}, httpOptions) + .post<boolean>(this.serverUrl + 'deleteData.php', {g: groups}) .pipe( catchError(ErrorHandler.handle) ); diff --git a/src/app/workspace/files/files.component.css b/src/app/workspace/files/files.component.css index ee1e74d5..58d6fe85 100644 --- a/src/app/workspace/files/files.component.css +++ b/src/app/workspace/files/files.component.css @@ -43,3 +43,8 @@ .checkinfo { color: darkgreen; } + +.mat-raised-button { + min-width: 100px; + margin: 2px; +} \ No newline at end of file diff --git a/src/app/workspace/files/files.component.html b/src/app/workspace/files/files.component.html index a4989ec2..d611835a 100644 --- a/src/app/workspace/files/files.component.html +++ b/src/app/workspace/files/files.component.html @@ -43,10 +43,10 @@ <!-- ============================================= --> <div class="uploads"> - <button mat-raised-button (click)="deleteFiles()" matTooltip="Markierte Dateien löschen" matTooltipPosition="above"> + <button mat-raised-button (click)="deleteFiles()" matTooltip="Markierte Dateien löschen" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'"> <mat-icon>delete</mat-icon> </button> - <button mat-raised-button (click)="hiddenfileinput.click()" matTooltip="Dateien hochladen/aktualisieren" matTooltipPosition="above"> + <button mat-raised-button (click)="hiddenfileinput.click()" matTooltip="Dateien hochladen/aktualisieren" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'"> <mat-icon>cloud_upload</mat-icon> </button> <button mat-raised-button (click)="checkWorkspace()" matTooltip="Arbeitsbereich prüfen" matTooltipPosition="above"> @@ -59,9 +59,9 @@ [httpUrl]="uploadUrl" [fileAlias]="fileNameAlias" [tokenName]="'at'" - [token]="token" + [token]="'kisduUjjw.;kiskw..9200'" [folderName]="'ws'" - [folder]="workspace" + [folder]="'workspace'" (uploadCompleteEvent)="updateFileList()"> </iqb-files-upload-queue> diff --git a/src/app/workspace/files/files.component.ts b/src/app/workspace/files/files.component.ts index e9350259..f3a49d04 100644 --- a/src/app/workspace/files/files.component.ts +++ b/src/app/workspace/files/files.component.ts @@ -24,10 +24,12 @@ import { IqbFilesUploadQueueComponent, IqbFilesUploadInputForDirective } from '. export class FilesComponent implements OnInit, OnDestroy { public serverfiles: MatTableDataSource<GetFileResponseData>; public displayedColumns = ['checked', 'filename', 'typelabel', 'filesize', 'filedatetime']; + public dataLoading = false; + private workspaceIdSubscription: Subscription = null; + + // for fileupload public uploadUrl = ''; public fileNameAlias = 'fileforvo'; - public dataLoading = false; - private logindataSubscription: Subscription = null; // for workspace-check public checkErrors = []; @@ -45,14 +47,12 @@ export class FilesComponent implements OnInit, OnDestroy { public messsageDialog: MatDialog, public snackBar: MatSnackBar ) { - this.uploadUrl = this.serverUrl + 'php_admin/uploadFile.php'; + this.uploadUrl = this.serverUrl + 'php/uploadFile.php'; } ngOnInit() { - this.logindataSubscription = this.mds.loginData$.subscribe(ld => { - const ws = this.wds.ws; - let at = ld ? ld.admintoken : ''; - this.updateFileList((ws <= 0) || (at.length === 0)); + this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(ws => { + this.updateFileList((this.wds.ws <= 0) || (this.mds.adminToken.length === 0)); }); } @@ -65,65 +65,66 @@ export class FilesComponent implements OnInit, OnDestroy { // *********************************************************************************** deleteFiles() { - this.checkErrors = []; - this.checkWarnings = []; - this.checkInfos = []; - - const filesToDelete = []; - this.serverfiles.data.forEach(element => { - if (element.isChecked) { - filesToDelete.push(element.type + '::' + element.filename); - } - }); - - if (filesToDelete.length > 0) { - let prompt = 'Sie haben '; - if (filesToDelete.length > 1) { - prompt = prompt + filesToDelete.length + ' Dateien ausgewählt. Sollen'; - } else { - prompt = prompt + ' eine Datei ausgewählt. Soll'; - } - const dialogRef = this.confirmDialog.open(ConfirmDialogComponent, { - width: '400px', - data: <ConfirmDialogData>{ - title: 'Löschen von Dateien', - content: prompt + ' diese gelöscht werden?', - confirmbuttonlabel: 'Löschen' + if (this.wds.wsRole === 'RW') { + this.checkErrors = []; + this.checkWarnings = []; + this.checkInfos = []; + + const filesToDelete = []; + this.serverfiles.data.forEach(element => { + if (element.isChecked) { + filesToDelete.push(element.type + '::' + element.filename); } }); - dialogRef.afterClosed().subscribe(result => { - if (result !== false) { - // ========================================================= - this.dataLoading = true; - this.bs.deleteFiles(filesToDelete).subscribe(deletefilesresponse => { - if (deletefilesresponse instanceof ServerError) { - this.wds.setNewErrorMsg(deletefilesresponse as ServerError); - } else { - const deletefilesresponseOk = deletefilesresponse as string; - if ((deletefilesresponseOk.length > 5) && (deletefilesresponseOk.substr(0, 2) === 'e:')) { - this.snackBar.open(deletefilesresponseOk.substr(2), 'Fehler', {duration: 1000}); + if (filesToDelete.length > 0) { + let prompt = 'Sie haben '; + if (filesToDelete.length > 1) { + prompt = prompt + filesToDelete.length + ' Dateien ausgewählt. Sollen'; + } else { + prompt = prompt + ' eine Datei ausgewählt. Soll'; + } + const dialogRef = this.confirmDialog.open(ConfirmDialogComponent, { + width: '400px', + data: <ConfirmDialogData>{ + title: 'Löschen von Dateien', + content: prompt + ' diese gelöscht werden?', + confirmbuttonlabel: 'Löschen' + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result !== false) { + // ========================================================= + this.dataLoading = true; + this.bs.deleteFiles(filesToDelete).subscribe(deletefilesresponse => { + if (deletefilesresponse instanceof ServerError) { + this.wds.setNewErrorMsg(deletefilesresponse as ServerError); } else { - this.snackBar.open(deletefilesresponseOk, '', {duration: 1000}); - this.updateFileList(); + const deletefilesresponseOk = deletefilesresponse as string; + if ((deletefilesresponseOk.length > 5) && (deletefilesresponseOk.substr(0, 2) === 'e:')) { + this.snackBar.open(deletefilesresponseOk.substr(2), 'Fehler', {duration: 1000}); + } else { + this.snackBar.open(deletefilesresponseOk, '', {duration: 1000}); + this.updateFileList(); + } + this.wds.setNewErrorMsg(); } - this.wds.setNewErrorMsg(); - } - }); - // ========================================================= - } - }); - } else { - this.messsageDialog.open(MessageDialogComponent, { - width: '400px', - data: <MessageDialogData>{ - title: 'Löschen von Dateien', - content: 'Bitte markieren Sie erst Dateien!', - type: MessageType.error - } - }); + }); + // ========================================================= + } + }); + } else { + this.messsageDialog.open(MessageDialogComponent, { + width: '400px', + data: <MessageDialogData>{ + title: 'Löschen von Dateien', + content: 'Bitte markieren Sie erst Dateien!', + type: MessageType.error + } + }); + } } - } // *********************************************************************************** @@ -132,7 +133,7 @@ export class FilesComponent implements OnInit, OnDestroy { this.checkWarnings = []; this.checkInfos = []; - if (empty) { + if (empty || this.wds.wsRole === 'MO') { this.serverfiles = new MatTableDataSource([]); } else { this.dataLoading = true; @@ -167,8 +168,6 @@ export class FilesComponent implements OnInit, OnDestroy { this.dataLoading = true; this.bs.checkWorkspace().subscribe( (checkResponse: CheckWorkspaceResponseData) => { - // this.serverfiles = new MatTableDataSource(filedataresponse); - // this.serverfiles.sort = this.sort; this.checkErrors = checkResponse.errors; this.checkWarnings = checkResponse.warnings; this.checkInfos = checkResponse.infos; @@ -184,8 +183,8 @@ export class FilesComponent implements OnInit, OnDestroy { // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % ngOnDestroy() { - if (this.logindataSubscription !== null) { - this.logindataSubscription.unsubscribe(); + if (this.workspaceIdSubscription !== null) { + this.workspaceIdSubscription.unsubscribe(); } } } diff --git a/src/app/workspace/monitor/monitor.component.css b/src/app/workspace/monitor/monitor.component.css index 0e471019..1c9da90e 100644 --- a/src/app/workspace/monitor/monitor.component.css +++ b/src/app/workspace/monitor/monitor.component.css @@ -22,3 +22,8 @@ .cellstyle1 { padding: 5px; } + +.mat-raised-button { + margin: 5px; + min-width: 100px; +} \ No newline at end of file diff --git a/src/app/workspace/monitor/monitor.component.html b/src/app/workspace/monitor/monitor.component.html index d823a3a7..6fbe2f3a 100644 --- a/src/app/workspace/monitor/monitor.component.html +++ b/src/app/workspace/monitor/monitor.component.html @@ -7,12 +7,12 @@ 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()" + <button mat-raised-button (click)="lock()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')" matTooltip="Sperre gestartete Testhefte für markierte Gruppen" matTooltipPosition="above"> <mat-icon>lock</mat-icon> </button> - <button mat-raised-button (click)="unlock()" [disabled]="!tableselectionCheckbox.hasValue()" + <button mat-raised-button (click)="unlock()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')" matTooltip="Gesperrte Testhefte für markierte Gruppen freigeben" matTooltipPosition="above"> <mat-icon>lock_open</mat-icon> </button> @@ -64,6 +64,11 @@ <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.bookletsLocked }} </mat-cell> </ng-container> + <ng-container matColumnDef="laststart"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Letzter Start</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.laststartStr }} </mat-cell> + </ng-container> + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> diff --git a/src/app/workspace/monitor/monitor.component.ts b/src/app/workspace/monitor/monitor.component.ts index 73cd8731..ffbbc201 100644 --- a/src/app/workspace/monitor/monitor.component.ts +++ b/src/app/workspace/monitor/monitor.component.ts @@ -1,23 +1,26 @@ import { BookletsStarted } from './../workspace.interfaces'; import { WorkspaceDataService } from './../workspacedata.service'; import { BackendService } from './../backend.service'; -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; import { MatSnackBar, MatSort, MatTableDataSource } from '@angular/material'; import { SelectionModel } from '@angular/cdk/collections'; import { saveAs } from 'file-saver'; import { MonitorData } from '../workspace.interfaces'; +import { Subscription } from 'rxjs'; +import { ServerError } from 'src/app/backend.service'; @Component({ templateUrl: './monitor.component.html', styleUrls: ['./monitor.component.css'] }) -export class MonitorComponent implements OnInit { +export class MonitorComponent implements OnInit, OnDestroy { displayedColumns: string[] = ['selectCheckbox', 'groupname', 'loginsPrepared', - 'personsPrepared', 'bookletsPrepared', 'bookletsStarted', 'bookletsLocked']; + 'personsPrepared', 'bookletsPrepared', 'bookletsStarted', 'bookletsLocked', 'laststart']; private monitorDataSource = new MatTableDataSource<MonitorData>([]); private tableselectionCheckbox = new SelectionModel<MonitorData>(true, []); + private workspaceIdSubscription: Subscription = null; private dataLoading = false; @ViewChild(MatSort) sort: MatSort; @@ -28,12 +31,14 @@ export class MonitorComponent implements OnInit { ) { } ngOnInit() { - this.updateTable(); + this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(ws => { + this.updateTable(); + }); } updateTable() { this.dataLoading = true; - this.tableselectionCheckbox.clear; + this.tableselectionCheckbox.clear(); this.bs.getMonitorData().subscribe( (monitorData: MonitorData[]) => { this.dataLoading = false; @@ -71,10 +76,10 @@ export class MonitorComponent implements OnInit { const lineDelimiter = '\n'; let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + - 'bookletname' + columnDelimiter + 'locked' + lineDelimiter; + 'bookletname' + columnDelimiter + 'locked' + columnDelimiter + 'laststart' + lineDelimiter; bookletList.forEach((b: BookletsStarted) => { myCsvData += '"' + b.groupname + '"' + columnDelimiter + '"' + b.loginname + '"' + columnDelimiter + '"' + b.code + '"' + columnDelimiter + - '"' + b.bookletname + '"' + columnDelimiter + '"' + (b.locked ? 'X' : '-') + '"' + lineDelimiter; + '"' + b.bookletname + '"' + columnDelimiter + '"' + (b.locked ? 'X' : '-') + '"' + columnDelimiter + '"' + b.laststart + '"' + lineDelimiter; }); var blob = new Blob([myCsvData], {type: "text/csv;charset=utf-8"}); saveAs(blob, "iqb-testcenter-bookletsStarted.csv"); @@ -97,16 +102,21 @@ export class MonitorComponent implements OnInit { selectedGroups.push(element.groupname); }); this.bs.lockBooklets(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(); + if (success instanceof ServerError) { + this.wds.setNewErrorMsg(success as ServerError); + this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Systemfehler', {duration: 3000}); + } else { + 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() { @@ -117,15 +127,27 @@ export class MonitorComponent implements OnInit { selectedGroups.push(element.groupname); }); this.bs.unlockBooklets(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(); + if (success instanceof ServerError) { + this.wds.setNewErrorMsg(success as ServerError); + this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Systemfehler', {duration: 3000}); + } else { + 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(); + }); + } + } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.workspaceIdSubscription !== null) { + this.workspaceIdSubscription.unsubscribe(); + } } } diff --git a/src/app/workspace/results/results.component.css b/src/app/workspace/results/results.component.css index 3c577b39..e43a89f4 100644 --- a/src/app/workspace/results/results.component.css +++ b/src/app/workspace/results/results.component.css @@ -1,12 +1,17 @@ -.columnhost { +/* .columnhost { width: 100%; display: flex; flex-direction: row; flex-wrap: wrap; align-items: flex-start; justify-content: left; -} +} */ -.mat-icon { +/* .mat-icon { margin-right: 5px; -} +} */ + +.mat-raised-button { + min-width: 100px; + margin: 2px; +} \ No newline at end of file diff --git a/src/app/workspace/results/results.component.html b/src/app/workspace/results/results.component.html index c28f9545..5ac0ebf4 100644 --- a/src/app/workspace/results/results.component.html +++ b/src/app/workspace/results/results.component.html @@ -2,7 +2,7 @@ <div class="spinner-container" *ngIf="dataLoading"> <mat-spinner></mat-spinner> </div> - <div fxLayout="row" fxLayoutGap="10px"> + <div fxLayout="row"> <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 @@ -15,7 +15,7 @@ matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above"> <mat-icon>cloud_download</mat-icon>Kommentare </button> - <button mat-raised-button (click)="deleteData()" [disabled]="!tableselectionCheckbox.hasValue()" + <button mat-raised-button (click)="deleteData()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')" matTooltip="Löschen Ergebnisdaten aus der Datenbank für markierte Gruppen" matTooltipPosition="above"> <mat-icon>delete</mat-icon> </button> @@ -62,6 +62,11 @@ <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_mean | number:'1.1-1'}} </mat-cell> </ng-container> + <ng-container matColumnDef="lastchange"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Letzte Änderung</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.lastchange | date:'dd.MM.yyyy HH:mm'}} </mat-cell> + </ng-container> + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> diff --git a/src/app/workspace/results/results.component.ts b/src/app/workspace/results/results.component.ts index e813e918..5b85a8e8 100644 --- a/src/app/workspace/results/results.component.ts +++ b/src/app/workspace/results/results.component.ts @@ -1,24 +1,26 @@ import { LogData } from './../workspace.interfaces'; import { WorkspaceDataService } from './../workspacedata.service'; import { ConfirmDialogComponent, ConfirmDialogData } from './../../iqb-common/confirm-dialog/confirm-dialog.component'; -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; import { BackendService } from './../backend.service'; import { MatSnackBar, MatSort, MatTableDataSource, MatDialog } from '@angular/material'; import { SelectionModel } from '@angular/cdk/collections'; import { saveAs } from 'file-saver'; import { ResultData, UnitResponse, ReviewData } from '../workspace.interfaces'; +import { Subscription } from 'rxjs'; @Component({ templateUrl: './results.component.html', styleUrls: ['./results.component.css'] }) -export class ResultsComponent implements OnInit { - displayedColumns: string[] = ['selectCheckbox', 'groupname', 'bookletsStarted', 'num_units_min', 'num_units_max', 'num_units_mean']; +export class ResultsComponent implements OnInit, OnDestroy { + displayedColumns: string[] = ['selectCheckbox', 'groupname', 'bookletsStarted', 'num_units_min', 'num_units_max', 'num_units_mean', 'lastchange']; private resultDataSource = new MatTableDataSource<ResultData>([]); // prepared for selection if needed sometime private tableselectionCheckbox = new SelectionModel<ResultData>(true, []); private dataLoading = false; + private workspaceIdSubscription: Subscription = null; @ViewChild(MatSort) sort: MatSort; @@ -30,19 +32,25 @@ export class ResultsComponent implements OnInit { ) { } ngOnInit() { - this.updateTable(); + this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(ws => { + this.updateTable(); + }); } updateTable() { - this.dataLoading = true; this.tableselectionCheckbox.clear(); - this.bs.getResultData().subscribe( - (resultData: ResultData[]) => { - this.dataLoading = false; - this.resultDataSource = new MatTableDataSource<ResultData>(resultData); - this.resultDataSource.sort = this.sort; - } - ) + if (this.wds.wsRole === 'MO') { + this.resultDataSource = new MatTableDataSource<ResultData>([]); + } else { + this.dataLoading = true; + this.bs.getResultData().subscribe( + (resultData: ResultData[]) => { + this.dataLoading = false; + this.resultDataSource = new MatTableDataSource<ResultData>(resultData); + this.resultDataSource.sort = this.sort; + } + ) + } } isAllSelected() { @@ -72,8 +80,8 @@ export class ResultsComponent implements OnInit { const lineDelimiter = '\n'; let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + 'bookletname' + columnDelimiter + 'unitname' + columnDelimiter + 'responses' + columnDelimiter + - 'restorePoint' + columnDelimiter + 'responseType' + lineDelimiter + 'response-ts' + lineDelimiter + - 'restorePoint-ts' + lineDelimiter + 'laststate'; + 'restorePoint' + columnDelimiter + 'responseType' + columnDelimiter + 'response-ts' + columnDelimiter + + 'restorePoint-ts' + columnDelimiter + 'laststate' + lineDelimiter; responseData.forEach((resp: UnitResponse) => { myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + columnDelimiter + '"' + resp.code + '"' + columnDelimiter + '"' + resp.bookletname + '"' + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter; @@ -239,4 +247,11 @@ export class ResultsComponent implements OnInit { }); } } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.workspaceIdSubscription !== null) { + this.workspaceIdSubscription.unsubscribe(); + } + } } diff --git a/src/app/workspace/workspace-routing.module.ts b/src/app/workspace/workspace-routing.module.ts index 0761950b..d4068ec0 100644 --- a/src/app/workspace/workspace-routing.module.ts +++ b/src/app/workspace/workspace-routing.module.ts @@ -11,12 +11,12 @@ const routes: Routes = [ path: 'ws/:ws', component: WorkspaceComponent, children: [ - {path: '', redirectTo: 'files', pathMatch: 'full'}, + {path: '', redirectTo: 'monitor', pathMatch: 'full'}, {path: 'files', component: FilesComponent}, {path: 'syscheck', component: SyscheckComponent}, {path: 'monitor', component: MonitorComponent}, {path: 'results', component: ResultsComponent}, - {path: '**', component: FilesComponent} + {path: '**', component: MonitorComponent} ] }]; diff --git a/src/app/workspace/workspace.component.html b/src/app/workspace/workspace.component.html index 45c74fcb..6f5cf00c 100644 --- a/src/app/workspace/workspace.component.html +++ b/src/app/workspace/workspace.component.html @@ -1,10 +1,12 @@ -<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="space-between center"> +<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="start center"> <a [routerLink]="['/']"> <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> </a> - <div class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</div> - <div>IQB-Testcenter Verwaltung</div> - <div>{{ pageTitle }}</div> + <div fxLayout="row wrap" fxLayoutAlign="space-between center" fxFlex> + <div class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</div> + <div>IQB-Testcenter Verwaltung</div> + <div>{{ wds.wsName }} ({{ wds.wsRole }})</div> + </div> </div> <div class="page-body"> <div class="adminbackground"> @@ -12,7 +14,7 @@ <nav mat-tab-nav-bar> <a mat-tab-link - *ngFor="let link of navLinks" + *ngFor="let link of wds.navLinks" [routerLink]="link.path" routerLinkActive #rla="routerLinkActive" [active]="rla.isActive"> diff --git a/src/app/workspace/workspace.component.ts b/src/app/workspace/workspace.component.ts index 2700ee25..c1380957 100644 --- a/src/app/workspace/workspace.component.ts +++ b/src/app/workspace/workspace.component.ts @@ -11,14 +11,6 @@ import { MatTabsModule, MatSelectModule, MatFormFieldModule } from '@angular/mat styleUrls: ['./workspace.component.css'] }) export class WorkspaceComponent implements OnInit, OnDestroy { - public navLinks = [ - {path: 'files', label: 'Dateien'}, - {path: 'syscheck', label: 'System-Check Berichte'}, - {path: 'monitor', label: 'Monitor'}, - {path: 'results', label: 'Ergebnisse'} - ]; - - public pageTitle = ''; private routingSubscription: Subscription = null; private logindataSubscription: Subscription = null; @@ -33,21 +25,11 @@ export class WorkspaceComponent implements OnInit, OnDestroy { ngOnInit() { this.routingSubscription = this.route.params.subscribe(params => { const ws = Number(params['ws']); - this.wds.setWorkspaceId(ws); - if ((this.mds.adminToken.length > 0) && (ws > 0)) { - this.pageTitle = this.mds.getWorkspaceName(ws) + ' (' + this.mds.getWorkspaceRole(ws) + ')'; - } else { - this.pageTitle = ''; - } + this.wds.setWorkspace(ws, this.mds.getWorkspaceRole(ws), this.mds.getWorkspaceName(ws)); }); this.logindataSubscription = this.mds.loginData$.subscribe(ld => { - const ws = this.wds.ws; - if (ws > 0) { - this.pageTitle = this.mds.getWorkspaceName(ws) + ' (' + this.mds.getWorkspaceRole(ws) + ')'; - } else { - this.pageTitle = ''; - } + this.wds.setWorkspace(this.wds.ws, this.mds.getWorkspaceRole(this.wds.ws), this.mds.getWorkspaceName(this.wds.ws)); }); } diff --git a/src/app/workspace/workspace.interfaces.ts b/src/app/workspace/workspace.interfaces.ts index 2053eb37..b53f17d1 100644 --- a/src/app/workspace/workspace.interfaces.ts +++ b/src/app/workspace/workspace.interfaces.ts @@ -29,6 +29,7 @@ export interface BookletsStarted { code: string; bookletname: string; locked: boolean; + laststart: Date; } export interface UnitResponse { @@ -52,6 +53,8 @@ export interface MonitorData { bookletsPrepared: number; bookletsStarted: number; bookletsLocked: number; + laststart: Date; + laststartStr: string; } export interface ResultData { @@ -60,6 +63,7 @@ export interface ResultData { num_units_min: number; num_units_max: number; num_units_mean: number; + lastchange: number; } export interface LogData { diff --git a/src/app/workspace/workspacedata.service.ts b/src/app/workspace/workspacedata.service.ts index 3aee6f83..97787c59 100644 --- a/src/app/workspace/workspacedata.service.ts +++ b/src/app/workspace/workspacedata.service.ts @@ -23,7 +23,32 @@ export class WorkspaceDataService { public get ws() : number { return this.workspaceId$.getValue(); } + private _wsRole = ''; + public get wsRole() : string { + return this._wsRole; + } + private _wsName = ''; + public get wsName() : string { + return this._wsName; + } + public navLinks = []; + // .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. + private navLinksRW = [ + {path: 'files', label: 'Dateien'}, + // {path: 'syscheck', label: 'System-Check Berichte'}, + {path: 'monitor', label: 'Monitor'}, + {path: 'results', label: 'Ergebnisse'} + ]; + private navLinksRO = [ + {path: 'files', label: 'Dateien'}, + // {path: 'syscheck', label: 'System-Check Berichte'}, + {path: 'monitor', label: 'Monitor'}, + {path: 'results', label: 'Ergebnisse'} + ]; + private navLinksMO = [ + {path: 'monitor', label: 'Monitor'} + ]; // ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc constructor ( @@ -66,7 +91,27 @@ export class WorkspaceDataService { } // ******************************************************************************************************* - setWorkspaceId(newId: number) { + setWorkspace(newId: number, newRole: string, newName: string) { + this._wsName = newName; + this._wsRole = newRole; + switch (newRole.toUpperCase()) { + case 'RW': { + this.navLinks = this.navLinksRW; + break; + } + case 'RO': { + this.navLinks = this.navLinksRO; + break; + } + case 'MO': { + this.navLinks = this.navLinksMO; + break; + } + default: { + this.navLinks = []; + break; + } + } this.workspaceId$.next(newId); } } -- GitLab