issue-35 #37

Merged
sebastian merged 4 commits from issue-35 into master 2023-10-29 16:16:34 +01:00
15 changed files with 451 additions and 17 deletions
Showing only changes of commit 6cfd48a962 - Show all commits

View File

@ -5,11 +5,15 @@
</component>
<component name="ChangeListManager">
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Forget single schedule">
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/tasks/TaskScope.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/tasks/TaskTaskgroupInfo.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/controller/TaskController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/controller/TaskController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/TaskRepository.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/TaskRepository.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/services/TaskService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/services/TaskService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/.openapi-generator/FILES" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/.openapi-generator/FILES" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/api/task.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/api/task.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/model/models.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/model/models.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/app-routing.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/app-routing.module.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/app.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/app.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/app.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/app.module.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../openapi.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../openapi.yaml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@ -21,8 +25,8 @@
<option name="RECENT_TEMPLATES">
<list>
<option value="Interface" />
<option value="Class" />
<option value="Enum" />
<option value="Class" />
</list>
</option>
</component>

View File

@ -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<Task> taskList = taskService.loadAllTasks(SecurityContextHolder.getContext().getAuthentication().getName(), scope);
List<TaskShortInfo> taskShortInfos = new ArrayList<>();
List<TaskEntityInfo> taskInfos = new ArrayList<>();
List<TaskTaskgroupInfo> 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));
}
}

View File

@ -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<TaskgroupEntityInfo> 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<Taskgroup> 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<TaskgroupEntityInfo> getTaskgroups() {
return taskgroups;
}
public void setTaskgroups(List<TaskgroupEntityInfo> taskgroups) {
this.taskgroups = taskgroups;
}
}

View File

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

View File

@ -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<TaskShortInfo> | Array<TaskEntityInfo>>;
public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskShortInfo> | Array<TaskEntityInfo>>>;
public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskShortInfo> | Array<TaskEntityInfo>>>;
public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskShortInfo> | Array<TaskTaskgroupInfo>>;
public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskShortInfo> | Array<TaskTaskgroupInfo>>>;
public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskShortInfo> | Array<TaskTaskgroupInfo>>>;
public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
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<TaskShortInfo> | Array<TaskEntityInfo>>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(scope))}/${encodeURIComponent(String(detailed))}`,
return this.httpClient.get<Array<TaskShortInfo> | Array<TaskTaskgroupInfo>>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(scope))}/${encodeURIComponent(String(detailed))}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,

View File

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

View File

@ -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<TaskgroupEntityInfo>;
}

View File

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

View File

@ -4,6 +4,7 @@
<button mat-button aria-label="Organize" *ngIf="authService.hasKey" [matMenuTriggerFor]="organizeMenu">Organize &#9662;</button>
<mat-menu #organizeMenu=matMenu>
<button mat-menu-item routerLink="taskgroups/" aria-label="Task groups">Taskgroups</button>
<button mat-menu-item routerLink="overdue/" aria-label="Overdue Tasks">Overdue Tasks</button>
<button mat-menu-item routerLink="reschedule/" aria-label="Missed Schedules">Missed Schedules</button>
</mat-menu>
<span class="example-spacer"></span>

View File

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

View File

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

View File

@ -0,0 +1,29 @@
<div class="container">
<app-navigation-link-list [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<mat-card *ngFor="let task of overdueTasks">
<mat-card-content>
<h3>
<span *ngFor="let taskgroup of task.taskgroups">
<a class="undecorated-link">{{taskgroup.taskgroupName}}</a>/
</span>
<a class="undecorated-link">{{task.taskName}}</a>
</h3>
<mat-progress-bar mode="determinate" [value]="calcProgress(task)"></mat-progress-bar>
<div class="originally-planned-container">
<div style="width: 100%">
<p style="display: inline-block"><i>Limit:</i> {{task.deadline}}</p>
</div>
<div style="width: 100%" class="reschedule-actions-container">
<div class="btn-group">
<button mat-raised-button color="primary" class="btn-group-itemleft">Schedule</button>
<button mat-raised-button class="btn-group-item yellowBtn" (click)="startNow(task)">Start now</button>
<button mat-raised-button class="btn-group-itemright greenBtn" (click)="finishTask(task)">Finish</button>
</div>
<div class="btn-group">
<button mat-raised-button color="warn" (click)="deleteTask(task)"><mat-icon>delete</mat-icon>Delete</button>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@ -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<OverdueTaskOverviewComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [OverdueTaskOverviewComponent]
});
fixture = TestBed.createComponent(OverdueTaskOverviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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) {
}
}

View File

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