Register Forgotten Activities

This commit is contained in:
Sebastian 2023-10-29 09:47:57 +01:00
parent 836247ff73
commit 2324976bd6
27 changed files with 629 additions and 49 deletions

View File

@ -5,8 +5,23 @@
</component>
<component name="ChangeListManager">
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Load worked minutes when reloading dashboard">
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskSchedule/ForgottenActivity.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskSchedule/ForgottenActivityMode.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/controller/TaskController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/controller/TaskController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/tasks/TaskShortInfo.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/tasks/TaskShortInfo.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/entities/timemanager/Task.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/entities/timemanager/Task.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/entities/timemanager/Taskgroup.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/entities/timemanager/Taskgroup.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/BasicTaskScheduleRepository.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/BasicTaskScheduleRepository.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/TaskRepository.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/TaskRepository.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/services/TaskScheduleService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/services/TaskScheduleService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/services/TaskService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/services/TaskService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/api/task.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/api/task.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/app.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/app.module.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/dashboard/active-schedule/active-schedule.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/dashboard/active-schedule/active-schedule.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/dashboard/active-schedule/active-schedule.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/dashboard/active-schedule/active-schedule.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../openapi.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../openapi.yaml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -16,16 +31,16 @@
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Enum" />
<option value="Interface" />
<option value="Class" />
<option value="Enum" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$/.." value="issue-23" />
<entry key="$PROJECT_DIR$/.." value="issue-29" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
@ -259,7 +274,8 @@
<MESSAGE value="Remove update spamming in console" />
<MESSAGE value="Start task now from Taskoverview in Dashboard" />
<MESSAGE value="Load worked minutes when reloading dashboard" />
<option name="LAST_COMMIT_MESSAGE" value="Load worked minutes when reloading dashboard" />
<MESSAGE value="Check if there is another active schedule when starting task now" />
<option name="LAST_COMMIT_MESSAGE" value="Check if there is another active schedule when starting task now" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
@ -271,12 +287,12 @@
</line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/services/TaskScheduleService.java</url>
<line>86</line>
<line>87</line>
<option name="timeStamp" value="5" />
</line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/services/TaskScheduleService.java</url>
<line>93</line>
<line>94</line>
<option name="timeStamp" value="6" />
</line-breakpoint>
</breakpoints>

View File

@ -201,4 +201,23 @@ public class ScheduleController {
int workedMinutes = taskScheduleService.getWorkedMinutes(user.get());
return ResponseEntity.ok(new ScheduleStatus(workedMinutes, taskScheduleService.isAnyScheduleMissed(user.get())));
}
@PostMapping("/schedules/{taskID}/forgotten")
public ResponseEntity<?> registerForgottenActivity(@PathVariable long taskID, @Valid @RequestBody ForgottenActivity forgottenActivity) {
PermissionResult<Task> permissionResult = taskService.getTaskPermissions(taskID, SecurityContextHolder.getContext().getAuthentication().getName());
if(!permissionResult.isHasPermissions()) {
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
}
if(permissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
}
ServiceResult<Integer> serviceResult = taskScheduleService.registerForgottenActivity(permissionResult.getResult(), forgottenActivity);
if(serviceResult.getExitCode() == ServiceExitCode.INVALID_OPERATION) {
return ResponseEntity.status(400).body(new SimpleStatusResponse("failed"));
} else {
return ResponseEntity.ok(new TaskScheduleStopResponse(serviceResult.getResult()));
}
}
}

View File

@ -4,6 +4,7 @@ import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.taskSchedule.ScheduleInfo;
import core.api.models.timemanager.tasks.TaskEntityInfo;
import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.api.models.timemanager.tasks.TaskShortInfo;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import core.services.*;
@ -13,6 +14,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@ -27,6 +31,24 @@ public class TaskController {
this.taskgroupService = taskgroupService;
}
@GetMapping("/tasks/all/{finished}")
public ResponseEntity<List<TaskShortInfo>> loadAllTasks(@PathVariable boolean finished) {
List<Task> taskList = taskService.loadAllTasks(SecurityContextHolder.getContext().getAuthentication().getName(), finished);
List<TaskShortInfo> taskShortInfos = new ArrayList<>();
for(Task task : taskList) {
List<Taskgroup> ancestors = Taskgroup.getAncestorList(task.getTaskgroup());
ancestors.add(task.getTaskgroup());
StringBuilder builder = new StringBuilder();
for(Taskgroup taskgroup : ancestors) {
builder.append(taskgroup.getTaskgroupName()).append("/");
}
builder.append(task.getTaskName());
taskShortInfos.add(new TaskShortInfo(task.getTaskID(), builder.toString()));
}
return ResponseEntity.ok(taskShortInfos);
}
@GetMapping("/tasks/{taskgroupID}/{status}")
public ResponseEntity<?> listTasksOfTaskgroup(@PathVariable long taskgroupID, @PathVariable String status) {
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());

View File

@ -0,0 +1,22 @@
package core.api.models.timemanager.taskSchedule;
public class ForgottenActivity {
private ForgottenActivityMode mode;
private int minutesSpent;
public ForgottenActivityMode getMode() {
return mode;
}
public void setMode(ForgottenActivityMode mode) {
this.mode = mode;
}
public int getMinutesSpent() {
return minutesSpent;
}
public void setMinutesSpent(int minutesSpent) {
this.minutesSpent = minutesSpent;
}
}

View File

@ -0,0 +1,8 @@
package core.api.models.timemanager.taskSchedule;
public enum ForgottenActivityMode {
MANUAL,
LAST,
PLANNED
}

View File

@ -12,6 +12,11 @@ public class TaskShortInfo {
this.taskName = task.getTaskName();
}
public TaskShortInfo(long taskID, String taskName) {
this.taskID = taskID;
this.taskName = taskName;
}
public long getTaskID() {
return taskID;
}

View File

@ -146,4 +146,8 @@ public class Task {
}
return false;
}
public void increaseActiveTime(int minutesSpent) {
this.workTime += minutesSpent;
}
}

View File

@ -95,6 +95,7 @@ public class Taskgroup {
ancestors.add(currentTaskgroup.parent);
currentTaskgroup = currentTaskgroup.parent;
}
//ancestors.add(taskgroup);
return ancestors;
}

View File

@ -36,4 +36,7 @@ public interface BasicTaskScheduleRepository extends CrudRepository<BasicTaskSch
Optional<BasicTaskSchedule> findActiveTaskSchedule(String username);
List<BasicTaskSchedule> findAllByStartTimeIsNull();
@Query(value = "SELECT bts FROM BasicTaskSchedule bts WHERE bts.task.taskgroup.user = ?1 AND bts.scheduleDate = ?2 AND bts.finishedTime IS NOT NULL")
List<BasicTaskSchedule> findAllFinishedByUserAndDate(User user, LocalDate now);
}

View File

@ -8,10 +8,14 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.util.List;
@Repository
public interface TaskRepository extends CrudRepository<Task, Long> {
@Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND t.finished = ?2")
List<Task> findAllByUser(String username, boolean finished);
@Transactional
@Modifying
@Query(value = "DELETE FROM Task t WHERE t.taskgroup = ?1")

View File

@ -1,6 +1,7 @@
package core.services;
import core.api.models.timemanager.taskSchedule.BasicTaskScheduleFieldInfo;
import core.api.models.timemanager.taskSchedule.ForgottenActivity;
import core.entities.User;
import core.entities.timemanager.BasicTaskSchedule;
import core.entities.timemanager.Task;
@ -157,4 +158,56 @@ public class TaskScheduleService {
}
return false;
}
public ServiceResult<Integer> registerForgottenActivity(Task task, ForgottenActivity forgottenActivity) {
switch (forgottenActivity.getMode()) {
case MANUAL -> {
task.increaseActiveTime(forgottenActivity.getMinutesSpent());
BasicTaskSchedule basicTaskSchedule = new BasicTaskSchedule(task, LocalDate.now());
LocalDateTime timeReference = LocalDateTime.now();
basicTaskSchedule.setStartTime(timeReference.minusMinutes(forgottenActivity.getMinutesSpent()));
basicTaskSchedule.setFinishedTime(timeReference);
task.getBasicTaskSchedules().add(basicTaskSchedule);
basicTaskScheduleRepository.save(basicTaskSchedule);
taskRepository.save(task);
return new ServiceResult<>(forgottenActivity.getMinutesSpent());
}
case LAST -> {
List<BasicTaskSchedule> schedules = basicTaskScheduleRepository.findAllFinishedByUserAndDate(task.getTaskgroup().getUser(), LocalDate.now());
if(schedules.isEmpty()) {
return new ServiceResult<>(ServiceExitCode.INVALID_OPERATION);
} else {
LocalDateTime timeReference = LocalDateTime.now();
BasicTaskSchedule nearestSchedule = null;
long nearestDuration = Long.MAX_VALUE;
for(BasicTaskSchedule schedule : schedules) {
if(nearestSchedule == null) {
nearestSchedule = schedule;
nearestDuration = Duration.between(nearestSchedule.getFinishedTime(), timeReference).getSeconds();
} else {
long currentDuration = Duration.between(schedule.getFinishedTime(), timeReference).getSeconds();
if(currentDuration < nearestDuration) {
nearestSchedule = schedule;
nearestDuration = currentDuration;
}
}
}
int minutesSpent = (int) Duration.between(nearestSchedule.getFinishedTime(), timeReference).toMinutes();
task.increaseActiveTime(minutesSpent);
BasicTaskSchedule basicTaskSchedule = new BasicTaskSchedule(task, timeReference.toLocalDate());
basicTaskSchedule.setStartTime(nearestSchedule.getFinishedTime());
basicTaskSchedule.setFinishedTime(timeReference);
basicTaskScheduleRepository.save(basicTaskSchedule);
taskRepository.save(task);
new ServiceResult<>(minutesSpent);
}
}
case PLANNED -> {
//Does not make sense until advanced schedule/moderate schedule is implemented
return new ServiceResult<>(0);
}
}
throw new RuntimeException("INVALID MODE");
}
}

View File

@ -98,4 +98,8 @@ public class TaskService {
taskScheduleService.deleteBasicSchedule(deletedTaskSchedule);
}
}
public List<Task> loadAllTasks(String username, boolean finished) {
return taskRepository.findAllByUser(username, finished);
}
}

View File

@ -17,6 +17,7 @@ model/accountDeleteRequest.ts
model/basicScheduleEntityInfo.ts
model/basicScheduleFieldInfo.ts
model/eMailChangeRequest.ts
model/forgottenActivityRequest.ts
model/inlineResponse200.ts
model/inlineResponse401.ts
model/inlineResponse403.ts

View File

@ -20,6 +20,7 @@ import { Observable } from 'rxjs';
import { BasicScheduleEntityInfo } from '../model/models';
import { BasicScheduleFieldInfo } from '../model/models';
import { ForgottenActivityRequest } from '../model/models';
import { ScheduleActivateInfo } from '../model/models';
import { ScheduleInfo } from '../model/models';
import { ScheduleStatus } from '../model/models';
@ -581,6 +582,76 @@ export class ScheduleService {
);
}
/**
* registers forgotten schedule
* Registers forgotten schedule
* @param taskID internal id of task
* @param forgottenActivityRequest
* @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 schedulesTaskIDForgottenPost(taskID: number, forgottenActivityRequest?: ForgottenActivityRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<TaskScheduleStopResponse>;
public schedulesTaskIDForgottenPost(taskID: number, forgottenActivityRequest?: ForgottenActivityRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<TaskScheduleStopResponse>>;
public schedulesTaskIDForgottenPost(taskID: number, forgottenActivityRequest?: ForgottenActivityRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<TaskScheduleStopResponse>>;
public schedulesTaskIDForgottenPost(taskID: number, forgottenActivityRequest?: ForgottenActivityRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (taskID === null || taskID === undefined) {
throw new Error('Required parameter taskID was null or undefined when calling schedulesTaskIDForgottenPost.');
}
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();
}
// to determine the Content-Type header
const consumes: string[] = [
'application/json'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
}
let responseType_: 'text' | 'json' = 'json';
if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
}
return this.httpClient.post<TaskScheduleStopResponse>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(taskID))}/forgotten`,
forgottenActivityRequest,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* schedule task now
* schedule task now

View File

@ -22,6 +22,7 @@ import { InlineResponse403 } from '../model/models';
import { SimpleStatusResponse } from '../model/models';
import { TaskEntityInfo } from '../model/models';
import { TaskFieldInfo } from '../model/models';
import { TaskShortInfo } from '../model/models';
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration';
@ -88,6 +89,65 @@ export class TaskService {
return httpParams;
}
/**
* edits an existing task
* edits an existing task
* @param finished fetches either finished or unfinished tasks
* @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 tasksAllFinishedGet(finished: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskShortInfo>>;
public tasksAllFinishedGet(finished: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskShortInfo>>>;
public tasksAllFinishedGet(finished: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskShortInfo>>>;
public tasksAllFinishedGet(finished: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (finished === null || finished === undefined) {
throw new Error('Required parameter finished was null or undefined when calling tasksAllFinishedGet.');
}
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<TaskShortInfo>>(`${this.configuration.basePath}/tasks/all/${encodeURIComponent(String(finished))}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* edits an existing task
* edits an existing task

View File

@ -0,0 +1,33 @@
/**
* 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.
*/
export interface ForgottenActivityRequest {
/**
* mode of register forgotten activity
*/
mode: ForgottenActivityRequest.ModeEnum;
/**
* number of minutes spent on task
*/
minutesSpent?: number;
}
export namespace ForgottenActivityRequest {
export type ModeEnum = 'MANUAL' | 'LAST' | 'PLANNED';
export const ModeEnum = {
Manual: 'MANUAL' as ModeEnum,
Last: 'LAST' as ModeEnum,
Planned: 'PLANNED' as ModeEnum
};
}

View File

@ -2,6 +2,7 @@ export * from './accountDeleteRequest';
export * from './basicScheduleEntityInfo';
export * from './basicScheduleFieldInfo';
export * from './eMailChangeRequest';
export * from './forgottenActivityRequest';
export * from './inlineResponse200';
export * from './inlineResponse401';
export * from './inlineResponse403';

View File

@ -67,6 +67,8 @@ import { ActiveScheduleComponent } from './dashboard/active-schedule/active-sche
import {MatTreeModule} from "@angular/material/tree";
import { TaskgroupOverviewComponent } from './dashboard/taskgroup-overview/taskgroup-overview.component';
import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.component';
import { ForgottenTaskStartDialogComponent } from './dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component';
import {MatAutocompleteModule} from "@angular/material/autocomplete";
@NgModule({
declarations: [
AppComponent,
@ -97,7 +99,8 @@ import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.c
DashboardComponent,
ActiveScheduleComponent,
TaskgroupOverviewComponent,
TaskOverviewComponent
TaskOverviewComponent,
ForgottenTaskStartDialogComponent
],
imports: [
BrowserModule,
@ -133,7 +136,8 @@ import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.c
MatExpansionModule,
CalendarModule.forRoot({provide: DateAdapter, useFactory: adapterFactory}),
MatSelectModule,
MatTreeModule
MatTreeModule,
MatAutocompleteModule
],
providers: [
HttpClientModule,

View File

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

View File

@ -1,7 +1,9 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ScheduleInfo, ScheduleService, TaskOverviewInfo} from "../../../api";
import {ScheduleInfo, ScheduleService, TaskOverviewInfo, TaskScheduleStopResponse} from "../../../api";
import {StopActiveScheduleInfo} from "./StopActiveScheduleInfo";
import {TaskOverviewComponent} from "../task-overview/task-overview.component";
import {MatDialog} from "@angular/material/dialog";
import {ForgottenTaskStartDialogComponent} from "../forgotten-task-start-dialog/forgotten-task-start-dialog.component";
@Component({
selector: 'app-active-schedule',
@ -16,8 +18,10 @@ export class ActiveScheduleComponent implements OnInit{
displayTime: string = "00:00:00"
@Output('onStopTask') scheduleStopEmitter = new EventEmitter<StopActiveScheduleInfo>;
@Output('registerForgotten') registerForgottenEmitter = new EventEmitter<TaskScheduleStopResponse>
constructor(private scheduleService: ScheduleService) {
constructor(private scheduleService: ScheduleService,
private dialog: MatDialog) {
}
@ -74,4 +78,12 @@ export class ActiveScheduleComponent implements OnInit{
this.activeSchedule = undefined
}
openForgettedActivityDialog() {
const dialogRef = this.dialog.open(ForgottenTaskStartDialogComponent, {width: "400px"})
dialogRef.afterClosed().subscribe(res => {
if(res != undefined) {
this.registerForgottenEmitter.emit(res);
}
})
}
}

View File

@ -8,7 +8,7 @@
</mat-card>
<h1 class="dashboard-heading">Now<b class="today-worked-info">Today: {{workedMinutesToday}} min</b></h1>
<app-active-schedule #activeSchedule (onStopTask)="stopedTask($event)"></app-active-schedule>
<app-active-schedule #activeSchedule (onStopTask)="stopedTask($event)" (registerForgotten)="registerForgotten($event)"></app-active-schedule>
<h1 class="dashboard-heading">Scheduled for today</h1>
<mat-card class="green-card" *ngIf="schedules.length == 0">

View File

@ -1,5 +1,11 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {BasicScheduleEntityInfo, ScheduleInfo, ScheduleService, TaskOverviewInfo} from "../../api";
import {
BasicScheduleEntityInfo,
ScheduleInfo,
ScheduleService,
TaskOverviewInfo,
TaskScheduleStopResponse
} from "../../api";
import {ActiveScheduleComponent} from "./active-schedule/active-schedule.component";
import {StopActiveScheduleInfo} from "./active-schedule/StopActiveScheduleInfo";
import {TaskOverviewComponent} from "./task-overview/task-overview.component";
@ -72,4 +78,8 @@ export class DashboardComponent implements OnInit{
this.schedules = this.schedules.filter(schedule => schedule.task.taskID !== task.taskID)
}
registerForgotten(taskScheduleStopResponse: TaskScheduleStopResponse) {
this.workedMinutesToday += taskScheduleStopResponse.workTime
}
}

View File

@ -0,0 +1,4 @@
.example-full-width {
width: 100%;
margin-left: 10px;
}

View File

@ -0,0 +1,31 @@
<h1 mat-dialog-title>Register forgotten activity</h1>
<div mat-dialog-content>
<mat-form-field class="example-full-width">
<mat-label>Number</mat-label>
<input type="text"
placeholder="Pick one"
matInput
[formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<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 mat-dialog-actions align="end">
<button mat-raised-button>Cancel</button>
<button mat-raised-button color="primary" (click)="registerActivity()">Register</button>
</div>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ForgottenTaskStartDialogComponent } from './forgotten-task-start-dialog.component';
describe('ForgottenTaskStartDialogComponent', () => {
let component: ForgottenTaskStartDialogComponent;
let fixture: ComponentFixture<ForgottenTaskStartDialogComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ForgottenTaskStartDialogComponent]
});
fixture = TestBed.createComponent(ForgottenTaskStartDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,80 @@
import {Component, OnInit} from '@angular/core';
import {ScheduleService, TaskService, TaskShortInfo} from "../../../api";
import {MatSnackBar} from "@angular/material/snack-bar";
import {DialogRef} from "@angular/cdk/dialog";
import {MatDialogRef} from "@angular/material/dialog";
import {filter, map, Observable, startWith} from "rxjs";
import {FormControl} from "@angular/forms";
@Component({
selector: 'app-forgotten-task-start-dialog',
templateUrl: './forgotten-task-start-dialog.component.html',
styleUrls: ['./forgotten-task-start-dialog.component.css']
})
export class ForgottenTaskStartDialogComponent implements OnInit{
tasks: TaskShortInfo[] = []
myControl: FormControl = new FormControl('');
filteredOptions: Observable<string[]> | undefined;
lastSchedule: boolean = false
plannedSchedule: boolean = false
minutesSpentControl: FormControl = new FormControl();
constructor(private taskService: TaskService,
private snackbar: MatSnackBar,
private dialogRef: MatDialogRef<ForgottenTaskStartDialogComponent>,
private scheduleService: ScheduleService) {
}
ngOnInit() {
this.taskService.tasksAllFinishedGet(false).subscribe({
next: resp => {
this.tasks = resp;
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
);
},
error: err => {
this.snackbar.open("Unexpected error", "", {duration: 2000});
}
})
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.tasks.map(x => x.taskName).filter(option => option.toLowerCase().includes(filterValue))
}
registerActivity() {
const task = this.tasks.find(task => task.taskName === this.myControl.value);
if(task != undefined) {
this.scheduleService.schedulesTaskIDForgottenPost(task.taskID, {
mode: "MANUAL",
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});
}
}
})
}
}
private determineRegisterMode(): string {
return "MANUAL";
}
}

View File

@ -883,6 +883,32 @@ paths:
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
/tasks/all/{finished}:
get:
security:
- API_TOKEN: []
tags:
- task
summary: edits an existing task
description: edits an existing task
parameters:
- name: finished
in: path
description: fetches either finished or unfinished tasks
required: true
schema:
type: boolean
example: true
responses:
200:
description: Operation successfull
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskShortInfo'
/tasks/{taskgroupID}/{status}:
get:
security:
@ -1619,7 +1645,55 @@ paths:
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
/schedules/{taskID}/forgotten:
post:
security:
- API_TOKEN: []
tags:
- schedule
description: Registers forgotten schedule
summary: registers forgotten schedule
parameters:
- name: taskID
in: path
description: internal id of task
required: true
schema:
type: number
example: 1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ForgottenActivityRequest'
responses:
200:
description: Operation successfull
content:
application/json:
schema:
$ref: '#/components/schemas/TaskScheduleStopResponse'
403:
description: No permission
content:
'application/json':
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
404:
description: Schedule does not exist
content:
'application/json':
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
400:
description: Operation not valid
content:
'application/json':
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
@ -2141,3 +2215,20 @@ components:
type: boolean
description: states whether a schedule was missed or not
example: true
ForgottenActivityRequest:
required:
- mode
additionalProperties: false
properties:
mode:
type: string
description: mode of register forgotten activity
example: MANUAL
enum:
- MANUAL
- LAST
- PLANNED
minutesSpent:
type: number
description: number of minutes spent on task
example: 10