From 0d64d9c72a803a7a2affe021d34ffdb5ae9a4c72 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 20 Dec 2023 20:53:37 +0100 Subject: [PATCH] Implement Schedule History --- .../api/controller/StatisticController.java | 18 +++--- .../timemanager/history/PastScheduleInfo.java | 43 +++++++++++++ .../timemanager/ScheduleRepository.java | 1 - .../core/services/TaskScheduleService.java | 29 +++++++-- frontend/src/api/api/history.service.ts | 60 ++++++++++++++++++ frontend/src/api/model/pastScheduleInfo.ts | 24 +++++++ frontend/src/app/app-routing.module.ts | 4 +- frontend/src/app/app.component.html | 1 + frontend/src/app/app.module.ts | 2 + .../schedule-history.component.css | 25 ++++++++ .../schedule-history.component.html | 46 ++++++++++++++ .../schedule-history.component.spec.ts | 21 +++++++ .../schedule-history.component.ts | 62 +++++++++++++++++++ openapi.yaml | 29 ++++++++- 14 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 backend/src/main/java/core/api/models/timemanager/history/PastScheduleInfo.java create mode 100644 frontend/src/api/model/pastScheduleInfo.ts create mode 100644 frontend/src/app/statistics/schedule-history/schedule-history.component.css create mode 100644 frontend/src/app/statistics/schedule-history/schedule-history.component.html create mode 100644 frontend/src/app/statistics/schedule-history/schedule-history.component.spec.ts create mode 100644 frontend/src/app/statistics/schedule-history/schedule-history.component.ts diff --git a/backend/src/main/java/core/api/controller/StatisticController.java b/backend/src/main/java/core/api/controller/StatisticController.java index 8867f1a..af632be 100644 --- a/backend/src/main/java/core/api/controller/StatisticController.java +++ b/backend/src/main/java/core/api/controller/StatisticController.java @@ -17,15 +17,10 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.parameters.P; import org.springframework.web.bind.annotation.*; -import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; +import java.time.*; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.time.temporal.TemporalAdjusters; +import java.util.*; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -70,4 +65,11 @@ public class StatisticController { }); return ResponseEntity.ok(activityInfos); } + + @GetMapping("/history/schedules/{date}") + public ResponseEntity getPastSchedules(@PathVariable String date) { + LocalDate dateArg = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + List abstractSchedules = taskScheduleService.findSchedulesByDate(SecurityContextHolder.getContext().getAuthentication().getName(), dateArg); + return ResponseEntity.ok(abstractSchedules.stream().map(AbstractSchedule::toScheduleInfo).toList()); + } } diff --git a/backend/src/main/java/core/api/models/timemanager/history/PastScheduleInfo.java b/backend/src/main/java/core/api/models/timemanager/history/PastScheduleInfo.java new file mode 100644 index 0000000..7a21120 --- /dev/null +++ b/backend/src/main/java/core/api/models/timemanager/history/PastScheduleInfo.java @@ -0,0 +1,43 @@ +package core.api.models.timemanager.history; + +import com.fasterxml.jackson.annotation.JsonProperty; +import core.api.models.timemanager.taskSchedule.scheduleInfos.ScheduleInfo; + +import java.util.List; + +public class PastScheduleInfo { + + @JsonProperty + private List mondaySchedules; + + @JsonProperty + private List tuesdaySchedules; + + @JsonProperty + private List wednesdaySchedules; + + @JsonProperty + private List thursdaySchedules; + + @JsonProperty + private List fridaySchedules; + + @JsonProperty + private List saturdaySchedules; + + @JsonProperty + private List sundaySchedules; + + public PastScheduleInfo(List mondaySchedules, List tuesdaySchedules, + List wednesdaySchedules, List thursdaySchedules, + List fridaySchedules, List saturdaySchedules, + List sundaySchedules) { + this.mondaySchedules = mondaySchedules; + this.tuesdaySchedules = tuesdaySchedules; + this.wednesdaySchedules = wednesdaySchedules; + this.thursdaySchedules = thursdaySchedules; + this.fridaySchedules = fridaySchedules; + this.saturdaySchedules = saturdaySchedules; + this.sundaySchedules = sundaySchedules; + } +} diff --git a/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java b/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java index df72a88..4486293 100644 --- a/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java +++ b/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java @@ -19,5 +19,4 @@ public interface ScheduleRepository extends CrudRepository getActiveScheduleOfUser(String username); - } diff --git a/backend/src/main/java/core/services/TaskScheduleService.java b/backend/src/main/java/core/services/TaskScheduleService.java index 2df2e3f..adcaf7f 100644 --- a/backend/src/main/java/core/services/TaskScheduleService.java +++ b/backend/src/main/java/core/services/TaskScheduleService.java @@ -1,9 +1,11 @@ package core.services; +import core.api.models.timemanager.history.PastScheduleInfo; import core.api.models.timemanager.taskSchedule.scheduleInfos.AdvancedScheduleFieldInfo; import core.api.models.timemanager.taskSchedule.scheduleInfos.AdvancedScheduleInfo; import core.api.models.timemanager.taskSchedule.scheduleInfos.BasicScheduleFieldInfo; import core.api.models.timemanager.taskSchedule.ForgottenScheduleInfo; +import core.api.models.timemanager.taskSchedule.scheduleInfos.ScheduleInfo; import core.entities.timemanager.AbstractSchedule; import core.entities.timemanager.AdvancedTaskSchedule; import core.entities.timemanager.BasicTaskSchedule; @@ -14,13 +16,12 @@ import core.repositories.timemanager.TaskRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; +import java.time.temporal.ChronoUnit; +import java.util.*; @Service public class TaskScheduleService { @@ -216,4 +217,24 @@ public class TaskScheduleService { public void deleteSchedules(List taskSchedules) { scheduleRepository.deleteAll(taskSchedules); } + + public List findSchedulesByDate(String username, LocalDate date) { + List allSchedules = scheduleRepository.findAllByUsername(username); + return findScheduleByDate(allSchedules, date); + } + + private List findScheduleByDate(List schedules, LocalDate date) { + List filteredSchedules = new ArrayList<>(); + for(AbstractSchedule schedule : schedules) { + if(schedule.isCompleted()) { + LocalDate startDate = LocalDate.from(schedule.getStartTime()); + LocalDate endDate = LocalDate.from(schedule.getStopTime()); + + if(startDate.isEqual(date) || endDate.isEqual(date)) { + filteredSchedules.add(schedule); + } + } + } + return filteredSchedules; + } } diff --git a/frontend/src/api/api/history.service.ts b/frontend/src/api/api/history.service.ts index bc7e6bc..1e5ec3d 100644 --- a/frontend/src/api/api/history.service.ts +++ b/frontend/src/api/api/history.service.ts @@ -18,6 +18,7 @@ import { HttpClient, HttpHeaders, HttpParams, import { CustomHttpParameterCodec } from '../encoder'; import { Observable } from 'rxjs'; +import { ScheduleInfo } from '../model/models'; import { ScheduleStatus } from '../model/models'; import { SimpleStatusResponse } from '../model/models'; import { TaskgroupActivityInfo } from '../model/models'; @@ -87,6 +88,65 @@ export class HistoryService { return httpParams; } + /** + * List past schedules + * Get schedules of the past + * @param date date + * @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 historySchedulesDateGet(date: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public historySchedulesDateGet(date: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public historySchedulesDateGet(date: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public historySchedulesDateGet(date: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (date === null || date === undefined) { + throw new Error('Required parameter date was null or undefined when calling historySchedulesDateGet.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.get>(`${this.configuration.basePath}/history/schedules/${encodeURIComponent(String(date))}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + /** * get number of active minutes * get number of worked minutes today diff --git a/frontend/src/api/model/pastScheduleInfo.ts b/frontend/src/api/model/pastScheduleInfo.ts new file mode 100644 index 0000000..e9a4703 --- /dev/null +++ b/frontend/src/api/model/pastScheduleInfo.ts @@ -0,0 +1,24 @@ +/** + * 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 { ScheduleInfo } from './scheduleInfo'; + + +export interface PastScheduleInfo { + MONDAY: Array; + TUESDAY: Array; + WEDNESDAY: Array; + THURSDAY: Array; + FRIDAY: Array; + SATURDAY: Array; + SUNDAY: Array; +} + diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 37b2049..1d22ed5 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -16,6 +16,7 @@ import { ForgottenTaskStartDialogComponent } from "./dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component"; import {TaskgroupActivityComponent} from "./statistics/taskgroup-activity/taskgroup-activity.component"; +import {ScheduleHistoryComponent} from "./statistics/schedule-history/schedule-history.component"; const routes: Routes = [ {path: '', component: MainComponent}, @@ -32,7 +33,8 @@ const routes: Routes = [ {path: 'active', component: ActiveTaskOverviewComponent}, {path: 'scheduler', component: DraggableSchedulerComponent}, {path: 'forgotten', component: ForgottenTaskStartDialogComponent}, - {path: 'statistics/taskgroup-activity', component: TaskgroupActivityComponent} + {path: 'statistics/taskgroup-activity', component: TaskgroupActivityComponent}, + {path: 'statistics/schedule-history', component: ScheduleHistoryComponent}, ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 58df28c..9e0ad25 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -14,6 +14,7 @@ + diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d04b089..d4d53e1 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -85,6 +85,7 @@ import {NgxSliderModule} from "ngx-slider-v2"; import {NgApexchartsModule} from "ng-apexcharts"; import { SimpleActivityDiagramComponent } from './statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component'; import { HeatmapActivityComponent } from './statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component'; +import { ScheduleHistoryComponent } from './statistics/schedule-history/schedule-history.component'; @NgModule({ declarations: [ AppComponent, @@ -128,6 +129,7 @@ import { HeatmapActivityComponent } from './statistics/taskgroup-activity/heatma TaskgroupActivityComponent, SimpleActivityDiagramComponent, HeatmapActivityComponent, + ScheduleHistoryComponent, ], imports: [ BrowserModule, diff --git a/frontend/src/app/statistics/schedule-history/schedule-history.component.css b/frontend/src/app/statistics/schedule-history/schedule-history.component.css new file mode 100644 index 0000000..11b4a81 --- /dev/null +++ b/frontend/src/app/statistics/schedule-history/schedule-history.component.css @@ -0,0 +1,25 @@ +.container { + margin: 20px auto; + width: 70%; +} + +.spacer { + margin-bottom: 2.5%; +} + + +@media screen and (max-width: 600px) { + .container { + width: 100%; + margin: 20px 10px; + } +} + +.long-form { + width: 100%; + margin-bottom: 10px; +} + +table { + width: 100%; +} diff --git a/frontend/src/app/statistics/schedule-history/schedule-history.component.html b/frontend/src/app/statistics/schedule-history/schedule-history.component.html new file mode 100644 index 0000000..058a7fa --- /dev/null +++ b/frontend/src/app/statistics/schedule-history/schedule-history.component.html @@ -0,0 +1,46 @@ +
+ + + + Choose a date + + MM/DD/YYYY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Taskgroup {{computeTaskgroupPath(element.taskgroupPath)}} Task {{element.task.taskName}} Starttime {{formatDate(element.startTime)}} Endtime {{formatDate(element.finishedTime)}} Duration {{calcDuration(element.startTime, element.finishedTime)}}
+ +
diff --git a/frontend/src/app/statistics/schedule-history/schedule-history.component.spec.ts b/frontend/src/app/statistics/schedule-history/schedule-history.component.spec.ts new file mode 100644 index 0000000..58ccb3a --- /dev/null +++ b/frontend/src/app/statistics/schedule-history/schedule-history.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ScheduleHistoryComponent } from './schedule-history.component'; + +describe('ScheduleHistoryComponent', () => { + let component: ScheduleHistoryComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ScheduleHistoryComponent] + }); + fixture = TestBed.createComponent(ScheduleHistoryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/statistics/schedule-history/schedule-history.component.ts b/frontend/src/app/statistics/schedule-history/schedule-history.component.ts new file mode 100644 index 0000000..2752c29 --- /dev/null +++ b/frontend/src/app/statistics/schedule-history/schedule-history.component.ts @@ -0,0 +1,62 @@ +import {Component, OnInit} from '@angular/core'; +import {HistoryService, ScheduleInfo, TaskgroupEntityInfo} from "../../../api"; +import * as moment from "moment"; +import {NavigationLink} from "../../navigation-link-list/navigation-link-list.component"; + +@Component({ + selector: 'app-schedule-history', + templateUrl: './schedule-history.component.html', + styleUrls: ['./schedule-history.component.css'] +}) +export class ScheduleHistoryComponent implements OnInit{ + defaultNavigationLinkPath: NavigationLink[] = [ + { + linkText: 'Dashboard', + routerLink: ['/'] + }, + { + linkText: 'Statistics', + routerLink: [] + }, + { + linkText: 'Taskgroup Activity', + routerLink: ['/statistics/taskgroup-activity'] + } + ]; + + selectedDate: Date = new Date(); + schedules: ScheduleInfo[] = [] + + displayedColumns: string[] = ['taskgroup', 'task', 'start', 'end', 'duration']; + + ngOnInit() { + this.historyService.historySchedulesDateGet(moment(this.selectedDate).format("YYYY-MM-DD")).subscribe({ + next: resp => { + this.schedules = resp; + } + }) + } + + constructor(private historyService: HistoryService) { + } + + computeTaskgroupPath(taskgroupPath: TaskgroupEntityInfo[]) { + let result = ""; + taskgroupPath.forEach(taskgroup => { + result += taskgroup.taskgroupName + "/"; + }) + return result; + } + + formatDate(date: string) { + return moment(date).format('dd, DD. MMM YYYY'); + } + + calcDuration(startDate: string, endDate: string) { + const start = moment(new Date(startDate)); + const end = moment(new Date(endDate)); + const duration = moment.duration(end.diff(start)); + + return duration.asMinutes() + " Minutes"; + } +} diff --git a/openapi.yaml b/openapi.yaml index b77674a..a246191 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1961,6 +1961,31 @@ paths: application/json: schema: $ref: '#/components/schemas/SimpleStatusResponse' + /history/schedules/{date}: + get: + security: + - API_TOKEN: [] + tags: + - history + description: Get schedules of the past + summary: List past schedules + parameters: + - name: date + in: path + description: date + required: true + schema: + type: string + format: date + responses: + 200: + description: Operatio successfull + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduleInfo' components: @@ -2637,4 +2662,6 @@ components: activeMinutes: type: number description: Number of minutes the task was active - example: 122 \ No newline at end of file + example: 122 + + \ No newline at end of file