Prettified all the things

This commit is contained in:
Nicolai Ort 2020-07-10 22:49:11 +02:00
parent 073b621494
commit ac0b901c35
50 changed files with 2155 additions and 1632 deletions

49
.prettierignore Normal file
View File

@ -0,0 +1,49 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
dist
tmp
out-tsc
assets
src\assets
# Only exists if Bazel was run
bazel-out
# dependencies
node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
.idea
.project
.classpath
.c9
*.launch
.settings/
*.sublime-workspace
package-lock.json
# IDE - VSCode
.vscode
# misc
.sass-cache
connect.lock
coverage
libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# Other ignore files
.gitignore
.dockerignore
.gitlab-ci.yml

View File

@ -3,6 +3,7 @@
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.7. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.7.
## Angular CLI ## Angular CLI
### Development server ### Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
@ -28,27 +29,32 @@ Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protrac
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
## Deploy with Docker ## Deploy with Docker
> Prerequisite: Docker and/or Docker Compose have to be installed on your system </br> > Prerequisite: Docker and/or Docker Compose have to be installed on your system </br>
> The Docker installation instructions for most operation systems can be found here: [https://docs.docker.com/get-docker/](https://docs.docker.com/get-docker/) </br> > The Docker installation instructions for most operation systems can be found here: [https://docs.docker.com/get-docker/](https://docs.docker.com/get-docker/) </br>
> The Docker Compose installation instructions for most operation systems can be found here: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/) > The Docker Compose installation instructions for most operation systems can be found here: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/)
> To use the prebuild images log into the private registry: docker login -u testuser -p LpH2v1mShPpC8Xeimkd2AISA03zaC+vq scrumdev.azurecr.io > To use the prebuild images log into the private registry: docker login -u testuser -p LpH2v1mShPpC8Xeimkd2AISA03zaC+vq scrumdev.azurecr.io
### Quick Deployment with Docker Compose ### Quick Deployment with Docker Compose
1. Clone the git repo 1. Clone the git repo
2. Open the cloned repo's folder in a shell of your choice (as long as it supports Docker) 2. Open the cloned repo's folder in a shell of your choice (as long as it supports Docker)
3. Tell Docker Compose to run the app alongside a database with `docker-compose up` 3. Tell Docker Compose to run the app alongside a database with `docker-compose up`
Other useful commands: Other useful commands:
* Run attached (in the background): `docker-compose up -d`
* Stop: `docker-compose down` - Run attached (in the background): `docker-compose up -d`
* Restart: `docker-compose restart` - Stop: `docker-compose down`
- Restart: `docker-compose restart`
### Build the Container yourself (and run it) ### Build the Container yourself (and run it)
1. Clone the git repo 1. Clone the git repo
2. Open the cloned repo's folder in a shell of your choice (as long as it supports Docker) 2. Open the cloned repo's folder in a shell of your choice (as long as it supports Docker)
3. Tell Docker to build the app under the name "taskboard/frontend" with `docker build -t taskboard/frontend .` 3. Tell Docker to build the app under the name "taskboard/frontend" with `docker build -t taskboard/frontend .`
4. Tell Docker to run the app on port 8080 with `docker run -p 8080:80 taskboard/frontend` 4. Tell Docker to run the app on port 8080 with `docker run -p 8080:80 taskboard/frontend`
Other useful commands: Other useful commands:
* Run detached: `docker run -d -p 8080:80 taskboard/frontend`
* Run under specified name: `docker run -p 8080:80 --name frontend taskboard/frontend` - Run detached: `docker run -d -p 8080:80 taskboard/frontend`
- Run under specified name: `docker run -p 8080:80 --name frontend taskboard/frontend`

View File

@ -20,17 +20,13 @@
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"preserveSymlinks": true, "preserveSymlinks": true,
"aot": true, "aot": true,
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
"src/favicon.ico",
"src/assets"
],
"styles": [ "styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss", "src/styles.scss",
"src/assets/scss/black-dashboard.scss" "src/assets/scss/black-dashboard.scss"
], ],
"scripts": [ "scripts": []
]
}, },
"configurations": { "configurations": {
"production": { "production": {
@ -87,13 +83,8 @@
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
"src/favicon.ico", "styles": ["src/styles.scss"],
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [] "scripts": []
} }
}, },
@ -105,9 +96,7 @@
"tsconfig.spec.json", "tsconfig.spec.json",
"e2e/tsconfig.json" "e2e/tsconfig.json"
], ],
"exclude": [ "exclude": ["**/node_modules/**"]
"**/node_modules/**"
]
} }
}, },
"e2e": { "e2e": {
@ -126,4 +115,4 @@
} }
}, },
"defaultProject": "frontend" "defaultProject": "frontend"
} }

View File

@ -1,12 +1,12 @@
version: "3" version: "3"
services: services:
app: app:
image: scrumdev.azurecr.io/taskboard/frontend:latest image: scrumdev.azurecr.io/taskboard/frontend:latest
restart: unless-stopped restart: unless-stopped
networks: networks:
- default - default
ports: ports:
- "8080:80" - "8080:80"
environment: environment:
API_URL: http://localhost:5001 API_URL: http://localhost:5001

View File

@ -2,31 +2,31 @@
// Protractor configuration file, see link for more information // Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts // https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter'); const { SpecReporter } = require("jasmine-spec-reporter");
/** /**
* @type { import("protractor").Config } * @type { import("protractor").Config }
*/ */
exports.config = { exports.config = {
allScriptsTimeout: 11000, allScriptsTimeout: 11000,
specs: [ specs: ["./src/**/*.e2e-spec.ts"],
'./src/**/*.e2e-spec.ts'
],
capabilities: { capabilities: {
browserName: 'chrome' browserName: "chrome",
}, },
directConnect: true, directConnect: true,
baseUrl: 'http://localhost:4200/', baseUrl: "http://localhost:4200/",
framework: 'jasmine', framework: "jasmine",
jasmineNodeOpts: { jasmineNodeOpts: {
showColors: true, showColors: true,
defaultTimeoutInterval: 30000, defaultTimeoutInterval: 30000,
print: function() {} print: function () {},
}, },
onPrepare() { onPrepare() {
require('ts-node').register({ require("ts-node").register({
project: require('path').join(__dirname, './tsconfig.json') project: require("path").join(__dirname, "./tsconfig.json"),
}); });
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); jasmine
} .getEnv()
}; .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
},
};

View File

@ -16,8 +16,10 @@ describe('workspace-project App', () => {
afterEach(async () => { afterEach(async () => {
// Assert that there are no errors emitted from the browser // Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER); const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({ expect(logs).not.toContain(
level: logging.Level.SEVERE, jasmine.objectContaining({
} as logging.Entry)); level: logging.Level.SEVERE,
} as logging.Entry)
);
}); });
}); });

View File

@ -6,6 +6,8 @@ export class AppPage {
} }
getTitleText(): Promise<string> { getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>; return element(by.css('app-root .content span')).getText() as Promise<
string
>;
} }
} }

View File

@ -4,10 +4,6 @@
"outDir": "../out-tsc/e2e", "outDir": "../out-tsc/e2e",
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es5",
"types": [ "types": ["jasmine", "jasminewd2", "node"]
"jasmine",
"jasminewd2",
"node"
]
} }
} }

View File

@ -3,30 +3,30 @@
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: "",
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ["jasmine", "@angular-devkit/build-angular"],
plugins: [ plugins: [
require('karma-jasmine'), require("karma-jasmine"),
require('karma-chrome-launcher'), require("karma-chrome-launcher"),
require('karma-jasmine-html-reporter'), require("karma-jasmine-html-reporter"),
require('karma-coverage-istanbul-reporter'), require("karma-coverage-istanbul-reporter"),
require('@angular-devkit/build-angular/plugins/karma') require("@angular-devkit/build-angular/plugins/karma"),
], ],
client: { client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false, // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/frontend'), dir: require("path").join(__dirname, "./coverage/frontend"),
reports: ['html', 'lcovonly', 'text-summary'], reports: ["html", "lcovonly", "text-summary"],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true,
}, },
reporters: ['progress', 'kjhtml'], reporters: ["progress", "kjhtml"],
port: 9876, port: 9876,
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ["Chrome"],
singleRun: false, singleRun: false,
restartOnFileChange: true restartOnFileChange: true,
}); });
}; };

View File

@ -4,7 +4,7 @@ import { DashboardComponent } from './components/dashboard/dashboard.component';
import { UserstoryTableComponent } from './components/tabels/userstory/userstory-table.component'; import { UserstoryTableComponent } from './components/tabels/userstory/userstory-table.component';
import { TaskTableComponent } from './components/tabels/task/task-table.component'; import { TaskTableComponent } from './components/tabels/task/task-table.component';
import { SprintTableComponent } from './components/tabels/sprint/sprint-table.component'; import { SprintTableComponent } from './components/tabels/sprint/sprint-table.component';
import {BacklogComponent} from './components/backlog/backlog.component'; import { BacklogComponent } from './components/backlog/backlog.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'tasks', component: TaskTableComponent }, { path: 'tasks', component: TaskTableComponent },

View File

@ -8,15 +8,15 @@
} }
.nav a { .nav a {
font-size: 1em; font-size: 1em;
} }
.nav a:hover:not(.active) { .nav a:hover:not(.active) {
font-size: 1.15em; font-size: 1.15em;
} }
.content { .content {
position: relative; position: relative;
float: right; float: right;
margin-top: 10px; margin-top: 10px;
} }

View File

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

View File

@ -5,12 +5,8 @@ import { AppComponent } from './app.component';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [RouterTestingModule],
RouterTestingModule declarations: [AppComponent],
],
declarations: [
AppComponent
],
}).compileComponents(); }).compileComponents();
})); }));
@ -30,6 +26,8 @@ describe('AppComponent', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges(); fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement; const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('frontend app is running!'); expect(compiled.querySelector('.content span').textContent).toContain(
'frontend app is running!'
);
}); });
}); });

View File

@ -3,7 +3,7 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css'],
}) })
export class AppComponent { export class AppComponent {
/* /*
@ -21,12 +21,11 @@ export class AppComponent {
} }
} }
*/ */
changeDashboardColor(color){ changeDashboardColor(color) {
var body = document.getElementsByTagName('body')[0]; var body = document.getElementsByTagName('body')[0];
if (body && color === 'white-content') { if (body && color === 'white-content') {
body.classList.add(color); body.classList.add(color);
} } else if (body.classList.contains('white-content')) {
else if(body.classList.contains('white-content')) {
body.classList.remove('white-content'); body.classList.remove('white-content');
} }
} }

View File

@ -1,45 +1,45 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module'; 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 './components/task-form/task-form.component'; import { TaskFormComponent } from './components/task-form/task-form.component';
import { UserstoryFormComponent } from './components/userstory-form/userstory-form.component'; import { UserstoryFormComponent } from './components/userstory-form/userstory-form.component';
import { SprintFormComponent } from './components/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 './components/tabels/userstory/userstory-table.component'; import { UserstoryTableComponent } from './components/tabels/userstory/userstory-table.component';
import { TaskTableComponent } from './components/tabels/task/task-table.component'; import { TaskTableComponent } from './components/tabels/task/task-table.component';
import { SprintTableComponent } from './components/tabels/sprint/sprint-table.component'; import { SprintTableComponent } from './components/tabels/sprint/sprint-table.component';
import { DashboardComponent } from './components/dashboard/dashboard.component'; import { DashboardComponent } from './components/dashboard/dashboard.component';
import { UserstoryInnerTableComponent } from './components/tabels/userstory-inner-table/userstory-inner-table.component'; import { UserstoryInnerTableComponent } from './components/tabels/userstory-inner-table/userstory-inner-table.component';
import { BacklogComponent } from './components/backlog/backlog.component'; import { BacklogComponent } from './components/backlog/backlog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
TaskTableComponent, TaskTableComponent,
TaskFormComponent, TaskFormComponent,
UserstoryTableComponent, UserstoryTableComponent,
UserstoryFormComponent, UserstoryFormComponent,
UserstoryTableComponent, UserstoryTableComponent,
SprintFormComponent, SprintFormComponent,
SprintTableComponent, SprintTableComponent,
DashboardComponent, DashboardComponent,
UserstoryInnerTableComponent, UserstoryInnerTableComponent,
BacklogComponent BacklogComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
HttpClientModule, HttpClientModule,
FormsModule, FormsModule,
NgbModule, NgbModule,
], ],
providers: [BackendService], providers: [BackendService],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule {} export class AppModule {}

View File

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

View File

@ -1,97 +1,134 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="content"> <div class="content">
<h3>Backlog</h3> <h3>Backlog</h3>
<div class="row"> <div class="row">
<div class="col-lg-6 container-fluid"></div> <div class="col-lg-6 container-fluid"></div>
<div align="right" class="col-lg-6 container-fluid"> <div align="right" class="col-lg-6 container-fluid">
<button class="btn btn-secondary" (click)="openSprintForm()">Neuer Sprint</button> <button class="btn btn-secondary" (click)="openSprintForm()">
</div> Neuer Sprint
</div> </button>
</div>
<div class="row">
<div class="col-lg-6">
<h3>Backlog</h3>
</div>
<div class="col-lg-6">
<div class="row px-3 py-2">
<ng-container *ngIf="selectedSprint === undefined">
<h3>
Aktuell läuft kein Sprint.
<a [routerLink]="['../sprints']">Zur Sprint Übersicht</a>
</h3>
</ng-container>
<ng-container *ngIf="selectedSprint != undefined">
<h3 *ngIf="selectedSprint === currentSprint">Aktueller Sprint:</h3>
<h3 *ngIf="selectedSprint !== currentSprint">Sprint:</h3>
<div ngbDropdown="ngbDropdown" class="dropdown">
<button ngbDropdownToggle="ngbDropdownToggle" class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{selectedSprint.title}}
({{toDateString(selectedSprint.startDate)}}
-
{{toDateString(selectedSprint.endDate)}})
</button>
<div ngbDropdownMenu="ngbDropdownMenu" class="dropdown-menu" aria-labelledby="dropdownMenu2">
<option ngbDropdownItem="ngbDropdownItem" *ngFor="let s of sprints" (click)="this.selectedSprint = s;">
{{s.title}}
({{toDateString(selectedSprint.startDate)}}
-
{{toDateString(selectedSprint.endDate)}})
</option>
</div>
</div>
</ng-container>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 container-fluid">
<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;" *ngIf="selectedSprint != undefined">
<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 class="col-lg-6 container-fluid">
<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>
</div>
<div class="row">
<div class="col-lg-6">
<h3>Backlog</h3>
</div>
<div class="col-lg-6">
<div class="row px-3 py-2">
<ng-container *ngIf="selectedSprint === undefined">
<h3>
Aktuell läuft kein Sprint.
<a [routerLink]="['../sprints']">Zur Sprint Übersicht</a>
</h3>
</ng-container>
<ng-container *ngIf="selectedSprint != undefined">
<h3 *ngIf="selectedSprint === currentSprint">Aktueller Sprint:</h3>
<h3 *ngIf="selectedSprint !== currentSprint">Sprint:</h3>
<div ngbDropdown="ngbDropdown" class="dropdown">
<button
ngbDropdownToggle="ngbDropdownToggle"
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenu2"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ selectedSprint.title }}
({{ toDateString(selectedSprint.startDate) }}
-
{{ toDateString(selectedSprint.endDate) }})
</button>
<div
ngbDropdownMenu="ngbDropdownMenu"
class="dropdown-menu"
aria-labelledby="dropdownMenu2"
>
<option
ngbDropdownItem="ngbDropdownItem"
*ngFor="let s of sprints"
(click)="this.selectedSprint = s"
>
{{ s.title }}
({{ toDateString(selectedSprint.startDate) }}
-
{{ toDateString(selectedSprint.endDate) }})
</option>
</div>
</div>
</ng-container>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 container-fluid">
<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;"
*ngIf="selectedSprint != undefined"
>
<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 class="col-lg-6 container-fluid">
<div *ngFor="let story of choosen" class="col-lg-6 container-fluid">
<div class="card" style="width: 150%;">
<div class="card-body">
<h4 class="card-title">{{ story.title }}</h4>
<h6 class="card-subtitle mb-2 text-muted">
Prio: {{ story.priority }}
</h6>
<p class="card-text">{{ story.content }}</p>
<div title="Badges">
<span class="badge badge-primary"
>Category: {{ story.categoryid || "N/A" }}</span
>
<span class="badge badge-info"
>Status: {{ story.statusid || "N/A" }}</span
>
</div>
<div style="text-align: right;">
<button
type="button"
rel="tooltip"
(click)="deleteFromSprintBacklog(story)"
class="btn btn-danger btn-sm btn-icon"
>
<i class="fa fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -9,15 +9,12 @@ import {
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { SprintFormComponent } from '../sprint-form/sprint-form.component'; import { SprintFormComponent } from '../sprint-form/sprint-form.component';
@Component({ @Component({
selector: 'app-backlog', selector: 'app-backlog',
templateUrl: './backlog.component.html', templateUrl: './backlog.component.html',
styleUrls: ['./backlog.component.css'], styleUrls: ['./backlog.component.css'],
}) })
export class BacklogComponent {
export class BacklogComponent{
public status: ScrumStatus[] = []; public status: ScrumStatus[] = [];
public categories: ScrumCategory[] = []; public categories: ScrumCategory[] = [];
public sprints: ScrumSprint[] = []; public sprints: ScrumSprint[] = [];
@ -26,12 +23,12 @@ export class BacklogComponent{
/** /**
* Constructor of the class that initialized the communication with the backend * Constructor of the class that initialized the communication with the backend
* @param backendService * @param backendService
* @param modalService * @param modalService
*/ */
constructor( constructor(
private backendService: BackendService, private backendService: BackendService,
private modalService: NgbModal, private modalService: NgbModal
) { ) {
backendService.getSprints().subscribe((response) => { backendService.getSprints().subscribe((response) => {
if (response.status > 399) { if (response.status > 399) {
@ -80,19 +77,19 @@ export class BacklogComponent{
* The relation to the sprint is determined by the userstory's sprintid. * The relation to the sprint is determined by the userstory's sprintid.
* If no sprint is selected it just returns an empty array. * If no sprint is selected it just returns an empty array.
*/ */
public get choosen(): ScrumUserstory[] public get choosen(): ScrumUserstory[] {
{ if (this.selectedSprint === undefined) {
if(this.selectedSprint === undefined){return null;} return null;
return this.storys.filter(u => u.sprintid == this.selectedSprint.id); }
return this.storys.filter((u) => u.sprintid == this.selectedSprint.id);
} }
/** /**
* Getter that returns an array with all userstories that aren't in any sprint. * Getter that returns an array with all userstories that aren't in any sprint.
* The relation to no sprint is determined by the userstory's sprintid being undefined. * The relation to no sprint is determined by the userstory's sprintid being undefined.
*/ */
public get backlog(): ScrumUserstory[] public get backlog(): ScrumUserstory[] {
{ return this.storys.filter((u) => u.sprintid === undefined);
return this.storys.filter(u => u.sprintid === undefined);
} }
/** /**
@ -101,7 +98,9 @@ export class BacklogComponent{
*/ */
public get currentSprint(): ScrumSprint { public get currentSprint(): ScrumSprint {
const now = Date.now(); const now = Date.now();
return this.sprints.find(s => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now); return this.sprints.find(
(s) => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now
);
} }
//#endregion getters //#endregion getters
@ -121,11 +120,11 @@ export class BacklogComponent{
}); });
} }
/** /**
* Deletes a userstory from the currently selected sprint by changing it's sprintid to undefined * Deletes a userstory from the currently selected sprint by changing it's sprintid to undefined
* @param userstory userstory object that shall be removed from the selected sprint's backlog * @param userstory userstory object that shall be removed from the selected sprint's backlog
*/ */
public deleteFromSprintBacklog(userstory: ScrumUserstory){ public deleteFromSprintBacklog(userstory: ScrumUserstory) {
userstory.sprintid = undefined; userstory.sprintid = undefined;
this.backendService.putUserstory(userstory).subscribe((response) => { this.backendService.putUserstory(userstory).subscribe((response) => {
if (response.status > 399) { if (response.status > 399) {
@ -145,12 +144,12 @@ export class BacklogComponent{
const modalRef = this.modalService.open(SprintFormComponent, { const modalRef = this.modalService.open(SprintFormComponent, {
backdrop: 'static', backdrop: 'static',
keyboard: true, keyboard: true,
size: "md", size: 'md',
}); });
modalRef.result.then(result => { modalRef.result.then((result) => {
this.sprints.push(result); this.sprints.push(result);
}); });
} }
//#endregion modals //#endregion modals
} }

View File

@ -1,36 +1,57 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="content"> <div class="content">
<h3>Dashboard</h3> <h3>Dashboard</h3>
<div> <div>
<div class="row px-3 py-2"> <div class="row px-3 py-2">
<ng-container *ngIf="selectedSprint === undefined"> <ng-container *ngIf="selectedSprint === undefined">
<h3 class="mr-3 text-primary">Alle Userstories</h3> <h3 class="mr-3 text-primary">Alle Userstories</h3>
<a [routerLink]="['sprints']">Lege einen Sprint an, um Userstories zu organisieren.</a> <a [routerLink]="['sprints']"
>Lege einen Sprint an, um Userstories zu organisieren.</a
>
</ng-container> </ng-container>
<ng-container *ngIf="selectedSprint !== undefined"> <ng-container *ngIf="selectedSprint !== undefined">
<h3
<h3 class="mr-3 text-primary" *ngIf="selectedSprint === currentSprint">Aktueller Sprint:</h3> class="mr-3 text-primary"
<h3 class="mr-3 text-primary" *ngIf="selectedSprint !== currentSprint">Sprint:</h3> *ngIf="selectedSprint === currentSprint"
<h3 class="mr-3 custom-text-secondary">{{selectedSprint.title}}</h3> >
<h3 class="mr-3">{{toDateString(selectedSprint.startDate)}} - {{toDateString(selectedSprint.endDate)}}</h3> 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"> <label class="mr-3">
<select class="select custom-select custom-text-secondary" [(ngModel)]="selectedSprint"> <select
class="select custom-select custom-text-secondary"
[(ngModel)]="selectedSprint"
>
<option class="bg-secondary text-dark" [ngValue]="currentSprint"> <option class="bg-secondary text-dark" [ngValue]="currentSprint">
{{currentSprint.title}} (aktuell) {{ currentSprint.title }} (aktuell)
</option> </option>
<option value="" disabled="disabled">─────────────────────────</option> <option value="" disabled="disabled"
<option class="text-dark" *ngFor="let sprint of sprints" [ngValue]="sprint"> >─────────────────────────</option
>
<option
class="text-dark"
*ngFor="let sprint of sprints"
[ngValue]="sprint"
>
<ng-container *ngIf="sprint === currentSprint"> <ng-container *ngIf="sprint === currentSprint">
{{sprint.title}} (aktuell) {{ sprint.title }} (aktuell)
</ng-container> </ng-container>
<ng-container *ngIf="sprint !== currentSprint"> <ng-container *ngIf="sprint !== currentSprint">
{{sprint.title}} ({{toDateString(sprint.startDate)}} - {{toDateString(sprint.endDate)}}) {{ sprint.title }} ({{ toDateString(sprint.startDate) }} -
{{ toDateString(sprint.endDate) }})
</ng-container> </ng-container>
</option> </option>
</select> </select>
@ -44,43 +65,40 @@
[class.text-warning]="getSprintUrgency() === 1" [class.text-warning]="getSprintUrgency() === 1"
[class.text-danger]="getSprintUrgency() === 0" [class.text-danger]="getSprintUrgency() === 0"
> >
Verbleibende Tage: {{getRemainingDaysInSprint()}} Verbleibende Tage: {{ getRemainingDaysInSprint() }}
</h3> </h3>
</ng-container> </ng-container>
</div> </div>
<div class="row"> <div class="row">
<div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12"> <div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<span class="text-large"> <span class="text-large">
Userstories Userstories
</span> </span>
</div> </div>
<div class="card-body"> <div class="card-body">
<div *ngIf="selectedSprint !== undefined && usedStatus.length === 0"> <div
Zum Sprint "{{selectedSprint.title}}" sind aktuell keine Userstories vorhanden. *ngIf="selectedSprint !== undefined && usedStatus.length === 0"
>
Zum Sprint "{{ selectedSprint.title }}" sind aktuell keine
Userstories vorhanden.
</div> </div>
<canvas id="done-stories-chart"></canvas> <canvas id="done-stories-chart"></canvas>
</div> </div>
</div> </div>
<div class="card-deck"> <div class="card-deck">
<div <div class="card text-center" *ngFor="let status of usedStatus">
class="card text-center"
*ngFor="let status of usedStatus"
>
<div class="card-header"> <div class="card-header">
<span class="text-large"> <span class="text-large">
{{status.title}} {{ status.title }}
</span> </span>
</div> </div>
<div class="card-body"> <div class="card-body">
<span class="text-very-large"> <span class="text-very-large">
<b>{{getNumberOfUserstoriesByStatus(status)}}</b> <b>{{ getNumberOfUserstoriesByStatus(status) }}</b>
</span> </span>
</div> </div>
</div> </div>
</div> </div>
@ -89,13 +107,13 @@
<div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12"> <div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<app-userstory-inner-table [storys]="selectedSprintUserstories"></app-userstory-inner-table> <app-userstory-inner-table
[storys]="selectedSprintUserstories"
></app-userstory-inner-table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,31 +1,44 @@
import {Component, OnChanges} from '@angular/core'; import { Component, OnChanges } from '@angular/core';
import {forkJoin} from 'rxjs'; import { forkJoin } from 'rxjs';
import Chart from 'chart.js'; import Chart from 'chart.js';
import {BackendService, ScrumSprint, ScrumStatus, ScrumUserstory} from '../../services/backend.service'; import {
BackendService,
ScrumSprint,
ScrumStatus,
ScrumUserstory,
} from '../../services/backend.service';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
templateUrl: 'dashboard.component.html', templateUrl: 'dashboard.component.html',
styleUrls: ['./dashboard.component.css'] styleUrls: ['./dashboard.component.css'],
}) })
export class DashboardComponent { export class DashboardComponent {
/** /**
* Returns the status that are used by at least one userstory. * Returns the status that are used by at least one userstory.
*/ */
public get usedStatus(): ScrumStatus[] { public get usedStatus(): ScrumStatus[] {
return this.status.filter(s => this.selectedSprintUserstories.find(us => us.statusid === s.id) !== undefined); return this.status.filter(
(s) =>
this.selectedSprintUserstories.find((us) => us.statusid === s.id) !==
undefined
);
} }
public get selectedSprintUserstories(): ScrumUserstory[] { public get selectedSprintUserstories(): ScrumUserstory[] {
if (this.selectedSprint === undefined) { if (this.selectedSprint === undefined) {
return this.userstories; return this.userstories;
} }
return this.userstories.filter(us => us.sprintid === this.selectedSprint.id); return this.userstories.filter(
(us) => us.sprintid === this.selectedSprint.id
);
} }
public get currentSprint(): ScrumSprint { public get currentSprint(): ScrumSprint {
const now = Date.now(); const now = Date.now();
return this.sprints.find(s => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now); return this.sprints.find(
(s) => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now
);
} }
private _selectedSprint: ScrumSprint; private _selectedSprint: ScrumSprint;
@ -53,18 +66,25 @@ export class DashboardComponent {
constructor(private backendService: BackendService) { constructor(private backendService: BackendService) {
// download userstories, status and sprints and update // download userstories, status and sprints and update
// the chart whenever a new response is there // the chart whenever a new response is there
forkJoin([backendService.getUserstories(), backendService.getAllStatus(), backendService.getSprints()]) forkJoin([
.subscribe(results => { backendService.getUserstories(),
const [userstoryResponse, statusResponse, sprintResponse] = results; backendService.getAllStatus(),
if (userstoryResponse.status > 399 || statusResponse.status > 399 || sprintResponse.status > 399) { backendService.getSprints(),
alert('Fehler'); ]).subscribe((results) => {
} else { const [userstoryResponse, statusResponse, sprintResponse] = results;
this.userstories.push(...userstoryResponse.body); if (
this.status.push(...statusResponse.body); userstoryResponse.status > 399 ||
this.sprints.push(...sprintResponse.body); statusResponse.status > 399 ||
this.createChart(); sprintResponse.status > 399
} ) {
}); alert('Fehler');
} else {
this.userstories.push(...userstoryResponse.body);
this.status.push(...statusResponse.body);
this.sprints.push(...sprintResponse.body);
this.createChart();
}
});
} }
/** /**
@ -77,31 +97,35 @@ export class DashboardComponent {
private createChart() { private createChart() {
// @ts-ignore // @ts-ignore
const context = document.getElementById('done-stories-chart').getContext('2d'); const context = document
.getElementById('done-stories-chart')
.getContext('2d');
if (this.usedStatus.length === 0) { if (this.usedStatus.length === 0) {
this.chart.destroy(); this.chart.destroy();
} } else {
else {
this.chart = new Chart(context, { this.chart = new Chart(context, {
type: 'pie', type: 'pie',
data: { data: {
labels: this.usedStatus.map(s => s.title), labels: this.usedStatus.map((s) => s.title),
datasets: [{ datasets: [
data: this.usedStatus.map(s => this.getNumberOfUserstoriesByStatus(s)), {
backgroundColor: this.getBackgroundColors(), data: this.usedStatus.map((s) =>
options: { this.getNumberOfUserstoriesByStatus(s)
legend: { ),
display: true, backgroundColor: this.getBackgroundColors(),
position: 'right', options: {
labels: { legend: {
fontColor: 'rgba(255, 255, 255, 0.8)' display: true,
} position: 'right',
} labels: {
} fontColor: 'rgba(255, 255, 255, 0.8)',
}] },
} },
},
},
],
},
}); });
this.chart.update(); this.chart.update();
this.chart.render(); this.chart.render();
@ -133,14 +157,18 @@ export class DashboardComponent {
} }
public getNumberOfUserstoriesByStatus(status: ScrumStatus): number { public getNumberOfUserstoriesByStatus(status: ScrumStatus): number {
return this.selectedSprintUserstories.filter(us => us.statusid === status.id).length; return this.selectedSprintUserstories.filter(
(us) => us.statusid === status.id
).length;
} }
public getRemainingDaysInSprint(): number { public getRemainingDaysInSprint(): number {
if (this.selectedSprint === undefined) { if (this.selectedSprint === undefined) {
return undefined; return undefined;
} }
return Math.floor((Date.parse(this.selectedSprint.endDate) - Date.now()) / 86400000); return Math.floor(
(Date.parse(this.selectedSprint.endDate) - Date.now()) / 86400000
);
} }
/** /**
@ -154,7 +182,8 @@ export class DashboardComponent {
return undefined; return undefined;
} }
const deltaFromNow = Date.parse(sprint.endDate) - now; const deltaFromNow = Date.parse(sprint.endDate) - now;
const deltaFromStart = Date.parse(sprint.endDate) - Date.parse(sprint.startDate); const deltaFromStart =
return Math.floor(3 * deltaFromNow / deltaFromStart); Date.parse(sprint.endDate) - Date.parse(sprint.startDate);
return Math.floor((3 * deltaFromNow) / deltaFromStart);
} }
} }

View File

@ -1,4 +1,4 @@
.modal-footer { .modal-footer {
border-top: 0px solid; border-top: 0px solid;
padding-top: 5%; padding-top: 5%;
} }

View File

@ -10,26 +10,56 @@
<form (ngSubmit)="onSubmit()"> <form (ngSubmit)="onSubmit()">
<div class="form-group"> <div class="form-group">
<label for="Title">Titel</label> <label for="Title">Titel</label>
<input type="text" class="form-control" id="Title" required name="title" [(ngModel)]="sprint.title" <input
id="titleField"> type="text"
class="form-control"
id="Title"
required
name="title"
[(ngModel)]="sprint.title"
id="titleField"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="date">Startdatum</label> <label for="date">Startdatum</label>
<input #startDate type="Date" class="form-control" id="Date" required name="date" <input
[value]="sprint.startDate | date: 'yyyy-MM-dd'" (change)="sprint.startDate=startDate.value" #startDate
id="startDateField"> 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>
<div class="form-group"> <div class="form-group">
<label for="Date">Enddatum</label> <label for="Date">Enddatum</label>
<input #endDate type="date" class="form-control" id="Date" required name="date" <input
[value]="sprint.endDate | date: 'yyyy-MM-dd'" (change)="sprint.endDate=endDate.value" id="endDateField"> #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>
<div> <div>
<button (click)="onClose()" type="dismiss" class="btn btn-secondary" data-dismiss="modal">Abbrechen <button
(click)="onClose()"
type="dismiss"
class="btn btn-secondary"
data-dismiss="modal"
>
Abbrechen
</button> </button>
<button type="submit" class="btn btn-primary">Sprint starten</button> <button type="submit" class="btn btn-primary">Sprint starten</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -89,4 +89,4 @@ export class SprintTableComponent extends TableComponentBase<ScrumSprint> {
modalRef.componentInstance.sprint = editSprint; modalRef.componentInstance.sprint = editSprint;
} }
//#endregion modals //#endregion modals
} }

View File

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

View File

@ -195,4 +195,4 @@ export class TaskTableComponent extends TableComponentBase<ScrumTask> {
modalRef.componentInstance.task = editTask; modalRef.componentInstance.task = editTask;
} }
//#endregion modals //#endregion modals
} }

View File

@ -1,99 +1,155 @@
<button class="btn btn-secondary my-3" (click)="openUserstoryForm()">Neue Userstory</button> <button class="btn btn-secondary my-3" (click)="openUserstoryForm()">
Neue Userstory
</button>
<table class="table"> <table class="table">
<thead>
<thead>
<tr> <tr>
<th (click)="sortById()" class="sortable"> <th (click)="sortById()" class="sortable">
<span>ID</span> <span>ID</span>
<span> <span>
<span *ngIf="sortBy != 'id'"><i class="fa fa-sort fa-lg"></i></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 && sortBy === 'id'"
<span *ngIf="sortDescending === false && sortBy === 'id'"><i class="fa fa-sort-down fa-lg"></i></span> ><i class="fa fa-sort-up fa-lg"></i
</span> ></span>
</th> <span *ngIf="sortDescending === false && sortBy === 'id'"
<th (click)="sortByTitle()" class="sortable"> ><i class="fa fa-sort-down fa-lg"></i
></span>
</span>
</th>
<th (click)="sortByTitle()" class="sortable">
<span>Titel</span> <span>Titel</span>
<span> <span>
<span *ngIf="sortBy != 'title'"><i class="fa fa-sort fa-lg"></i></span> <span *ngIf="sortBy != 'title'"
<span *ngIf="sortDescending && sortBy === 'title'"><i class="fa fa-sort-up fa-lg"></i></span> ><i class="fa fa-sort fa-lg"></i
<span *ngIf="sortDescending === false && sortBy === 'title'"><i class="fa fa-sort-down fa-lg"></i></span> ></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> </span>
</th> </th>
<th (click)="sortByTasks()" class="sortable"> <th (click)="sortByTasks()" class="sortable">
<span>Tasks</span> <span>Tasks</span>
<span> <span>
<span *ngIf="sortBy != 'tasks'"><i class="fa fa-sort fa-lg"></i></span> <span *ngIf="sortBy != 'tasks'"
<span *ngIf="sortDescending && sortBy === 'tasks'"><i class="fa fa-sort-up fa-lg"></i></span> ><i class="fa fa-sort fa-lg"></i
<span *ngIf="sortDescending === false && sortBy === 'tasks'"><i class="fa fa-sort-down fa-lg"></i></span> ></span>
</span> <span *ngIf="sortDescending && sortBy === 'tasks'"
</th> ><i class="fa fa-sort-up fa-lg"></i
<th (click)="sortByStatus()" class="sortable"> ></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>Status</span>
<span> <span>
<span *ngIf="sortBy != 'statusid'"><i class="fa fa-sort fa-lg"></i></span> <span *ngIf="sortBy != 'statusid'"
<span *ngIf="sortDescending && sortBy === 'statusid'"><i class="fa fa-sort-up fa-lg"></i></span> ><i class="fa fa-sort fa-lg"></i
<span *ngIf="sortDescending === false && sortBy === 'statusid'"><i class="fa fa-sort-down fa-lg"></i></span> ></span>
</span> <span *ngIf="sortDescending && sortBy === 'statusid'"
</th> ><i class="fa fa-sort-up fa-lg"></i
<th class="sortable"> ></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> <span (click)="sortByPrio()">Priorität: </span>
<div ngbDropdown class="d-inline-block"> <div ngbDropdown class="d-inline-block">
<span id="dropdownBasic1" ngbDropdownToggle>{{filterPriority || "All"}}</span> <span id="dropdownBasic1" ngbDropdownToggle>{{
<div ngbDropdownMenu aria-labelledby="dropdownBasic1"> filterPriority || "All"
<option ngbDropdownItem (click)="filterPriority=null">All</option> }}</span>
<option ngbDropdownItem *ngFor="let p of getAllPriorities()" (click)="filterPriority=p">{{p}}</option> <div ngbDropdownMenu aria-labelledby="dropdownBasic1">
</div> <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> <span (click)="sortByPrio()">
<th (click)="sortByCategory()" class="sortable"> <span *ngIf="sortBy != 'priority'"
><i class="fa fa-sort fa-lg"></i
></span>
<span *ngIf="sortDescending && sortBy === 'priority'"
><i class="fa fa-sort-up fa-lg"></i
></span>
<span *ngIf="sortDescending === false && sortBy === 'priority'"
><i class="fa fa-sort-down fa-lg"></i
></span>
</span>
</div>
</th>
<th (click)="sortByCategory()" class="sortable">
<span>Category</span> <span>Category</span>
<span> <span>
<span *ngIf="sortBy != 'categoryid'"><i class="fa fa-sort fa-lg"></i></span> <span *ngIf="sortBy != 'categoryid'"
<span *ngIf="sortDescending && sortBy === 'categoryid'"><i class="fa fa-sort-up fa-lg"></i></span> ><i class="fa fa-sort fa-lg"></i
<span *ngIf="sortDescending === false && sortBy === 'categoryid'"><i class="fa fa-sort-down fa-lg"></i></span> ></span>
</span> <span *ngIf="sortDescending && sortBy === 'categoryid'"
</th> ><i class="fa fa-sort-up fa-lg"></i
<th></th> ></span>
<span *ngIf="sortDescending === false && sortBy === 'categoryid'"
><i class="fa fa-sort-down fa-lg"></i
></span>
</span>
</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let userstory of filteredItems" [class.table-info]="userstory.id === highlightId"> <tr
<td>{{userstory.id}}</td> *ngFor="let userstory of filteredItems"
<td>{{userstory.title}}</td> [class.table-info]="userstory.id === highlightId"
<td> >
<a [routerLink]="['/tasks', {userstoryId: userstory.id}]"> <td>{{ userstory.id }}</td>
{{getNumberOfTasks(userstory)}} Tasks <td>{{ userstory.title }}</td>
<td>
<a [routerLink]="['/tasks', { userstoryId: userstory.id }]">
{{ getNumberOfTasks(userstory) }} Tasks
</a> </a>
</td> </td>
<td> <td>
<a [routerLink]="['/status', {id: userstory.statusid}]"> <a [routerLink]="['/status', { id: userstory.statusid }]">
{{getStatusTitleById(userstory.statusid)}} {{ getStatusTitleById(userstory.statusid) }}
</a> </a>
</td> </td>
<td>{{userstory.priority}}</td> <td>{{ userstory.priority }}</td>
<td> <td>
<a [routerLink]="['/categories', {id: userstory.categoryid}]"> <a [routerLink]="['/categories', { id: userstory.categoryid }]">
{{getCategoryTitleById(userstory.categoryid)}} {{ getCategoryTitleById(userstory.categoryid) }}
</a> </a>
</td> </td>
<td> <td>
<button type="button" rel="tooltip" (click)="openUserstoryForm(userstory)" class="btn btn-success btn-sm btn-icon"> <button
<i class="fa fa-pencil-alt"></i> type="button"
</button> rel="tooltip"
<button type="button" rel="tooltip" (click)="deleteUserstory(userstory)" class="btn btn-danger btn-sm btn-icon"> (click)="openUserstoryForm(userstory)"
<i class="fa fa-trash"></i> class="btn btn-success btn-sm btn-icon"
</button> >
</td> <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> </tr>
</tbody> </tbody>
</table>
</table>

View File

@ -3,4 +3,4 @@
<h3>Userstories</h3> <h3>Userstories</h3>
<app-userstory-inner-table [storys]="items"></app-userstory-inner-table> <app-userstory-inner-table [storys]="items"></app-userstory-inner-table>
</div> </div>
</div> </div>

View File

@ -1,3 +1,3 @@
.modal .modal-content { .modal .modal-content {
display: contents; display: contents;
} }

View File

@ -1,118 +1,230 @@
<!--Popup form to create and modify a task--> <!--Popup form to create and modify a task-->
<div class="card" style="width: 100%;"> <div class="card" style="width: 100%;">
<div class="container"> <div class="container">
<div class="card-body"> <div class="card-body">
<div style="text-align: right;"> <div style="text-align: right;">
<i class="fa fa-times fa-2x" (click)="onClose()"></i> <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 class="row"> </div>
<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 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> </div>
</div>

View File

@ -8,9 +8,8 @@ describe('TaskFormComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ TaskFormComponent ] declarations: [TaskFormComponent],
}) }).compileComponents();
.compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {

View File

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

View File

@ -1,3 +1,3 @@
.modal .modal-content { .modal .modal-content {
display: contents; display: contents;
} }

View File

@ -1,140 +1,278 @@
<!--Popup form to create and modify a task--> <!--Popup form to create and modify a task-->
<div class="card" style="width: 100%;"> <div class="card" style="width: 100%;">
<div class="container"> <div class="container">
<div class="card-body"> <div class="card-body">
<div style="text-align: right;"> <div style="text-align: right;">
<i class="fa fa-times fa-2x" (click)="onClose()"></i> <i class="fa fa-times fa-2x" (click)="onClose()"></i>
</div> </div>
<div class="row"> <div class="row">
<div class="col-8" style="text-align: left;"> <div class="col-8" style="text-align: left;">
<h4 class="card-title">Neue Userstory anlegen</h4> <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 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> </div>
</div>

View File

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

View File

@ -1,282 +1,282 @@
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`;
return this.httpClient.get<ScrumTask[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumTask[]>(url, { observe: 'response' });
} }
public getTask(id: number): Observable<HttpResponse<ScrumTask>> { public getTask(id: number): Observable<HttpResponse<ScrumTask>> {
const url = `${environment.apiUrl}/tasks/${id}`; const url = `${environment.apiUrl}/tasks/${id}`;
return this.httpClient.get<ScrumTask>(url, { observe: 'response' }); return this.httpClient.get<ScrumTask>(url, { observe: 'response' });
} }
public postTask(task: ScrumTask): Observable<HttpResponse<ScrumTask>> { public postTask(task: ScrumTask): Observable<HttpResponse<ScrumTask>> {
const url = `${environment.apiUrl}/tasks`; const url = `${environment.apiUrl}/tasks`;
return this.httpClient.post<ScrumTask>(url, task, { observe: 'response' }); return this.httpClient.post<ScrumTask>(url, task, { observe: 'response' });
} }
public putTask(task: ScrumTask): Observable<HttpResponse<any>> { public putTask(task: ScrumTask): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/tasks/${task.id}`; const url = `${environment.apiUrl}/tasks/${task.id}`;
return this.httpClient.put(url, task, { observe: 'response' }); return this.httpClient.put(url, task, { observe: 'response' });
} }
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`;
return this.httpClient.get<ScrumUserstory[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumUserstory[]>(url, { observe: 'response' });
} }
public getUserstory(id: number): Observable<HttpResponse<ScrumUserstory>> { public getUserstory(id: number): Observable<HttpResponse<ScrumUserstory>> {
const url = `${environment.apiUrl}/userstories/${id}`; const url = `${environment.apiUrl}/userstories/${id}`;
return this.httpClient.get<ScrumUserstory>(url, { observe: 'response' }); return this.httpClient.get<ScrumUserstory>(url, { observe: 'response' });
} }
public postUserstory( public postUserstory(
userstory: ScrumUserstory userstory: ScrumUserstory
): Observable<HttpResponse<ScrumUserstory>> { ): Observable<HttpResponse<ScrumUserstory>> {
const url = `${environment.apiUrl}/userstories`; const url = `${environment.apiUrl}/userstories`;
return this.httpClient.post<ScrumUserstory>(url, userstory, { return this.httpClient.post<ScrumUserstory>(url, userstory, {
observe: 'response', observe: 'response',
}); });
} }
public putUserstory( public putUserstory(
userstory: ScrumUserstory userstory: ScrumUserstory
): Observable<HttpResponse<any>> { ): 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( public deleteUserstory(
userstory: ScrumUserstory userstory: ScrumUserstory
): Observable<HttpResponse<any>> { ): 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`;
return this.httpClient.get<ScrumSprint[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumSprint[]>(url, { observe: 'response' });
} }
public getSprint(id: number): Observable<HttpResponse<ScrumSprint>> { public getSprint(id: number): Observable<HttpResponse<ScrumSprint>> {
const url = `${environment.apiUrl}/sprints/${id}`; const url = `${environment.apiUrl}/sprints/${id}`;
return this.httpClient.get<ScrumSprint>(url, { observe: 'response' }); return this.httpClient.get<ScrumSprint>(url, { observe: 'response' });
} }
public postSprint( public postSprint(
sprint: ScrumSprint sprint: ScrumSprint
): Observable<HttpResponse<ScrumSprint>> { ): Observable<HttpResponse<ScrumSprint>> {
const url = `${environment.apiUrl}/sprints`; const url = `${environment.apiUrl}/sprints`;
return this.httpClient.post<ScrumSprint>(url, sprint, { return this.httpClient.post<ScrumSprint>(url, sprint, {
observe: 'response', observe: 'response',
}); });
} }
public putSprint(sprint: ScrumSprint): Observable<HttpResponse<any>> { public putSprint(sprint: ScrumSprint): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/sprints/${sprint.id}`; const url = `${environment.apiUrl}/sprints/${sprint.id}`;
return this.httpClient.put(url, sprint, { observe: 'response' }); return this.httpClient.put(url, sprint, { observe: 'response' });
} }
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`;
return this.httpClient.get<ScrumCategory[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumCategory[]>(url, { observe: 'response' });
} }
public getCategory(id: number): Observable<HttpResponse<ScrumCategory>> { public getCategory(id: number): Observable<HttpResponse<ScrumCategory>> {
const url = `${environment.apiUrl}/categories/${id}`; const url = `${environment.apiUrl}/categories/${id}`;
return this.httpClient.get<ScrumCategory>(url, { observe: 'response' }); return this.httpClient.get<ScrumCategory>(url, { observe: 'response' });
} }
public postCategory( public postCategory(
category: ScrumCategory category: ScrumCategory
): Observable<HttpResponse<ScrumCategory>> { ): Observable<HttpResponse<ScrumCategory>> {
const url = `${environment.apiUrl}/categories`; const url = `${environment.apiUrl}/categories`;
return this.httpClient.post<ScrumCategory>(url, category, { return this.httpClient.post<ScrumCategory>(url, category, {
observe: 'response', observe: 'response',
}); });
} }
public putCategory(category: ScrumCategory): Observable<HttpResponse<any>> { public putCategory(category: ScrumCategory): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/categories/${category.id}`; const url = `${environment.apiUrl}/categories/${category.id}`;
return this.httpClient.put(url, category, { observe: 'response' }); return this.httpClient.put(url, category, { observe: 'response' });
} }
public deleteCategory( public deleteCategory(
category: ScrumCategory category: ScrumCategory
): Observable<HttpResponse<any>> { ): 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`;
return this.httpClient.get<ScrumStatus[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumStatus[]>(url, { observe: 'response' });
} }
public getStatus(id: number): Observable<HttpResponse<ScrumStatus>> { public getStatus(id: number): Observable<HttpResponse<ScrumStatus>> {
const url = `${environment.apiUrl}/status/${id}`; const url = `${environment.apiUrl}/status/${id}`;
return this.httpClient.get<ScrumStatus>(url, { observe: 'response' }); return this.httpClient.get<ScrumStatus>(url, { observe: 'response' });
} }
public postStatus( public postStatus(
status: ScrumStatus status: ScrumStatus
): Observable<HttpResponse<ScrumStatus>> { ): Observable<HttpResponse<ScrumStatus>> {
const url = `${environment.apiUrl}/status`; const url = `${environment.apiUrl}/status`;
return this.httpClient.post<ScrumStatus>(url, status, { return this.httpClient.post<ScrumStatus>(url, status, {
observe: 'response', observe: 'response',
}); });
} }
public putStatus(status: ScrumStatus): Observable<HttpResponse<any>> { public putStatus(status: ScrumStatus): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/status/${status.id}`; const url = `${environment.apiUrl}/status/${status.id}`;
return this.httpClient.put(url, status, { observe: 'response' }); return this.httpClient.put(url, status, { observe: 'response' });
} }
public deleteStatus(status: ScrumStatus): Observable<HttpResponse<any>> { public deleteStatus(status: ScrumStatus): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/status/${status.id}`; const url = `${environment.apiUrl}/status/${status.id}`;
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`;
return this.httpClient.get<ScrumUser[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumUser[]>(url, { observe: 'response' });
} }
public getUser(id: number): Observable<HttpResponse<ScrumUser>> { public getUser(id: number): Observable<HttpResponse<ScrumUser>> {
const url = `${environment.apiUrl}/users/${id}`; const url = `${environment.apiUrl}/users/${id}`;
return this.httpClient.get<ScrumUser>(url, { observe: 'response' }); return this.httpClient.get<ScrumUser>(url, { observe: 'response' });
} }
public postUser(user: ScrumUser): Observable<HttpResponse<ScrumUser>> { public postUser(user: ScrumUser): Observable<HttpResponse<ScrumUser>> {
const url = `${environment.apiUrl}/users`; const url = `${environment.apiUrl}/users`;
return this.httpClient.post<ScrumUser>(url, user, { observe: 'response' }); return this.httpClient.post<ScrumUser>(url, user, { observe: 'response' });
} }
public putUser(user: ScrumUser): Observable<HttpResponse<any>> { public putUser(user: ScrumUser): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/users/${user.id}`; const url = `${environment.apiUrl}/users/${user.id}`;
return this.httpClient.put(url, user, { observe: 'response' }); return this.httpClient.put(url, user, { observe: 'response' });
} }
public deleteUser(user: ScrumUser): Observable<HttpResponse<any>> { public deleteUser(user: ScrumUser): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/users/${user.id}`; const url = `${environment.apiUrl}/users/${user.id}`;
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`;
return this.httpClient.get<ScrumProject[]>(url, { observe: 'response' }); return this.httpClient.get<ScrumProject[]>(url, { observe: 'response' });
} }
public getProject(id: number): Observable<HttpResponse<ScrumProject>> { public getProject(id: number): Observable<HttpResponse<ScrumProject>> {
const url = `${environment.apiUrl}/projects/${id}`; const url = `${environment.apiUrl}/projects/${id}`;
return this.httpClient.get<ScrumProject>(url, { observe: 'response' }); return this.httpClient.get<ScrumProject>(url, { observe: 'response' });
} }
public postProject( public postProject(
project: ScrumProject project: ScrumProject
): Observable<HttpResponse<ScrumProject>> { ): Observable<HttpResponse<ScrumProject>> {
const url = `${environment.apiUrl}/projects`; const url = `${environment.apiUrl}/projects`;
return this.httpClient.post<ScrumProject>(url, project, { return this.httpClient.post<ScrumProject>(url, project, {
observe: 'response', observe: 'response',
}); });
} }
public putProject(project: ScrumProject): Observable<HttpResponse<any>> { public putProject(project: ScrumProject): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/projects/${project.id}`; const url = `${environment.apiUrl}/projects/${project.id}`;
return this.httpClient.put(url, project, { observe: 'response' }); return this.httpClient.put(url, project, { observe: 'response' });
} }
public deleteProject(project: ScrumProject): Observable<HttpResponse<any>> { public deleteProject(project: ScrumProject): Observable<HttpResponse<any>> {
const url = `${environment.apiUrl}/projects/${project.id}`; const url = `${environment.apiUrl}/projects/${project.id}`;
return this.httpClient.delete(url, { observe: 'response' }); return this.httpClient.delete(url, { observe: 'response' });
} }
} }
export enum Priority { export enum Priority {
High = 'high', High = 'high',
Medium = 'medium', Medium = 'medium',
Low = 'low', Low = 'low',
} }
export interface ScrumTask { export interface ScrumTask {
id?: number; id?: number;
title: string; title: string;
content?: string; content?: string;
statusid?: number; statusid?: number;
categoryid?: number; categoryid?: number;
assignedtoid?: number; assignedtoid?: number;
sprintid?: number; sprintid?: number;
projectid?: number; projectid?: number;
userstoryid?: number; userstoryid?: number;
priority?: Priority; priority?: Priority;
} }
export interface ScrumUserstory { export interface ScrumUserstory {
id?: number; id?: number;
title: string; title: string;
content?: string; content?: string;
priority?: Priority; priority?: Priority;
statusid?: number; statusid?: number;
categoryid?: number; categoryid?: number;
sprintid?: number; sprintid?: number;
createdbyid?: number; createdbyid?: number;
projectid?: number; projectid?: number;
} }
export interface ScrumSprint{ export interface ScrumSprint {
id?: number; id?: number;
title: string; title: string;
description?: string; description?: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
projectid?: number; projectid?: number;
} }
export interface ScrumCategory { export interface ScrumCategory {
id?: number; id?: number;
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;
} }
export interface ScrumProject { export interface ScrumProject {
id?: number; id?: number;
title: string; title: string;
isprivate: boolean; isprivate: boolean;
} }

View File

@ -1,4 +1,4 @@
import {Priority} from './backend.service'; import { Priority } from './backend.service';
export function sortByNumberAscending<T>(items: T[], key: (T) => number) { export function sortByNumberAscending<T>(items: T[], key: (T) => number) {
return items.sort((a, b) => key(a) - key(b)); return items.sort((a, b) => key(a) - key(b));
@ -9,7 +9,7 @@ export function sortByStringAscending<T>(items: T[], key: (T) => string) {
} }
export function sortByDateAscending<T>(items: T[], key: (T) => Date) { export function sortByDateAscending<T>(items: T[], key: (T) => Date) {
return items.sort((a, b) => <any>key(b) - <any>key(a)); return items.sort((a, b) => <any>key(b) - <any>key(a));
} }
export function getNumberForPriority(priority: Priority): number { export function getNumberForPriority(priority: Priority): number {
@ -21,4 +21,4 @@ export function getNumberForPriority(priority: Priority): number {
case Priority.Low: case Priority.Low:
return 0; return 0;
} }
}; }

View File

@ -1,3 +1,3 @@
export const environment = { export const environment = {
production: true production: true,
}; };

View File

@ -4,8 +4,8 @@
export const environment = { export const environment = {
production: false, production: false,
apiUrl: window["env"]["apiUrl"] || "default", apiUrl: window['env']['apiUrl'] || 'default',
debug: window["env"]["debug"] || false debug: window['env']['debug'] || false,
}; };
/* /*

View File

@ -1,18 +1,23 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<title>Frontend</title> <title>Frontend</title>
<base href="/"> <base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico" />
<script src="assets/env.js"></script> <script src="assets/env.js"></script>
<!-- Fonts and icons --> <!-- Fonts and icons -->
<link href="https://fonts.googleapis.com/css?family=Poppins:200,300,400,600,700,800" rel="stylesheet"> <link
<link href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" rel="stylesheet"/> href="https://fonts.googleapis.com/css?family=Poppins:200,300,400,600,700,800"
</head> rel="stylesheet"
<body> />
<app-root></app-root> <link
</body> href="https://use.fontawesome.com/releases/v5.0.6/css/all.css"
rel="stylesheet"
/>
</head>
<body>
<app-root></app-root>
</body>
</html> </html>

View File

@ -8,5 +8,6 @@ if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic()
.catch(err => console.error(err)); .bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@ -59,8 +59,7 @@ import '@angular/localize/init';
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
*/ */
import 'zone.js/dist/zone'; // Included with Angular CLI. import 'zone.js/dist/zone'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
* APPLICATION IMPORTS * APPLICATION IMPORTS

View File

@ -1,9 +1,8 @@
/* 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 { .custom-text-secondary {
color: white; color: white;
} }
.white-content .custom-text-secondary { .white-content .custom-text-secondary {
color: black !important; color: black !important;
} }

View File

@ -1,17 +1,17 @@
@import "~bootstrap/scss/bootstrap"; @import "~bootstrap/scss/bootstrap";
.modal .modal-content { .modal .modal-content {
display: contents; display: contents;
} }
th.sortable:hover { th.sortable:hover {
text-decoration: underline; text-decoration: underline;
} }
.content { .content {
position: relative; position: relative;
float: left; float: left;
margin-top: 10px; margin-top: 10px;
margin-left: 20px; margin-left: 20px;
width: 80%; width: 80%;
} }

View File

@ -4,7 +4,7 @@ import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
declare const require: any; declare const require: any;

View File

@ -4,11 +4,6 @@
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "types": []
}, },
"files": [ "files": ["src/main.ts", "src/polyfills.ts"],
"src/main.ts", "include": ["src/**/*.d.ts"]
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
} }

View File

@ -11,10 +11,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "es2015", "target": "es2015",
"lib": [ "lib": ["es2018", "dom"]
"es2018",
"dom"
]
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"fullTemplateTypeCheck": true, "fullTemplateTypeCheck": true,

View File

@ -2,17 +2,8 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": ["jasmine", "node"]
"jasmine",
"node"
]
}, },
"files": [ "files": ["src/test.ts", "src/polyfills.ts"],
"src/test.ts", "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
} }

View File

@ -2,10 +2,7 @@
"extends": "tslint:recommended", "extends": "tslint:recommended",
"rules": { "rules": {
"align": { "align": {
"options": [ "options": ["parameters", "statements"]
"parameters",
"statements"
]
}, },
"array-type": false, "array-type": false,
"arrow-return-shorthand": true, "arrow-return-shorthand": true,
@ -16,34 +13,16 @@
"component-class-suffix": true, "component-class-suffix": true,
"contextual-lifecycle": true, "contextual-lifecycle": true,
"directive-class-suffix": true, "directive-class-suffix": true,
"directive-selector": [ "directive-selector": [true, "attribute", "app", "camelCase"],
true, "component-selector": [true, "element", "app", "kebab-case"],
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"eofline": true, "eofline": true,
"import-blacklist": [ "import-blacklist": [true, "rxjs/Rx"],
true,
"rxjs/Rx"
],
"import-spacing": true, "import-spacing": true,
"indent": { "indent": {
"options": [ "options": ["spaces"]
"spaces"
]
}, },
"max-classes-per-file": false, "max-classes-per-file": false,
"max-line-length": [ "max-line-length": [true, 140],
true,
140
],
"member-ordering": [ "member-ordering": [
true, true,
{ {
@ -55,35 +34,17 @@
] ]
} }
], ],
"no-console": [ "no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false, "no-empty": false,
"no-inferrable-types": [ "no-inferrable-types": [true, "ignore-params"],
true,
"ignore-params"
],
"no-non-null-assertion": true, "no-non-null-assertion": true,
"no-redundant-jsdoc": true, "no-redundant-jsdoc": true,
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-var-requires": false, "no-var-requires": false,
"object-literal-key-quotes": [ "object-literal-key-quotes": [true, "as-needed"],
true, "quotemark": [true, "single"],
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": { "semicolon": {
"options": [ "options": ["always"]
"always"
]
}, },
"space-before-function-paren": { "space-before-function-paren": {
"options": { "options": {
@ -113,11 +74,7 @@
] ]
}, },
"variable-name": { "variable-name": {
"options": [ "options": ["ban-keywords", "check-format", "allow-pascal-case"]
"ban-keywords",
"check-format",
"allow-pascal-case"
]
}, },
"whitespace": { "whitespace": {
"options": [ "options": [
@ -142,7 +99,5 @@
"use-lifecycle-interface": true, "use-lifecycle-interface": true,
"use-pipe-transform-interface": true "use-pipe-transform-interface": true
}, },
"rulesDirectory": [ "rulesDirectory": ["codelyzer"]
"codelyzer" }
]
}