issue-31 #33
@ -7,6 +7,7 @@
|
||||
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Load worked minutes when reloading dashboard">
|
||||
<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/services/TaskScheduleService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/services/TaskScheduleService.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../frontend/src/app/dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@ -16,16 +17,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 +260,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,14 +273,19 @@
|
||||
</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>
|
||||
<line-breakpoint enabled="true" type="java-line">
|
||||
<url>file://$PROJECT_DIR$/src/main/java/core/services/TaskScheduleService.java</url>
|
||||
<line>202</line>
|
||||
<option name="timeStamp" value="10" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
</component>
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package core.api.models.timemanager.taskSchedule;
|
||||
|
||||
public enum ForgottenActivityMode {
|
||||
|
||||
MANUAL,
|
||||
LAST,
|
||||
PLANNED
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -146,4 +146,8 @@ public class Task {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void increaseActiveTime(int minutesSpent) {
|
||||
this.workTime += minutesSpent;
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ public class Taskgroup {
|
||||
ancestors.add(currentTaskgroup.parent);
|
||||
currentTaskgroup = currentTaskgroup.parent;
|
||||
}
|
||||
//ancestors.add(taskgroup);
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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,59 @@ 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);
|
||||
return new ServiceResult<>(minutesSpent);
|
||||
}
|
||||
}
|
||||
case PLANNED -> {
|
||||
//Does not make sense until advanced schedule/moderate schedule is implemented
|
||||
return new ServiceResult<>(0);
|
||||
}
|
||||
default -> {
|
||||
throw new RuntimeException("INVALID MODE");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -98,4 +98,8 @@ public class TaskService {
|
||||
taskScheduleService.deleteBasicSchedule(deletedTaskSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Task> loadAllTasks(String username, boolean finished) {
|
||||
return taskRepository.findAllByUser(username, finished);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
33
frontend/src/api/model/forgottenActivityRequest.ts
Normal file
33
frontend/src/api/model/forgottenActivityRequest.ts
Normal 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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
.example-full-width {
|
||||
width: 100%;
|
||||
margin-left: 10px;
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -0,0 +1,87 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ForgottenActivityRequest, 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";
|
||||
import ModeEnum = ForgottenActivityRequest.ModeEnum;
|
||||
|
||||
@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: 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});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private determineRegisterMode(): ModeEnum {
|
||||
if(this.lastSchedule) {
|
||||
return "LAST";
|
||||
} else if(this.plannedSchedule) {
|
||||
return "PLANNED";
|
||||
} else {
|
||||
return "MANUAL";
|
||||
}
|
||||
}
|
||||
}
|
93
openapi.yaml
93
openapi.yaml
@ -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
|
Loading…
Reference in New Issue
Block a user