issue-44 #75
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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({
|
||||||
|
@ -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">
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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>
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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!),
|
||||||
error: err => {
|
draggable: false,
|
||||||
this.snackbar.open("Unexpected error", "", {duration: 2000});
|
resizable: {
|
||||||
|
beforeStart: false,
|
||||||
|
afterEnd: false
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
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);
|
||||||
registerActivity() {
|
this.events.push(event);
|
||||||
const task = this.tasks.find(task => task.taskName === this.myControl.value);
|
|
||||||
if(task != undefined) {
|
|
||||||
/*this.scheduleService.schedulesTaskIDForgottenPost(task.taskID, {
|
|
||||||
mode: this.determineRegisterMode(),
|
|
||||||
minutesSpent: this.minutesSpentControl.value
|
|
||||||
}).subscribe({
|
|
||||||
next: resp => {
|
|
||||||
this.dialogRef.close(resp);
|
|
||||||
},
|
|
||||||
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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventDropped({
|
||||||
|
event,
|
||||||
|
newStart,
|
||||||
|
newEnd,
|
||||||
|
allDay,
|
||||||
|
}: 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({
|
||||||
|
next: resp => {
|
||||||
|
this.router.navigateByUrl("/");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user