Upcoming tasks

This commit is contained in:
Sebastian 2023-10-29 17:20:24 +01:00
parent 2cabc09598
commit a9c3931ced
14 changed files with 254 additions and 35 deletions

View File

@ -5,7 +5,16 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Fix marking finished task as overdue"> <list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Fix marking finished task as overdue">
<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" /> <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/models/timemanager/tasks/TaskScope.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/tasks/TaskScope.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/api/task.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/api/task.service.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$/../frontend/src/app/tasks/task-editor/task-editor.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/tasks/task-editor/task-editor.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../openapi.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../openapi.yaml" 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,34 +50,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"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"RequestMappingsPanelOrder0": "0", &quot;RequestMappingsPanelOrder0&quot;: &quot;0&quot;,
"RequestMappingsPanelOrder1": "1", &quot;RequestMappingsPanelOrder1&quot;: &quot;1&quot;,
"RequestMappingsPanelWidth0": "75", &quot;RequestMappingsPanelWidth0&quot;: &quot;75&quot;,
"RequestMappingsPanelWidth1": "75", &quot;RequestMappingsPanelWidth1&quot;: &quot;75&quot;,
"RunOnceActivity.OpenProjectViewOnStart": "true", &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"WebServerToolWindowFactoryState": "false", &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
"extract.method.default.visibility": "private", &quot;extract.method.default.visibility&quot;: &quot;private&quot;,
"git-widget-placeholder": "issue-11-angular-update", &quot;git-widget-placeholder&quot;: &quot;issue-11-angular-update&quot;,
"last_directory_selection": "D:/Programmierprojekte/TimeManager/backend/src/main/java/core/api/models/timemanager", &quot;last_directory_selection&quot;: &quot;D:/Programmierprojekte/TimeManager/backend/src/main/java/core/api/models/timemanager&quot;,
"last_opened_file_path": "D:/Programmierprojekte/Dicewars/client", &quot;last_opened_file_path&quot;: &quot;D:/Programmierprojekte/Dicewars/client&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"settings.editor.selected.configurable": "swagger", &quot;settings.editor.selected.configurable&quot;: &quot;swagger&quot;,
"ts.external.directory.path": "/snap/intellij-idea-ultimate/459/plugins/javascript-impl/jsLanguageServicesImpl/external", &quot;ts.external.directory.path&quot;: &quot;/snap/intellij-idea-ultimate/459/plugins/javascript-impl/jsLanguageServicesImpl/external&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}, },
"keyToStringList": { &quot;keyToStringList&quot;: {
"DatabaseDriversLRU": [ &quot;DatabaseDriversLRU&quot;: [
"mariadb" &quot;mariadb&quot;
] ]
} }
}]]></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" />
@ -320,6 +329,11 @@
<line>202</line> <line>202</line>
<option name="timeStamp" value="10" /> <option name="timeStamp" value="10" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/services/TaskService.java</url>
<line>69</line>
<option name="timeStamp" value="13" />
</line-breakpoint>
</breakpoints> </breakpoints>
</breakpoint-manager> </breakpoint-manager>
</component> </component>

View File

@ -4,5 +4,7 @@ public enum TaskScope {
FINISHED, FINISHED,
UNFINISHED, UNFINISHED,
OVERDUE; OVERDUE,
UPCOMING,
ACTIVE;
} }

View File

@ -29,4 +29,7 @@ public interface TaskRepository extends CrudRepository<Task, Long> {
@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") @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); List<Task> findAllOverdue(String username, LocalDate now);
@Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND t.startDate IS NOT NULL AND t.startDate > ?2 AND t.finished = FALSE")
List<Task> findAllUpcoming(String username, LocalDate now);
} }

View File

@ -109,6 +109,9 @@ public class TaskService {
case OVERDUE -> { case OVERDUE -> {
return taskRepository.findAllOverdue(username, LocalDate.now()); return taskRepository.findAllOverdue(username, LocalDate.now());
} }
case UPCOMING -> {
return taskRepository.findAllUpcoming(username, LocalDate.now());
}
default -> { default -> {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -98,10 +98,10 @@ 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 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 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' | 'UPCOMING' | 'ACTIVE', 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' | 'UPCOMING' | 'ACTIVE', 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' | 'UPCOMING' | 'ACTIVE', 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> { public tasksAllScopeDetailedGet(scope: 'FINISHED' | 'UNFINISHED' | 'OVERDUE' | 'UPCOMING' | 'ACTIVE', detailed: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (scope === null || scope === undefined) { if (scope === null || scope === undefined) {
throw new Error('Required parameter scope was null or undefined when calling tasksAllScopeDetailedGet.'); throw new Error('Required parameter scope was null or undefined when calling tasksAllScopeDetailedGet.');
} }

View File

@ -9,6 +9,7 @@ import {TaskDetailOverviewComponent} from "./tasks/task-detail-overview/task-det
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"; import {OverdueTaskOverviewComponent} from "./overdue-task-overview/overdue-task-overview.component";
import {UpcomingTaskOverviewComponent} from "./upcoming-task-overview/upcoming-task-overview.component";
const routes: Routes = [ const routes: Routes = [
{path: '', component: MainComponent}, {path: '', component: MainComponent},
@ -20,7 +21,8 @@ const routes: Routes = [
{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} {path: 'overdue', component: OverdueTaskOverviewComponent},
{path: 'upcoming', component: UpcomingTaskOverviewComponent}
]; ];
@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="upcoming/" aria-label="Upcoming Tasks">Upcoming Tasks</button>
<button mat-menu-item routerLink="overdue/" aria-label="Overdue Tasks">Overdue Tasks</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>

View File

@ -72,6 +72,7 @@ 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'; import { OverdueTaskOverviewComponent } from './overdue-task-overview/overdue-task-overview.component';
import { UpcomingTaskOverviewComponent } from './upcoming-task-overview/upcoming-task-overview.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -106,7 +107,8 @@ import { OverdueTaskOverviewComponent } from './overdue-task-overview/overdue-ta
ForgottenTaskStartDialogComponent, ForgottenTaskStartDialogComponent,
MissedSchedulesComponent, MissedSchedulesComponent,
MissedScheduleForgetConfirmationDialogComponent, MissedScheduleForgetConfirmationDialogComponent,
OverdueTaskOverviewComponent OverdueTaskOverviewComponent,
UpcomingTaskOverviewComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -48,6 +48,8 @@ export class TaskEditorComponent implements OnInit {
} }
createTask() { createTask() {
console.log(this.startDate.value)
console.log(this.endDate.value)
this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, { this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, {
taskName: this.nameCtrl.value, taskName: this.nameCtrl.value,
eta: this.etaCtrl.value, eta: this.etaCtrl.value,
@ -72,8 +74,8 @@ export class TaskEditorComponent implements OnInit {
} }
editTask() { editTask() {
const startingDate = this.startDate.value.length == 0? null:this.startDate.value.length const startingDate = this.startDate.value.length == 0? null:this.startDate.value
const deadline = this.endDate.value.length == 0? null:this.endDate.value.length const deadline = this.endDate.value.length == 0? null:this.endDate.value
this.taskService.tasksTaskIDPost(this.editorData.task!.taskID, { this.taskService.tasksTaskIDPost(this.editorData.task!.taskID, {
taskName: this.nameCtrl.value, taskName: this.nameCtrl.value,
eta: this.etaCtrl.value, eta: this.etaCtrl.value,

View File

@ -0,0 +1,77 @@
.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;
}
.grayBtn {
background-color: #444444;
color: white;
}

View File

@ -0,0 +1,24 @@
<div class="container">
<app-navigation-link-list [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<mat-card *ngFor="let task of upcomingTasks">
<mat-card-content>
<h3>
<span *ngFor="let taskgroup of task.taskgroups">
<a class="undecorated-link" [routerLink]="['/taskgroups', taskgroup.taskgroupID]">{{taskgroup.taskgroupName}}</a> /
</span>
<a class="undecorated-link" [routerLink]="['/taskgroups', task.taskgroups[task.taskgroups.length-1].taskgroupID, 'tasks', task.taskID]">{{task.taskName}}</a>
</h3>
<p><i>ETA: </i>{{task.eta}} Minutes</p>
<p *ngIf="task.deadline != undefined"><i>Deadline: </i>{{task.deadline}}</p>
<div class="reschedule-actions-container">
<div class="btn-group">
<button mat-raised-button color="primary" class="btn-group-itemleft" [routerLink]="['/taskgroups', task.taskgroups[task.taskgroups.length-1].taskgroupID, 'tasks', task.taskID, 'schedule']">Schedule</button>
<button mat-raised-button class="grayBtn btn-group-itemright" (click)="editTask(task)">Edit</button>
</div>
<div class="btn-group">
<button mat-raised-button color="warn" (click)="deleteTask(task)"><mat-icon>delete</mat-icon>Delete</button>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UpcomingTaskOverviewComponent } from './upcoming-task-overview.component';
describe('UpcomingTaskOverviewComponent', () => {
let component: UpcomingTaskOverviewComponent;
let fixture: ComponentFixture<UpcomingTaskOverviewComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UpcomingTaskOverviewComponent]
});
fixture = TestBed.createComponent(UpcomingTaskOverviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,66 @@
import {Component, OnInit} from '@angular/core';
import {TaskService, TaskTaskgroupInfo} from "../../api";
import {NavigationLink} from "../navigation-link-list/navigation-link-list.component";
import {MatSnackBar} from "@angular/material/snack-bar";
import {TaskEditorData} from "../tasks/task-editor/TaskEditorData";
import {TaskEditorComponent} from "../tasks/task-editor/task-editor.component";
import {MatDialog} from "@angular/material/dialog";
@Component({
selector: 'app-upcoming-task-overview',
templateUrl: './upcoming-task-overview.component.html',
styleUrls: ['./upcoming-task-overview.component.css']
})
export class UpcomingTaskOverviewComponent implements OnInit{
upcomingTasks: TaskTaskgroupInfo[] = []
defaultNavigationLinkPath: NavigationLink[] = [
{
linkText: "Dashboard",
routerLink: ['/']
},
{
linkText: "Upcoming Tasks",
routerLink: ["/upcoming"]
}
]
constructor(private taskService: TaskService,
private snackbar: MatSnackBar,
private dialog: MatDialog) {
}
ngOnInit(): void {
this.taskService.tasksAllScopeDetailedGet("UPCOMING", true).subscribe({
next: resp => {
this.upcomingTasks = resp as TaskTaskgroupInfo[];
}
})
}
deleteTask(deletedTask: TaskTaskgroupInfo) {
this.taskService.tasksTaskIDDelete(deletedTask.taskID).subscribe({
next: resp => {
this.upcomingTasks = this.upcomingTasks.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});
}
}
})
}
editTask(editedTask: TaskTaskgroupInfo) {
const taskEditorInfo: TaskEditorData = {
task: editedTask,
taskgroupID: editedTask.taskgroups[editedTask.taskgroups.length-1].taskgroupID
};
this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"})
}
}

View File

@ -902,6 +902,8 @@ paths:
- FINISHED - FINISHED
- UNFINISHED - UNFINISHED
- OVERDUE - OVERDUE
- UPCOMING
- ACTIVE
- name: detailed - name: detailed
in: path in: path
description: determines whether an detailed response is required or not description: determines whether an detailed response is required or not