Implement Schedule History
This commit is contained in:
parent
84123f6664
commit
0d64d9c72a
@ -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<AbstractSchedule> abstractSchedules = taskScheduleService.findSchedulesByDate(SecurityContextHolder.getContext().getAuthentication().getName(), dateArg);
|
||||
return ResponseEntity.ok(abstractSchedules.stream().map(AbstractSchedule::toScheduleInfo).toList());
|
||||
}
|
||||
}
|
||||
|
@ -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<ScheduleInfo> mondaySchedules;
|
||||
|
||||
@JsonProperty
|
||||
private List<ScheduleInfo> tuesdaySchedules;
|
||||
|
||||
@JsonProperty
|
||||
private List<ScheduleInfo> wednesdaySchedules;
|
||||
|
||||
@JsonProperty
|
||||
private List<ScheduleInfo> thursdaySchedules;
|
||||
|
||||
@JsonProperty
|
||||
private List<ScheduleInfo> fridaySchedules;
|
||||
|
||||
@JsonProperty
|
||||
private List<ScheduleInfo> saturdaySchedules;
|
||||
|
||||
@JsonProperty
|
||||
private List<ScheduleInfo> sundaySchedules;
|
||||
|
||||
public PastScheduleInfo(List<ScheduleInfo> mondaySchedules, List<ScheduleInfo> tuesdaySchedules,
|
||||
List<ScheduleInfo> wednesdaySchedules, List<ScheduleInfo> thursdaySchedules,
|
||||
List<ScheduleInfo> fridaySchedules, List<ScheduleInfo> saturdaySchedules,
|
||||
List<ScheduleInfo> sundaySchedules) {
|
||||
this.mondaySchedules = mondaySchedules;
|
||||
this.tuesdaySchedules = tuesdaySchedules;
|
||||
this.wednesdaySchedules = wednesdaySchedules;
|
||||
this.thursdaySchedules = thursdaySchedules;
|
||||
this.fridaySchedules = fridaySchedules;
|
||||
this.saturdaySchedules = saturdaySchedules;
|
||||
this.sundaySchedules = sundaySchedules;
|
||||
}
|
||||
}
|
@ -19,5 +19,4 @@ public interface ScheduleRepository extends CrudRepository<AbstractSchedule, Lon
|
||||
|
||||
@Query(value = "SELECT s FROM AbstractSchedule s WHERE s.task.taskgroup.user.username = ?1 AND s.startTime is NOT NULL and s.stopTime is NULL")
|
||||
Optional<AbstractSchedule> getActiveScheduleOfUser(String username);
|
||||
|
||||
}
|
||||
|
@ -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<AbstractSchedule> taskSchedules) {
|
||||
scheduleRepository.deleteAll(taskSchedules);
|
||||
}
|
||||
|
||||
public List<AbstractSchedule> findSchedulesByDate(String username, LocalDate date) {
|
||||
List<AbstractSchedule> allSchedules = scheduleRepository.findAllByUsername(username);
|
||||
return findScheduleByDate(allSchedules, date);
|
||||
}
|
||||
|
||||
private List<AbstractSchedule> findScheduleByDate(List<AbstractSchedule> schedules, LocalDate date) {
|
||||
List<AbstractSchedule> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<Array<ScheduleInfo>>;
|
||||
public historySchedulesDateGet(date: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<ScheduleInfo>>>;
|
||||
public historySchedulesDateGet(date: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<ScheduleInfo>>>;
|
||||
public historySchedulesDateGet(date: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||
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<Array<ScheduleInfo>>(`${this.configuration.basePath}/history/schedules/${encodeURIComponent(String(date))}`,
|
||||
{
|
||||
context: localVarHttpContext,
|
||||
responseType: <any>responseType_,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: localVarHeaders,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get number of active minutes
|
||||
* get number of worked minutes today
|
||||
|
24
frontend/src/api/model/pastScheduleInfo.ts
Normal file
24
frontend/src/api/model/pastScheduleInfo.ts
Normal file
@ -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<ScheduleInfo>;
|
||||
TUESDAY: Array<ScheduleInfo>;
|
||||
WEDNESDAY: Array<ScheduleInfo>;
|
||||
THURSDAY: Array<ScheduleInfo>;
|
||||
FRIDAY: Array<ScheduleInfo>;
|
||||
SATURDAY: Array<ScheduleInfo>;
|
||||
SUNDAY: Array<ScheduleInfo>;
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
<mat-menu #statisticsMenu=matMenu>
|
||||
<button mat-menu-item routerLink="statistics/taskgroup-activity">Activity</button>
|
||||
<button mat-menu-item routerLink="statistics/schedule-history">History</button>
|
||||
</mat-menu>
|
||||
<span class="example-spacer"></span>
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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%;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<div class="container">
|
||||
<app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
|
||||
|
||||
<mat-form-field class="long-form">
|
||||
<mat-label>Choose a date</mat-label>
|
||||
<input matInput [matDatepicker]="picker" [(ngModel)]="selectedDate">
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<table mat-table [dataSource]="schedules" class="mat-elevation-z8">
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="taskgroup">
|
||||
<th mat-header-cell *matHeaderCellDef> Taskgroup </th>
|
||||
<td mat-cell *matCellDef="let element"> {{computeTaskgroupPath(element.taskgroupPath)}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="task">
|
||||
<th mat-header-cell *matHeaderCellDef> Task </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.task.taskName}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="start">
|
||||
<th mat-header-cell *matHeaderCellDef> Starttime </th>
|
||||
<td mat-cell *matCellDef="let element"> {{formatDate(element.startTime)}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="end">
|
||||
<th mat-header-cell *matHeaderCellDef> Endtime </th>
|
||||
<td mat-cell *matCellDef="let element"> {{formatDate(element.finishedTime)}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="duration">
|
||||
<th mat-header-cell *matHeaderCellDef> Duration </th>
|
||||
<td mat-cell *matCellDef="let element"> {{calcDuration(element.startTime, element.finishedTime)}} </td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
</div>
|
@ -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<ScheduleHistoryComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ScheduleHistoryComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(ScheduleHistoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -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";
|
||||
}
|
||||
}
|
27
openapi.yaml
27
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:
|
||||
@ -2638,3 +2663,5 @@ components:
|
||||
type: number
|
||||
description: Number of minutes the task was active
|
||||
example: 122
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user