diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml index a1e3896..f7c95af 100644 --- a/backend/.idea/workspace.xml +++ b/backend/.idea/workspace.xml @@ -6,15 +6,11 @@ - - - - diff --git a/backend/src/main/java/core/repositories/timemanager/TaskRepository.java b/backend/src/main/java/core/repositories/timemanager/TaskRepository.java index 19887b4..fd8d118 100644 --- a/backend/src/main/java/core/repositories/timemanager/TaskRepository.java +++ b/backend/src/main/java/core/repositories/timemanager/TaskRepository.java @@ -32,4 +32,7 @@ public interface TaskRepository extends CrudRepository { @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 findAllUpcoming(String username, LocalDate now); + + @Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND t.startDate IS NULL OR t.startDate <= ?2 AND t.finished = FALSE") + List findAllActive(String username, LocalDate now); } diff --git a/backend/src/main/java/core/services/TaskService.java b/backend/src/main/java/core/services/TaskService.java index ae003fb..843d3e1 100644 --- a/backend/src/main/java/core/services/TaskService.java +++ b/backend/src/main/java/core/services/TaskService.java @@ -112,6 +112,9 @@ public class TaskService { case UPCOMING -> { return taskRepository.findAllUpcoming(username, LocalDate.now()); } + case ACTIVE -> { + return taskRepository.findAllActive(username, LocalDate.now()); + } default -> { return new ArrayList<>(); } diff --git a/frontend/src/app/active-task-overview/active-task-overview.component.css b/frontend/src/app/active-task-overview/active-task-overview.component.css new file mode 100644 index 0000000..391520c --- /dev/null +++ b/frontend/src/app/active-task-overview/active-task-overview.component.css @@ -0,0 +1,89 @@ +.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; +} + +.overdue-warning { + background-color: orange; + color: white; + padding: 10px; + border-radius: 6px; +} + +.progress-bar { + margin-top: 20px; + margin-bottom: 20px; +} diff --git a/frontend/src/app/active-task-overview/active-task-overview.component.html b/frontend/src/app/active-task-overview/active-task-overview.component.html new file mode 100644 index 0000000..38223e7 --- /dev/null +++ b/frontend/src/app/active-task-overview/active-task-overview.component.html @@ -0,0 +1,33 @@ +
+ + + + +

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

+
+ This task is overdue! +
+ +
+

ETA: {{task.eta}} Minutes

+

Deadline: {{task.deadline}}

+
+
+ + + + +
+
+ +
+
+
+
+
+
diff --git a/frontend/src/app/active-task-overview/active-task-overview.component.spec.ts b/frontend/src/app/active-task-overview/active-task-overview.component.spec.ts new file mode 100644 index 0000000..04a07d0 --- /dev/null +++ b/frontend/src/app/active-task-overview/active-task-overview.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ActiveTaskOverviewComponent } from './active-task-overview.component'; + +describe('ActiveTaskOverviewComponent', () => { + let component: ActiveTaskOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ActiveTaskOverviewComponent] + }); + fixture = TestBed.createComponent(ActiveTaskOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/active-task-overview/active-task-overview.component.ts b/frontend/src/app/active-task-overview/active-task-overview.component.ts new file mode 100644 index 0000000..c945579 --- /dev/null +++ b/frontend/src/app/active-task-overview/active-task-overview.component.ts @@ -0,0 +1,117 @@ +import {Component, OnInit} from '@angular/core'; +import {NavigationLink} from "../navigation-link-list/navigation-link-list.component"; +import {ScheduleService, TaskService, TaskTaskgroupInfo} from "../../api"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {Router} from "@angular/router"; +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-active-task-overview', + templateUrl: './active-task-overview.component.html', + styleUrls: ['./active-task-overview.component.css'] +}) +export class ActiveTaskOverviewComponent implements OnInit{ + tasks: TaskTaskgroupInfo[] = [] + defaultNavigationLinkPath: NavigationLink[] = [ + { + linkText: "Dashboard", + routerLink: ['/'] + }, + { + linkText: "Active Tasks", + routerLink: ["/active"] + } + ] + + constructor(private taskService: TaskService, + private snackbar: MatSnackBar, + private scheduleService: ScheduleService, + private router: Router, + private dialog: MatDialog) { + } + + ngOnInit(): void { + this.taskService.tasksAllScopeDetailedGet("ACTIVE", true).subscribe({ + next: resp => { + this.tasks = resp as TaskTaskgroupInfo[] + }, + 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}); + } + } + }) + } + + calcProgress(task: TaskTaskgroupInfo): number { + console.log(task.workTime / task.eta * 100) + return task.workTime / task.eta * 100 + } + + startTaskNow(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}); + } + } + }) + } + + deleteTask(deletedTask: TaskTaskgroupInfo) { + this.taskService.tasksTaskIDDelete(deletedTask.taskID).subscribe({ + next: resp => { + this.tasks = this.tasks.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"}) + } + + finishTask(task: TaskTaskgroupInfo) { + this.taskService.tasksTaskIDFinishPost(task.taskID).subscribe({ + next: resp => { + this.tasks = this.tasks.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}); + } + } + }) + } +} diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 3a79afa..7c0a94c 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -10,6 +10,7 @@ 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"; import {UpcomingTaskOverviewComponent} from "./upcoming-task-overview/upcoming-task-overview.component"; +import {ActiveTaskOverviewComponent} from "./active-task-overview/active-task-overview.component"; const routes: Routes = [ {path: '', component: MainComponent}, @@ -22,7 +23,8 @@ const routes: Routes = [ {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule/:scheduleID', component: SchedulerComponent}, {path: 'reschedule', component: MissedSchedulesComponent}, {path: 'overdue', component: OverdueTaskOverviewComponent}, - {path: 'upcoming', component: UpcomingTaskOverviewComponent} + {path: 'upcoming', component: UpcomingTaskOverviewComponent}, + {path: 'active', component: ActiveTaskOverviewComponent} ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 5c5ef46..c242a74 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 eb37d3d..b718252 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -73,6 +73,7 @@ import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.co 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 { UpcomingTaskOverviewComponent } from './upcoming-task-overview/upcoming-task-overview.component'; +import { ActiveTaskOverviewComponent } from './active-task-overview/active-task-overview.component'; @NgModule({ declarations: [ AppComponent, @@ -108,7 +109,8 @@ import { UpcomingTaskOverviewComponent } from './upcoming-task-overview/upcoming MissedSchedulesComponent, MissedScheduleForgetConfirmationDialogComponent, OverdueTaskOverviewComponent, - UpcomingTaskOverviewComponent + UpcomingTaskOverviewComponent, + ActiveTaskOverviewComponent ], imports: [ BrowserModule,