diff --git a/angular.json b/angular.json index bfc0c1b..226ed4c 100644 --- a/angular.json +++ b/angular.json @@ -26,7 +26,8 @@ ], "styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", - "src/styles.css" + "src/styles.css", + "src/assets/scss/black-dashboard.scss" ], "scripts": [] }, diff --git a/package.json b/package.json index 100dd04..598596f 100644 --- a/package.json +++ b/package.json @@ -46,5 +46,13 @@ "ts-node": "~8.3.0", "tslint": "~6.1.0", "typescript": "~3.8.3" - } + }, + "description": "This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.7.", + "main": "karma.conf.js", + "repository": { + "type": "git", + "url": "https://git.informatik.fh-nuernberg.de/scrum-taskboard/frontend.git" + }, + "author": "", + "license": "ISC" } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 8af9687..3f2c2cc 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,20 +1,21 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; - -import { TaskListComponent } from './task-list/task-list.component'; -import { UserstoryListComponent } from './userstory-list/userstory-list.component'; import { DashboardComponent } from './dashboard/dashboard.component'; +import { UserstoryTableComponent } from './userstory-table/userstory-table.component'; +import { TaskTableComponent } from './task-table/task-table.component'; +import { SprintTableComponent } from './sprint-table/sprint-table.component'; const routes: Routes = [ - { path: 'tasks', component: TaskListComponent }, - { path: 'userstories', component: UserstoryListComponent }, + { path: 'tasks', component: TaskTableComponent }, + { path: 'userstories', component: UserstoryTableComponent }, { path: 'dashboard', component: DashboardComponent }, + { path: 'sprints', component: SprintTableComponent }, { path: '', redirectTo: '/tasks', pathMatch: 'full' }, ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] + imports: [ RouterModule.forRoot(routes) ], + exports: [ RouterModule ] }) -export class AppRoutingModule { } +export class AppRoutingModule {} diff --git a/src/app/app.component.html b/src/app/app.component.html index 90c6b64..2e44c1d 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1 +1,39 @@ - \ No newline at end of file + +
+
+ + + + +
+
\ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e9f6ffa..6efd665 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,4 +5,42 @@ import { Component } from '@angular/core'; templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent {} +export class AppComponent { + /* + changeSidebarColor(color){ + var sidebar = document.getElementsByClassName('sidebar')[0]; + var mainPanel = document.getElementsByClassName('main-panel')[0]; + + this.sidebarColor = color; + + if(sidebar != undefined){ + sidebar.setAttribute('data',color); + } + if(mainPanel != undefined){ + mainPanel.setAttribute('data',color); + } + } + */ + changeDashboardColor(color){ + var body = document.getElementsByTagName('body')[0]; + if (body && color === 'white-content') { + body.classList.add(color); + } + else if(body.classList.contains('white-content')) { + body.classList.remove('white-content'); + } + } + // function that adds color white/transparent to the navbar on resize (this is for the collapse) + /* + updateColor = () => { + var navbar = document.getElementsByClassName('navbar')[0]; + if (window.innerWidth < 993 && !this.isCollapsed) { + navbar.classList.add('bg-white'); + navbar.classList.remove('navbar-transparent'); + } else { + navbar.classList.remove('bg-white'); + navbar.classList.add('navbar-transparent'); + } + }; + */ +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index bbf75fb..3109692 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,21 +7,26 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { BackendService } from './services/backend.service'; -import { TaskListComponent } from './task-list/task-list.component'; import { TaskFormComponent } from './task-form/task-form.component'; -import { UserstoryListComponent } from './userstory-list/userstory-list.component'; import { UserstoryFormComponent } from './userstory-form/userstory-form.component'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { SprintFormComponent } from './sprint-form/sprint-form.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { UserstoryTableComponent } from './userstory-table/userstory-table.component'; +import { TaskTableComponent } from './task-table/task-table.component'; +import { SprintTableComponent } from './sprint-table/sprint-table.component'; import { DashboardComponent } from './dashboard/dashboard.component'; @NgModule({ declarations: [ AppComponent, - TaskListComponent, + TaskTableComponent, TaskFormComponent, - UserstoryListComponent, + UserstoryTableComponent, UserstoryFormComponent, - DashboardComponent + UserstoryTableComponent, + SprintFormComponent, + SprintTableComponent, + DashboardComponent, ], imports: [ BrowserModule, diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index e295541..470e6f3 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -11,8 +11,8 @@ export class DashboardComponent implements OnInit { /** * Returns the status that are used by at least one userstory. */ - private get usedStatus(): ScrumStatus[] { - return this.status.filter(s => this.userstories.find(us => us.status === s.id) !== undefined); + public get usedStatus(): ScrumStatus[] { + return this.status.filter(s => this.userstories.find(us => us.statusid === s.id) !== undefined); } private status: ScrumStatus[]; @@ -47,11 +47,11 @@ export class DashboardComponent implements OnInit { {id: 1, title: "Done", description:""}, ]; this.userstories = [ - {status: 0, title:""}, - {status: 0, title:""}, - {status: 0, title:""}, - {status: 1, title:""}, - {status: 1, title:""}, + {statusid: 0, title:""}, + {statusid: 0, title:""}, + {statusid: 0, title:""}, + {statusid: 1, title:""}, + {statusid: 1, title:""}, ]; this.sprints = [ {description:"", title:"", project: 0, startDate: new Date(2020, 5, 22), endDate: new Date(2020, 5, 28)}, @@ -101,7 +101,7 @@ export class DashboardComponent implements OnInit { } public getNumberOfUserstoriesByStatus(status: ScrumStatus): number { - return this.userstories.filter(us => us.status === status.id).length; + return this.userstories.filter(us => us.statusid === status.id).length; } public getRemainingDaysInSprint(): number { diff --git a/src/app/services/backend.service.ts b/src/app/services/backend.service.ts index 239f82a..ed9c838 100644 --- a/src/app/services/backend.service.ts +++ b/src/app/services/backend.service.ts @@ -9,7 +9,7 @@ export class BackendService { constructor(private httpClient: HttpClient) {} - + // Tasks public getTasks(): Observable> { const url = `${environment.apiUrl}/tasks`; @@ -117,7 +117,7 @@ export class BackendService { return this.httpClient.delete(url, { observe: 'response' }); } - + // Status public getAllStatus(): Observable> { const url = `${environment.apiUrl}/status`; @@ -143,8 +143,8 @@ export class BackendService { const url = `${environment.apiUrl}/status/${status.id}`; return this.httpClient.delete(url, { observe: 'response' }); } - - + + // Users public getUsers(): Observable> { const url = `${environment.apiUrl}/users`; @@ -171,7 +171,7 @@ export class BackendService { return this.httpClient.delete(url, { observe: 'response' }); } - + // Projects public getProjects(): Observable> { const url = `${environment.apiUrl}/projects`; @@ -209,12 +209,12 @@ export interface ScrumTask { id?: number; title: string; content?: string; - status?: number; - category?: number; - assignedto?: number; - sprint?: number; - project?: number; - userstory?: number; + statusid?: number; + categoryid?: number; + assignedtoid?: number; + sprintid?: number; + projectid?: number; + userstoryid?: number; priority?: Priority; } @@ -223,16 +223,16 @@ export interface ScrumUserstory { title: string; content?: string; priority?: Priority; - status?: number; - category?: number; - createdby?: number; - project?: number; + statusid?: number; + categoryid?: number; + createdbyid?: number; + projectid?: number; } export interface ScrumSprint{ id?: number; title: string; - description: string; + description?: string; startDate: Date; endDate: Date; project: number; @@ -262,4 +262,4 @@ export interface ScrumProject { id?: number; title: string; isprivate: boolean; -} \ No newline at end of file +} diff --git a/src/app/services/sorting.service.ts b/src/app/services/sorting.service.ts new file mode 100644 index 0000000..59b90e6 --- /dev/null +++ b/src/app/services/sorting.service.ts @@ -0,0 +1,24 @@ +import {Priority} from './backend.service'; + +export function sortByNumberAscending(items: T[], key: (T) => number) { + return items.sort((a, b) => key(a) - key(b)); +} + +export function sortByStringAscending(items: T[], key: (T) => string) { + return items.sort((a, b) => key(a).localeCompare(key(b))); +} + +export function sortByDateAscending(items: T[], key: (T) => Date) { + return items.sort((a, b) => key(b) - key(a)); +} + +export function getNumberForPriority(priority: Priority): number { + switch (priority) { + case Priority.High: + return 2; + case Priority.Medium: + return 1; + case Priority.Low: + return 0; + } +}; diff --git a/src/app/services/table-component.base.ts b/src/app/services/table-component.base.ts new file mode 100644 index 0000000..6206c42 --- /dev/null +++ b/src/app/services/table-component.base.ts @@ -0,0 +1,51 @@ +import {sortByNumberAscending, sortByStringAscending, sortByDateAscending} from './sorting.service'; +import {Priority} from './backend.service'; + +export abstract class TableComponentBase { + public sortBy: string; + public sortDescending = false; + public items: T[] = []; + + protected doNumericSort(by: string, key: (item: T) => number) { + if (this.sortBy === by) { + this.sortDescending = !this.sortDescending; + } else { + this.sortBy = by; + } + + this.items = sortByNumberAscending(this.items, key); + if (this.sortDescending) { + this.items = this.items.reverse(); + } + } + + protected doStringSort(by: string, key: (item: T) => string) { + if (this.sortBy === by) { + this.sortDescending = !this.sortDescending; + } else { + this.sortBy = by; + } + + this.items = sortByStringAscending(this.items, key); + if (this.sortDescending) { + this.items = this.items.reverse(); + } + } + + protected doDateSort(by: string, key: (item: T) => Date) { + if (this.sortBy === by) { + this.sortDescending = !this.sortDescending; + } else { + this.sortBy = by; + } + + this.items = sortByDateAscending(this.items, key); + if (this.sortDescending) { + this.items = this.items.reverse(); + } + } + + public getAllPriorities(): string[] { + return Object.values(Priority); + } +} diff --git a/src/app/sprint-form/sprint-form.component.css b/src/app/sprint-form/sprint-form.component.css new file mode 100644 index 0000000..717de4c --- /dev/null +++ b/src/app/sprint-form/sprint-form.component.css @@ -0,0 +1,4 @@ +.modal-footer { + border-top: 0px solid; + padding-top: 5%; +} \ No newline at end of file diff --git a/src/app/sprint-form/sprint-form.component.html b/src/app/sprint-form/sprint-form.component.html new file mode 100644 index 0000000..d8b7ecd --- /dev/null +++ b/src/app/sprint-form/sprint-form.component.html @@ -0,0 +1,33 @@ + \ No newline at end of file diff --git a/src/app/sprint-form/sprint-form.component.ts b/src/app/sprint-form/sprint-form.component.ts new file mode 100644 index 0000000..f19c1bb --- /dev/null +++ b/src/app/sprint-form/sprint-form.component.ts @@ -0,0 +1,62 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { + BackendService, + ScrumTask, + Priority, + ScrumStatus, + ScrumCategory, + ScrumUser, + ScrumProject, + ScrumUserstory, + ScrumSprint +} from '../services/backend.service'; +import { Observable } from 'rxjs'; +import { HttpResponse } from '@angular/common/http'; + +@Component({ + selector: 'app-task-form', + templateUrl: './sprint-form.component.html', + styleUrls: ['./sprint-form.component.css'], +}) +export class SprintFormComponent implements OnInit { + @Input() public sprint: ScrumSprint; + public editing: Boolean; + public sprintid: string; + + constructor( + private backendService: BackendService, + private activeModalService: NgbActiveModal + ) { } + + ngOnInit(): void { + if (this.sprint === null || this.sprint === undefined) { + this.sprint = { title: '', startDate: new Date(), endDate: new Date(), project: 0 }; //project id: static counter? + this.editing = false; + } else { + this.editing = true; + } + document.getElementById('titleField').focus(); + } + + onSubmit() { + if (this.editing) { + this.backendService.putSprint(this.sprint).subscribe((response) => { + if (response.status > 399) { + alert('Fehler'); + } + }); + } else { + this.backendService.postSprint(this.sprint).subscribe((response) => { + if (response.status > 399) { + alert('Fehler'); + } + }); + } + this.activeModalService.close(this.sprint); + } + + onClose() { + this.activeModalService.dismiss(this.sprint); + } +} diff --git a/src/app/sprint-table/sprint-table.component.css b/src/app/sprint-table/sprint-table.component.css new file mode 100644 index 0000000..5b74f7f --- /dev/null +++ b/src/app/sprint-table/sprint-table.component.css @@ -0,0 +1,8 @@ +table { + table-layout: fixed; + } + + th.sortable:hover { + text-decoration: underline; + } + \ No newline at end of file diff --git a/src/app/sprint-table/sprint-table.component.html b/src/app/sprint-table/sprint-table.component.html new file mode 100644 index 0000000..e824be9 --- /dev/null +++ b/src/app/sprint-table/sprint-table.component.html @@ -0,0 +1,62 @@ +
+ +

+ Sprints +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ ID + + + + + + Titel + + + + + + Start + + + + + + End + + + + +
{{sprint.id}}{{sprint.title}}{{sprint.startDate | date:'dd.MM.yyyy'}}{{sprint.endDate | date:'dd.MM.yyyy'}} + + +
+
+ \ No newline at end of file diff --git a/src/app/sprint-table/sprint-table.component.ts b/src/app/sprint-table/sprint-table.component.ts new file mode 100644 index 0000000..fc53bfc --- /dev/null +++ b/src/app/sprint-table/sprint-table.component.ts @@ -0,0 +1,87 @@ +import {Component} from '@angular/core'; +import {BackendService, ScrumSprint} from '../services/backend.service'; +import {TableComponentBase} from '../services/table-component.base'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ActivatedRoute, ParamMap, Router} from '@angular/router'; +import { SprintFormComponent } from '../sprint-form/sprint-form.component'; + +@Component({ + selector: 'app-sprint', + templateUrl: './sprint-table.component.html', + styleUrls: ['./sprint-table.component.css'] +}) +export class SprintTableComponent extends TableComponentBase { + public filterSprintId: number | null = null; + public highlightId: number; + + public get filteredItems() { + return this.items.filter(sprint => + (this.filterSprintId === null || sprint.id === this.filterSprintId) + ); + } + + constructor( + private backendService: BackendService, private modalService: NgbModal, + private route: ActivatedRoute, private router: Router + ) { + super(); + + this.applyFilterParameters(route.snapshot.paramMap); + route.paramMap.subscribe(map => this.applyFilterParameters(map)); + + backendService.getSprints().subscribe(response => { + if (response.status > 399) { + alert('Fehler'); + } else { + this.items.push(...response.body); + } + }); + } + + private applyFilterParameters(params: ParamMap) { + if (params.has('id')) { + this.highlightId = parseInt(params.get('id')); + } + } + + public deleteSprint(sprint: ScrumSprint) { + this.backendService.deleteSprint(sprint).subscribe(response => { + if (response.status > 399) { + alert('Fehler'); + } + }); + const index = this.items.indexOf(sprint); + if (index !== -1) { + this.items.splice(index, 1); + } + } + + public openSprintForm(editSprint?: ScrumSprint) { + const modalRef = this.modalService.open(SprintFormComponent, { + backdrop: 'static', + keyboard: true, + }); + if (editSprint === null) { + modalRef.result.then(result => { + this.items.push(result); + }); + } + modalRef.componentInstance.sprint = editSprint; + } + + sortById() { + this.doNumericSort('id', sprint => sprint.id); + } + + sortByTitle() { + this.doStringSort('title', sprint => sprint.title); + } + + sortByStartDate() { + this.doDateSort('startDate', sprint => sprint.startDate); + } + + sortByEndDate() { + this.doDateSort('endDate', sprint => sprint.endDate); + } +} diff --git a/src/app/task-form/task-form.component.css b/src/app/task-form/task-form.component.css index e69de29..2b8a6fe 100644 --- a/src/app/task-form/task-form.component.css +++ b/src/app/task-form/task-form.component.css @@ -0,0 +1,13 @@ +.modal-footer { + border-top: 0px solid; + padding-top: 5%; +} +.modal-content { + width: 1040px; + right: 55%; + border: 0px; +} + +.modal { + margin: 0 auto; +} diff --git a/src/app/task-form/task-form.component.html b/src/app/task-form/task-form.component.html index b0a5f4e..5aa9229 100644 --- a/src/app/task-form/task-form.component.html +++ b/src/app/task-form/task-form.component.html @@ -1,30 +1,68 @@