diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml index ef82d37..f4860ad 100644 --- a/backend/.idea/workspace.xml +++ b/backend/.idea/workspace.xml @@ -5,11 +5,15 @@ - + - - + + + + + + diff --git a/backend/src/main/java/core/api/controller/TaskController.java b/backend/src/main/java/core/api/controller/TaskController.java index ef989d1..342640a 100644 --- a/backend/src/main/java/core/api/controller/TaskController.java +++ b/backend/src/main/java/core/api/controller/TaskController.java @@ -2,10 +2,7 @@ package core.api.controller; import core.api.models.auth.SimpleStatusResponse; import core.api.models.timemanager.taskSchedule.ScheduleInfo; -import core.api.models.timemanager.tasks.TaskEntityInfo; -import core.api.models.timemanager.tasks.TaskFieldInfo; -import core.api.models.timemanager.tasks.TaskScope; -import core.api.models.timemanager.tasks.TaskShortInfo; +import core.api.models.timemanager.tasks.*; import core.entities.timemanager.Task; import core.entities.timemanager.Taskgroup; import core.services.*; @@ -36,13 +33,13 @@ public class TaskController { public ResponseEntity loadAllTasksShort(@PathVariable TaskScope scope, @PathVariable boolean detailed) { List taskList = taskService.loadAllTasks(SecurityContextHolder.getContext().getAuthentication().getName(), scope); List taskShortInfos = new ArrayList<>(); - List taskInfos = new ArrayList<>(); + List taskInfos = new ArrayList<>(); for(Task task : taskList) { StringBuilder builder = loadTaskgroupPath(task); if(!detailed) { taskShortInfos.add(new TaskShortInfo(task.getTaskID(), builder.toString())); } else { - taskInfos.add(new TaskEntityInfo(task)); + taskInfos.add(new TaskTaskgroupInfo(task)); } } diff --git a/backend/src/main/java/core/api/models/timemanager/tasks/TaskTaskgroupInfo.java b/backend/src/main/java/core/api/models/timemanager/tasks/TaskTaskgroupInfo.java new file mode 100644 index 0000000..b46e438 --- /dev/null +++ b/backend/src/main/java/core/api/models/timemanager/tasks/TaskTaskgroupInfo.java @@ -0,0 +1,118 @@ +package core.api.models.timemanager.tasks; + +import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; +import core.entities.timemanager.Task; +import core.entities.timemanager.Taskgroup; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +public class TaskTaskgroupInfo { + + private long taskID; + private String taskName; + + private int eta; + + private LocalDate startDate; + + private LocalDate deadline; + + private boolean overdue; + + private boolean finished; + + private int workTime; + + private List taskgroups; + + public TaskTaskgroupInfo(Task task) { + this.taskID = task.getTaskID(); + this.taskName = task.getTaskName(); + this.eta = task.getEta(); + this.startDate = task.getStartDate(); + this.deadline = task.getDeadline(); + if(task.getDeadline() != null) { + this.overdue = LocalDate.now().isAfter(task.getDeadline()); + } + this.finished = task.isFinished(); + this.workTime = task.getWorkTime(); + + List taskgroupList = Taskgroup.getAncestorList(task.getTaskgroup()); + taskgroupList.add(task.getTaskgroup()); + this.taskgroups = taskgroupList.stream().map(TaskgroupEntityInfo::new).toList(); + } + + public long getTaskID() { + return taskID; + } + + public void setTaskID(long taskID) { + this.taskID = taskID; + } + + public String getTaskName() { + return taskName; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public int getEta() { + return eta; + } + + public void setEta(int eta) { + this.eta = eta; + } + + public LocalDate getStartDate() { + return startDate; + } + + public void setStartDate(LocalDate startDate) { + this.startDate = startDate; + } + + public LocalDate getDeadline() { + return deadline; + } + + public void setDeadline(LocalDate deadline) { + this.deadline = deadline; + } + + public boolean isOverdue() { + return overdue; + } + + public void setOverdue(boolean overdue) { + this.overdue = overdue; + } + + public boolean isFinished() { + return finished; + } + + public void setFinished(boolean finished) { + this.finished = finished; + } + + public int getWorkTime() { + return workTime; + } + + public void setWorkTime(int workTime) { + this.workTime = workTime; + } + + public List getTaskgroups() { + return taskgroups; + } + + public void setTaskgroups(List taskgroups) { + this.taskgroups = taskgroups; + } +} diff --git a/frontend/src/api/.openapi-generator/FILES b/frontend/src/api/.openapi-generator/FILES index e0272ae..51b9f45 100644 --- a/frontend/src/api/.openapi-generator/FILES +++ b/frontend/src/api/.openapi-generator/FILES @@ -40,6 +40,7 @@ model/taskFieldInfo.ts model/taskOverviewInfo.ts model/taskScheduleStopResponse.ts model/taskShortInfo.ts +model/taskTaskgroupInfo.ts model/taskgroupDetailInfo.ts model/taskgroupEntityInfo.ts model/taskgroupFieldInfo.ts diff --git a/frontend/src/api/api/task.service.ts b/frontend/src/api/api/task.service.ts index 7032140..c523119 100644 --- a/frontend/src/api/api/task.service.ts +++ b/frontend/src/api/api/task.service.ts @@ -23,6 +23,7 @@ import { SimpleStatusResponse } from '../model/models'; import { TaskEntityInfo } from '../model/models'; import { TaskFieldInfo } from '../model/models'; import { TaskShortInfo } from '../model/models'; +import { TaskTaskgroupInfo } from '../model/models'; import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; import { Configuration } from '../configuration'; @@ -97,9 +98,9 @@ export class TaskService { * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable | Array>; - public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable | Array>>; - public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable | Array>>; + public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable | Array>; + public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable | Array>>; + public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable | Array>>; public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { if (scope === null || scope === undefined) { throw new Error('Required parameter scope was null or undefined when calling tasksAllScopeDetailedGet.'); @@ -140,7 +141,7 @@ export class TaskService { responseType_ = 'text'; } - return this.httpClient.get | Array>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(scope))}/${encodeURIComponent(String(detailed))}`, + return this.httpClient.get | Array>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(scope))}/${encodeURIComponent(String(detailed))}`, { context: localVarHttpContext, responseType: responseType_, diff --git a/frontend/src/api/model/models.ts b/frontend/src/api/model/models.ts index 030f3a8..0515444 100644 --- a/frontend/src/api/model/models.ts +++ b/frontend/src/api/model/models.ts @@ -24,6 +24,7 @@ export * from './taskFieldInfo'; export * from './taskOverviewInfo'; export * from './taskScheduleStopResponse'; export * from './taskShortInfo'; +export * from './taskTaskgroupInfo'; export * from './taskgroupDetailInfo'; export * from './taskgroupEntityInfo'; export * from './taskgroupFieldInfo'; diff --git a/frontend/src/api/model/taskTaskgroupInfo.ts b/frontend/src/api/model/taskTaskgroupInfo.ts new file mode 100644 index 0000000..30743f1 --- /dev/null +++ b/frontend/src/api/model/taskTaskgroupInfo.ts @@ -0,0 +1,50 @@ +/** + * API Title + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { TaskgroupEntityInfo } from './taskgroupEntityInfo'; + + +export interface TaskTaskgroupInfo { + /** + * internal id of task + */ + taskID: number; + /** + * name of task + */ + taskName: string; + /** + * expected time to finish task + */ + eta: number; + /** + * date from which the task can be started + */ + startDate: string; + /** + * date until the task has to be finished + */ + deadline: string; + /** + * determines whether the task is overdue + */ + overdue: boolean; + /** + * determines whether the task is finished + */ + finished: boolean; + /** + * number in minutes that was already worked on this task + */ + workTime: number; + taskgroups: Array; +} + diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 66a658a..374891d 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import {TaskgroupDashboardComponent} from "./taskgroups/taskgroup-dashboard/task import {TaskDetailOverviewComponent} from "./tasks/task-detail-overview/task-detail-overview.component"; import {SchedulerComponent} from "./schedules/scheduler/scheduler.component"; import {MissedSchedulesComponent} from "./missed-schedules/missed-schedules.component"; +import {OverdueTaskOverviewComponent} from "./overdue-task-overview/overdue-task-overview.component"; const routes: Routes = [ {path: '', component: MainComponent}, @@ -18,7 +19,8 @@ const routes: Routes = [ {path: 'taskgroups/:taskgroupID/tasks/:taskID', component: TaskDetailOverviewComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule', component: SchedulerComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule/:scheduleID', component: SchedulerComponent}, - {path: 'reschedule', component: MissedSchedulesComponent} + {path: 'reschedule', component: MissedSchedulesComponent}, + {path: 'overdue', component: OverdueTaskOverviewComponent} ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index e37af02..375a529 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -4,6 +4,7 @@ + diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 8bf5716..e1b297e 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -71,6 +71,7 @@ import { ForgottenTaskStartDialogComponent } from './dashboard/forgotten-task-st import {MatAutocompleteModule} from "@angular/material/autocomplete"; import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.component'; import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component'; +import { OverdueTaskOverviewComponent } from './overdue-task-overview/overdue-task-overview.component'; @NgModule({ declarations: [ AppComponent, @@ -104,7 +105,8 @@ import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedu TaskOverviewComponent, ForgottenTaskStartDialogComponent, MissedSchedulesComponent, - MissedScheduleForgetConfirmationDialogComponent + MissedScheduleForgetConfirmationDialogComponent, + OverdueTaskOverviewComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/overdue-task-overview/overdue-task-overview.component.css b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.css new file mode 100644 index 0000000..9c4e31d --- /dev/null +++ b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.css @@ -0,0 +1,72 @@ +.container { + margin: 20px auto; + width: 70%; +} + +.spacer { + margin-bottom: 2.5%; +} + + +@media screen and (max-width: 600px) { + .container { + width: 100%; + margin: 20px 10px; + } +} + + +.undecorated-link { + text-decoration: none; + color: black; +} + +.undecorated-link:hover { + color: #3498db; +} + +.originally-planned-container { + border-style: solid; + border-color: #e1e1e1; + border-width: 1px; + margin-top: 10px; + border-radius: 5px; + padding: 10px; + +} + +.reschedule-actions-container { + display: flex; + justify-content: space-between; + flex-direction: row; +} + +.btn-group { + display: inline-block; +} + + +.yellowBtn { + background-color: #f39c12; + color: white; + border-radius: 0; +} + +.greenBtn { + background-color: #00bc8c; + color: white; +} + +.btn-group-item { + border-radius: 0; +} + +.btn-group-itemleft { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group-itemright { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} diff --git a/frontend/src/app/overdue-task-overview/overdue-task-overview.component.html b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.html new file mode 100644 index 0000000..9fd9445 --- /dev/null +++ b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.html @@ -0,0 +1,29 @@ +
+ + + +

+ + {{taskgroup.taskgroupName}}/ + + {{task.taskName}} +

+ +
+
+

Limit: {{task.deadline}}

+
+
+
+ + + +
+
+ +
+
+
+
+
+
diff --git a/frontend/src/app/overdue-task-overview/overdue-task-overview.component.spec.ts b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.spec.ts new file mode 100644 index 0000000..a3ebca3 --- /dev/null +++ b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OverdueTaskOverviewComponent } from './overdue-task-overview.component'; + +describe('OverdueTaskOverviewComponent', () => { + let component: OverdueTaskOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [OverdueTaskOverviewComponent] + }); + fixture = TestBed.createComponent(OverdueTaskOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/overdue-task-overview/overdue-task-overview.component.ts b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.ts new file mode 100644 index 0000000..b0decdc --- /dev/null +++ b/frontend/src/app/overdue-task-overview/overdue-task-overview.component.ts @@ -0,0 +1,85 @@ +import {Component, OnInit} from '@angular/core'; +import {ScheduleService, TaskEntityInfo, TaskService, TaskShortInfo, TaskTaskgroupInfo} from "../../api"; +import {NavigationLink} from "../navigation-link-list/navigation-link-list.component"; +import {Router} from "@angular/router"; +import {MatSnackBar} from "@angular/material/snack-bar"; + +@Component({ + selector: 'app-overdue-task-overview', + templateUrl: './overdue-task-overview.component.html', + styleUrls: ['./overdue-task-overview.component.css'] +}) +export class OverdueTaskOverviewComponent implements OnInit{ + + overdueTasks: TaskTaskgroupInfo[] = [] + defaultNavigationLinkPath: NavigationLink[] = [ + { + linkText: "Dashboard", + routerLink: ['/'] + }, + { + linkText: "Overdue Tasks", + routerLink: ["/overdue"] + } + ] + constructor(private taskService: TaskService, + private scheduleService: ScheduleService, + private router: Router, + private snackbar: MatSnackBar) { + + } + + ngOnInit() { + this.taskService.tasksAllScopeDetailedGet("OVERDUE", true).subscribe({ + next: resp => { + this.overdueTasks = resp as TaskTaskgroupInfo[]; + } + }) + } + + calcProgress(task: TaskTaskgroupInfo): number { + console.log(task.workTime / task.eta * 100) + return task.workTime / task.eta * 100 + } + startNow(task: TaskTaskgroupInfo) { + this.scheduleService.schedulesTaskIDNowPost(task.taskID).subscribe({ + next: resp => { + this.router.navigateByUrl("/"); + }, + error: err => { + if(err.status == 403) { + this.snackbar.open("No permissions", "", {duration: 2000}); + } else if(err.status == 404) { + this.snackbar.open("Not found", "", {duration: 2000}); + } else if(err.status == 409) { + this.snackbar.open("Task already running", "", {duration: 2000}); + } else { + this.snackbar.open("Unexpected error", "", {duration: 2000}); + } + } + }) + } + + finishTask(task: TaskTaskgroupInfo) { + this.taskService.tasksTaskIDFinishPost(task.taskID).subscribe({ + next: resp => { + this.overdueTasks = this.overdueTasks.filter(t => t.taskID !== task.taskID) + }, + error: err => { + if(err.status == 403) { + this.snackbar.open("No permissions", "", {duration: 2000}); + } else if(err.status == 404) { + this.snackbar.open("Not found", "", {duration: 2000}); + } else { + this.snackbar.open("Unexpected error", "", {duration: 2000}); + } + } + }) + } + + deleteTask(task: TaskTaskgroupInfo) { + + } + + +} diff --git a/openapi.yaml b/openapi.yaml index 279fc80..8eaa491 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -921,7 +921,7 @@ paths: $ref: '#/components/schemas/TaskShortInfo' - type: array items: - $ref: '#/components/schemas/TaskEntityInfo' + $ref: '#/components/schemas/TaskTaskgroupInfo' /tasks/{taskgroupID}/{status}: @@ -2063,6 +2063,56 @@ components: type: number description: number in minutes that was already worked on this task example: 10 + TaskTaskgroupInfo: + required: + - taskID + - taskName + - eta + - startDate + - deadline + - overdue + - finished + - workTime + - taskgroups + additionalProperties: false + properties: + taskID: + type: number + description: internal id of task + example: 1 + taskName: + type: string + description: name of task + example: Vorlesung schauen + eta: + type: number + description: expected time to finish task + example: 10 + minimum: 0 + startDate: + type: string + format: date + description: date from which the task can be started + deadline: + type: string + format: date + description: date until the task has to be finished + overdue: + type: boolean + description: determines whether the task is overdue + example: True + finished: + type: boolean + description: determines whether the task is finished + example: True + workTime: + type: number + description: number in minutes that was already worked on this task + example: 10 + taskgroups: + type: array + items: + $ref: '#/components/schemas/TaskgroupEntityInfo' TaskFieldInfo: required: - taskName