issue-35 #37

Merged
sebastian merged 4 commits from issue-35 into master 2023-10-29 16:16:34 +01:00
19 changed files with 574 additions and 61 deletions

View File

@ -4,8 +4,8 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Forget single schedule"> <list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Fix marking finished task as overdue">
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../frontend/src/app/overdue-task-overview/overdue-task-overview.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/overdue-task-overview/overdue-task-overview.component.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -41,33 +41,34 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;RequestMappingsPanelOrder0&quot;: &quot;0&quot;, "RequestMappingsPanelOrder0": "0",
&quot;RequestMappingsPanelOrder1&quot;: &quot;1&quot;, "RequestMappingsPanelOrder1": "1",
&quot;RequestMappingsPanelWidth0&quot;: &quot;75&quot;, "RequestMappingsPanelWidth0": "75",
&quot;RequestMappingsPanelWidth1&quot;: &quot;75&quot;, "RequestMappingsPanelWidth1": "75",
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;, "RunOnceActivity.OpenProjectViewOnStart": "true",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;, "WebServerToolWindowFactoryState": "false",
&quot;git-widget-placeholder&quot;: &quot;issue-11-angular-update&quot;, "extract.method.default.visibility": "private",
&quot;last_directory_selection&quot;: &quot;D:/Programmierprojekte/TimeManager/backend/src/main/java/core/api/models/timemanager&quot;, "git-widget-placeholder": "issue-11-angular-update",
&quot;last_opened_file_path&quot;: &quot;D:/Programmierprojekte/Dicewars/client&quot;, "last_directory_selection": "D:/Programmierprojekte/TimeManager/backend/src/main/java/core/api/models/timemanager",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "last_opened_file_path": "D:/Programmierprojekte/Dicewars/client",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;settings.editor.selected.configurable&quot;: &quot;swagger&quot;, "nodejs_package_manager_path": "npm",
&quot;ts.external.directory.path&quot;: &quot;/snap/intellij-idea-ultimate/459/plugins/javascript-impl/jsLanguageServicesImpl/external&quot;, "settings.editor.selected.configurable": "swagger",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "ts.external.directory.path": "/snap/intellij-idea-ultimate/459/plugins/javascript-impl/jsLanguageServicesImpl/external",
"vue.rearranger.settings.migration": "true"
}, },
&quot;keyToStringList&quot;: { "keyToStringList": {
&quot;DatabaseDriversLRU&quot;: [ "DatabaseDriversLRU": [
&quot;mariadb&quot; "mariadb"
] ]
} }
}</component> }]]></component>
<component name="RunManager"> <component name="RunManager">
<configuration name="DemoApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true"> <configuration name="DemoApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<module name="demo" /> <module name="demo" />
@ -258,7 +259,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1698573369764</updated> <updated>1698573369764</updated>
</task> </task>
<option name="localTasksCounter" value="20" /> <task id="LOCAL-00020" summary="Fix marking finished task as overdue">
<option name="closed" value="true" />
<created>1698592265846</created>
<option name="number" value="00020" />
<option name="presentableId" value="LOCAL-00020" />
<option name="project" value="LOCAL" />
<updated>1698592265846</updated>
</task>
<option name="localTasksCounter" value="21" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -285,7 +294,8 @@
<MESSAGE value="Check if there is another active schedule when starting task now" /> <MESSAGE value="Check if there is another active schedule when starting task now" />
<MESSAGE value="List missed Schedules" /> <MESSAGE value="List missed Schedules" />
<MESSAGE value="Forget single schedule" /> <MESSAGE value="Forget single schedule" />
<option name="LAST_COMMIT_MESSAGE" value="Forget single schedule" /> <MESSAGE value="Fix marking finished task as overdue" />
<option name="LAST_COMMIT_MESSAGE" value="Fix marking finished task as overdue" />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>

View File

@ -2,9 +2,7 @@ package core.api.controller;
import core.api.models.auth.SimpleStatusResponse; import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.taskSchedule.ScheduleInfo; import core.api.models.timemanager.taskSchedule.ScheduleInfo;
import core.api.models.timemanager.tasks.TaskEntityInfo; import core.api.models.timemanager.tasks.*;
import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.api.models.timemanager.tasks.TaskShortInfo;
import core.entities.timemanager.Task; import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup; import core.entities.timemanager.Taskgroup;
import core.services.*; import core.services.*;
@ -31,11 +29,29 @@ public class TaskController {
this.taskgroupService = taskgroupService; this.taskgroupService = taskgroupService;
} }
@GetMapping("/tasks/all/{finished}") @GetMapping("/tasks/all/{scope}/{detailed}")
public ResponseEntity<List<TaskShortInfo>> loadAllTasks(@PathVariable boolean finished) { public ResponseEntity<?> loadAllTasksShort(@PathVariable TaskScope scope, @PathVariable boolean detailed) {
List<Task> taskList = taskService.loadAllTasks(SecurityContextHolder.getContext().getAuthentication().getName(), finished); List<Task> taskList = taskService.loadAllTasks(SecurityContextHolder.getContext().getAuthentication().getName(), scope);
List<TaskShortInfo> taskShortInfos = new ArrayList<>(); List<TaskShortInfo> taskShortInfos = new ArrayList<>();
List<TaskTaskgroupInfo> taskInfos = new ArrayList<>();
for(Task task : taskList) { for(Task task : taskList) {
StringBuilder builder = loadTaskgroupPath(task);
if(!detailed) {
taskShortInfos.add(new TaskShortInfo(task.getTaskID(), builder.toString()));
} else {
taskInfos.add(new TaskTaskgroupInfo(task));
}
}
if(detailed) {
return ResponseEntity.ok(taskInfos);
} else {
return ResponseEntity.ok(taskShortInfos);
}
}
private StringBuilder loadTaskgroupPath(Task task) {
List<Taskgroup> ancestors = Taskgroup.getAncestorList(task.getTaskgroup()); List<Taskgroup> ancestors = Taskgroup.getAncestorList(task.getTaskgroup());
ancestors.add(task.getTaskgroup()); ancestors.add(task.getTaskgroup());
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@ -43,10 +59,7 @@ public class TaskController {
builder.append(taskgroup.getTaskgroupName()).append("/"); builder.append(taskgroup.getTaskgroupName()).append("/");
} }
builder.append(task.getTaskName()); builder.append(task.getTaskName());
taskShortInfos.add(new TaskShortInfo(task.getTaskID(), builder.toString())); return builder;
}
return ResponseEntity.ok(taskShortInfos);
} }
@GetMapping("/tasks/{taskgroupID}/{status}") @GetMapping("/tasks/{taskgroupID}/{status}")

View File

@ -0,0 +1,8 @@
package core.api.models.timemanager.tasks;
public enum TaskScope {
FINISHED,
UNFINISHED,
OVERDUE;
}

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

@ -8,6 +8,7 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.time.LocalDate;
import java.util.List; import java.util.List;
@Repository @Repository
@ -25,4 +26,7 @@ public interface TaskRepository extends CrudRepository<Task, Long> {
@Modifying @Modifying
@Query(value = "DELETE FROM Task t WHERE t.taskID = ?1") @Query(value = "DELETE FROM Task t WHERE t.taskID = ?1")
void deleteByTaskID(long taskID); void deleteByTaskID(long taskID);
@Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND t.deadline is NOT NULL AND t.deadline > ?2 AND t.finished = FALSE")
List<Task> findAllOverdue(String username, LocalDate now);
} }

View File

@ -1,6 +1,7 @@
package core.services; package core.services;
import core.api.models.timemanager.tasks.TaskFieldInfo; import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.api.models.timemanager.tasks.TaskScope;
import core.entities.timemanager.BasicTaskSchedule; import core.entities.timemanager.BasicTaskSchedule;
import core.entities.timemanager.Task; import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup; import core.entities.timemanager.Taskgroup;
@ -10,6 +11,7 @@ import core.repositories.timemanager.TaskgroupRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
@ -99,7 +101,18 @@ public class TaskService {
} }
} }
public List<Task> loadAllTasks(String username, boolean finished) { public List<Task> loadAllTasks(String username, TaskScope scope) {
return taskRepository.findAllByUser(username, finished); switch (scope) {
case UNFINISHED, FINISHED -> {
return taskRepository.findAllByUser(username, scope.equals(TaskScope.FINISHED));
}
case OVERDUE -> {
return taskRepository.findAllOverdue(username, LocalDate.now());
}
default -> {
return new ArrayList<>();
}
}
} }
} }

View File

@ -40,6 +40,7 @@ model/taskFieldInfo.ts
model/taskOverviewInfo.ts model/taskOverviewInfo.ts
model/taskScheduleStopResponse.ts model/taskScheduleStopResponse.ts
model/taskShortInfo.ts model/taskShortInfo.ts
model/taskTaskgroupInfo.ts
model/taskgroupDetailInfo.ts model/taskgroupDetailInfo.ts
model/taskgroupEntityInfo.ts model/taskgroupEntityInfo.ts
model/taskgroupFieldInfo.ts model/taskgroupFieldInfo.ts

View File

@ -23,6 +23,7 @@ import { SimpleStatusResponse } from '../model/models';
import { TaskEntityInfo } from '../model/models'; import { TaskEntityInfo } from '../model/models';
import { TaskFieldInfo } from '../model/models'; import { TaskFieldInfo } from '../model/models';
import { TaskShortInfo } from '../model/models'; import { TaskShortInfo } from '../model/models';
import { TaskTaskgroupInfo } from '../model/models';
import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration'; import { Configuration } from '../configuration';
@ -92,16 +93,20 @@ export class TaskService {
/** /**
* edits an existing task * edits an existing task
* edits an existing task * edits an existing task
* @param finished fetches either finished or unfinished tasks * @param scope defines scope of fetched tasks
* @param detailed determines whether an detailed response is required or not
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @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. * @param reportProgress flag to report request and response progress.
*/ */
public tasksAllFinishedGet(finished: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskShortInfo>>; public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskShortInfo> | Array<TaskTaskgroupInfo>>;
public tasksAllFinishedGet(finished: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskShortInfo>>>; 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 tasksAllFinishedGet(finished: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskShortInfo>>>; 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 tasksAllFinishedGet(finished: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> { public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE', detailed: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (finished === null || finished === undefined) { if (scope === null || scope === undefined) {
throw new Error('Required parameter finished was null or undefined when calling tasksAllFinishedGet.'); throw new Error('Required parameter scope was null or undefined when calling tasksAllScopeDetailedGet.');
}
if (detailed === null || detailed === undefined) {
throw new Error('Required parameter detailed was null or undefined when calling tasksAllScopeDetailedGet.');
} }
let localVarHeaders = this.defaultHeaders; let localVarHeaders = this.defaultHeaders;
@ -136,7 +141,7 @@ export class TaskService {
responseType_ = 'text'; responseType_ = 'text';
} }
return this.httpClient.get<Array<TaskShortInfo>>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(finished))}`, return this.httpClient.get<Array<TaskShortInfo> | Array<TaskTaskgroupInfo>>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(scope))}/${encodeURIComponent(String(detailed))}`,
{ {
context: localVarHttpContext, context: localVarHttpContext,
responseType: <any>responseType_, responseType: <any>responseType_,

View File

@ -24,6 +24,7 @@ export * from './taskFieldInfo';
export * from './taskOverviewInfo'; export * from './taskOverviewInfo';
export * from './taskScheduleStopResponse'; export * from './taskScheduleStopResponse';
export * from './taskShortInfo'; export * from './taskShortInfo';
export * from './taskTaskgroupInfo';
export * from './taskgroupDetailInfo'; export * from './taskgroupDetailInfo';
export * from './taskgroupEntityInfo'; export * from './taskgroupEntityInfo';
export * from './taskgroupFieldInfo'; 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 {TaskDetailOverviewComponent} from "./tasks/task-detail-overview/task-detail-overview.component";
import {SchedulerComponent} from "./schedules/scheduler/scheduler.component"; import {SchedulerComponent} from "./schedules/scheduler/scheduler.component";
import {MissedSchedulesComponent} from "./missed-schedules/missed-schedules.component"; import {MissedSchedulesComponent} from "./missed-schedules/missed-schedules.component";
import {OverdueTaskOverviewComponent} from "./overdue-task-overview/overdue-task-overview.component";
const routes: Routes = [ const routes: Routes = [
{path: '', component: MainComponent}, {path: '', component: MainComponent},
@ -18,7 +19,8 @@ const routes: Routes = [
{path: 'taskgroups/:taskgroupID/tasks/:taskID', component: TaskDetailOverviewComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID', component: TaskDetailOverviewComponent},
{path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule', component: SchedulerComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule', component: SchedulerComponent},
{path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule/:scheduleID', component: SchedulerComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule/:scheduleID', component: SchedulerComponent},
{path: 'reschedule', component: MissedSchedulesComponent} {path: 'reschedule', component: MissedSchedulesComponent},
{path: 'overdue', component: OverdueTaskOverviewComponent}
]; ];
@NgModule({ @NgModule({

View File

@ -4,6 +4,7 @@
<button mat-button aria-label="Organize" *ngIf="authService.hasKey" [matMenuTriggerFor]="organizeMenu">Organize &#9662;</button> <button mat-button aria-label="Organize" *ngIf="authService.hasKey" [matMenuTriggerFor]="organizeMenu">Organize &#9662;</button>
<mat-menu #organizeMenu=matMenu> <mat-menu #organizeMenu=matMenu>
<button mat-menu-item routerLink="taskgroups/" aria-label="Task groups">Taskgroups</button> <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> <button mat-menu-item routerLink="reschedule/" aria-label="Missed Schedules">Missed Schedules</button>
</mat-menu> </mat-menu>
<span class="example-spacer"></span> <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 {MatAutocompleteModule} from "@angular/material/autocomplete";
import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.component'; 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 { 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({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -104,7 +105,8 @@ import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedu
TaskOverviewComponent, TaskOverviewComponent,
ForgottenTaskStartDialogComponent, ForgottenTaskStartDialogComponent,
MissedSchedulesComponent, MissedSchedulesComponent,
MissedScheduleForgetConfirmationDialogComponent MissedScheduleForgetConfirmationDialogComponent,
OverdueTaskOverviewComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -30,7 +30,7 @@ export class ForgottenTaskStartDialogComponent implements OnInit{
} }
ngOnInit() { ngOnInit() {
this.taskService.tasksAllFinishedGet(false).subscribe({ this.taskService.tasksAllScopeDetailedGet("UNFINISHED", false).subscribe({
next: resp => { next: resp => {
this.tasks = resp; this.tasks = resp;
this.filteredOptions = this.myControl.valueChanges.pipe( this.filteredOptions = this.myControl.valueChanges.pipe(

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,98 @@
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(deletedTask: TaskTaskgroupInfo) {
this.taskService.tasksTaskIDDelete(deletedTask.taskID).subscribe({
next: resp => {
this.overdueTasks = this.overdueTasks.filter(task => task.taskID !== deletedTask.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});
}
}
})
}
}

View File

@ -883,7 +883,7 @@ paths:
schema: schema:
type: object type: object
$ref: "#/components/schemas/SimpleStatusResponse" $ref: "#/components/schemas/SimpleStatusResponse"
/tasks/all/{finished}: /tasks/all/{scope}/{detailed}:
get: get:
security: security:
- API_TOKEN: [] - API_TOKEN: []
@ -892,9 +892,19 @@ paths:
summary: edits an existing task summary: edits an existing task
description: edits an existing task description: edits an existing task
parameters: parameters:
- name: finished - name: scope
in: path in: path
description: fetches either finished or unfinished tasks description: defines scope of fetched tasks
required: true
schema:
type: string
enum:
- FINISHED
- UNFINISHED
- OVERDUE
- name: detailed
in: path
description: determines whether an detailed response is required or not
required: true required: true
schema: schema:
type: boolean type: boolean
@ -905,9 +915,14 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: array oneOf:
- type: array
items: items:
$ref: '#/components/schemas/TaskShortInfo' $ref: '#/components/schemas/TaskShortInfo'
- type: array
items:
$ref: '#/components/schemas/TaskTaskgroupInfo'
/tasks/{taskgroupID}/{status}: /tasks/{taskgroupID}/{status}:
get: get:
@ -2048,6 +2063,56 @@ components:
type: number type: number
description: number in minutes that was already worked on this task description: number in minutes that was already worked on this task
example: 10 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: TaskFieldInfo:
required: required:
- taskName - taskName