Merge branch 'merge/dashboard-sidebar-backlog' into 'master'

Merge dashboard + sidebar + backlog

See merge request scrum-taskboard/frontend!6
This commit is contained in:
ortni79929 2020-07-08 13:35:52 +02:00
commit 84d22de553
47 changed files with 3524 additions and 1410 deletions

1767
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
"@ng-bootstrap/ng-bootstrap": "^6.0.0", "@ng-bootstrap/ng-bootstrap": "^6.0.0",
"bootstrap": "^4.4.0", "bootstrap": "^4.4.0",
"chart.js": "^2.9.3", "chart.js": "^2.9.3",
"component": "^1.1.0",
"rxjs": "~6.5.4", "rxjs": "~6.5.4",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"

View File

@ -1,16 +1,17 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { UserstoryTableComponent } from './userstory-table/userstory-table.component'; import { UserstoryTableComponent } from './components/userstory-table/userstory-table.component';
import { TaskTableComponent } from './task-table/task-table.component'; import { TaskTableComponent } from './components/task-table/task-table.component';
import { SprintTableComponent } from './sprint-table/sprint-table.component'; import { SprintTableComponent } from './components/sprint-table/sprint-table.component';
// import { DashboardComponent } from './dashboard/dashboard.component'; import {BacklogComponent} from './components/backlog-table/backlog.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'tasks', component: TaskTableComponent }, { path: 'tasks', component: TaskTableComponent },
{ path: 'userstories', component: UserstoryTableComponent }, { path: 'userstories', component: UserstoryTableComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'sprints', component: SprintTableComponent }, { path: 'sprints', component: SprintTableComponent },
// { path: 'dashboard', component: DashboardComponent }, { path: 'backlog', component: BacklogComponent },
{ path: '', redirectTo: '/tasks', pathMatch: 'full' }, { path: '', redirectTo: '/tasks', pathMatch: 'full' },
]; ];

View File

@ -0,0 +1,22 @@
.sidebar {
width: 150px;
position: relative;
float: left;
margin-top: 10px;
bottom: 0;
opacity: 95%;
}
.nav a {
font-size: 1em;
}
.nav a:hover:not(.active) {
font-size: 1.15em;
}
.content {
position: relative;
float: right;
margin-top: 10px;
}

View File

@ -1,39 +1,80 @@
<router-outlet></router-outlet> <div class="container-fluid">
<div class=" fixed-plugin">
<div class=" show-dropdown" ngbDropdown> <div class="sidebar">
<a data-toggle="dropdown" ngbDropdownToggle> <div class="logo">
<i class="fa fa-cog fa-2x"></i> <a class="simple-text logo-normal">
Taskboard
</a> </a>
<ul ngbDropdownMenu> </div>
<!-- <ul class="nav">
<li class=" header-title">Sidebar Background</li> <li class="active ">
<li class=" adjustments-line"> <a [routerLink]="['dashboard']">
<a class=" switch-trigger background-color" href="javascript:void(0)"> <p>Dashboard</p>
<div class=" badge-colors text-center">
<span
class=" badge filter badge-danger"
[ngClass]="{'active':sidebarColor==='red'}" (click)="changeSidebarColor('red')"
>
</span>
<span
class=" badge filter badge-primary"
[ngClass]="{'active':sidebarColor==='primary'}" (click)="changeSidebarColor('primary')"
>
</span>
<span class=" badge filter badge-info" [ngClass]="{'active':sidebarColor==='blue'}" (click)="changeSidebarColor('blue')"> </span>
<span class=" badge filter badge-success" [ngClass]="{'active':sidebarColor==='green'}" (click)="changeSidebarColor('green')">
</span>
</div>
<div class=" clearfix"></div>
</a> </a>
</li> </li>
--> <li>
<li class=" adjustments-line text-center color-change"> <a [routerLink]="['backlog']">
<span class=" color-label"> LIGHT MODE </span> <p>Backlog</p>
<span class=" badge light-badge mr-2" (click)="changeDashboardColor('white-content')"> </span> </a>
<span class=" badge dark-badge ml-2" (click)="changeDashboardColor('black-content')"> </span> </li>
<span class=" color-label"> DARK MODE </span> <li>
<a [routerLink]="['sprints']">
<p>Sprints</p>
</a>
</li>
<li>
<a [routerLink]="['userstories']">
<p>Userstories</p>
</a>
</li>
<li>
<a [routerLink]="['tasks']">
<p>Tasks</p>
</a>
</li> </li>
</ul> </ul>
</div>
<router-outlet></router-outlet>
<div class="content">
<div class=" fixed-plugin">
<div class=" show-dropdown" ngbDropdown>
<a data-toggle="dropdown" ngbDropdownToggle>
<i class="fa fa-cog fa-2x"></i>
</a>
<ul ngbDropdownMenu>
<!--
<li class=" header-title">Sidebar Background</li>
<li class=" adjustments-line">
<a class=" switch-trigger background-color" href="javascript:void(0)">
<div class=" badge-colors text-center">
<span
class=" badge filter badge-danger"
[ngClass]="{'active':sidebarColor==='red'}" (click)="changeSidebarColor('red')"
>
</span>
<span
class=" badge filter badge-primary"
[ngClass]="{'active':sidebarColor==='primary'}" (click)="changeSidebarColor('primary')"
>
</span>
<span class=" badge filter badge-info" [ngClass]="{'active':sidebarColor==='blue'}" (click)="changeSidebarColor('blue')"> </span>
<span class=" badge filter badge-success" [ngClass]="{'active':sidebarColor==='green'}" (click)="changeSidebarColor('green')">
</span>
</div>
<div class=" clearfix"></div>
</a>
</li>
-->
<li class=" adjustments-line text-center color-change">
<span class=" color-label"> LIGHT MODE </span>
<span class=" badge light-badge mr-2" (click)="changeDashboardColor('white-content')"> </span>
<span class=" badge dark-badge ml-2" (click)="changeDashboardColor('black-content')"> </span>
<span class=" color-label"> DARK MODE </span>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</div>

View File

@ -7,14 +7,16 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { BackendService } from './services/backend.service'; import { BackendService } from './services/backend.service';
import { TaskFormComponent } from './task-form/task-form.component'; import { TaskFormComponent } from './components/task-form/task-form.component';
import { UserstoryFormComponent } from './userstory-form/userstory-form.component'; import { UserstoryFormComponent } from './components/userstory-form/userstory-form.component';
import { SprintFormComponent } from './sprint-form/sprint-form.component'; import { SprintFormComponent } from './components/sprint-form/sprint-form.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { UserstoryTableComponent } from './userstory-table/userstory-table.component'; import { UserstoryTableComponent } from './components/userstory-table/userstory-table.component';
import { TaskTableComponent } from './task-table/task-table.component'; import { TaskTableComponent } from './components/task-table/task-table.component';
import { SprintTableComponent } from './sprint-table/sprint-table.component'; import { SprintTableComponent } from './components/sprint-table/sprint-table.component';
// import { DashboardComponent } from './dashboard/dashboard.component'; import { DashboardComponent } from './components/dashboard/dashboard.component';
import { UserstoryInnerTableComponent } from './components/userstory-inner-table/userstory-inner-table.component';
import { BacklogComponent } from './components/backlog-table/backlog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -26,7 +28,9 @@ import { SprintTableComponent } from './sprint-table/sprint-table.component';
UserstoryTableComponent, UserstoryTableComponent,
SprintFormComponent, SprintFormComponent,
SprintTableComponent, SprintTableComponent,
// DashboardComponent, DashboardComponent,
UserstoryInnerTableComponent,
BacklogComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -1,3 +1,3 @@
th.sortable:hover { th.sortable:hover {
text-decoration: underline; text-decoration: underline;
} }

View File

@ -0,0 +1,62 @@
<div class="container-fluid">
<div class="content">
<h3>Backlog</h3>
<div class="row">
<div class="col-lg-6 container-fluid">
</div>
<div align="right" class="col-lg-6 container-fluid">
<button class="btn btn-secondary" (click)="openSprintForm()">Neuer Sprint</button>
</div>
</div>
<div class="row">
<div class="col-lg-6 container-fluid">
<h4>Backlog</h4>
<div *ngFor="let story of backlog" class="col-lg-6 container-fluid">
<div class="card" style="width: 150%;">
<div class="card-body">
<h4 class="card-title">{{story.title}}</h4>
<h6 class="card-subtitle mb-2 text-muted">Prio: {{story.priority}}</h6>
<p class="card-text">{{story.content}}</p>
<div title="Badges">
<span class="badge badge-primary">Category: {{story.categoryid || "N/A"}}</span>
<span class="badge badge-info">Status: {{story.statusid || "N/A"}}</span>
</div>
<div style="text-align: right;">
<button type="button" rel="tooltip" (click)="addToSprintBacklog(story)"
class="btn btn-sm btn-success btn-icon">
<i class="fas fa-plus-square"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div></div>
<div class="col-lg-6 container-fluid">
<h4>Sprint-Backlog - Aktueller Sprint: {{this.currentSprint.title}}</h4>
<div *ngFor="let story of choosen" class="col-lg-6 container-fluid">
<div class="card" style="width: 150%;">
<div class="card-body">
<h4 class="card-title">{{story.title}}</h4>
<h6 class="card-subtitle mb-2 text-muted">Prio: {{story.priority}}</h6>
<p class="card-text">{{story.content}}</p>
<div title="Badges">
<span class="badge badge-primary">Category: {{story.categoryid || "N/A"}}</span>
<span class="badge badge-info">Status: {{story.statusid || "N/A"}}</span>
</div>
<div style="text-align: right;">
<button type="button" rel="tooltip" (click)="deleteFromSprintBacklog(story)"
class="btn btn-danger btn-sm btn-icon">
<i class="fa fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,160 @@
import { Component } from '@angular/core';
import {
BackendService,
ScrumTask,
ScrumUserstory,
ScrumStatus,
ScrumCategory,
ScrumSprint,
} 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 } from '@angular/router';
import { SprintFormComponent } from '../sprint-form/sprint-form.component';
@Component({
selector: 'app-backlog',
templateUrl: './backlog.component.html',
styleUrls: ['./backlog.component.css'],
})
export class BacklogComponent extends TableComponentBase<
ScrumUserstory
> {
public tasks: ScrumTask[] = [];
public filterPriority: string | null = null;
public status: ScrumStatus[] = [];
public categories: ScrumCategory[] = [];
public sprints: ScrumSprint[] = [];
public backlog: ScrumUserstory[] = [];
public choosen: ScrumUserstory[] = [];
constructor(
private backendService: BackendService,
private modalService: NgbModal,
private route: ActivatedRoute,
) {
super();
backendService.getSprints().subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
} else {
this.sprints.push(...response.body);
}
});
backendService.getUserstories().subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
} else {
this.backlog = response.body.filter(u => u.sprintid == null);
this.choosen = response.body.filter(u => u.sprintid == this.currentSprint.id);
}
});
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);
}
});
}
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,
size: 'lg'
});
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;
}
getCategoryTitleById(id) {
var category = this.categories.find((x) => x.id === id);
if (!category) {
return 'N/A';
}
return category.title;
}
// Sprint-Backlog
public addToSprintBacklog(userstory: ScrumUserstory) {
this.choosen.push(userstory);
const index = this.backlog.indexOf(userstory);
this.backlog.splice(index, 1);
userstory.sprintid = this.currentSprint.id;
this.backendService.putUserstory(userstory).subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
}
});
}
public deleteFromSprintBacklog(userstory: ScrumUserstory){
const index = this.choosen.indexOf(userstory);
this.choosen.splice(index, 1);
this.backlog.push(userstory)
userstory.sprintid = null;
this.backendService.putUserstory(userstory).subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
}
});
}
public get currentSprint(): ScrumSprint {
const now = Date.now();
return this.sprints.find(s => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now);
}
public openSprintForm(editSprint?: ScrumSprint) {
const modalRef = this.modalService.open(SprintFormComponent, {
backdrop: 'static',
keyboard: true,
});
if (editSprint === null) {
modalRef.result.then(result => {
this.items.push(result);
});
}
modalRef.componentInstance.sprint = editSprint;
}
}

View File

@ -0,0 +1,7 @@
.text-large {
font-size: 1.2rem;
}
.text-very-large {
font-size: 2.4rem;
}

View File

@ -0,0 +1,101 @@
<div class="container-fluid">
<div class="content">
<h3>Dashboard</h3>
<div>
<div class="row px-3 py-2">
<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>
<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
</span>
</div>
<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>
</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>
</div>
</div>

View File

@ -0,0 +1,160 @@
import {Component, OnChanges} from '@angular/core';
import {forkJoin} from 'rxjs';
import Chart from 'chart.js';
import {BackendService, ScrumSprint, ScrumStatus, ScrumUserstory} from '../../services/backend.service';
@Component({
selector: 'app-dashboard',
templateUrl: 'dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent {
/**
* Returns the status that are used by at least one userstory.
*/
public get usedStatus(): ScrumStatus[] {
return this.status.filter(s => this.selectedSprintUserstories.find(us => us.statusid === s.id) !== undefined);
}
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) {
// 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()}`;
}
private createChart() {
// @ts-ignore
const context = document.getElementById('done-stories-chart').getContext('2d');
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[] {
const baseColors = [
'rgb(255, 153, 102)',
'rgb(255, 102, 102)',
'rgb(153, 204, 255)',
'rgb(102, 153, 102)',
'rgb(204, 204, 153)',
'rgb(153, 102, 204)',
'rgb(204, 102, 102)',
'rgb(255, 204, 153)',
'rgb(153, 102, 255)',
'rgb(204, 204, 204)',
'rgb(102, 255, 204)',
'rgb(102, 153, 255)',
'rgb(153, 102, 153)',
'rgb(204, 204, 255)',
];
const colors = [];
while (colors.length < this.usedStatus.length) {
colors.push(...baseColors);
}
return colors;
}
public getNumberOfUserstoriesByStatus(status: ScrumStatus): number {
return this.selectedSprintUserstories.filter(us => us.statusid === status.id).length;
}
public getRemainingDaysInSprint(): number {
if (this.selectedSprint === undefined) {
return undefined;
}
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 = Date.now();
const sprint = this.selectedSprint;
if (sprint === undefined) {
return undefined;
}
const deltaFromNow = Date.parse(sprint.endDate) - now;
const deltaFromStart = Date.parse(sprint.endDate) - Date.parse(sprint.startDate);
return Math.floor(3 * deltaFromNow / deltaFromStart);
}
}

View File

@ -0,0 +1,35 @@
<!--Popup form to create an modify sprints-->
<div class="card" style="width: 100%;">
<div class="container">
<div class="card-body">
<div style="text-align: right;">
<i class="fa fa-times fa-2x" (click)="onClose()"></i>
</div>
<h4 class="card-title">Neuen Sprint anlegen</h4>
<form (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="Title">Titel</label>
<input type="text" class="form-control" id="Title" required name="title" [(ngModel)]="sprint.title"
id="titleField">
</div>
<div class="form-group">
<label for="date">Startdatum</label>
<input #startDate type="Date" class="form-control" id="Date" required name="date"
[value]="sprint.startDate | date: 'yyyy-MM-dd'" (change)="sprint.startDate=startDate.value"
id="startDateField">
</div>
<div class="form-group">
<label for="Date">Enddatum</label>
<input #endDate type="date" class="form-control" id="Date" required name="date"
[value]="sprint.endDate | date: 'yyyy-MM-dd'" (change)="sprint.endDate=endDate.value" id="endDateField">
</div>
<div>
<button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal">Abbrechen
</button>
<button type="submit" class="btn btn-primary">Sprint starten</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,60 +1,60 @@
import { Component, OnInit, Input } from '@angular/core'; // Importing necessary components and interfaces.
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { Component, OnInit, Input } from '@angular/core';
import { import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
BackendService, import {
ScrumTask, BackendService,
Priority, ScrumSprint
ScrumStatus, } from '../../services/backend.service';
ScrumCategory,
ScrumUser, @Component({
ScrumProject, selector: 'app-task-form',
ScrumUserstory, templateUrl: './sprint-form.component.html',
ScrumSprint styleUrls: ['./sprint-form.component.css']
} from '../services/backend.service'; })
import { Observable } from 'rxjs';
import { HttpResponse } from '@angular/common/http'; // Class implements the logic for a popup window form to create and modify sprints.
export class SprintFormComponent implements OnInit {
@Component({ @Input() public sprint: ScrumSprint;
selector: 'app-task-form', public editing: Boolean;
templateUrl: './sprint-form.component.html', public sprintid: string;
styleUrls: [ './sprint-form.component.css' ]
}) constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) { }
export class SprintFormComponent implements OnInit {
@Input() public sprint: ScrumSprint; // If no sprint exists a new one will be created.
public editing: Boolean; // In other cases the sprint exists and gets modifiable.
public sprintid: string; ngOnInit(): void {
if (this.sprint === null || this.sprint === undefined) {
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) {} this.sprint = { title: '', startDate: '', endDate: '' };
this.editing = false;
ngOnInit(): void { } else {
if (this.sprint === null || this.sprint === undefined) { this.editing = true;
this.sprint = { title: '', startDate: new Date(), endDate: new Date()}; //project id missing... }
this.editing = false; document.getElementById('titleField').focus();
} else { }
this.editing = true;
} // A new created sprint will be saved in backend (POST).
document.getElementById('titleField').focus(); // If a sprint already exists, modifying results an update (PUT) to the backend.
} onSubmit() {
if (this.editing) {
onSubmit() { this.backendService.putSprint(this.sprint).subscribe((response) => {
if (this.editing) { if (response.status > 399) {
this.backendService.putSprint(this.sprint).subscribe((response) => { alert('Fehler');
if (response.status > 399) { }
alert('Fehler'); });
} } else {
}); this.backendService.postSprint(this.sprint).subscribe((response) => {
} else { console.log('Sprint gespeichert!');
this.backendService.postSprint(this.sprint).subscribe((response) => { if (response.status > 399) {
console.log('Sprint gespeichert!'); alert('Fehler');
if (response.status > 399) { }
alert('Fehler'); });
} }
}); // Closes the popup window after submitting/canceling.
} this.activeModalService.close(this.sprint);
this.activeModalService.close(this.sprint); }
}
// Closes the popup form window (by clicking "close button").
onClose() { onClose() {
this.activeModalService.dismiss(this.sprint); this.activeModalService.dismiss(this.sprint);
} }
} }

View File

@ -0,0 +1,66 @@
<div class="container-fluid">
<div class="content">
<h3>
Sprints
</h3>
<button class="btn btn-secondary" (click)="openSprintForm()">Neuer Sprint</button>
<table class="table">
<thead>
<tr>
<th (click)="sortById()" class="sortable">
<span>ID</span>
<span>
<span *ngIf="sortBy != 'id'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'id'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'id'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTitle()" class="sortable">
<span>Titel</span>
<span>
<span *ngIf="sortBy != 'title'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'title'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByStartDate()" class="sortable">
<span>Start</span>
<span>
<span *ngIf="sortBy != 'startDate'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'startDate'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'startDate'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByEndDate()" class="sortable">
<span>End</span>
<span>
<span *ngIf="sortBy != 'endDate'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'endDate'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'endDate'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let sprint of filteredItems" [class.table-info]="sprint.id === highlightId">
<td>{{sprint.id}}</td>
<td>{{sprint.title}}</td>
<td>{{sprint.startDate | date:'dd.MM.yyyy'}}</td>
<td>{{sprint.endDate | date:'dd.MM.yyyy'}}</td>
<td>
<button type="button" rel="tooltip" (click)="openSprintForm(sprint)" class="btn btn-success btn-sm btn-icon">
<i class="fa fa-pencil-alt"></i>
</button>
<button type="button" rel="tooltip" (click)="deleteSprint(sprint)" class="btn btn-danger btn-sm btn-icon">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,87 +1,87 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {BackendService, ScrumSprint} from '../services/backend.service'; import {BackendService, ScrumSprint} from '../../services/backend.service';
import {TableComponentBase} from '../services/table-component.base'; import {TableComponentBase} from '../../services/table-component.base';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {ActivatedRoute, ParamMap, Router} from '@angular/router'; import {ActivatedRoute, ParamMap, Router} from '@angular/router';
import { SprintFormComponent } from '../sprint-form/sprint-form.component'; import { SprintFormComponent } from '../sprint-form/sprint-form.component';
@Component({ @Component({
selector: 'app-sprint', selector: 'app-sprint',
templateUrl: './sprint-table.component.html', templateUrl: './sprint-table.component.html',
styleUrls: ['./sprint-table.component.css'] styleUrls: ['./sprint-table.component.css']
}) })
export class SprintTableComponent extends TableComponentBase<ScrumSprint> { export class SprintTableComponent extends TableComponentBase<ScrumSprint> {
public filterSprintId: number | null = null; public filterSprintId: number | null = null;
public highlightId: number; public highlightId: number;
public get filteredItems() { public get filteredItems() {
return this.items.filter(sprint => return this.items.filter(sprint =>
(this.filterSprintId === null || sprint.id === this.filterSprintId) (this.filterSprintId === null || sprint.id === this.filterSprintId)
); );
} }
constructor( constructor(
private backendService: BackendService, private modalService: NgbModal, private backendService: BackendService, private modalService: NgbModal,
private route: ActivatedRoute, private router: Router private route: ActivatedRoute, private router: Router
) { ) {
super(); super();
this.applyFilterParameters(route.snapshot.paramMap); this.applyFilterParameters(route.snapshot.paramMap);
route.paramMap.subscribe(map => this.applyFilterParameters(map)); route.paramMap.subscribe(map => this.applyFilterParameters(map));
backendService.getSprints().subscribe(response => { backendService.getSprints().subscribe(response => {
if (response.status > 399) { if (response.status > 399) {
alert('Fehler'); alert('Fehler');
} else { } else {
this.items.push(...response.body); this.items.push(...response.body);
} }
}); });
} }
private applyFilterParameters(params: ParamMap) { private applyFilterParameters(params: ParamMap) {
if (params.has('id')) { if (params.has('id')) {
this.highlightId = parseInt(params.get('id')); this.highlightId = parseInt(params.get('id'));
} }
} }
public deleteSprint(sprint: ScrumSprint) { public deleteSprint(sprint: ScrumSprint) {
this.backendService.deleteSprint(sprint).subscribe(response => { this.backendService.deleteSprint(sprint).subscribe(response => {
if (response.status > 399) { if (response.status > 399) {
alert('Fehler'); alert('Fehler');
} }
}); });
const index = this.items.indexOf(sprint); const index = this.items.indexOf(sprint);
if (index !== -1) { if (index !== -1) {
this.items.splice(index, 1); this.items.splice(index, 1);
} }
} }
public openSprintForm(editSprint?: ScrumSprint) { public openSprintForm(editSprint?: ScrumSprint) {
const modalRef = this.modalService.open(SprintFormComponent, { const modalRef = this.modalService.open(SprintFormComponent, {
backdrop: 'static', backdrop: 'static',
keyboard: true, keyboard: true,
}); });
if (editSprint === null) { if (editSprint === null) {
modalRef.result.then(result => { modalRef.result.then(result => {
this.items.push(result); this.items.push(result);
}); });
} }
modalRef.componentInstance.sprint = editSprint; modalRef.componentInstance.sprint = editSprint;
} }
sortById() { sortById() {
this.doNumericSort('id', sprint => sprint.id); this.doNumericSort('id', sprint => sprint.id);
} }
sortByTitle() { sortByTitle() {
this.doStringSort('title', sprint => sprint.title); this.doStringSort('title', sprint => sprint.title);
} }
sortByStartDate() { sortByStartDate() {
this.doDateSort('startDate', sprint => sprint.startDate); this.doStringSort('startDate', sprint => sprint.startDate);
} }
sortByEndDate() { sortByEndDate() {
this.doDateSort('endDate', sprint => sprint.endDate); this.doStringSort('endDate', sprint => sprint.endDate);
} }
} }

View File

@ -0,0 +1,118 @@
<!--Popup form to create and modify a task-->
<div class="card" style="width: 100%;">
<div class="container">
<div class="card-body">
<div style="text-align: right;">
<i class="fa fa-times fa-2x" (click)="onClose()"></i>
</div>
<div class="row">
<div class="col-8" style="text-align: left;">
<h4 class="card-title">Neuen Task anlegen</h4>
<div ngbDropdown class="dropdown card-text">
<span ngbDropdownToggle class="dropdown" id="dropdownMenuUserstory" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Gehört zu Story: {{getUserstoryTitleById(task.userstoryid) || "Keine"}}
<i class="fa fa-caret-down"></i>
</span>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenuUserstory">
<option ngbDropdownItem *ngFor="let userstory of userstories"
(click)="task.userstoryid = userstory.id">{{ userstory.title }}</option>
</div>
</div>
</div>
<div class="col-4"></div>
</div>
<form (ngSubmit)="onSubmit()">
<div class="row">
<div class="col-8">
<div class="form-group">
<label for="Title">Titel</label>
<input type="text" class="form-control" id="Title" required name="title"
[(ngModel)]="task.title" id="titleField" />
</div>
<div class="form-group">
<label for="Inhalt">What to do?</label>
<textarea type="text" class="form-control" id="Story" required name="story" rows="5"
[(ngModel)]="task.content"></textarea>
</div>
</div>
<div class="col-4">
<div ngbDropdown class="dropdown">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Prio: {{task.priority}}
</button>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" (click)="task.priority=p">
{{p}}</option>
</div>
</div>
<div class="form-group">
<div ngbDropdown class="dropdown" [autoClose]="false">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
id="dropdownMenu3" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
Status: {{getStatusTitleById(task.statusid)}}
</button>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
<div class="card-text" for="Inhalt">Status wählen</div>
<option disable-auto-close ngbDropdownItem *ngFor="let status of allStatus"
(click)="task.statusid = status.id">{{ status.title }}</option>
<div class="dropdown-divider"></div>
<div class="card-text" for="Inhalt">Neuer Status</div>
<input #statusname type="text" id="statusname" class="dropdown-item"
(change)="status.title=statusname.value" placeholder="New Title..."
style="background-color: rgba(211, 211, 211, 0.342);">
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
(click)="createTaskStatus(status)">Status anlegen</button>
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
(click)="deleteStatus(task.statusid)">Status löschen</button>
</div>
</div>
<div class="dropdown-menu">
<select class="form-control custom-select mr-sm-2" id="status" required name="status"
[(ngModel)]="task.statusid">
<option *ngFor="let status of allStatus" [value]="status.id">{{
status.title
}}</option>
</select>
</div>
</div>
<div class="form-group">
<div ngbDropdown class="dropdown" [autoClose]="true">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
id="dropdownMenu4" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
Bearbeiter: {{getAuthorById(task.assignedtoid)}}
</button>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
<div class="card-text" for="Inhalt">User wählen</div>
<option disable-auto-close ngbDropdownItem *ngFor="let user of allUser"
(click)="task.assignedtoid = user.id">{{ user.name }}</option>
</div>
</div>
<div class="dropdown-menu">
<select class="form-control custom-select mr-sm-2" id="assignedto" required name="assignedto"
[(ngModel)]="task.assignedtoid">
<option *ngFor="let user of allUser" [value]="user.id">{{
user.name
}}</option>
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal">
Abbrechen
</button>
<button type="submit" class="btn btn-primary">Erstellen</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,158 +1,200 @@
import { Component, OnInit, Input } from '@angular/core'; // Importing necessary components and interfaces.
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { Component, OnInit, Input } from '@angular/core';
import { import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
BackendService, import {
ScrumTask, BackendService,
Priority, ScrumTask,
ScrumStatus, Priority,
ScrumCategory, ScrumStatus,
ScrumUser, ScrumCategory,
ScrumProject, ScrumUser,
ScrumUserstory ScrumProject,
} from '../services/backend.service'; ScrumUserstory
import { Observable } from 'rxjs'; } from '../../services/backend.service';
import { HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs';
import { HttpResponse } from '@angular/common/http';
@Component({
selector: 'app-task-form', @Component({
templateUrl: './task-form.component.html', selector: 'app-task-form',
styleUrls: [ './task-form.component.css' ] templateUrl: './task-form.component.html',
}) styleUrls: ['./task-form.component.css']
export class TaskFormComponent implements OnInit { })
@Input() public task: ScrumTask;
public editing: boolean; // Class implements the logic for a popup window form to create and modify tasks.
public creating: boolean; export class TaskFormComponent implements OnInit {
public userstoryId: string; @Input() public task: ScrumTask;
public userstories: any[] = []; public editing: boolean;
public allStatus: any[] = []; public creating: boolean;
public status: ScrumStatus = {title: "", description: ""}; public userstoryId: string;
public userstories: any[] = [];
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) { public allStatus: any[] = [];
this.getUserStories(); public status: ScrumStatus = { title: "", description: "" };
this.getTaskStatus(); public allUser: any[] = [];
} public user: ScrumUser = { name: "" };
ngOnInit(): void { constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) {
if (this.task === null || this.task === undefined) { this.getUserStories();
this.task = { title: '' }; this.getTaskStatus();
this.editing = false; this.getAllUsers();
this.creating = false; }
} else if (this.task.userstoryid) {
this.editing = true; // If no task exists a new one will be created.
} else { // In other cases the task exists and gets modifiable.
this.creating = true; ngOnInit(): void {
} if (this.task === null || this.task === undefined) {
document.getElementById('titleField').focus(); this.task = { title: '' };
this.getRelatedStory(); this.editing = false;
} this.creating = false;
} else if (this.task.userstoryid) {
onSubmit() { this.editing = true;
if (this.editing) { } else {
this.backendService.putTask(this.task).subscribe((response) => { this.creating = true;
if (response.status > 399) { }
alert('Fehler'); document.getElementById('titleField').focus();
} this.getRelatedStory();
}); }
} else { // A new created task will be saved in backend (POST).
this.backendService.postTask(this.task).subscribe((response) => { // If a task already exists, modifying results an update (PUT) to the backend.
if (response.status > 399) { onSubmit() {
alert('Fehler'); if (this.editing) {
} this.backendService.putTask(this.task).subscribe((response) => {
}); if (response.status > 399) {
} alert('Fehler');
this.activeModalService.close(this.task); }
} });
} else {
onClose() { this.backendService.postTask(this.task).subscribe((response) => {
this.activeModalService.dismiss(this.task); if (response.status > 399) {
} alert('Fehler');
}
getRelatedStory() { });
if(!this.task.userstoryid) }
{ // Closes the popup window after submitting/canceling.
return null; this.activeModalService.close(this.task);
} }
this.backendService.getUserstory(this.task.userstoryid).subscribe((response) => {
if (response.status > 399) { // Closes the popup form window (by clicking "close button").
alert('Fehler'); onClose() {
} else { this.activeModalService.dismiss(this.task);
this.userstoryId = response.body.title; }
}
}); // Getting the userstory which is related to a task.
} // The related story will be shown in popup window of a task.
getRelatedStory() {
getUserStories() { if (!this.task.userstoryid) {
this.backendService.getUserstories().subscribe((response) => { return null;
if (response.status > 399) { }
alert('Fehler'); this.backendService.getUserstory(this.task.userstoryid).subscribe((response) => {
} else { if (response.status > 399) {
this.userstories.push(...response.body); alert('Fehler');
} } else {
}); this.userstoryId = response.body.title;
} }
});
getTaskStatus() { }
this.backendService.getAllStatus().subscribe((response) => {
if (response.status > 399) { // Getting all userstories from backend to show in a dropdown in popup window.
alert('Fehler'); getUserStories() {
} else { this.backendService.getUserstories().subscribe((response) => {
this.allStatus.push(...response.body); if (response.status > 399) {
} alert('Fehler');
}); } else {
} this.userstories.push(...response.body);
}
createTaskStatus(status:ScrumStatus) { });
this.backendService.postStatus(status).subscribe((response) => { }
if (response.status > 399) {
alert('Fehler'); // Getting all available status from backend to list it in status-dropdown in popup window.
} getTaskStatus() {
else this.backendService.getAllStatus().subscribe((response) => {
{ if (response.status > 399) {
this.allStatus.push(response.body); alert('Fehler');
} } else {
}); this.allStatus.push(...response.body);
} }
});
deleteStatus(id: number) { }
var status = this.allStatus.find((x) => x.id === id);
this.backendService.deleteStatus(status).subscribe((response) => { // If desired a new arbitrary status (such as "Waiting") can be created, which will be stored in an array.
if (response.status > 399) { // The new status is available to all tasks.
alert('Fehler'); createTaskStatus(status: ScrumStatus) {
} this.backendService.postStatus(status).subscribe((response) => {
else if (response.status > 399) {
{ alert('Fehler');
const index = this.allStatus.indexOf(status); }
if (index !== -1) { else {
this.allStatus.splice(index, 1); this.allStatus.push(response.body);
} }
} });
this.task.statusid=null; }
});
} // A custom status can even be deleted if not used anymore.
// This will remove the status from status-array.
getAllPriorities(): string[] { deleteStatus(id: number) {
return Object.values(Priority); var status = this.allStatus.find((x) => x.id === id);
} this.backendService.deleteStatus(status).subscribe((response) => {
if (response.status > 399) {
getUserstoryTitleById(id: number): string{ alert('Fehler');
if (!id) { }
return null; else {
} const index = this.allStatus.indexOf(status);
var story = this.userstories.find((x) => x.id === id); if (index !== -1) {
if (!story) { this.allStatus.splice(index, 1);
return null; }
} }
return story.title; this.task.statusid = null;
} });
}
getStatusTitleById(id: number) :string{
if (!id) { // Getting the values of the Priority enum to be shown in a dropdown in popup window.
return null; getAllPriorities(): Priority[] {
} return Object.values(Priority);
var status = this.allStatus.find((x) => x.id === id); }
if (!status) {
return null; // necessary?????????????????????????????????????????????????????
} getUserstoryTitleById(id: number): string {
return status.title; if (!id) {
} return null;
} }
var story = this.userstories.find((x) => x.id === id);
if (!story) {
return null;
}
return story.title;
}
// Shows the before choosen status in the status-field in the popup window.
getStatusTitleById(id: number): string {
if (!id) {
return null;
}
var status = this.allStatus.find((x) => x.id === id);
if (!status) {
return null;
}
return status.title;
}
// Getting all taskboard users from backend to show in a dropdown in popup window.
getAllUsers() {
this.backendService.getUsers().subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
} else {
this.allUser.push(...response.body);
}
});
}
// Shows the before assigned user in the author-field in the popup window.
getAuthorById(id: number): string {
if (!id) {
return null;
}
var user = this.allUser.find((x) => x.id === id);
if (!user) {
return null;
}
return user.name;
}
}

View File

@ -0,0 +1,126 @@
<div class="container-fluid">
<div class="content">
<h3>
<a *ngIf="filterUserstoryId" [routerLink]="['/userstories', {id: filterUserstoryId}]">
Userstory #{{filterUserstoryId}}
&gt;
</a>
Tasks
</h3>
<div *ngIf="filterUserstoryId">
<a [routerLink]="'/tasks'">Alle Tasks anzeigen</a>
</div>
<button class="btn btn-secondary" (click)="openTaskForm()">Neuer Task</button>
<table class="table">
<thead>
<tr>
<th (click)="sortById()" class="sortable">
<span>ID</span>
<span>
<span *ngIf="sortBy != 'id'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'id'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTitle()" class="sortable">
<span>Titel</span>
<span>
<span *ngIf="sortBy != 'title'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'title'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTasks()" class="sortable">
<span>Userstory</span>
<span>
<span *ngIf="sortBy != 'userstory'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'userstory'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'userstory'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByStatus()" class="sortable">
<span>Status</span>
<span>
<span *ngIf="sortBy != 'statusid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'statusid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'statusid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th class="sortable">
<div class="d-inline-block">
<span (click)="sortByPrio()">Priorität: </span>
<div ngbDropdown class="d-inline-block">
<span id="dropdownBasic1" ngbDropdownToggle>{{filterPriority || "All"}}</span>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<option ngbDropdownItem (click)="filterPriority=null">All</option>
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" (click)="filterPriority=p">{{p}}</option>
</div>
</div>
<span (click)="sortByPrio()">
<span *ngIf="sortBy != 'priority'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'priority'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'priority'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</div>
</th>
<th (click)="sortByAssigned()" class="sortable">
<span>Assigned To</span>
<span>
<span *ngIf="sortBy != 'assignedtoid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'assignedtoid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'assignedtoid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByCategory()" class="sortable">
<span>Category</span>
<span>
<span *ngIf="sortBy != 'categoryid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'categoryid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'categoryid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let task of filteredItems" [class.table-info]="task.id === highlightId">
<td>{{task.id}}</td>
<td>{{task.title}}</td>
<td>
<a [routerLink]="['/userstories', {id: task.userstoryid}]">
US #{{task.userstoryid}}
</a>
</td>
<td>
<a [routerLink]="['/status', {id: task.statusid}]">
{{getStatusTitleById(task.statusid)}}
</a>
</td>
<td>{{task.priority}}</td>
<td>
<a [routerLink]="['/users', {id: task.assignedtoid}]">
{{getUserNameById(task.assignedtoid)}}
</a>
</td>
<td>
<a [routerLink]="['/categories', {id: task.categoryid}]">
{{getCategoryTitleById(task.categoryid)}}
</a>
</td>
<td>
<button type="button" rel="tooltip" (click)="openTaskForm(task)" class="btn btn-success btn-sm btn-icon">
<i class="fa fa-pencil-alt"></i>
</button>
<button type="button" rel="tooltip" (click)="deleteTask(task)" class="btn btn-danger btn-sm btn-icon">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -5,16 +5,16 @@ import {
ScrumStatus, ScrumStatus,
ScrumUser, ScrumUser,
ScrumCategory, ScrumCategory,
} from '../services/backend.service'; } from '../../services/backend.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { TaskFormComponent } from '../task-form/task-form.component'; import { TaskFormComponent } from '../task-form/task-form.component';
import { TableComponentBase } from '../services/table-component.base'; import { TableComponentBase } from '../../services/table-component.base';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { getNumberForPriority } from '../services/sorting.service'; import { getNumberForPriority } from '../../services/sorting.service';
import { NONE_TYPE } from '@angular/compiler'; import { NONE_TYPE } from '@angular/compiler';
@Component({ @Component({
selector: 'app-userstory-table', selector: 'app-task-table',
templateUrl: './task-table.component.html', templateUrl: './task-table.component.html',
styleUrls: ['./task-table.component.css'], styleUrls: ['./task-table.component.css'],
}) })

View File

@ -1,144 +1,140 @@
<div class="card" style="width: 100%;"> <!--Popup form to create and modify a task-->
<div class="container">
<div class="card-body"> <div class="card" style="width: 100%;">
<div style="text-align: right;"> <div class="container">
<i class="fa fa-times fa-2x" (click)="onClose()"></i> <div class="card-body">
</div> <div style="text-align: right;">
<div class="row"> <i class="fa fa-times fa-2x" (click)="onClose()"></i>
<div class="col-8" style="text-align: left;"> </div>
<h4 class="card-title">Neue Userstory anlegen</h4> <div class="row">
</div> <div class="col-8" style="text-align: left;">
<div class="col-4"></div> <h4 class="card-title">Neue Userstory anlegen</h4>
</div> </div>
<form (ngSubmit)="onSubmit()"> <div class="col-4"></div>
<div class="row"> </div>
<div class="col-8"> <form (ngSubmit)="onSubmit()">
<div class="form-group"> <div class="row">
<label for="Title">Titel</label> <div class="col-8">
<input type="text" class="form-control" id="Title" required name="title" <div class="form-group">
[(ngModel)]="userstory.title" id="titleField" /> <label for="Title">Titel</label>
</div> <input type="text" class="form-control" id="Title" required name="title"
<div class="form-group"> [(ngModel)]="userstory.title" id="titleField" />
<label for="Inhalt">Story</label> </div>
<textarea type="text" class="form-control" id="Story" required name="story" rows="5" <div class="form-group">
[(ngModel)]="userstory.content"></textarea> <label for="Inhalt">Story</label>
</div> <textarea type="text" class="form-control" id="Story" required name="story" rows="5"
</div> [(ngModel)]="userstory.content"></textarea>
<div class="col-4"> </div>
<div ngbDropdown class="dropdown"> </div>
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button" <div class="col-4">
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <div ngbDropdown class="dropdown">
Prio: {{userstory.priority}} <button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
</button> id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2"> Prio: {{userstory.priority}}
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" </button>
(click)="userstory.priority=p"> <div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
{{p}}</option> <option ngbDropdownItem *ngFor="let p of getAllPriorities()"
</div> (click)="userstory.priority=p">
</div> {{p}}</option>
</div>
<!-- Section to choose, create or delete a random status for the userstory. --> </div>
<div class="form-group"> <div class="form-group">
<div ngbDropdown class="dropdown" [autoClose]="false"> <div ngbDropdown class="dropdown" [autoClose]="false">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button" <button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"> aria-expanded="false">
Status: {{getStatusTitleById(userstory.statusid)}} Status: {{getStatusTitleById(userstory.statusid)}}
</button> </button>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2"> <div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
<div class="card-text" for="Inhalt">Status wählen</div> <div class="card-text" for="Inhalt">Status wählen</div>
<option disable-auto-close ngbDropdownItem *ngFor="let status of allStatus" <option disable-auto-close ngbDropdownItem *ngFor="let status of allStatus"
(click)="userstory.statusid = status.id">{{ status.title }}</option> (click)="userstory.statusid = status.id">{{ status.title }}</option>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<div class="card-text" for="Inhalt">Neuer Status</div> <div class="card-text" for="Inhalt">Neuer Status</div>
<input #statusname type="text" id="statusname" class="dropdown-item" <input #statusname type="text" id="statusname" class="dropdown-item"
(change)="status.title=statusname.value" placeholder="New Title..." (change)="status.title=statusname.value" placeholder="New Title..."
style="background-color: rgba(211, 211, 211, 0.342);"> style="background-color: rgba(211, 211, 211, 0.342);">
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button" <button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
(click)="createUserstoryStatus(status)">Status anlegen</button> (click)="createUserstoryStatus(status)">Status anlegen</button>
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button" <button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
(click)="deleteStatus(userstory.statusid)">Status löschen</button> (click)="deleteStatus(userstory.statusid)">Status löschen</button>
</div> </div>
</div> </div>
<div class="dropdown-menu"> <div class="dropdown-menu">
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio" <select class="form-control custom-select mr-sm-2" id="prio" required name="prio"
[(ngModel)]="userstory.statusid"> [(ngModel)]="userstory.statusid">
<option *ngFor="let status of allStatus" [value]="status.id">{{ <option *ngFor="let status of allStatus" [value]="status.id">{{
status.title status.title
}}</option> }}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<!-- Section to choose, create or delete a random category for the userstory. --> <div ngbDropdown class="dropdown" [autoClose]="false">
<div class="form-group"> <button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
<div ngbDropdown class="dropdown" [autoClose]="false"> id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true"
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button" aria-expanded="false">
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" Kategorie: {{getCategoryById(userstory.categoryid)}}
aria-expanded="false"> </button>
Kategorie: {{getCategoryById(userstory.categoryid)}} <div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
</button> <div class="card-text" for="Inhalt">Kategorie wählen</div>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2"> <option disable-auto-close ngbDropdownItem *ngFor="let category of allCategories"
<div class="card-text" for="Inhalt">Kategorie wählen</div> (click)="userstory.categoryid = category.id">{{ category.title }}</option>
<option disable-auto-close ngbDropdownItem *ngFor="let category of allCategories"
(click)="userstory.categoryid = category.id">{{ category.title }}</option> <div class="dropdown-divider"></div>
<div class="card-text" for="Inhalt">Neue Kategorie</div>
<div class="dropdown-divider"></div> <input #categoryname type="text" id="categoryname" class="dropdown-item"
<div class="card-text" for="Inhalt">Neue Kategorie</div> (change)="category.title=categoryname.value" placeholder="New Title..."
<input #categoryname type="text" id="categoryname" class="dropdown-item" style="background-color: rgba(211, 211, 211, 0.342);">
(change)="category.title=categoryname.value" placeholder="New Title..." <button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
style="background-color: rgba(211, 211, 211, 0.342);"> (click)="createUserstoryCategory(category)">Kategorie anlegen</button>
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button" <button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
(click)="createUserstoryCategory(category)">Kategorie anlegen</button> (click)="deleteCategory(userstory.categoryid)">Kategorie löschen</button>
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button" </div>
(click)="deleteCategory(userstory.categoryid)">Kategorie löschen</button> </div>
</div> <div class="dropdown-menu">
</div> <select class="form-control custom-select mr-sm-2" id="prio" required name="prio"
<div class="dropdown-menu"> [(ngModel)]="userstory.categoryid">
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio" <option *ngFor="let category of allCategories" [value]="category.id">{{
[(ngModel)]="userstory.categoryid"> category.title
<option *ngFor="let category of allCategories" [value]="category.id">{{ }}</option>
category.title </select>
}}</option> </div>
</select> </div>
</div> <div class="form-group">
</div> <div ngbDropdown class="dropdown" [autoClose]="true">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
<!-- Section to choose an author for the userstory. --> id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true"
<div class="form-group"> aria-expanded="false">
<div ngbDropdown class="dropdown" [autoClose]="true"> Autor: {{getAuthorById(userstory.createdbyid)}}
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button" </button>
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" <div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
aria-expanded="false"> <div class="card-text" for="Inhalt">User wählen</div>
Autor: {{getAuthorById(userstory.createdbyid)}} <option disable-auto-close ngbDropdownItem *ngFor="let user of allUser"
</button> (click)="userstory.createdbyid = user.id">{{ user.name }}</option>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2"> </div>
<div class="card-text" for="Inhalt">User wählen</div> </div>
<option disable-auto-close ngbDropdownItem *ngFor="let user of allUser" <div class="dropdown-menu">
(click)="userstory.createdbyid = user.id">{{ user.name }}</option> <select class="form-control custom-select mr-sm-2" id="prio" required name="prio"
</div> [(ngModel)]="userstory.createdbyid">
</div> <option *ngFor="let user of allUser" [value]="user.id">{{
<div class="dropdown-menu"> user.name
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio" }}</option>
[(ngModel)]="userstory.createdbyid"> </select>
<option *ngFor="let user of allUser" [value]="user.id">{{ </div>
user.name </div>
}}</option> </div>
</select> </div>
</div> <div class="row">
</div> <div class="col-6">
</div> <button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal">
</div> Abbrechen
<div class="row"> </button>
<div class="col-6"> <button type="submit" class="btn btn-primary">Erstellen</button>
<button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal"> </div>
Abbrechen </div>
</button> </form>
<button type="submit" class="btn btn-primary">Erstellen</button> </div>
</div> </div>
</div>
</form>
</div>
</div>
</div> </div>

View File

@ -1,191 +1,207 @@
import { Component, OnInit, Input } from '@angular/core'; // Importing necessary components and interfaces.
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { Component, OnInit, Input } from '@angular/core';
import { BackendService, ScrumUserstory, Priority } from '../services/backend.service'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { import { BackendService, ScrumUserstory, Priority } from '../../services/backend.service';
ScrumTask, import {
ScrumStatus, ScrumTask,
ScrumCategory, ScrumStatus,
ScrumUser, ScrumCategory,
ScrumProject, ScrumUser,
} from '../services/backend.service'; ScrumProject,
} from '../../services/backend.service';
@Component({
selector: 'app-userstory-form', @Component({
templateUrl: './userstory-form.component.html', selector: 'app-userstory-form',
styleUrls: ['./userstory-form.component.css'] templateUrl: './userstory-form.component.html',
}) styleUrls: ['./userstory-form.component.css']
export class UserstoryFormComponent implements OnInit { })
@Input() public userstory: ScrumUserstory;
public allStatus: any[] = []; // Class implements the logic for a popup window form to create and modify userstories.
public status: ScrumStatus = { title: "", description: "" }; export class UserstoryFormComponent implements OnInit {
public allUser: any[] = []; @Input() public userstory: ScrumUserstory;
public user: ScrumUser = { name: "" }; public allStatus: any[] = [];
public allCategories: any[] = []; public status: ScrumStatus = { title: "", description: "" };
public category: ScrumCategory = { title: ""}; public allUser: any[] = [];
private editing: boolean; public user: ScrumUser = { name: "" };
public allCategories: any[] = [];
public category: ScrumCategory = { title: "" };
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) { private editing: boolean;
this.getUserstoryStatus();
this.getAllUsers(); constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) {
this.getUserstoryCategory(); this.getUserstoryStatus();
} this.getAllUsers();
this.getUserstoryCategory();
ngOnInit(): void { }
if (this.userstory === null || this.userstory === undefined) {
this.userstory = { title: '' }; // If no userstory exists a new one will be created.
this.editing = false; // In other cases the userstory exists and gets modifiable.
} else { ngOnInit(): void {
this.editing = true; if (this.userstory === null || this.userstory === undefined) {
} this.userstory = { title: '' };
document.getElementById('titleField').focus(); this.editing = false;
} } else {
this.editing = true;
onSubmit() { }
if (this.editing) { document.getElementById('titleField').focus();
this.backendService.putUserstory(this.userstory).subscribe((response) => { }
if (response.status > 399) {
alert('Fehler'); // A new created userstory will be saved in backend (POST).
} // If a userstory already exists, modifying results an update (PUT) to the backend.
}); onSubmit() {
} else { if (this.editing) {
this.backendService.postUserstory(this.userstory).subscribe((response) => { this.backendService.putUserstory(this.userstory).subscribe((response) => {
if (response.status > 399) { if (response.status > 399) {
alert('Fehler'); alert('Fehler');
} }
}); });
} } else {
this.activeModalService.close(this.userstory); this.backendService.postUserstory(this.userstory).subscribe((response) => {
} if (response.status > 399) {
onClose() { alert('Fehler');
this.activeModalService.dismiss(this.userstory); }
} });
}
// Methods for adding, choosing and deleting random status tags. // Closes the popup window after submitting/canceling.
this.activeModalService.close(this.userstory);
getUserstoryStatus() { }
this.backendService.getAllStatus().subscribe((response) => {
if (response.status > 399) { // Closes the popup form window (by clicking "close button").
alert('Fehler'); onClose() {
} else { this.activeModalService.dismiss(this.userstory);
this.allStatus.push(...response.body); }
}
}); // Getting all available status from backend to list it in status-dropdown in popup window.
} getUserstoryStatus() {
this.backendService.getAllStatus().subscribe((response) => {
createUserstoryStatus(status: ScrumStatus) { if (response.status > 399) {
this.backendService.postStatus(status).subscribe((response) => { alert('Fehler');
if (response.status > 399) { } else {
alert('Fehler'); this.allStatus.push(...response.body);
} }
else { });
this.allStatus.push(response.body); }
}
}); // If desired a new arbitrary status (such as "Waiting") can be created, which will be stored in an array.
} // The new status is available to all userstories.
createUserstoryStatus(status: ScrumStatus) {
deleteStatus(id: number) { this.backendService.postStatus(status).subscribe((response) => {
var status = this.allStatus.find((x) => x.id === id); if (response.status > 399) {
this.backendService.deleteStatus(status).subscribe((response) => { alert('Fehler');
if (response.status > 399) { }
alert('Fehler'); else {
} this.allStatus.push(response.body);
else { }
const index = this.allStatus.indexOf(status); });
if (index !== -1) { }
this.allStatus.splice(index, 1);
} // A custom status can even be deleted if not used anymore.
} // This will remove the status from status-array.
this.userstory.statusid = null; deleteStatus(id: number) {
}); var status = this.allStatus.find((x) => x.id === id);
} this.backendService.deleteStatus(status).subscribe((response) => {
if (response.status > 399) {
getAllPriorities(): string[] { alert('Fehler');
return Object.values(Priority); }
} else {
const index = this.allStatus.indexOf(status);
getStatusTitleById(id: number): string { if (index !== -1) {
if (!id) { this.allStatus.splice(index, 1);
return null; }
} }
var status = this.allStatus.find((x) => x.id === id); this.userstory.statusid = null;
if (!status) { });
return null; }
}
return status.title; // Getting the values of the Priority enum to be shown in a dropdown in popup window.
} getAllPriorities(): Priority[] {
return Object.values(Priority);
// Methods for choosing creator of a userstory. }
getAllUsers() { // Shows the before choosen status in the status-field in the popup window.
this.backendService.getUsers().subscribe((response) => { getStatusTitleById(id: number): string {
if (response.status > 399) { if (!id) {
alert('Fehler'); return null;
} else { }
this.allUser.push(...response.body); var status = this.allStatus.find((x) => x.id === id);
} if (!status) {
}); return null;
} }
return status.title;
getAuthorById(id: number): string { }
if (!id) {
return null; // Getting all taskboard users from backend to show in a dropdown in popup window.
} getAllUsers() {
var user = this.allUser.find((x) => x.id === id); this.backendService.getUsers().subscribe((response) => {
if (!user) { if (response.status > 399) {
return null; alert('Fehler');
} } else {
return user.name; this.allUser.push(...response.body);
} }
});
// Methods for creating, choosing and deleting categories for a userstory. }
getUserstoryCategory() { // Shows the before assigned user in the author-field in the popup window.
this.backendService.getCategories().subscribe((response) => { getAuthorById(id: number): string {
if (response.status > 399) { if (!id) {
alert('Fehler'); return null;
} else { }
this.allCategories.push(...response.body); var user = this.allUser.find((x) => x.id === id);
} if (!user) {
}); return null;
} }
return user.name;
createUserstoryCategory(category: ScrumCategory) { }
this.backendService.postCategory(category).subscribe((response) => {
if (response.status > 399) { // Getting all available categories from backend to list it in status-dropdown in popup window.
alert('Fehler'); getUserstoryCategory() {
} this.backendService.getCategories().subscribe((response) => {
else { if (response.status > 399) {
this.allCategories.push(response.body); alert('Fehler');
} } else {
}); this.allCategories.push(...response.body);
} }
});
deleteCategory(id: number) { }
var category = this.allCategories.find((x) => x.id === id);
this.backendService.deleteCategory(category).subscribe((response) => { // If desired a new arbitrary category can be created, which will be stored in an array.
if (response.status > 399) { // The new category is available to all userstories.
alert('Fehler'); createUserstoryCategory(category: ScrumCategory) {
} this.backendService.postCategory(category).subscribe((response) => {
else { if (response.status > 399) {
const index = this.allCategories.indexOf(category); alert('Fehler');
if (index !== -1) { }
this.allCategories.splice(index, 1); else {
} this.allCategories.push(response.body);
} }
this.userstory.categoryid = null; });
}); }
}
// A custom category can even be deleted if not used anymore.
getCategoryById(id: number): string { // This will remove the category from category-array.
if (!id) { deleteCategory(id: number) {
return null; var category = this.allCategories.find((x) => x.id === id);
} this.backendService.deleteCategory(category).subscribe((response) => {
var category = this.allCategories.find((x) => x.id === id); if (response.status > 399) {
if (!category) { alert('Fehler');
return null; }
} else {
return category.title; const index = this.allCategories.indexOf(category);
} if (index !== -1) {
this.allCategories.splice(index, 1);
} }
}
this.userstory.categoryid = null;
});
}
// Shows the before choosen category in the category-field in the popup window.
getCategoryById(id: number): string {
if (!id) {
return null;
}
var category = this.allCategories.find((x) => x.id === id);
if (!category) {
return null;
}
return category.title;
}
}

View File

@ -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>

View File

@ -1,31 +1,31 @@
import { Component } from '@angular/core'; import { Component, Input } from '@angular/core';
import { import {
BackendService, BackendService,
ScrumTask, ScrumTask,
ScrumUserstory, ScrumUserstory,
ScrumStatus, ScrumStatus,
ScrumCategory, ScrumCategory,
} from '../services/backend.service'; } from '../../services/backend.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { TableComponentBase } from '../services/table-component.base'; import { TableComponentBase } from '../../services/table-component.base';
import { getNumberForPriority } from '../services/sorting.service'; import { getNumberForPriority } from '../../services/sorting.service';
import { UserstoryFormComponent } from '../userstory-form/userstory-form.component'; import { UserstoryFormComponent } from '../userstory-form/userstory-form.component';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
@Component({ @Component({
selector: 'app-userstory-table', selector: 'app-userstory-inner-table',
templateUrl: './userstory-table.component.html', templateUrl: './userstory-inner-table.component.html',
styleUrls: ['./userstory-table.component.css'], styleUrls: ['./userstory-inner-table.component.css']
}) })
export class UserstoryTableComponent extends TableComponentBase< export class UserstoryInnerTableComponent extends TableComponentBase<ScrumUserstory> {
ScrumUserstory
> {
public tasks: ScrumTask[] = []; public tasks: ScrumTask[] = [];
public filterPriority: string | null = null; public filterPriority: string | null = null;
public highlightId: number; public highlightId: number;
public status: ScrumStatus[] = []; public status: ScrumStatus[] = [];
public categories: ScrumCategory[] = []; public categories: ScrumCategory[] = [];
@Input() public items: ScrumUserstory[] = [];
public get filteredItems() { public get filteredItems() {
return this.items.filter( return this.items.filter(
(task) => (task) =>
@ -44,13 +44,6 @@ export class UserstoryTableComponent extends TableComponentBase<
this.applyFilterParameters(this.route.snapshot.paramMap); this.applyFilterParameters(this.route.snapshot.paramMap);
this.route.paramMap.subscribe((map) => this.applyFilterParameters(map)); this.route.paramMap.subscribe((map) => this.applyFilterParameters(map));
backendService.getUserstories().subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
} else {
this.items.push(...response.body);
}
});
backendService.getTasks().subscribe((response) => { backendService.getTasks().subscribe((response) => {
if (response.status > 399) { if (response.status > 399) {
alert('Fehler'); alert('Fehler');
@ -96,7 +89,6 @@ export class UserstoryTableComponent extends TableComponentBase<
const modalRef = this.modalService.open(UserstoryFormComponent, { const modalRef = this.modalService.open(UserstoryFormComponent, {
backdrop: 'static', backdrop: 'static',
keyboard: true, keyboard: true,
size: 'lg'
}); });
if (editUserstory === null) { if (editUserstory === null) {
modalRef.result.then((result) => { modalRef.result.then((result) => {

View File

@ -0,0 +1,6 @@
<div class="container-fluid">
<div class="content">
<h3>Userstories</h3>
<app-userstory-inner-table [items]="items"></app-userstory-inner-table>
</div>
</div>

View File

@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import {
BackendService,
ScrumUserstory,
} from '../../services/backend.service';
@Component({
selector: 'app-userstory-table',
templateUrl: './userstory-table.component.html',
styleUrls: ['./userstory-table.component.css'],
})
export class UserstoryTableComponent {
public items: ScrumUserstory[] = [];
constructor(private backendService: BackendService) {
backendService.getUserstories().subscribe((response) => {
if (response.status > 399) {
alert('Fehler');
} else {
this.items.push(...response.body);
}
});
}
}

View File

@ -1,3 +0,0 @@
/* .text-2em {
font-size: 2rem;
} */

View File

@ -1,60 +0,0 @@
{% comment %} <div class="container-fluid">
<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">
<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">
Userstories
</div>
<div class="card-body">
<canvas id="done-stories-chart"></canvas>
</div>
</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>
<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> {% endcomment %}

View File

@ -1,159 +0,0 @@
// import { Component, OnInit } from '@angular/core';
// // import Chart from 'chart.js';
// import {
// BackendService,
// ScrumStatus,
// ScrumUser,
// ScrumUserstory,
// ScrumSprint,
// } from '../services/backend.service';
// @Component({
// selector: 'app-dashboard',
// templateUrl: 'dashboard.component.html',
// styleUrls: ['./dashboard.component.css'],
// })
// export class DashboardComponent implements OnInit {
// /**
// * 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
// );
// }
// private status: ScrumStatus[];
// private userstories: ScrumUserstory[];
// private sprints: ScrumSprint[];
// 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),
// },
// ];
// }
// ngOnInit(): void {
// // @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(),
// },
// ],
// },
// });
// }
// private getBackgroundColors(): string[] {
// const baseColors = [
// 'rgb(255, 153, 102)',
// 'rgb(255, 102, 102)',
// 'rgb(153, 204, 255)',
// 'rgb(102, 153, 102)',
// 'rgb(204, 204, 153)',
// 'rgb(153, 102, 204)',
// 'rgb(204, 102, 102)',
// 'rgb(255, 204, 153)',
// 'rgb(153, 102, 255)',
// 'rgb(204, 204, 204)',
// 'rgb(102, 255, 204)',
// 'rgb(102, 153, 255)',
// 'rgb(153, 102, 153)',
// 'rgb(204, 204, 255)',
// ];
// const colors = [];
// while (colors.length < this.usedStatus.length) {
// colors.push(...baseColors);
// }
// return colors;
// }
// public getNumberOfUserstoriesByStatus(status: ScrumStatus): number {
// return this.userstories.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) {
// return undefined;
// }
// const daysDelta = Math.floor(
// (currentSprint.endDate.getTime() - now.getTime()) / 86400000
// );
// return daysDelta;
// }
// public getSprintUrgency(): number {
// const now = new Date();
// const currentSprint = this.sprints.find(
// (s) => s.endDate > now && s.startDate < now
// );
// if (currentSprint === undefined) {
// return undefined;
// }
// const deltaFromNow = currentSprint.endDate.getTime() - now.getTime();
// const deltaFromStart =
// currentSprint.endDate.getTime() - currentSprint.startDate.getTime();
// return Math.floor((3 * deltaFromNow) / deltaFromStart);
// }
// }

View File

@ -242,17 +242,18 @@ export interface ScrumUserstory {
priority?: Priority; priority?: Priority;
statusid?: number; statusid?: number;
categoryid?: number; categoryid?: number;
sprintid?: number;
createdbyid?: number; createdbyid?: number;
projectid?: number; projectid?: number;
} }
export interface ScrumSprint { export interface ScrumSprint{
id?: number; id?: number;
title: string; title: string;
description?: string; description?: string;
startDate: Date; startDate: string;
endDate: Date; endDate: string;
project?: number; projectid?: number;
} }
export interface ScrumCategory { export interface ScrumCategory {

View File

@ -1,32 +0,0 @@
<div class="card" style="width: 100%;">
<div class="container">
<div class="card-body">
<div style="text-align: right;">
<i class="fa fa-times fa-2x" (click)="onClose()"></i>
</div>
<h4 class="card-title">Neuen Sprint anlegen</h4>
<form (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="Title">Titel</label>
<input type="text" class="form-control" id="Title" required name="title" [(ngModel)]="sprint.title"
id="titleField">
</div>
<div class="form-group">
<label for="date">Startdatum</label>
<input #startDate type="Date" class="form-control" id="Date" required name="date" [value]="sprint.startDate | date: 'yyyy-MM-dd'" (change)="sprint.startDate=startDate.value"
id="titleField">
</div>
<div class="form-group">
<label for="Date">Enddatum</label>
<input #endDate type="date" class="form-control" id="Date" required name="date" [value]="sprint.endDate | date: 'yyyy-MM-dd'" (change)="sprint.endDate=endDate.value"
id="titleField">
</div>
<div>
<button (click)="onClose()" type="dismiss" class="btn btn-secondary"
data-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-primary">Sprint starten</button>
</div>
</form>
</div>
</div>

View File

@ -1,3 +0,0 @@
th.sortable:hover {
text-decoration: underline;
}

View File

@ -1,66 +0,0 @@
<div class="container-fluid">
<h3>
Sprints
</h3>
<button class="btn btn-secondary" (click)="openSprintForm()">Neuer Sprint</button>
<table class="table">
<thead>
<tr>
<th (click)="sortById()" class="sortable">
<span>ID</span>
<span>
<span *ngIf="sortBy != 'id'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'id'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'id'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTitle()" class="sortable">
<span>Titel</span>
<span>
<span *ngIf="sortBy != 'title'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'title'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByStartDate()" class="sortable">
<span>Start</span>
<span>
<span *ngIf="sortBy != 'startDate'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'startDate'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'startDate'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByEndDate()" class="sortable">
<span>End</span>
<span>
<span *ngIf="sortBy != 'endDate'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'endDate'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'endDate'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let sprint of filteredItems" [class.table-info]="sprint.id === highlightId">
<td>{{sprint.id}}</td>
<td>{{sprint.title}}</td>
<td>{{sprint.startDate | date:'dd.MM.yyyy'}}</td>
<td>{{sprint.endDate | date:'dd.MM.yyyy'}}</td>
<td>
<button type="button" rel="tooltip" (click)="openSprintForm(sprint)" class="btn btn-success btn-sm btn-icon">
<i class="fa fa-pencil-alt"></i>
</button>
<button type="button" rel="tooltip" (click)="deleteSprint(sprint)" class="btn btn-danger btn-sm btn-icon">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,83 +0,0 @@
<div class="card" style="width: 100%;">
<div class="container">
<div class="card-body">
<div style="text-align: right;">
<i class="fa fa-times fa-2x" (click)="onClose()"></i>
</div>
<div class="row">
<div class="col-8" style="text-align: left;">
<h4 class="card-title">Neuen Task anlegen</h4>
<div ngbDropdown class="dropdown card-text">
<span ngbDropdownToggle class="dropdown" id="dropdownMenuUserstory" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Gehört zu Story: {{getUserstoryTitleById(task.userstoryid) || "Keine"}}
<i class="fa fa-caret-down"></i>
</span>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenuUserstory">
<option ngbDropdownItem *ngFor="let userstory of userstories" (click)="task.userstoryid = userstory.id">{{ userstory.title }}</option>
</div>
</div>
</div>
<div class="col-4"></div>
</div>
<form (ngSubmit)="onSubmit()">
<div class="row">
<div class="col-8">
<div class="form-group">
<label for="Title">Titel</label>
<input type="text" class="form-control" id="Title" required name="title" [(ngModel)]="task.title" id="titleField"/>
</div>
<div class="form-group">
<label for="Inhalt">What to do?</label>
<textarea type="text" class="form-control" id="Story" required name="story" rows="5" [(ngModel)]="task.content"></textarea>
</div>
</div>
<div class="col-4">
<div ngbDropdown class="dropdown">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Prio: {{task.priority}}
</button>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" (click)="task.priority=p">{{p}}</option>
</div>
</div>
<div class="form-group">
<div ngbDropdown class="dropdown" [autoClose]="false">
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Status: {{getStatusTitleById(task.statusid)}}
</button>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
<div class="card-text" for="Inhalt">Status wählen</div>
<option disable-auto-close ngbDropdownItem *ngFor="let status of allStatus" (click)="task.statusid = status.id">{{ status.title }}</option>
<div class="dropdown-divider"></div>
<div class="card-text" for="Inhalt">Neuer Status</div>
<input #statusname type="text" id="statusname" class="dropdown-item" (change)="status.title=statusname.value" placeholder="New Title..." style="background-color: rgba(211, 211, 211, 0.342);">
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button" (click)="createTaskStatus(status)">Status anlegen</button>
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button" (click)="deleteStatus(task.statusid)">Status löschen</button>
</div>
</div>
<div class="dropdown-menu">
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio" [(ngModel)]="task.statusid">
<option *ngFor="let status of allStatus" [value]="status.id">{{
status.title
}}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="Inhalt">Assigned User</label>
<input type="text" class="form-control" id="Author" required name="author"/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal">
Abbrechen
</button>
<button type="submit" class="btn btn-primary">Erstellen</button>
</div>
</div>
</form></div>
</div>
</div>

View File

@ -1,126 +0,0 @@
<div class="container-fluid">
<h3>
<a *ngIf="filterUserstoryId" [routerLink]="['/userstories', {id: filterUserstoryId}]">
Userstory #{{filterUserstoryId}}
&gt;
</a>
Tasks
</h3>
<div *ngIf="filterUserstoryId">
<a [routerLink]="'/tasks'">Alle Tasks anzeigen</a>
</div>
<button class="btn btn-secondary" (click)="openTaskForm()">Neuer Task</button>
<table class="table">
<thead>
<tr>
<th (click)="sortById()" class="sortable">
<span>ID</span>
<span>
<span *ngIf="sortBy != 'id'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'id'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTitle()" class="sortable">
<span>Titel</span>
<span>
<span *ngIf="sortBy != 'title'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'title'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTasks()" class="sortable">
<span>Userstory</span>
<span>
<span *ngIf="sortBy != 'userstory'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'userstory'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'userstory'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByStatus()" class="sortable">
<span>Status</span>
<span>
<span *ngIf="sortBy != 'statusid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'statusid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'statusid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th class="sortable">
<div class="d-inline-block">
<span (click)="sortByPrio()">Priorität: </span>
<div ngbDropdown class="d-inline-block">
<span id="dropdownBasic1" ngbDropdownToggle>{{filterPriority || "All"}}</span>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<option ngbDropdownItem (click)="filterPriority=null">All</option>
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" (click)="filterPriority=p">{{p}}</option>
</div>
</div>
<span (click)="sortByPrio()">
<span *ngIf="sortBy != 'priority'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'priority'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'priority'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</div>
</th>
<th (click)="sortByAssigned()" class="sortable">
<span>Assigned To</span>
<span>
<span *ngIf="sortBy != 'assignedtoid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'assignedtoid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'assignedtoid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByCategory()" class="sortable">
<span>Category</span>
<span>
<span *ngIf="sortBy != 'categoryid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'categoryid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'categoryid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let task of filteredItems" [class.table-info]="task.id === highlightId">
<td>{{task.id}}</td>
<td>{{task.title}}</td>
<td>
<a [routerLink]="['/userstories', {id: task.userstoryid}]">
US #{{task.userstoryid}}
</a>
</td>
<td>
<a [routerLink]="['/status', {id: task.statusid}]">
{{getStatusTitleById(task.statusid)}}
</a>
</td>
<td>{{task.priority}}</td>
<td>
<a [routerLink]="['/users', {id: task.assignedtoid}]">
{{getUserNameById(task.assignedtoid)}}
</a>
</td>
<td>
<a [routerLink]="['/categories', {id: task.categoryid}]">
{{getCategoryTitleById(task.categoryid)}}
</a>
</td>
<td>
<button type="button" rel="tooltip" (click)="openTaskForm(task)" class="btn btn-success btn-sm btn-icon">
<i class="fa fa-pencil-alt"></i>
</button>
<button type="button" rel="tooltip" (click)="deleteTask(task)" class="btn btn-danger btn-sm btn-icon">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,3 +0,0 @@
th.sortable:hover {
text-decoration: underline;
}

View File

@ -1,106 +0,0 @@
<div class="container-fluid">
<h3>Userstories</h3>
<button class="btn btn-secondary" (click)="openUserstoryForm()">Neue Userstory</button>
<table class="table">
<thead>
<tr>
<th (click)="sortById()" class="sortable">
<span>ID</span>
<span>
<span *ngIf="sortBy != 'id'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'id'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'id'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTitle()" class="sortable">
<span>Titel</span>
<span>
<span *ngIf="sortBy != 'title'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'title'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByTasks()" class="sortable">
<span>Tasks</span>
<span>
<span *ngIf="sortBy != 'tasks'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'tasks'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'tasks'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th (click)="sortByStatus()" class="sortable">
<span>Status</span>
<span>
<span *ngIf="sortBy != 'statusid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'statusid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'statusid'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</th>
<th class="sortable">
<div class="d-inline-block">
<div class="d-inline-block">
<span (click)="sortByPrio()">Priorität: </span>
<div ngbDropdown class="d-inline-block">
<span id="dropdownBasic1" ngbDropdownToggle>{{filterPriority || "All"}}</span>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<option ngbDropdownItem (click)="filterPriority=null">All</option>
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" (click)="filterPriority=p">{{p}}</option>
</div>
</div>
<span (click)="sortByPrio()">
<span *ngIf="sortBy != 'priority'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'priority'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'priority'"><i class="fa fa-sort-down fa-lg"></i></span>
</span>
</div>
</div>
</th>
<th (click)="sortByCategory()" class="sortable">
<span>Category</span>
<span>
<span *ngIf="sortBy != 'categoryid'"><i class="fa fa-sort fa-lg"></i></span>
<span *ngIf="sortDescending && sortBy === 'categoryid'"><i class="fa fa-sort-up fa-lg"></i></span>
<span *ngIf="sortDescending === false && sortBy === 'categoryid'"><i class="fa fa-sort-down fa-lg"></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>
</div>

9
src/styles.css Normal file
View File

@ -0,0 +1,9 @@
/* 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;
}

View File

@ -2,4 +2,16 @@
.modal .modal-content { .modal .modal-content {
display: contents; display: contents;
} }
th.sortable:hover {
text-decoration: underline;
}
.content {
position: relative;
float: left;
margin-top: 10px;
margin-left: 20px;
width: 80%;
}