TSK-843 designed report table and made single row collapsable

This commit is contained in:
Mustapha Zorgati 2019-04-03 17:06:42 +02:00 committed by Holger Hagen
parent b75be882f1
commit d8b1829a53
10 changed files with 101 additions and 45 deletions

View File

@ -38,12 +38,10 @@ import pro.taskana.rest.RestConfiguration;
@SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class MonitorControllerRestDocumentation { public class MonitorControllerRestDocumentation {
@LocalServerPort
int port;
@Rule @Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@LocalServerPort
int port;
@Autowired @Autowired
private WebApplicationContext context; private WebApplicationContext context;
@ -70,6 +68,8 @@ public class MonitorControllerRestDocumentation {
fieldWithPath("meta.name").description("Name of the report"), fieldWithPath("meta.name").description("Name of the report"),
fieldWithPath("meta.date").description("Date of the report creation"), fieldWithPath("meta.date").description("Date of the report creation"),
fieldWithPath("meta.header").description("Column-headers of the report"), fieldWithPath("meta.header").description("Column-headers of the report"),
fieldWithPath("meta.expHeader").description(
"Expandable Column-headers which match the depth of the foldable rows within the report."),
fieldWithPath("meta.rowDesc").description("Descriptions for the rows the report"), fieldWithPath("meta.rowDesc").description("Descriptions for the rows the report"),
fieldWithPath("meta.totalDesc").description("Description for the report itself"), fieldWithPath("meta.totalDesc").description("Description for the report itself"),
subsectionWithPath("rows").description("Object holding the rows of the report.\n" subsectionWithPath("rows").description("Object holding the rows of the report.\n"

View File

@ -8,4 +8,5 @@ export class ReportInfoDataIterable {
key: string; key: string;
val: ReportInfoData; val: ReportInfoData;
depth: number; depth: number;
display = false;
} }

View File

@ -14,12 +14,12 @@ import {MonitorComponent} from './monitor.component';
import {TasksComponent} from './tasks/tasks.component'; import {TasksComponent} from './tasks/tasks.component';
import {WorkbasketComponent} from './workbasket/workbasket.component'; import {WorkbasketComponent} from './workbasket/workbasket.component';
import {ClassificationTasksComponent} from './classification-tasks/classification-tasks.component'; import {ClassificationTasksComponent} from './classification-tasks/classification-tasks.component';
import {ReportRowComponent} from "./report/row/row.component"; import {ReportRowComponent} from './report/row/row.component';
import {TimestampComponent} from "./timestamp/timestamp.component"; import {TimestampComponent} from './timestamp/timestamp.component';
import {RestConnectorService} from './services/restConnector/rest-connector.service'; import {RestConnectorService} from './services/restConnector/rest-connector.service';
import {MapToIterable} from "../shared/pipes/mapToIterable/mapToIterable"; import {MapToIterable} from '../shared/pipes/mapToIterable/mapToIterable';
const MODULES = [ const MODULES = [
CommonModule, CommonModule,

View File

@ -1,26 +1,30 @@
<div *ngIf="reportData" class="report table table-body-striped"> <div *ngIf="reportData" class="report table table-body-striped">
<div class="table-header"> <div class="table-header">
<div class="table-row"> <div class="table-row">
<div class="table-cell table-cell--bold table-cell--justify">{{reportData.meta.rowDesc}}</div> <div
[ngClass]="{'table-cell--border-right': currentExpHeaders === 0}"
class="table-cell table-cell--bold table-cell--justify">{{reportData.meta.rowDesc}}</div>
<ng-container *ngFor="let header of reportData.meta.expHeader; let i = index"> <ng-container *ngFor="let header of reportData.meta.expHeader; let i = index">
<ng-container *ngIf="i < currentExpHeaders"> <ng-container *ngIf="i < currentExpHeaders">
<div class="table-cell table-cell--bold table-cell--justify">{{header}}</div> <div [ngClass]="{'table-cell--border-right': currentExpHeaders - 1 === i}"
class="table-cell table-cell--bold table-cell--justify">{{header}}</div>
</ng-container> </ng-container>
</ng-container> </ng-container>
<div *ngFor="let header of reportData.meta.header" class="table-cell table-cell--bold">{{header}}</div> <div *ngFor="let header of reportData.meta.header"
<div class="table-cell table-cell--bold">{{reportData.meta.totalDesc}}</div> class="table-cell table-cell--bold">{{header}}</div>
<div class="table-cell table-cell--bold table-cell--border-left">{{reportData.meta.totalDesc}}</div>
</div> </div>
</div> </div>
<monitor-report-row (expandedDepth)="expandHeader($event, i)" <taskana-report-row (expandedDepth)="expandHeader($event, i)"
*ngFor="let row of reportData.rows | mapToIterable | orderBy:['key']; let i = index" *ngFor="let row of reportData.rows | mapToIterable | orderBy:['key']; let i = index"
[headers]="reportData.meta.header" [headers]="reportData.meta.header"
[maxTableDepth]="currentExpHeaders" [maxTableDepth]="currentExpHeaders"
[row]="row" [row]="row"
class="table-body"></monitor-report-row> class="table-body"></taskana-report-row>
<monitor-report-row (expandedDepth)="expandHeader($event, expHeaders.length - 1)" <taskana-report-row (expandedDepth)="expandHeader($event, expHeaders.length - 1)"
[headers]="reportData.meta.header" [headers]="reportData.meta.header"
[maxTableDepth]="currentExpHeaders" [maxTableDepth]="currentExpHeaders"
[row]="_sumRow" [row]="_sumRow"
bold="true" bold="true"
class="table-footer"></monitor-report-row> class="table-footer"></taskana-report-row>
</div> </div>

View File

@ -1,6 +1,6 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import {ReportData} from 'app/monitor/models/report-data'; import {ReportData} from 'app/monitor/models/report-data';
import {ReportInfoDataIterable} from "../models/report-info-data"; import {ReportInfoDataIterable} from '../models/report-info-data';
@Component({ @Component({
selector: 'taskana-report', selector: 'taskana-report',
@ -11,12 +11,9 @@ export class ReportComponent implements OnInit {
expHeaders: Array<number>; expHeaders: Array<number>;
currentExpHeaders: number = 0; currentExpHeaders = 0;
_sumRow: ReportInfoDataIterable; _sumRow: ReportInfoDataIterable;
constructor() {
}
private _reportData: ReportData; private _reportData: ReportData;
get reportData(): ReportData { get reportData(): ReportData {
@ -32,6 +29,9 @@ export class ReportComponent implements OnInit {
this._sumRow.key = reportData.meta.totalDesc; this._sumRow.key = reportData.meta.totalDesc;
} }
constructor() {
}
ngOnInit(): void { ngOnInit(): void {
} }

View File

@ -1,17 +1,26 @@
<ng-container *ngIf="headers && flatRows"> <ng-container *ngIf="headers && flatRows">
<ng-container *ngFor="let row of flatRows; let i = index"> <ng-container *ngFor="let row of flatRows; let i = index">
<div *ngIf="currentDepth >= row.depth" class="table-row"> <div *ngIf="row.display"
[ngClass]="{'table-row--highlight': row.depth === 0 && currentDepth > 0,
'table-row--hover': row.depth > 0 && currentDepth > 0,
'table-row--white': row.depth > 0 && currentDepth > 0}"
class="table-row">
<div *ngFor="let _ of range(row.depth)" class="table-cell"></div> <div *ngFor="let _ of range(row.depth)" class="table-cell"></div>
<div (click)="toggleFold(row.depth)" <div (click)="toggleFold(i)"
[ngClass]="{'table-cell--clickable': maxDepth > row.depth, 'table-cell--bold' : bold}" [ngClass]="{'table-cell--clickable': maxDepth > row.depth,
'table-cell--bold' : bold || row.depth === 0 && currentDepth > 0,
'table-cell--border-right': row.depth === maxTableDepth}"
class="table-cell table-cell--justify"> class="table-cell table-cell--justify">
{{row.key}} <span *ngIf="maxDepth > row.depth"
class="material-icons md-18">{{ canRowCollapse(i) ? "expand_more" : "expand_less"}}</span>{{row.key}}
</div> </div>
<div *ngFor="let _ of range(maxTableDepth - row.depth)" class="table-cell"></div> <div *ngFor="let _ of range(maxTableDepth - row.depth); let i = index"
<div *ngFor="let header of headers" [ngClass]="{'table-cell--bold' : bold}" class="table-cell"> [ngClass]="{'table-cell--border-right': i === maxTableDepth - row.depth - 1}" class="table-cell"></div>
<div *ngFor="let header of headers"
[ngClass]="{'table-cell--bold' : bold || row.depth === 0 && currentDepth > 0}" class="table-cell">
{{row.val.cells[header]}} {{row.val.cells[header]}}
</div> </div>
<div class="table-cell table-cell--bold"> <div class="table-cell table-cell--bold table-cell--border-left">
{{row.val.total}} {{row.val.total}}
</div> </div>
</div> </div>

View File

@ -1,4 +1,19 @@
@import './src/assets/_colors';
.table-cell--clickable { .table-cell--clickable {
cursor: pointer; cursor: pointer;
} }
.table-row--highlight,
.table-row--hover:hover {
background-color: #f9f9f9;
}
.table-row--highlight > .table-cell {
border-bottom: 2px solid #ddd;
}
.table-row--white {
background-color: white;
}

View File

@ -1,9 +1,9 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ReportInfoDataIterable} from "../../models/report-info-data"; import {ReportInfoDataIterable} from '../../models/report-info-data';
import {MapToIterable} from "../../../shared/pipes/mapToIterable/mapToIterable"; import {MapToIterable} from '../../../shared/pipes/mapToIterable/mapToIterable';
@Component({ @Component({
selector: 'monitor-report-row', selector: 'taskana-report-row',
templateUrl: './row.component.html', templateUrl: './row.component.html',
styleUrls: ['./row.component.scss'] styleUrls: ['./row.component.scss']
}) })
@ -12,19 +12,15 @@ export class ReportRowComponent implements OnInit {
@Input() @Input()
headers: Array<string>; headers: Array<string>;
@Input() @Input()
bold: boolean = false; bold = false;
@Input() @Input()
maxTableDepth: number = 0; maxTableDepth = 0;
@Output() @Output()
expandedDepth: EventEmitter<number> = new EventEmitter<number>(); expandedDepth: EventEmitter<number> = new EventEmitter<number>();
currentDepth = 0;
maxDepth: number; maxDepth: number;
currentDepth: number = 0;
flatRows: Array<ReportInfoDataIterable>; flatRows: Array<ReportInfoDataIterable>;
constructor(private mapToIterable: MapToIterable) {
}
private _row: ReportInfoDataIterable; private _row: ReportInfoDataIterable;
get row(): ReportInfoDataIterable { get row(): ReportInfoDataIterable {
@ -38,25 +34,47 @@ export class ReportRowComponent implements OnInit {
this.maxDepth = this.flatten(row, 0); this.maxDepth = this.flatten(row, 0);
} }
constructor(private mapToIterable: MapToIterable) {
}
ngOnInit() { ngOnInit() {
} }
toggleFold(depth: number) { toggleFold(index: number): void {
this.currentDepth = depth == this.currentDepth && depth < this.maxDepth ? depth + 1 : depth; const toggleRow = this.flatRows[index++];
if (toggleRow.depth < this.maxDepth) {
const firstChildRow = this.flatRows[index++];
firstChildRow.display = !firstChildRow.display;
let end = false;
for (let i = index; i < this.flatRows.length && !end; i++) {
const row = this.flatRows[i];
end = row.depth <= toggleRow.depth;
if (!end) {
row.display = firstChildRow.display && row.depth === firstChildRow.depth;
}
}
this.currentDepth = Math.max(...this.flatRows.filter(r => r.display).map(r => r.depth));
this.expandedDepth.emit(this.currentDepth); this.expandedDepth.emit(this.currentDepth);
} }
}
range(depth: number): Array<null> { range(depth: number): Array<null> {
return new Array<null>(Math.max(depth, 0)); return new Array<null>(Math.max(depth, 0));
} }
canRowCollapse(index: number) {
return !this.flatRows[index + 1].display;
}
private flatten(row: ReportInfoDataIterable, depth: number): number { private flatten(row: ReportInfoDataIterable, depth: number): number {
row.depth = depth; row.depth = depth;
row.display = depth === 0;
this.flatRows.push(row); this.flatRows.push(row);
if (row.val.foldableRows) { if (row.val.foldableRows) {
depth = Math.max(...this.mapToIterable.transform(row.val.foldableRows) depth = Math.max(...this.mapToIterable.transform(row.val.foldableRows)
.sort((a, b) => a.key.localeCompare(b.key)) .sort((a, b) => a.key.localeCompare(b.key))
.map(r => this.flatten(r, depth + 1))) .map(r => this.flatten(r, depth + 1)));
} }
return depth; return depth;
} }

View File

@ -1,6 +1,6 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {ReportData} from "../models/report-data"; import {ReportData} from '../models/report-data';
import {RestConnectorService} from "../services/restConnector/rest-connector.service"; import {RestConnectorService} from '../services/restConnector/rest-connector.service';
@Component({ @Component({
selector: 'taskana-monitor-timestamp', selector: 'taskana-monitor-timestamp',

View File

@ -72,3 +72,12 @@
min-width: 0; min-width: 0;
} }
.table-cell--border-right {
border-right: 2px solid #ddd;
}
.table-cell--border-left {
border-left: 2px solid #ddd;
}