Merge branch 'master' into feature/sidebar
This commit is contained in:
commit
d6f897e897
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,6 +22,7 @@ speed-measure-plugin*.json
|
|||||||
*.launch
|
*.launch
|
||||||
.settings/
|
.settings/
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
# IDE - VSCode
|
# IDE - VSCode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
@ -4,7 +4,7 @@ WORKDIR /build
|
|||||||
|
|
||||||
## Install app dependencies
|
## Install app dependencies
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install --unsafe-perm
|
||||||
|
|
||||||
## Bundle app source
|
## Bundle app source
|
||||||
COPY . .
|
COPY . .
|
||||||
|
@ -26,10 +26,11 @@
|
|||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
"src/styles.css",
|
"src/styles.scss",
|
||||||
"src/assets/scss/black-dashboard.scss"
|
"src/assets/scss/black-dashboard.scss"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": [
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
@ -91,7 +92,7 @@
|
|||||||
"src/assets"
|
"src/assets"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.css"
|
"src/styles.scss"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
}
|
}
|
||||||
|
13471
package-lock.json
generated
13471
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
|||||||
"@ng-bootstrap/ng-bootstrap": "^6.0.0",
|
"@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"
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
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 './dashboard/dashboard.component';
|
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 {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: 'dashboard', component: DashboardComponent },
|
||||||
{ path: 'sprints', component: SprintTableComponent },
|
{ path: 'sprints', component: SprintTableComponent },
|
||||||
|
{ path: 'backlog', component: BacklogComponent },
|
||||||
{ path: '', redirectTo: '/tasks', pathMatch: 'full' },
|
{ path: '', redirectTo: '/tasks', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ RouterModule.forRoot(routes) ],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
exports: [ RouterModule ]
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
@ -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: [
|
||||||
@ -27,17 +29,17 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
|||||||
SprintFormComponent,
|
SprintFormComponent,
|
||||||
SprintTableComponent,
|
SprintTableComponent,
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
|
UserstoryInnerTableComponent,
|
||||||
|
BacklogComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule
|
NgbModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [BackendService],
|
||||||
BackendService,
|
bootstrap: [AppComponent],
|
||||||
],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {}
|
||||||
|
3
src/app/components/backlog-table/backlog.component.css
Normal file
3
src/app/components/backlog-table/backlog.component.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
th.sortable:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
62
src/app/components/backlog-table/backlog.component.html
Normal file
62
src/app/components/backlog-table/backlog.component.html
Normal 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>
|
160
src/app/components/backlog-table/backlog.component.ts
Normal file
160
src/app/components/backlog-table/backlog.component.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
7
src/app/components/dashboard/dashboard.component.css
Normal file
7
src/app/components/dashboard/dashboard.component.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.text-large {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-very-large {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
}
|
101
src/app/components/dashboard/dashboard.component.html
Normal file
101
src/app/components/dashboard/dashboard.component.html
Normal 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>
|
160
src/app/components/dashboard/dashboard.component.ts
Normal file
160
src/app/components/dashboard/dashboard.component.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
35
src/app/components/sprint-form/sprint-form.component.html
Normal file
35
src/app/components/sprint-form/sprint-form.component.html
Normal 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>
|
60
src/app/components/sprint-form/sprint-form.component.ts
Normal file
60
src/app/components/sprint-form/sprint-form.component.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Importing necessary components and interfaces.
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {
|
||||||
|
BackendService,
|
||||||
|
ScrumSprint
|
||||||
|
} from '../../services/backend.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-task-form',
|
||||||
|
templateUrl: './sprint-form.component.html',
|
||||||
|
styleUrls: ['./sprint-form.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
// Class implements the logic for a popup window form to create and modify sprints.
|
||||||
|
export class SprintFormComponent implements OnInit {
|
||||||
|
@Input() public sprint: ScrumSprint;
|
||||||
|
public editing: Boolean;
|
||||||
|
public sprintid: string;
|
||||||
|
|
||||||
|
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) { }
|
||||||
|
|
||||||
|
// If no sprint exists a new one will be created.
|
||||||
|
// In other cases the sprint exists and gets modifiable.
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.sprint === null || this.sprint === undefined) {
|
||||||
|
this.sprint = { title: '', startDate: '', endDate: '' };
|
||||||
|
this.editing = false;
|
||||||
|
} else {
|
||||||
|
this.editing = true;
|
||||||
|
}
|
||||||
|
document.getElementById('titleField').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A new created sprint will be saved in backend (POST).
|
||||||
|
// If a sprint already exists, modifying results an update (PUT) to the backend.
|
||||||
|
onSubmit() {
|
||||||
|
if (this.editing) {
|
||||||
|
this.backendService.putSprint(this.sprint).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.backendService.postSprint(this.sprint).subscribe((response) => {
|
||||||
|
console.log('Sprint gespeichert!');
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Closes the popup window after submitting/canceling.
|
||||||
|
this.activeModalService.close(this.sprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes the popup form window (by clicking "close button").
|
||||||
|
onClose() {
|
||||||
|
this.activeModalService.dismiss(this.sprint);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
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';
|
||||||
@ -78,10 +78,10 @@ export class SprintTableComponent extends TableComponentBase<ScrumSprint> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
3
src/app/components/task-form/task-form.component.css
Normal file
3
src/app/components/task-form/task-form.component.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.modal .modal-content {
|
||||||
|
display: contents;
|
||||||
|
}
|
118
src/app/components/task-form/task-form.component.html
Normal file
118
src/app/components/task-form/task-form.component.html
Normal 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>
|
200
src/app/components/task-form/task-form.component.ts
Normal file
200
src/app/components/task-form/task-form.component.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Importing necessary components and interfaces.
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {
|
||||||
|
BackendService,
|
||||||
|
ScrumTask,
|
||||||
|
Priority,
|
||||||
|
ScrumStatus,
|
||||||
|
ScrumCategory,
|
||||||
|
ScrumUser,
|
||||||
|
ScrumProject,
|
||||||
|
ScrumUserstory
|
||||||
|
} from '../../services/backend.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { HttpResponse } from '@angular/common/http';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-task-form',
|
||||||
|
templateUrl: './task-form.component.html',
|
||||||
|
styleUrls: ['./task-form.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
// Class implements the logic for a popup window form to create and modify tasks.
|
||||||
|
export class TaskFormComponent implements OnInit {
|
||||||
|
@Input() public task: ScrumTask;
|
||||||
|
public editing: boolean;
|
||||||
|
public creating: boolean;
|
||||||
|
public userstoryId: string;
|
||||||
|
public userstories: any[] = [];
|
||||||
|
public allStatus: any[] = [];
|
||||||
|
public status: ScrumStatus = { title: "", description: "" };
|
||||||
|
public allUser: any[] = [];
|
||||||
|
public user: ScrumUser = { name: "" };
|
||||||
|
|
||||||
|
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) {
|
||||||
|
this.getUserStories();
|
||||||
|
this.getTaskStatus();
|
||||||
|
this.getAllUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no task exists a new one will be created.
|
||||||
|
// In other cases the task exists and gets modifiable.
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.task === null || this.task === undefined) {
|
||||||
|
this.task = { title: '' };
|
||||||
|
this.editing = false;
|
||||||
|
this.creating = false;
|
||||||
|
} else if (this.task.userstoryid) {
|
||||||
|
this.editing = true;
|
||||||
|
} else {
|
||||||
|
this.creating = true;
|
||||||
|
}
|
||||||
|
document.getElementById('titleField').focus();
|
||||||
|
this.getRelatedStory();
|
||||||
|
}
|
||||||
|
// A new created task will be saved in backend (POST).
|
||||||
|
// If a task already exists, modifying results an update (PUT) to the backend.
|
||||||
|
onSubmit() {
|
||||||
|
if (this.editing) {
|
||||||
|
this.backendService.putTask(this.task).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.backendService.postTask(this.task).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Closes the popup window after submitting/canceling.
|
||||||
|
this.activeModalService.close(this.task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes the popup form window (by clicking "close button").
|
||||||
|
onClose() {
|
||||||
|
this.activeModalService.dismiss(this.task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting the userstory which is related to a task.
|
||||||
|
// The related story will be shown in popup window of a task.
|
||||||
|
getRelatedStory() {
|
||||||
|
if (!this.task.userstoryid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.backendService.getUserstory(this.task.userstoryid).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
} else {
|
||||||
|
this.userstoryId = response.body.title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting all userstories from backend to show in a dropdown in popup window.
|
||||||
|
getUserStories() {
|
||||||
|
this.backendService.getUserstories().subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
} else {
|
||||||
|
this.userstories.push(...response.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting all available status from backend to list it in status-dropdown in popup window.
|
||||||
|
getTaskStatus() {
|
||||||
|
this.backendService.getAllStatus().subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
} 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 tasks.
|
||||||
|
createTaskStatus(status: ScrumStatus) {
|
||||||
|
this.backendService.postStatus(status).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.allStatus.push(response.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom status can even be deleted if not used anymore.
|
||||||
|
// This will remove the status from status-array.
|
||||||
|
deleteStatus(id: number) {
|
||||||
|
var status = this.allStatus.find((x) => x.id === id);
|
||||||
|
this.backendService.deleteStatus(status).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const index = this.allStatus.indexOf(status);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.allStatus.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.task.statusid = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting the values of the Priority enum to be shown in a dropdown in popup window.
|
||||||
|
getAllPriorities(): Priority[] {
|
||||||
|
return Object.values(Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// necessary?????????????????????????????????????????????????????
|
||||||
|
getUserstoryTitleById(id: number): string {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -5,15 +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';
|
||||||
|
|
||||||
@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'],
|
||||||
})
|
})
|
||||||
@ -102,6 +103,7 @@ export class TaskTableComponent extends TableComponentBase<ScrumTask> {
|
|||||||
const modalRef = this.modalService.open(TaskFormComponent, {
|
const modalRef = this.modalService.open(TaskFormComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
keyboard: true,
|
keyboard: true,
|
||||||
|
size: 'lg',
|
||||||
});
|
});
|
||||||
if (editTask === null) {
|
if (editTask === null) {
|
||||||
modalRef.result.then((result) => {
|
modalRef.result.then((result) => {
|
@ -0,0 +1,3 @@
|
|||||||
|
.modal .modal-content {
|
||||||
|
display: contents;
|
||||||
|
}
|
140
src/app/components/userstory-form/userstory-form.component.html
Normal file
140
src/app/components/userstory-form/userstory-form.component.html
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<!--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">Neue Userstory anlegen</h4>
|
||||||
|
</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)]="userstory.title" id="titleField" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Inhalt">Story</label>
|
||||||
|
<textarea type="text" class="form-control" id="Story" required name="story" rows="5"
|
||||||
|
[(ngModel)]="userstory.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: {{userstory.priority}}
|
||||||
|
</button>
|
||||||
|
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||||
|
<option ngbDropdownItem *ngFor="let p of getAllPriorities()"
|
||||||
|
(click)="userstory.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(userstory.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)="userstory.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)="createUserstoryStatus(status)">Status anlegen</button>
|
||||||
|
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
|
||||||
|
(click)="deleteStatus(userstory.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)]="userstory.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]="false">
|
||||||
|
<button ngbDropdownToggle class="btn btn-secondary dropdown-toggle" type="button"
|
||||||
|
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true"
|
||||||
|
aria-expanded="false">
|
||||||
|
Kategorie: {{getCategoryById(userstory.categoryid)}}
|
||||||
|
</button>
|
||||||
|
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||||
|
<div class="card-text" for="Inhalt">Kategorie wählen</div>
|
||||||
|
<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>
|
||||||
|
<input #categoryname type="text" id="categoryname" class="dropdown-item"
|
||||||
|
(change)="category.title=categoryname.value" placeholder="New Title..."
|
||||||
|
style="background-color: rgba(211, 211, 211, 0.342);">
|
||||||
|
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
|
||||||
|
(click)="createUserstoryCategory(category)">Kategorie anlegen</button>
|
||||||
|
<button disable-auto-close ngbDropdownItem class="dropdown-item" type="button"
|
||||||
|
(click)="deleteCategory(userstory.categoryid)">Kategorie löschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio"
|
||||||
|
[(ngModel)]="userstory.categoryid">
|
||||||
|
<option *ngFor="let category of allCategories" [value]="category.id">{{
|
||||||
|
category.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="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true"
|
||||||
|
aria-expanded="false">
|
||||||
|
Autor: {{getAuthorById(userstory.createdbyid)}}
|
||||||
|
</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)="userstory.createdbyid = user.id">{{ user.name }}</option>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio"
|
||||||
|
[(ngModel)]="userstory.createdbyid">
|
||||||
|
<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>
|
207
src/app/components/userstory-form/userstory-form.component.ts
Normal file
207
src/app/components/userstory-form/userstory-form.component.ts
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// Importing necessary components and interfaces.
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { BackendService, ScrumUserstory, Priority } from '../../services/backend.service';
|
||||||
|
import {
|
||||||
|
ScrumTask,
|
||||||
|
ScrumStatus,
|
||||||
|
ScrumCategory,
|
||||||
|
ScrumUser,
|
||||||
|
ScrumProject,
|
||||||
|
} from '../../services/backend.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-userstory-form',
|
||||||
|
templateUrl: './userstory-form.component.html',
|
||||||
|
styleUrls: ['./userstory-form.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
// Class implements the logic for a popup window form to create and modify userstories.
|
||||||
|
export class UserstoryFormComponent implements OnInit {
|
||||||
|
@Input() public userstory: ScrumUserstory;
|
||||||
|
public allStatus: any[] = [];
|
||||||
|
public status: ScrumStatus = { title: "", description: "" };
|
||||||
|
public allUser: any[] = [];
|
||||||
|
public user: ScrumUser = { name: "" };
|
||||||
|
public allCategories: any[] = [];
|
||||||
|
public category: ScrumCategory = { title: "" };
|
||||||
|
private editing: boolean;
|
||||||
|
|
||||||
|
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) {
|
||||||
|
this.getUserstoryStatus();
|
||||||
|
this.getAllUsers();
|
||||||
|
this.getUserstoryCategory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no userstory exists a new one will be created.
|
||||||
|
// In other cases the userstory exists and gets modifiable.
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.userstory === null || this.userstory === undefined) {
|
||||||
|
this.userstory = { title: '' };
|
||||||
|
this.editing = false;
|
||||||
|
} else {
|
||||||
|
this.editing = true;
|
||||||
|
}
|
||||||
|
document.getElementById('titleField').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A new created userstory will be saved in backend (POST).
|
||||||
|
// If a userstory already exists, modifying results an update (PUT) to the backend.
|
||||||
|
onSubmit() {
|
||||||
|
if (this.editing) {
|
||||||
|
this.backendService.putUserstory(this.userstory).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.backendService.postUserstory(this.userstory).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Closes the popup window after submitting/canceling.
|
||||||
|
this.activeModalService.close(this.userstory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes the popup form window (by clicking "close button").
|
||||||
|
onClose() {
|
||||||
|
this.activeModalService.dismiss(this.userstory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting all available status from backend to list it in status-dropdown in popup window.
|
||||||
|
getUserstoryStatus() {
|
||||||
|
this.backendService.getAllStatus().subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
} 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) {
|
||||||
|
this.backendService.postStatus(status).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.allStatus.push(response.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom status can even be deleted if not used anymore.
|
||||||
|
// This will remove the status from status-array.
|
||||||
|
deleteStatus(id: number) {
|
||||||
|
var status = this.allStatus.find((x) => x.id === id);
|
||||||
|
this.backendService.deleteStatus(status).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const index = this.allStatus.indexOf(status);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.allStatus.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.userstory.statusid = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting the values of the Priority enum to be shown in a dropdown in popup window.
|
||||||
|
getAllPriorities(): Priority[] {
|
||||||
|
return Object.values(Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting all available categories from backend to list it in status-dropdown in popup window.
|
||||||
|
getUserstoryCategory() {
|
||||||
|
this.backendService.getCategories().subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
} else {
|
||||||
|
this.allCategories.push(...response.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If desired a new arbitrary category can be created, which will be stored in an array.
|
||||||
|
// The new category is available to all userstories.
|
||||||
|
createUserstoryCategory(category: ScrumCategory) {
|
||||||
|
this.backendService.postCategory(category).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.allCategories.push(response.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom category can even be deleted if not used anymore.
|
||||||
|
// This will remove the category from category-array.
|
||||||
|
deleteCategory(id: number) {
|
||||||
|
var category = this.allCategories.find((x) => x.id === id);
|
||||||
|
this.backendService.deleteCategory(category).subscribe((response) => {
|
||||||
|
if (response.status > 399) {
|
||||||
|
alert('Fehler');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
@ -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');
|
@ -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>
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
.text-2em {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
|
|
||||||
<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>
|
|
@ -1,127 +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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,12 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {HttpClient, HttpResponse} from '@angular/common/http';
|
import { HttpClient, HttpResponse } from '@angular/common/http';
|
||||||
import {Observable} from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import {environment} from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BackendService {
|
export class BackendService {
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {}
|
constructor(private httpClient: HttpClient) {}
|
||||||
|
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
public getTasks(): Observable<HttpResponse<ScrumTask[]>> {
|
public getTasks(): Observable<HttpResponse<ScrumTask[]>> {
|
||||||
const url = `${environment.apiUrl}/tasks`;
|
const url = `${environment.apiUrl}/tasks`;
|
||||||
@ -33,10 +30,9 @@ export class BackendService {
|
|||||||
|
|
||||||
public deleteTask(task: ScrumTask): Observable<HttpResponse<any>> {
|
public deleteTask(task: ScrumTask): Observable<HttpResponse<any>> {
|
||||||
const url = `${environment.apiUrl}/tasks/${task.id}`;
|
const url = `${environment.apiUrl}/tasks/${task.id}`;
|
||||||
return this.httpClient.delete(url, {observe: 'response'});
|
return this.httpClient.delete(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Userstories
|
// Userstories
|
||||||
public getUserstories(): Observable<HttpResponse<ScrumUserstory[]>> {
|
public getUserstories(): Observable<HttpResponse<ScrumUserstory[]>> {
|
||||||
const url = `${environment.apiUrl}/userstories`;
|
const url = `${environment.apiUrl}/userstories`;
|
||||||
@ -48,22 +44,29 @@ export class BackendService {
|
|||||||
return this.httpClient.get<ScrumUserstory>(url, { observe: 'response' });
|
return this.httpClient.get<ScrumUserstory>(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public postUserstory(userstory: ScrumUserstory): Observable<HttpResponse<ScrumUserstory>> {
|
public postUserstory(
|
||||||
|
userstory: ScrumUserstory
|
||||||
|
): Observable<HttpResponse<ScrumUserstory>> {
|
||||||
const url = `${environment.apiUrl}/userstories`;
|
const url = `${environment.apiUrl}/userstories`;
|
||||||
return this.httpClient.post<ScrumUserstory>(url, userstory, { observe: 'response' });
|
return this.httpClient.post<ScrumUserstory>(url, userstory, {
|
||||||
|
observe: 'response',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public putUserstory(userstory: ScrumUserstory): Observable<HttpResponse<any>> {
|
public putUserstory(
|
||||||
|
userstory: ScrumUserstory
|
||||||
|
): Observable<HttpResponse<any>> {
|
||||||
const url = `${environment.apiUrl}/userstories/${userstory.id}`;
|
const url = `${environment.apiUrl}/userstories/${userstory.id}`;
|
||||||
return this.httpClient.put(url, userstory, { observe: 'response' });
|
return this.httpClient.put(url, userstory, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteUserstory(userstory: ScrumUserstory): Observable<HttpResponse<any>> {
|
public deleteUserstory(
|
||||||
|
userstory: ScrumUserstory
|
||||||
|
): Observable<HttpResponse<any>> {
|
||||||
const url = `${environment.apiUrl}/userstories/${userstory.id}`;
|
const url = `${environment.apiUrl}/userstories/${userstory.id}`;
|
||||||
return this.httpClient.delete(url, {observe: 'response'});
|
return this.httpClient.delete(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sprints
|
// Sprints
|
||||||
public getSprints(): Observable<HttpResponse<ScrumSprint[]>> {
|
public getSprints(): Observable<HttpResponse<ScrumSprint[]>> {
|
||||||
const url = `${environment.apiUrl}/sprints`;
|
const url = `${environment.apiUrl}/sprints`;
|
||||||
@ -75,9 +78,13 @@ export class BackendService {
|
|||||||
return this.httpClient.get<ScrumSprint>(url, { observe: 'response' });
|
return this.httpClient.get<ScrumSprint>(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public postSprint(sprint: ScrumSprint): Observable<HttpResponse<ScrumSprint>> {
|
public postSprint(
|
||||||
|
sprint: ScrumSprint
|
||||||
|
): Observable<HttpResponse<ScrumSprint>> {
|
||||||
const url = `${environment.apiUrl}/sprints`;
|
const url = `${environment.apiUrl}/sprints`;
|
||||||
return this.httpClient.post<ScrumSprint>(url, sprint, { observe: 'response' });
|
return this.httpClient.post<ScrumSprint>(url, sprint, {
|
||||||
|
observe: 'response',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public putSprint(sprint: ScrumSprint): Observable<HttpResponse<any>> {
|
public putSprint(sprint: ScrumSprint): Observable<HttpResponse<any>> {
|
||||||
@ -87,10 +94,9 @@ export class BackendService {
|
|||||||
|
|
||||||
public deleteSprint(sprint: ScrumSprint): Observable<HttpResponse<any>> {
|
public deleteSprint(sprint: ScrumSprint): Observable<HttpResponse<any>> {
|
||||||
const url = `${environment.apiUrl}/sprints/${sprint.id}`;
|
const url = `${environment.apiUrl}/sprints/${sprint.id}`;
|
||||||
return this.httpClient.delete(url, {observe: 'response'});
|
return this.httpClient.delete(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Categories
|
// Categories
|
||||||
public getCategories(): Observable<HttpResponse<ScrumCategory[]>> {
|
public getCategories(): Observable<HttpResponse<ScrumCategory[]>> {
|
||||||
const url = `${environment.apiUrl}/categories`;
|
const url = `${environment.apiUrl}/categories`;
|
||||||
@ -102,9 +108,13 @@ export class BackendService {
|
|||||||
return this.httpClient.get<ScrumCategory>(url, { observe: 'response' });
|
return this.httpClient.get<ScrumCategory>(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public postCategory(category: ScrumCategory): Observable<HttpResponse<ScrumCategory>> {
|
public postCategory(
|
||||||
|
category: ScrumCategory
|
||||||
|
): Observable<HttpResponse<ScrumCategory>> {
|
||||||
const url = `${environment.apiUrl}/categories`;
|
const url = `${environment.apiUrl}/categories`;
|
||||||
return this.httpClient.post<ScrumCategory>(url, category, { observe: 'response' });
|
return this.httpClient.post<ScrumCategory>(url, category, {
|
||||||
|
observe: 'response',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public putCategory(category: ScrumCategory): Observable<HttpResponse<any>> {
|
public putCategory(category: ScrumCategory): Observable<HttpResponse<any>> {
|
||||||
@ -112,12 +122,13 @@ export class BackendService {
|
|||||||
return this.httpClient.put(url, category, { observe: 'response' });
|
return this.httpClient.put(url, category, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteCategory(category: ScrumCategory): Observable<HttpResponse<any>> {
|
public deleteCategory(
|
||||||
|
category: ScrumCategory
|
||||||
|
): Observable<HttpResponse<any>> {
|
||||||
const url = `${environment.apiUrl}/categories/${category.id}`;
|
const url = `${environment.apiUrl}/categories/${category.id}`;
|
||||||
return this.httpClient.delete(url, { observe: 'response' });
|
return this.httpClient.delete(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
public getAllStatus(): Observable<HttpResponse<ScrumStatus[]>> {
|
public getAllStatus(): Observable<HttpResponse<ScrumStatus[]>> {
|
||||||
const url = `${environment.apiUrl}/status`;
|
const url = `${environment.apiUrl}/status`;
|
||||||
@ -129,9 +140,13 @@ export class BackendService {
|
|||||||
return this.httpClient.get<ScrumStatus>(url, { observe: 'response' });
|
return this.httpClient.get<ScrumStatus>(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public postStatus(status: ScrumStatus): Observable<HttpResponse<ScrumStatus>> {
|
public postStatus(
|
||||||
|
status: ScrumStatus
|
||||||
|
): Observable<HttpResponse<ScrumStatus>> {
|
||||||
const url = `${environment.apiUrl}/status`;
|
const url = `${environment.apiUrl}/status`;
|
||||||
return this.httpClient.post<ScrumStatus>(url, status, { observe: 'response' });
|
return this.httpClient.post<ScrumStatus>(url, status, {
|
||||||
|
observe: 'response',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public putStatus(status: ScrumStatus): Observable<HttpResponse<any>> {
|
public putStatus(status: ScrumStatus): Observable<HttpResponse<any>> {
|
||||||
@ -144,7 +159,6 @@ export class BackendService {
|
|||||||
return this.httpClient.delete(url, { observe: 'response' });
|
return this.httpClient.delete(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
public getUsers(): Observable<HttpResponse<ScrumUser[]>> {
|
public getUsers(): Observable<HttpResponse<ScrumUser[]>> {
|
||||||
const url = `${environment.apiUrl}/users`;
|
const url = `${environment.apiUrl}/users`;
|
||||||
@ -171,7 +185,6 @@ export class BackendService {
|
|||||||
return this.httpClient.delete(url, { observe: 'response' });
|
return this.httpClient.delete(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Projects
|
// Projects
|
||||||
public getProjects(): Observable<HttpResponse<ScrumProject[]>> {
|
public getProjects(): Observable<HttpResponse<ScrumProject[]>> {
|
||||||
const url = `${environment.apiUrl}/projects`;
|
const url = `${environment.apiUrl}/projects`;
|
||||||
@ -183,9 +196,13 @@ export class BackendService {
|
|||||||
return this.httpClient.get<ScrumProject>(url, { observe: 'response' });
|
return this.httpClient.get<ScrumProject>(url, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public postProject(project: ScrumProject): Observable<HttpResponse<ScrumProject>> {
|
public postProject(
|
||||||
|
project: ScrumProject
|
||||||
|
): Observable<HttpResponse<ScrumProject>> {
|
||||||
const url = `${environment.apiUrl}/projects`;
|
const url = `${environment.apiUrl}/projects`;
|
||||||
return this.httpClient.post<ScrumProject>(url, project, { observe: 'response' });
|
return this.httpClient.post<ScrumProject>(url, project, {
|
||||||
|
observe: 'response',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public putProject(project: ScrumProject): Observable<HttpResponse<any>> {
|
public putProject(project: ScrumProject): Observable<HttpResponse<any>> {
|
||||||
@ -200,9 +217,9 @@ export class BackendService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum Priority {
|
export enum Priority {
|
||||||
High="high",
|
High = 'high',
|
||||||
Medium="medium",
|
Medium = 'medium',
|
||||||
Low="low"
|
Low = 'low',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrumTask {
|
export interface ScrumTask {
|
||||||
@ -225,6 +242,7 @@ 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;
|
||||||
}
|
}
|
||||||
@ -233,9 +251,9 @@ 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 {
|
||||||
@ -243,16 +261,15 @@ export interface ScrumCategory {
|
|||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
project: number;
|
project?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrumStatus {
|
export interface ScrumStatus {
|
||||||
id? : number;
|
id?: number;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ScrumUser {
|
export interface ScrumUser {
|
||||||
id?: number;
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<div class="modal-content p-3">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">Neuen Sprint anlegen</h4>
|
|
||||||
<button (click)="onClose()" type="button" class="close" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<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 type="Date" class="form-control" id="Date" required name="date" [(ngModel)]="sprint.startDate"
|
|
||||||
id="titleField">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Date">Enddatum</label>
|
|
||||||
<input type="date" class="form-control" id="Date" required name="date" [(ngModel)]="sprint.endDate"
|
|
||||||
id="titleField">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<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>
|
|
@ -1,62 +0,0 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import {
|
|
||||||
BackendService,
|
|
||||||
ScrumTask,
|
|
||||||
Priority,
|
|
||||||
ScrumStatus,
|
|
||||||
ScrumCategory,
|
|
||||||
ScrumUser,
|
|
||||||
ScrumProject,
|
|
||||||
ScrumUserstory,
|
|
||||||
ScrumSprint
|
|
||||||
} from '../services/backend.service';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { HttpResponse } from '@angular/common/http';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-task-form',
|
|
||||||
templateUrl: './sprint-form.component.html',
|
|
||||||
styleUrls: ['./sprint-form.component.css'],
|
|
||||||
})
|
|
||||||
export class SprintFormComponent implements OnInit {
|
|
||||||
@Input() public sprint: ScrumSprint;
|
|
||||||
public editing: Boolean;
|
|
||||||
public sprintid: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private backendService: BackendService,
|
|
||||||
private activeModalService: NgbActiveModal
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (this.sprint === null || this.sprint === undefined) {
|
|
||||||
this.sprint = { title: '', startDate: new Date(), endDate: new Date(), project: 0 }; //project id: static counter?
|
|
||||||
this.editing = false;
|
|
||||||
} else {
|
|
||||||
this.editing = true;
|
|
||||||
}
|
|
||||||
document.getElementById('titleField').focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
if (this.editing) {
|
|
||||||
this.backendService.putSprint(this.sprint).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.backendService.postSprint(this.sprint).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.activeModalService.close(this.sprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
this.activeModalService.dismiss(this.sprint);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
th.sortable:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: 20px;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
.modal-footer {
|
|
||||||
border-top: 0px solid;
|
|
||||||
padding-top: 5%;
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
width: 1040px;
|
|
||||||
right: 55%;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
<div class="modal-content p-3">
|
|
||||||
<div class="modal-header">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<h4 class="modal-title">Neuen Task anlegen</h4>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<h6 class="modal-caption text-muted"> Gehört zu Story: <a href="#" id="userstoryTitle">{{this.userstoryId}}</a></h6>
|
|
||||||
</tr>
|
|
||||||
<!--getUserstory fehlt noch-->
|
|
||||||
</table>
|
|
||||||
<button (click)="onClose()" type="button" class="close" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form (ngSubmit)="onSubmit()">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<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>
|
|
||||||
<div class="col-md-1"></div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Prio">Prio</label>
|
|
||||||
<select class="form-control custom-select mr-sm-2" id="prio" required name="prio"
|
|
||||||
[(ngModel)]="task.priority">
|
|
||||||
<option value="low">Low</option>
|
|
||||||
<option value="medium">Medium</option>
|
|
||||||
<option value="high">High</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<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-md-1"></div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Inhalt">Status</label>
|
|
||||||
<input type="text" class="form-control" id="Status" required name="status"
|
|
||||||
[(ngModel)]="task.statusid">
|
|
||||||
</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="modal-footer">
|
|
||||||
<button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal">Abbrechen</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Erstellen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,72 +0,0 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import {
|
|
||||||
BackendService,
|
|
||||||
ScrumTask,
|
|
||||||
Priority,
|
|
||||||
ScrumStatus,
|
|
||||||
ScrumCategory,
|
|
||||||
ScrumUser,
|
|
||||||
ScrumProject,
|
|
||||||
ScrumUserstory,
|
|
||||||
} from '../services/backend.service';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { HttpResponse } from '@angular/common/http';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-task-form',
|
|
||||||
templateUrl: './task-form.component.html',
|
|
||||||
styleUrls: ['./task-form.component.css'],
|
|
||||||
})
|
|
||||||
export class TaskFormComponent implements OnInit {
|
|
||||||
@Input() public task: ScrumTask;
|
|
||||||
public editing: Boolean;
|
|
||||||
public userstoryId: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private backendService: BackendService,
|
|
||||||
private activeModalService: NgbActiveModal
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (this.task === null || this.task === undefined) {
|
|
||||||
this.task = { title: '' };
|
|
||||||
this.editing = false;
|
|
||||||
} else {
|
|
||||||
this.editing = true;
|
|
||||||
}
|
|
||||||
document.getElementById('titleField').focus();
|
|
||||||
this.getRelatedStory();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
if (this.editing) {
|
|
||||||
this.backendService.putTask(this.task).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.backendService.postTask(this.task).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.activeModalService.close(this.task);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
this.activeModalService.dismiss(this.task);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRelatedStory() {
|
|
||||||
this.backendService.getUserstory(2).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
} else {
|
|
||||||
this.userstoryId = response.body.title;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
th.sortable:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: 20px;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
.modal-footer {
|
|
||||||
border-top: 0px solid;
|
|
||||||
padding-top: 5%;
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
width: 1040px;
|
|
||||||
right: 55%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
<!-- <div class="modal-lg"> -->
|
|
||||||
<div class="modal-content p-3 text-dark">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">Neue Userstory anlegen</h4>
|
|
||||||
<button (click)="onClose()" type="button" class="close" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form (ngSubmit)="onSubmit()">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Title">Titel</label>
|
|
||||||
<input type="text" class="form-control" id="Title" required name="title"
|
|
||||||
[(ngModel)]="userstory.title" id="titleField">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-1"></div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Prio">Prio</label>
|
|
||||||
<select class="custom-select mr-sm-2" id="prio" required name="prio"
|
|
||||||
[(ngModel)]="userstory.priority">
|
|
||||||
<option value="low">Low</option>
|
|
||||||
<option value="medium">Medium</option>
|
|
||||||
<option value="high">High</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Inhalt">Story</label>
|
|
||||||
<textarea type="text" class="form-control" id="Story" required name="story" rows="5"
|
|
||||||
[(ngModel)]="userstory.content"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-1"></div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Inhalt">Status</label>
|
|
||||||
<input type="text" class="form-control" id="Status" required name="status"
|
|
||||||
[(ngModel)]="userstory.statusid">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Inhalt">Autor</label>
|
|
||||||
<input type="text" class="form-control" id="Author" required name="author"
|
|
||||||
[(ngModel)]="userstory.createdbyid">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button (click)="onClose()" type="dismiss" class="btn btn-secondary"
|
|
||||||
data-dismiss="modal">Abbrechen</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Erstellen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- </div> -->
|
|
@ -1,49 +0,0 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { BackendService, ScrumUserstory, Priority } from '../services/backend.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-userstory-form',
|
|
||||||
templateUrl: './userstory-form.component.html',
|
|
||||||
styleUrls: [ './userstory-form.component.css' ]
|
|
||||||
})
|
|
||||||
export class UserstoryFormComponent implements OnInit {
|
|
||||||
@Input() public userstory: ScrumUserstory;
|
|
||||||
private editing: boolean;
|
|
||||||
|
|
||||||
constructor(private backendService: BackendService, private activeModalService: NgbActiveModal) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (this.userstory === null || this.userstory === undefined) {
|
|
||||||
this.userstory = { title: '' };
|
|
||||||
this.editing = false;
|
|
||||||
} else {
|
|
||||||
this.editing = true;
|
|
||||||
}
|
|
||||||
document.getElementById('titleField').focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
if (this.editing) {
|
|
||||||
this.backendService.putUserstory(this.userstory).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.backendService.postUserstory(this.userstory).subscribe((response) => {
|
|
||||||
if (response.status > 399) {
|
|
||||||
alert('Fehler');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.activeModalService.close(this.userstory);
|
|
||||||
}
|
|
||||||
onClose() {
|
|
||||||
this.activeModalService.dismiss(this.userstory);
|
|
||||||
}
|
|
||||||
|
|
||||||
//focusTitleField() {
|
|
||||||
// document.getElementById('titleField').focus();
|
|
||||||
//}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
<div class="container-fluid">
|
|
||||||
<div class="content">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
@ -1 +1,9 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
.custom-text-secondary {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-content .custom-text-secondary {
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,9 @@
|
|||||||
|
@import "~bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
.modal .modal-content {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
th.sortable:hover {
|
th.sortable:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user