Merge remote-tracking branch 'origin/master'
All checks were successful
Java CI with Maven / test (push) Successful in 1m15s
Java CI with Maven / build-and-push-frontend (push) Successful in 2m14s
Java CI with Maven / build-and-push-backend (push) Successful in 1m25s

This commit is contained in:
Sebastian Böckelmann 2023-11-19 09:29:28 +01:00
commit da0cc90de6
8 changed files with 257 additions and 78 deletions

View File

@ -1,5 +1,7 @@
package core.api.models.timemanager.taskSchedule; package core.api.models.timemanager.taskSchedule;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -7,9 +9,11 @@ import java.time.LocalDateTime;
public class ForgottenScheduleInfo { public class ForgottenScheduleInfo {
@NotNull @NotNull
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
private LocalDateTime startTime; private LocalDateTime startTime;
@NotNull @NotNull
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
private LocalDateTime endTime; private LocalDateTime endTime;
public ForgottenScheduleInfo(LocalDateTime startTime, LocalDateTime endTime) { public ForgottenScheduleInfo(LocalDateTime startTime, LocalDateTime endTime) {

View File

@ -17,7 +17,7 @@ public abstract class ScheduleInfo {
@JsonProperty @JsonProperty
private LocalDateTime startTime; private LocalDateTime startTime;
@JsonProperty @JsonProperty
private LocalDateTime stopTime; private LocalDateTime finishedTime;
@JsonProperty @JsonProperty
private int activeMinutes; private int activeMinutes;
@JsonProperty @JsonProperty
@ -29,7 +29,7 @@ public abstract class ScheduleInfo {
this.scheduleID = scheduleID; this.scheduleID = scheduleID;
this.scheduleType = scheduleType; this.scheduleType = scheduleType;
this.startTime = startTime; this.startTime = startTime;
this.stopTime = stopTime; this.finishedTime = stopTime;
this.activeMinutes = activeMinutes; this.activeMinutes = activeMinutes;
this.task = new TaskShortInfo(task); this.task = new TaskShortInfo(task);
this.taskgroupPath = taskgroupPath; this.taskgroupPath = taskgroupPath;

View File

@ -39,6 +39,7 @@ public class Task {
private int workTime; private int workTime;
public Task() { public Task() {
this.basicTaskSchedules = new ArrayList<>();
} }
public Task(Taskgroup taskgroup, TaskFieldInfo taskFieldInfo) { public Task(Taskgroup taskgroup, TaskFieldInfo taskFieldInfo) {

View File

@ -12,6 +12,9 @@ import {OverdueTaskOverviewComponent} from "./overdue-task-overview/overdue-task
import {UpcomingTaskOverviewComponent} from "./upcoming-task-overview/upcoming-task-overview.component"; import {UpcomingTaskOverviewComponent} from "./upcoming-task-overview/upcoming-task-overview.component";
import {ActiveTaskOverviewComponent} from "./active-task-overview/active-task-overview.component"; import {ActiveTaskOverviewComponent} from "./active-task-overview/active-task-overview.component";
import {DraggableSchedulerComponent} from "./schedules/draggable-scheduler/draggable-scheduler.component"; import {DraggableSchedulerComponent} from "./schedules/draggable-scheduler/draggable-scheduler.component";
import {
ForgottenTaskStartDialogComponent
} from "./dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component";
const routes: Routes = [ const routes: Routes = [
{path: '', component: MainComponent}, {path: '', component: MainComponent},
@ -26,7 +29,8 @@ const routes: Routes = [
{path: 'overdue', component: OverdueTaskOverviewComponent}, {path: 'overdue', component: OverdueTaskOverviewComponent},
{path: 'upcoming', component: UpcomingTaskOverviewComponent}, {path: 'upcoming', component: UpcomingTaskOverviewComponent},
{path: 'active', component: ActiveTaskOverviewComponent}, {path: 'active', component: ActiveTaskOverviewComponent},
{path: 'scheduler', component: DraggableSchedulerComponent} {path: 'scheduler', component: DraggableSchedulerComponent},
{path: 'forgotten', component: ForgottenTaskStartDialogComponent}
]; ];
@NgModule({ @NgModule({

View File

@ -1,7 +1,7 @@
<mat-card class="green-card" *ngIf="activeSchedule == undefined"> <mat-card class="green-card" *ngIf="activeSchedule == undefined">
<mat-card-content> <mat-card-content>
<p>Currently there is no task in progress.</p> <p>Currently there is no task in progress.</p>
<a class="btn-link" (click)="openForgettedActivityDialog()">Did you forget to start an activity?</a> <a class="btn-link" routerLink="/forgotten">Did you forget to start an activity?</a>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card *ngIf="activeSchedule != undefined"> <mat-card *ngIf="activeSchedule != undefined">

View File

@ -1,4 +1,51 @@
.example-full-width { .container {
width: 100%; margin: 20px auto;
margin-left: 10px; width: 80%;
display: flex;
justify-content: space-between;
} }
.spacer {
margin-bottom: 2.5%;
}
::ng-deep .cal-week-view .cal-time-events {
max-height: 500px !important;
overflow-y: auto;
}
@media screen and (max-width: 600px) {
.container {
width: 100%;
margin: 20px 10px;
}
}
.calendar-container {
width: 75%;
}
.task-container {
width: 23%;
}
::ng-deep .mat-mdc-list-base {
--mdc-list-list-item-label-text-color: black
}
::ng-deep .mat-mdc-list-base .taskgroup-btn, ::ng-deep .mat-mdc-list-base .taskgroup-last-btn {
--mdc-list-list-item-label-text-color: black
}
.task-card {
background-color: #f3f3f3;
border: 0;
line-height: 4em;
}
.lightBlueBtn {
background-color: #3498db;
color: white;
}

View File

@ -1,31 +1,49 @@
<h1 mat-dialog-title>Register forgotten activity</h1> <div class="container">
<div mat-dialog-content> <div class="calendar-container">
<mat-form-field class="example-full-width"> <h1 mat-dialog-title>Register forgotten activity</h1>
<mat-label>Number</mat-label> <mwl-calendar-day-view
<input type="text" [viewDate]="viewDate"
placeholder="Pick one" [events]="events"
matInput [refresh]="refresh"
[formControl]="myControl" [dayStartHour]="8"
[matAutocomplete]="auto"> [dayEndHour]="22"
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"> [snapDraggedEvents]="false"
<mat-option *ngFor="let option of filteredOptions | async" [value]="option"> (eventTimesChanged)="eventDropped($event)" (eventClicked)="eventClicked('Click', $event.event)"
{{option}} >
</mat-option> </mwl-calendar-day-view>
</mat-autocomplete> <div style="float: right">
</mat-form-field> <button mat-raised-button color="primary" [disabled]="!taskgroupSelected" (click)="register()">Register</button>
</div>
</div>
<div class="task-container">
<div *ngIf="tasks.length > 0 && !taskgroupSelected">
<mat-action-list style="padding: 0;">
<button mat-list-item class="lightBlueBtn" [routerLink]="['/scheduler']">Tasks</button>
</mat-action-list>
<div style="margin-bottom: 20px">
<div mwlDroppable
(drop)="externalDrop($event.dropData.event)"
dragOverClass="drag-over">
<div *ngFor="let event of tasks" mwlDraggable
[dropData]="{event: event}"
[touchStartLongPress]="{ delay: 300, delta: 30 }"
dragActiveClass="drag-active" class="task-card">
<div>
<a href="javascript:;" [style.color]="event.color" style="text-decoration: none; color: black; margin-left: 20px">
{{ event.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<app-taskgroup-overview (taskgroupSelected)="onSelectTaskgroup($event)"></app-taskgroup-overview>
</div>
<mat-checkbox *ngIf="!plannedSchedule" [(ngModel)]="lastSchedule">Automatic starttime based on <b>last</b> schedule</mat-checkbox>
<mat-checkbox *ngIf="!lastSchedule" [(ngModel)]="plannedSchedule">Automatic starttime based on <b>planned</b> schedule</mat-checkbox>
<mat-form-field class="example-full-width" *ngIf="!lastSchedule && !plannedSchedule">
<mat-label>Minutes spent</mat-label>
<input matInput type="number" [formControl]="minutesSpentControl">
</mat-form-field>
</div> </div>
<div mat-dialog-actions align="end">
<button mat-raised-button>Cancel</button>
<button mat-raised-button color="primary" (click)="registerActivity()" [disabled]="true">Register</button>
</div>

View File

@ -1,76 +1,181 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {ForgottenActivityRequest, ScheduleService, TaskService, TaskShortInfo} from "../../../api"; import {
ForgottenActivityRequest,
ScheduleService,
TaskgroupEntityInfo,
TaskOverviewInfo,
TaskService,
TaskShortInfo
} from "../../../api";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar} from "@angular/material/snack-bar";
import {DialogRef} from "@angular/cdk/dialog"; import {DialogRef} from "@angular/cdk/dialog";
import {MatDialogRef} from "@angular/material/dialog"; import {MatDialogRef} from "@angular/material/dialog";
import {filter, map, Observable, startWith} from "rxjs"; import {filter, map, Observable, startWith, Subject} from "rxjs";
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
import {CalendarEvent, CalendarEventAction, CalendarEventTimesChangedEvent} from "angular-calendar";
import * as moment from "moment/moment";
import {EventColor} from "calendar-utils";
import {TaskOverviewData} from "../taskgroup-overview/taskgroup-overview.component";
import {Router} from "@angular/router";
const colors: Record<string, EventColor> = {
red: {
primary: '#ad2121',
secondary: '#FAE3E3',
},
blue: {
primary: '#1e90ff',
secondary: '#D1E8FF',
},
yellow: {
primary: '#e3bc08',
secondary: '#FDF1BA',
},
};
@Component({ @Component({
selector: 'app-forgotten-task-start-dialog', selector: 'app-forgotten-task-start-dialog',
templateUrl: './forgotten-task-start-dialog.component.html', templateUrl: './forgotten-task-start-dialog.component.html',
styleUrls: ['./forgotten-task-start-dialog.component.css'] styleUrls: ['./forgotten-task-start-dialog.component.css']
}) })
export class ForgottenTaskStartDialogComponent implements OnInit{ export class ForgottenTaskStartDialogComponent implements OnInit{
tasks: TaskShortInfo[] = []
myControl: FormControl = new FormControl(''); myControl: FormControl = new FormControl('');
filteredOptions: Observable<string[]> | undefined; filteredOptions: Observable<string[]> | undefined;
viewDate: Date = new Date();
events: CalendarEvent[] = [];
refresh = new Subject<void>();
lastSchedule: boolean = false lastSchedule: boolean = false
plannedSchedule: boolean = false plannedSchedule: boolean = false
minutesSpentControl: FormControl = new FormControl(); minutesSpentControl: FormControl = new FormControl();
tasks: CalendarEvent[] = [];
taskgroupID: number | undefined
constructor(private taskService: TaskService, taskgroupSelected: boolean = false;
private snackbar: MatSnackBar,
private dialogRef: MatDialogRef<ForgottenTaskStartDialogComponent>, actions: CalendarEventAction[] = [
{
label: '<i class="fas fa-fw fa-trash-alt"></i>',
a11yLabel: 'Delete',
onClick: ({ event }: { event: CalendarEvent }): void => {
this.events = this.events.filter((iEvent) => iEvent !== event);
this.eventClicked('Deleted', event);
},
},
];
constructor(private router: Router,
private scheduleService: ScheduleService) { private scheduleService: ScheduleService) {
} }
ngOnInit() { ngOnInit() {
this.taskService.tasksAllScopeDetailedGet("UNFINISHED", false).subscribe({ this.scheduleService.schedulesDateStartableGet(moment().format("yyyy-MM-DD"), false).subscribe({
next: resp => { next: resp => {
this.tasks = resp; resp.forEach(schedule => {
this.filteredOptions = this.myControl.valueChanges.pipe( this.events.push({
startWith(''), title: this.computeTaskPath(schedule.taskgroupPath, schedule.task),
map(value => this._filter(value || '')), color: colors['red'],
); start: new Date(schedule.startTime),
end: new Date(schedule.finishedTime!),
draggable: false,
resizable: {
beforeStart: false,
afterEnd: false
}, },
error: err => { meta: {
this.snackbar.open("Unexpected error", "", {duration: 2000}); taskID: schedule.task.taskID,
scheduleID: schedule.scheduleID,
taskgroupID: schedule.taskgroupPath[schedule.taskgroupPath.length-1].taskgroupID
},
})
})
this.refresh.next()
} }
}) })
} }
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.tasks.map(x => x.taskName).filter(option => option.toLowerCase().includes(filterValue)) externalDrop(event: CalendarEvent) {
if (this.events.indexOf(event) === -1) {
this.events = this.events.filter((iEvent) => iEvent !== event);
this.events.push(event);
}
} }
registerActivity() { eventDropped({
const task = this.tasks.find(task => task.taskName === this.myControl.value); event,
if(task != undefined) { newStart,
/*this.scheduleService.schedulesTaskIDForgottenPost(task.taskID, { newEnd,
mode: this.determineRegisterMode(), allDay,
minutesSpent: this.minutesSpentControl.value }: CalendarEventTimesChangedEvent): void {
const externalIndex = this.tasks.indexOf(event);
if (typeof allDay !== 'undefined') {
event.allDay = allDay;
}
if (externalIndex > -1) {
this.tasks.splice(externalIndex, 1);
this.events.push(event);
this.taskgroupSelected = true;
}
event.start = newStart;
if (newEnd) {
event.end = newEnd;
}
this.events = [...this.events];
}
onSelectTaskgroup(taskgroup: TaskOverviewData) {
this.tasks = [];
taskgroup.tasks.forEach(task => {
this.tasks.push({
title: task.taskName,
color: colors['yellow'],
start: new Date(),
draggable: true,
cssClass: 'test',
resizable: {
beforeStart: true,
afterEnd: true
},
meta: {
taskID: task.taskID,
scheduleID: undefined,
taskgroupID: taskgroup.taskgroupID
},
actions: this.actions
})
})
this.taskgroupID = taskgroup.taskgroupID;
}
private computeTaskPath(taskgroupPath: Array<TaskgroupEntityInfo>, task: TaskShortInfo) {
let result = "";
taskgroupPath.forEach(taskgroupPathPart => {
result += taskgroupPathPart.taskgroupName + "/"
});
result += task!.taskName
return result;
}
eventClicked(click: string, event: CalendarEvent) {
if(click == 'Deleted') {
this.events = this.events.filter(se => se !== event);
this.taskgroupSelected = false;
}
}
register() {
this.scheduleService.schedulesTaskIDForgottenPost(this.events[0].meta.taskID, {
startTime: moment(this.events[0].start).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
endTime: moment(this.events[0].end).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
}).subscribe({ }).subscribe({
next: resp => { next: resp => {
this.dialogRef.close(resp); this.router.navigateByUrl("/");
},
error: err => {
if(err.status == 400) {
this.snackbar.open("Invalid Operation", "", {duration: 2000});
} else if(err.status == 403) {
this.snackbar.open("No permission", "", {duration: 2000});
} else if(err.status == 404) {
this.snackbar.open("Task not found", "", {duration: 2000});
} else {
this.snackbar.open("Unexpected error", "", {duration: 2000});
}
}
})*/
} }
})
} }
} }