Refine dashboard
This commit is contained in:
parent
a27de02468
commit
301407c0c4
1764
package-lock.json
generated
1764
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
||||
"@ng-bootstrap/ng-bootstrap": "^6.0.0",
|
||||
"bootstrap": "^4.4.0",
|
||||
"chart.js": "^2.9.3",
|
||||
"component": "^1.1.0",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^1.10.0",
|
||||
"zone.js": "~0.10.2"
|
||||
|
@ -14,7 +14,8 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { UserstoryTableComponent } from './userstory-table/userstory-table.component';
|
||||
import { TaskTableComponent } from './task-table/task-table.component';
|
||||
import { SprintTableComponent } from './sprint-table/sprint-table.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { UserstoryInnerTableComponent } from './userstory-inner-table/userstory-inner-table.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -27,6 +28,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
SprintFormComponent,
|
||||
SprintTableComponent,
|
||||
DashboardComponent,
|
||||
UserstoryInnerTableComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -1,3 +1,7 @@
|
||||
.text-2em {
|
||||
font-size: 2rem;
|
||||
.text-large {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.text-very-large {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
@ -1,60 +1,98 @@
|
||||
<div class="container-fluid">
|
||||
<div class="mx-5 my-3">
|
||||
|
||||
<div class="row px-3 py-2">
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
<div class="row px-3 py-2">
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
|
||||
<div class="row px-3 py-2">
|
||||
<h2>Current sprint</h2>
|
||||
</div>
|
||||
<div class="row px-3 py-2">
|
||||
|
||||
<div class="row">
|
||||
<ng-container *ngIf="selectedSprint === undefined">
|
||||
<h3 class="mr-3 text-primary">Alle Userstories</h3>
|
||||
<a [routerLink]="['sprints']">Lege einen Sprint an, um Userstories zu organisieren.</a>
|
||||
</ng-container>
|
||||
|
||||
<div class="p-3 xl-col-6 lg-col-6 md-col-12 sm-col-12 xs-col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ng-container *ngIf="selectedSprint !== undefined">
|
||||
|
||||
<h3 class="mr-3 text-primary" *ngIf="selectedSprint === currentSprint">Aktueller Sprint:</h3>
|
||||
<h3 class="mr-3 text-primary" *ngIf="selectedSprint !== currentSprint">Sprint:</h3>
|
||||
<h3 class="mr-3 custom-text-secondary">{{selectedSprint.title}}</h3>
|
||||
<h3 class="mr-3">{{toDateString(selectedSprint.startDate)}} - {{toDateString(selectedSprint.endDate)}}</h3>
|
||||
|
||||
<label class="mr-3">
|
||||
<select class="select custom-select custom-text-secondary" [(ngModel)]="selectedSprint">
|
||||
<option class="bg-secondary text-dark" [ngValue]="currentSprint">
|
||||
{{currentSprint.title}} (aktuell)
|
||||
</option>
|
||||
<option value="" disabled="disabled">─────────────────────────</option>
|
||||
<option class="text-dark" *ngFor="let sprint of sprints" [ngValue]="sprint">
|
||||
<ng-container *ngIf="sprint === currentSprint">
|
||||
{{sprint.title}} (aktuell)
|
||||
</ng-container>
|
||||
<ng-container *ngIf="sprint !== currentSprint">
|
||||
{{sprint.title}} ({{toDateString(sprint.startDate)}} - {{toDateString(sprint.endDate)}})
|
||||
</ng-container>
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<span class="mr-5"></span>
|
||||
<h3
|
||||
*ngIf="selectedSprint === currentSprint"
|
||||
class="mr-3 custom-text-secondary"
|
||||
[class.text-success]="getSprintUrgency() === 2"
|
||||
[class.text-warning]="getSprintUrgency() === 1"
|
||||
[class.text-danger]="getSprintUrgency() === 0"
|
||||
>
|
||||
Verbleibende Tage: {{getRemainingDaysInSprint()}}
|
||||
</h3>
|
||||
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="text-large">
|
||||
Userstories
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="done-stories-chart"></canvas>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 xl-col-6 lg-col-6 md-col-12 sm-col-12 xs-col-12">
|
||||
<div class="card-deck">
|
||||
|
||||
<div
|
||||
class="card"
|
||||
[class.border-danger]="getSprintUrgency() == 0"
|
||||
[class.border-warning]="getSprintUrgency() == 1"
|
||||
[class.border-success]="getSprintUrgency() == 2"
|
||||
*ngIf="getRemainingDaysInSprint() !== undefined"
|
||||
>
|
||||
<div class="card-header">
|
||||
Days remaining
|
||||
<div class="card-body">
|
||||
<div *ngIf="selectedSprint !== undefined && usedStatus.length === 0">
|
||||
Zum Sprint "{{selectedSprint.title}}" sind aktuell keine Userstories vorhanden.
|
||||
</div>
|
||||
<canvas id="done-stories-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-deck">
|
||||
<div
|
||||
class="card text-center"
|
||||
*ngFor="let status of usedStatus"
|
||||
>
|
||||
<div class="card-header">
|
||||
<span class="text-large">
|
||||
{{status.title}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="text-very-large">
|
||||
<b>{{getNumberOfUserstoriesByStatus(status)}}</b>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<span class="text-2em">
|
||||
{{getRemainingDaysInSprint()}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" *ngFor="let status of usedStatus">
|
||||
<div class="card-header">
|
||||
Userstories: {{status.title}}
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<span class="text-2em">
|
||||
{{getNumberOfUserstoriesByStatus(status)}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-userstory-inner-table [items]="selectedSprintUserstories"></app-userstory-inner-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -1,79 +1,111 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Component, OnChanges} from '@angular/core';
|
||||
import {forkJoin} from 'rxjs';
|
||||
import Chart from 'chart.js';
|
||||
import {BackendService, ScrumStatus, ScrumUser, ScrumUserstory, ScrumSprint} from '../services/backend.service';
|
||||
import {BackendService, ScrumSprint, ScrumStatus, ScrumUserstory} from '../services/backend.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: 'dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.css']
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
export class DashboardComponent {
|
||||
/**
|
||||
* Returns the status that are used by at least one userstory.
|
||||
*/
|
||||
public get usedStatus(): ScrumStatus[] {
|
||||
return this.status.filter(s => this.userstories.find(us => us.statusid === s.id) !== undefined);
|
||||
return this.status.filter(s => this.selectedSprintUserstories.find(us => us.statusid === s.id) !== undefined);
|
||||
}
|
||||
|
||||
private status: ScrumStatus[];
|
||||
private userstories: ScrumUserstory[];
|
||||
private sprints: ScrumSprint[];
|
||||
public get selectedSprintUserstories(): ScrumUserstory[] {
|
||||
if (this.selectedSprint === undefined) {
|
||||
return this.userstories;
|
||||
}
|
||||
return this.userstories.filter(us => us.sprintid === this.selectedSprint.id);
|
||||
}
|
||||
|
||||
public get currentSprint(): ScrumSprint {
|
||||
const now = Date.now();
|
||||
return this.sprints.find(s => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now);
|
||||
}
|
||||
|
||||
private _selectedSprint: ScrumSprint;
|
||||
public get selectedSprint(): ScrumSprint {
|
||||
if (this._selectedSprint === undefined) {
|
||||
if (this.currentSprint === undefined) {
|
||||
return this.sprints[0];
|
||||
}
|
||||
return this.currentSprint;
|
||||
}
|
||||
return this._selectedSprint;
|
||||
}
|
||||
|
||||
public set selectedSprint(value) {
|
||||
this._selectedSprint = value;
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
public status: ScrumStatus[] = [];
|
||||
public userstories: ScrumUserstory[] = [];
|
||||
public sprints: ScrumSprint[] = [];
|
||||
|
||||
public chart: Chart;
|
||||
|
||||
constructor(private backendService: BackendService) {
|
||||
// backendService.getUserstories().subscribe(response => {
|
||||
// if (response.status > 399) {
|
||||
// alert('Fehler');
|
||||
// } else {
|
||||
// this.userstories.push(...response.body);
|
||||
// }
|
||||
// });
|
||||
// backendService.getAllStatus().subscribe(response => {
|
||||
// if (response.status > 399) {
|
||||
// alert('Fehler');
|
||||
// } else {
|
||||
// this.status.push(...response.body);
|
||||
// }
|
||||
// });
|
||||
// backendService.getSprints().subscribe(response => {
|
||||
// if (response.status > 399) {
|
||||
// alert('Fehler');
|
||||
// } else {
|
||||
// this.sprints.push(...response.body);
|
||||
// }
|
||||
// });
|
||||
|
||||
this.status = [
|
||||
{id: 0, title: "In progress", description:""},
|
||||
{id: 1, title: "Done", description:""},
|
||||
];
|
||||
this.userstories = [
|
||||
{statusid: 0, title:""},
|
||||
{statusid: 0, title:""},
|
||||
{statusid: 0, title:""},
|
||||
{statusid: 1, title:""},
|
||||
{statusid: 1, title:""},
|
||||
];
|
||||
this.sprints = [
|
||||
{description:"", title:"", project: 0, startDate: new Date(2020, 5, 22), endDate: new Date(2020, 5, 28)},
|
||||
{description:"", title:"", project: 0, startDate: new Date(2020, 5, 29), endDate: new Date(2020, 6, 5)},
|
||||
]
|
||||
// download userstories, status and sprints and update
|
||||
// the chart whenever a new response is there
|
||||
forkJoin([backendService.getUserstories(), backendService.getAllStatus(), backendService.getSprints()])
|
||||
.subscribe(results => {
|
||||
const [userstoryResponse, statusResponse, sprintResponse] = results;
|
||||
if (userstoryResponse.status > 399 || statusResponse.status > 399 || sprintResponse.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.userstories.push(...userstoryResponse.body);
|
||||
this.status.push(...statusResponse.body);
|
||||
this.sprints.push(...sprintResponse.body);
|
||||
this.createChart();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date in the following format: 1.7.2020
|
||||
*/
|
||||
public toDateString(isoFormatString) {
|
||||
const date = new Date(isoFormatString);
|
||||
return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
private createChart() {
|
||||
// @ts-ignore
|
||||
const context = document.getElementById('done-stories-chart').getContext('2d');
|
||||
const chart = new Chart(context, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: this.usedStatus.map(s => s.title),
|
||||
datasets: [{
|
||||
label: 'Done stories',
|
||||
data: this.usedStatus.map(s => this.getNumberOfUserstoriesByStatus(s)),
|
||||
backgroundColor: this.getBackgroundColors(),
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
if (this.usedStatus.length === 0) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
else {
|
||||
this.chart = new Chart(context, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: this.usedStatus.map(s => s.title),
|
||||
datasets: [{
|
||||
data: this.usedStatus.map(s => this.getNumberOfUserstoriesByStatus(s)),
|
||||
backgroundColor: this.getBackgroundColors(),
|
||||
options: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
labels: {
|
||||
fontColor: 'rgba(255, 255, 255, 0.8)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
this.chart.update();
|
||||
this.chart.render();
|
||||
}
|
||||
}
|
||||
|
||||
private getBackgroundColors(): string[] {
|
||||
@ -101,27 +133,28 @@ export class DashboardComponent implements OnInit {
|
||||
}
|
||||
|
||||
public getNumberOfUserstoriesByStatus(status: ScrumStatus): number {
|
||||
return this.userstories.filter(us => us.statusid === status.id).length;
|
||||
return this.selectedSprintUserstories.filter(us => us.statusid === status.id).length;
|
||||
}
|
||||
|
||||
public getRemainingDaysInSprint(): number {
|
||||
const now = new Date();
|
||||
const currentSprint = this.sprints.find(s => s.endDate > now && s.startDate < now);
|
||||
if (currentSprint === undefined) {
|
||||
if (this.selectedSprint === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const daysDelta = Math.floor((currentSprint.endDate.getTime() - now.getTime()) / 86400000);
|
||||
return daysDelta;
|
||||
return Math.floor((Date.parse(this.selectedSprint.endDate) - Date.now()) / 86400000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "urgency" of the current sprint (ie what percentage of the sprint is remaining)
|
||||
* as an integer from 0 (most urgent) to 2 (least urgent).
|
||||
*/
|
||||
public getSprintUrgency(): number {
|
||||
const now = new Date();
|
||||
const currentSprint = this.sprints.find(s => s.endDate > now && s.startDate < now);
|
||||
if (currentSprint === undefined) {
|
||||
const now = Date.now();
|
||||
const sprint = this.selectedSprint;
|
||||
if (sprint === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const deltaFromNow = currentSprint.endDate.getTime() - now.getTime();
|
||||
const deltaFromStart = currentSprint.endDate.getTime() - currentSprint.startDate.getTime();
|
||||
const deltaFromNow = Date.parse(sprint.endDate) - now;
|
||||
const deltaFromStart = Date.parse(sprint.endDate) - Date.parse(sprint.startDate);
|
||||
return Math.floor(3 * deltaFromNow / deltaFromStart);
|
||||
}
|
||||
}
|
||||
|
@ -233,8 +233,8 @@ export interface ScrumSprint{
|
||||
id?: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
project: number;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { getNumberForPriority } from '../services/sorting.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-userstory-table',
|
||||
selector: 'app-task-table',
|
||||
templateUrl: './task-table.component.html',
|
||||
styleUrls: ['./task-table.component.css'],
|
||||
})
|
||||
|
@ -0,0 +1,90 @@
|
||||
<button class="btn btn-secondary my-3" (click)="openUserstoryForm()">Neue Userstory</button>
|
||||
|
||||
<table class="table">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th (click)="sortById()" class="sortable">
|
||||
<span>ID</span>
|
||||
<span *ngIf="sortBy === 'id'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByTitle()" class="sortable">
|
||||
<span>Titel</span>
|
||||
<span *ngIf="sortBy === 'title'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByTasks()" class="sortable">
|
||||
<span>Tasks</span>
|
||||
<span *ngIf="sortBy === 'tasks'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByStatus()" class="sortable">
|
||||
<span>Status</span>
|
||||
<span *ngIf="sortBy === 'statusid'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByPrio()" class="sortable">
|
||||
<span>Priorität</span>
|
||||
<label class="pl-3" (click)="$event.stopPropagation()">
|
||||
<select [(ngModel)]="filterPriority">
|
||||
<option [ngValue]="null" selected></option>
|
||||
<option *ngFor="let p of getAllPriorities()" [ngValue]="p">{{p}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<span *ngIf="sortBy === 'priority'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByCategory()" class="sortable">
|
||||
<span>Category</span>
|
||||
<span *ngIf="sortBy === 'categoryid'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr *ngFor="let userstory of filteredItems" [class.table-info]="userstory.id === highlightId">
|
||||
<td>{{userstory.id}}</td>
|
||||
<td>{{userstory.title}}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/tasks', {userstoryId: userstory.id}]">
|
||||
{{getNumberOfTasks(userstory)}} Tasks
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/status', {id: userstory.statusid}]">
|
||||
{{getStatusTitleById(userstory.statusid)}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{userstory.priority}}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/categories', {id: userstory.categoryid}]">
|
||||
{{getCategoryTitleById(userstory.categoryid)}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" rel="tooltip" (click)="openUserstoryForm(userstory)" class="btn btn-success btn-sm btn-icon">
|
||||
<i class="fa fa-pencil-alt"></i>
|
||||
</button>
|
||||
<button type="button" rel="tooltip" (click)="deleteUserstory(userstory)" class="btn btn-danger btn-sm btn-icon">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
143
src/app/userstory-inner-table/userstory-inner-table.component.ts
Normal file
143
src/app/userstory-inner-table/userstory-inner-table.component.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import {
|
||||
BackendService,
|
||||
ScrumTask,
|
||||
ScrumUserstory,
|
||||
ScrumStatus,
|
||||
ScrumCategory,
|
||||
} from '../services/backend.service';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TableComponentBase } from '../services/table-component.base';
|
||||
import { getNumberForPriority } from '../services/sorting.service';
|
||||
import { UserstoryFormComponent } from '../userstory-form/userstory-form.component';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-userstory-inner-table',
|
||||
templateUrl: './userstory-inner-table.component.html',
|
||||
styleUrls: ['./userstory-inner-table.component.css']
|
||||
})
|
||||
export class UserstoryInnerTableComponent extends TableComponentBase<ScrumUserstory> {
|
||||
public tasks: ScrumTask[] = [];
|
||||
public filterPriority: string | null = null;
|
||||
public highlightId: number;
|
||||
public status: ScrumStatus[] = [];
|
||||
public categories: ScrumCategory[] = [];
|
||||
|
||||
@Input() public items: ScrumUserstory[] = [];
|
||||
|
||||
public get filteredItems() {
|
||||
return this.items.filter(
|
||||
(task) =>
|
||||
this.filterPriority === null || task.priority === this.filterPriority
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private backendService: BackendService,
|
||||
private modalService: NgbModal,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {
|
||||
super();
|
||||
|
||||
this.applyFilterParameters(this.route.snapshot.paramMap);
|
||||
this.route.paramMap.subscribe((map) => this.applyFilterParameters(map));
|
||||
|
||||
backendService.getTasks().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.tasks.push(...response.body);
|
||||
}
|
||||
});
|
||||
backendService.getAllStatus().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.status.push(...response.body);
|
||||
}
|
||||
});
|
||||
backendService.getCategories().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.categories.push(...response.body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private applyFilterParameters(params: ParamMap) {
|
||||
if (params.has('id')) {
|
||||
this.highlightId = parseInt(params.get('id'));
|
||||
}
|
||||
}
|
||||
|
||||
public deleteUserstory(userstory: ScrumUserstory) {
|
||||
this.backendService.deleteUserstory(userstory).subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
}
|
||||
});
|
||||
const index = this.items.indexOf(userstory);
|
||||
if (index !== -1) {
|
||||
this.items.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public openUserstoryForm(editUserstory?: ScrumUserstory) {
|
||||
const modalRef = this.modalService.open(UserstoryFormComponent, {
|
||||
backdrop: 'static',
|
||||
keyboard: true,
|
||||
});
|
||||
if (editUserstory === null) {
|
||||
modalRef.result.then((result) => {
|
||||
this.items.push(result);
|
||||
});
|
||||
}
|
||||
modalRef.componentInstance.userstory = editUserstory;
|
||||
}
|
||||
|
||||
public getNumberOfTasks(userstory: ScrumUserstory) {
|
||||
return this.tasks.filter((t) => t.userstoryid === userstory.id).length;
|
||||
}
|
||||
|
||||
public sortById() {
|
||||
this.doNumericSort('id', (us) => us.id);
|
||||
}
|
||||
|
||||
public sortByTitle() {
|
||||
this.doStringSort('title', (us) => us.title);
|
||||
}
|
||||
|
||||
public sortByPrio() {
|
||||
this.doNumericSort('priority', (us) => getNumberForPriority(us.priority));
|
||||
}
|
||||
|
||||
public sortByTasks() {
|
||||
this.doNumericSort('tasks', (us) => this.getNumberOfTasks(us));
|
||||
}
|
||||
|
||||
sortByStatus() {
|
||||
this.doNumericSort('statusid', (us) => us.statusid);
|
||||
}
|
||||
sortByCategory() {
|
||||
this.doNumericSort('categoryid', (us) => us.categoryid);
|
||||
}
|
||||
|
||||
getStatusTitleById(id) {
|
||||
var status = this.status.find((x) => x.id === id);
|
||||
if (!status) {
|
||||
return 'N/A';
|
||||
}
|
||||
return status.title;
|
||||
}
|
||||
|
||||
getCategoryTitleById(id) {
|
||||
var category = this.categories.find((x) => x.id === id);
|
||||
if (!category) {
|
||||
return 'N/A';
|
||||
}
|
||||
return category.title;
|
||||
}
|
||||
}
|
@ -1,95 +1,4 @@
|
||||
<div class="mx-5 my-3">
|
||||
|
||||
<h3 class="my-1">Userstories</h3>
|
||||
|
||||
<button class="btn btn-secondary my-3" (click)="openUserstoryForm()">Neue Userstory</button>
|
||||
|
||||
<table class="table">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th (click)="sortById()" class="sortable">
|
||||
<span>ID</span>
|
||||
<span *ngIf="sortBy === 'id'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByTitle()" class="sortable">
|
||||
<span>Titel</span>
|
||||
<span *ngIf="sortBy === 'title'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByTasks()" class="sortable">
|
||||
<span>Tasks</span>
|
||||
<span *ngIf="sortBy === 'tasks'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByStatus()" class="sortable">
|
||||
<span>Status</span>
|
||||
<span *ngIf="sortBy === 'statusid'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByPrio()" class="sortable">
|
||||
<span>Priorität</span>
|
||||
<label class="pl-3" (click)="$event.stopPropagation()">
|
||||
<select [(ngModel)]="filterPriority">
|
||||
<option [ngValue]="null" selected></option>
|
||||
<option *ngFor="let p of getAllPriorities()" [ngValue]="p">{{p}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<span *ngIf="sortBy === 'priority'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="sortByCategory()" class="sortable">
|
||||
<span>Category</span>
|
||||
<span *ngIf="sortBy === 'categoryid'" class="pl-3">
|
||||
<span *ngIf="sortDescending"><i class="fa fa-sort-up"></i></span>
|
||||
<span *ngIf="sortDescending === false"><i class="fa fa-sort-down"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr *ngFor="let userstory of filteredItems" [class.table-info]="userstory.id === highlightId">
|
||||
<td>{{userstory.id}}</td>
|
||||
<td>{{userstory.title}}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/tasks', {userstoryId: userstory.id}]">
|
||||
{{getNumberOfTasks(userstory)}} Tasks
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/status', {id: userstory.statusid}]">
|
||||
{{getStatusTitleById(userstory.statusid)}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{userstory.priority}}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/categories', {id: userstory.categoryid}]">
|
||||
{{getCategoryTitleById(userstory.categoryid)}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" rel="tooltip" (click)="openUserstoryForm(userstory)" class="btn btn-success btn-sm btn-icon">
|
||||
<i class="fa fa-pencil-alt"></i>
|
||||
</button>
|
||||
<button type="button" rel="tooltip" (click)="deleteUserstory(userstory)" class="btn btn-danger btn-sm btn-icon">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
<app-userstory-inner-table [items]="items"></app-userstory-inner-table>
|
||||
</div>
|
||||
|
@ -1,49 +1,18 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
BackendService,
|
||||
ScrumTask,
|
||||
ScrumUserstory,
|
||||
ScrumStatus,
|
||||
ScrumCategory,
|
||||
} from '../services/backend.service';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TableComponentBase } from '../services/table-component.base';
|
||||
import { getNumberForPriority } from '../services/sorting.service';
|
||||
import { UserstoryFormComponent } from '../userstory-form/userstory-form.component';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-userstory-table',
|
||||
templateUrl: './userstory-table.component.html',
|
||||
styleUrls: ['./userstory-table.component.css'],
|
||||
})
|
||||
export class UserstoryTableComponent extends TableComponentBase<
|
||||
ScrumUserstory
|
||||
> {
|
||||
public tasks: ScrumTask[] = [];
|
||||
public filterPriority: string | null = null;
|
||||
public highlightId: number;
|
||||
public status: ScrumStatus[] = [];
|
||||
public categories: ScrumCategory[] = [];
|
||||
|
||||
public get filteredItems() {
|
||||
return this.items.filter(
|
||||
(task) =>
|
||||
this.filterPriority === null || task.priority === this.filterPriority
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private backendService: BackendService,
|
||||
private modalService: NgbModal,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {
|
||||
super();
|
||||
|
||||
this.applyFilterParameters(this.route.snapshot.paramMap);
|
||||
this.route.paramMap.subscribe((map) => this.applyFilterParameters(map));
|
||||
export class UserstoryTableComponent {
|
||||
public items: ScrumUserstory[] = [];
|
||||
|
||||
constructor(private backendService: BackendService) {
|
||||
backendService.getUserstories().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
@ -51,100 +20,5 @@ export class UserstoryTableComponent extends TableComponentBase<
|
||||
this.items.push(...response.body);
|
||||
}
|
||||
});
|
||||
backendService.getTasks().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.tasks.push(...response.body);
|
||||
}
|
||||
});
|
||||
backendService.getAllStatus().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.status.push(...response.body);
|
||||
}
|
||||
});
|
||||
backendService.getCategories().subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
} else {
|
||||
this.categories.push(...response.body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private applyFilterParameters(params: ParamMap) {
|
||||
if (params.has('id')) {
|
||||
this.highlightId = parseInt(params.get('id'));
|
||||
}
|
||||
}
|
||||
|
||||
public deleteUserstory(userstory: ScrumUserstory) {
|
||||
this.backendService.deleteUserstory(userstory).subscribe((response) => {
|
||||
if (response.status > 399) {
|
||||
alert('Fehler');
|
||||
}
|
||||
});
|
||||
const index = this.items.indexOf(userstory);
|
||||
if (index !== -1) {
|
||||
this.items.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public openUserstoryForm(editUserstory?: ScrumUserstory) {
|
||||
const modalRef = this.modalService.open(UserstoryFormComponent, {
|
||||
backdrop: 'static',
|
||||
keyboard: true,
|
||||
});
|
||||
if (editUserstory === null) {
|
||||
modalRef.result.then((result) => {
|
||||
this.items.push(result);
|
||||
});
|
||||
}
|
||||
modalRef.componentInstance.userstory = editUserstory;
|
||||
}
|
||||
|
||||
public getNumberOfTasks(userstory: ScrumUserstory) {
|
||||
return this.tasks.filter((t) => t.userstoryid === userstory.id).length;
|
||||
}
|
||||
|
||||
public sortById() {
|
||||
this.doNumericSort('id', (us) => us.id);
|
||||
}
|
||||
|
||||
public sortByTitle() {
|
||||
this.doStringSort('title', (us) => us.title);
|
||||
}
|
||||
|
||||
public sortByPrio() {
|
||||
this.doNumericSort('priority', (us) => getNumberForPriority(us.priority));
|
||||
}
|
||||
|
||||
public sortByTasks() {
|
||||
this.doNumericSort('tasks', (us) => this.getNumberOfTasks(us));
|
||||
}
|
||||
|
||||
sortByStatus() {
|
||||
this.doNumericSort('statusid', (us) => us.statusid);
|
||||
}
|
||||
sortByCategory() {
|
||||
this.doNumericSort('categoryid', (us) => us.categoryid);
|
||||
}
|
||||
|
||||
getStatusTitleById(id) {
|
||||
var status = this.status.find((x) => x.id === id);
|
||||
if (!status) {
|
||||
return 'N/A';
|
||||
}
|
||||
return status.title;
|
||||
}
|
||||
|
||||
getCategoryTitleById(id) {
|
||||
var category = this.categories.find((x) => x.id === id);
|
||||
if (!category) {
|
||||
return 'N/A';
|
||||
}
|
||||
return category.title;
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,9 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
.custom-text-secondary {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.white-content .custom-text-secondary {
|
||||
color: black !important;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user