Merge pull request 'issue-25' (#27) from issue-25 into issue-18
Reviewed-on: Sebastian/TimeManager#27
This commit is contained in:
commit
79d280b3c0
@ -4,11 +4,13 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Removed unused TaskgroupShortInfo.java">
|
<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/ScheduleStatus.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" 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/models/timemanager/taskSchedule/ActiveMinutesInformation.java" beforeDir="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/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/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/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" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -60,7 +62,9 @@
|
|||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "swagger",
|
"settings.editor.selected.configurable": "swagger",
|
||||||
|
"ts.external.directory.path": "/snap/intellij-idea-ultimate/459/plugins/javascript-impl/jsLanguageServicesImpl/external",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
@ -99,7 +103,11 @@
|
|||||||
<workItem from="1698067098771" duration="4770000" />
|
<workItem from="1698067098771" duration="4770000" />
|
||||||
<workItem from="1698127431684" duration="2039000" />
|
<workItem from="1698127431684" duration="2039000" />
|
||||||
<workItem from="1698164397550" duration="2329000" />
|
<workItem from="1698164397550" duration="2329000" />
|
||||||
<workItem from="1698246651541" duration="766000" />
|
<workItem from="1698246651541" duration="5106000" />
|
||||||
|
<workItem from="1698298897364" duration="4242000" />
|
||||||
|
<workItem from="1698426430946" duration="665000" />
|
||||||
|
<workItem from="1698474363766" duration="3686000" />
|
||||||
|
<workItem from="1698499063683" duration="7685000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Structure Taskgroups in Hierarchies">
|
<task id="LOCAL-00001" summary="Structure Taskgroups in Hierarchies">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@ -165,7 +173,71 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1698168406655</updated>
|
<updated>1698168406655</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="9" />
|
<task id="LOCAL-00009" summary="Fix Foreign Key Constraint Fail when deleting Taskgroups">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698247531000</created>
|
||||||
|
<option name="number" value="00009" />
|
||||||
|
<option name="presentableId" value="LOCAL-00009" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698247531000</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00010" summary="Stop and Finish TaskSchedules">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698306038382</created>
|
||||||
|
<option name="number" value="00010" />
|
||||||
|
<option name="presentableId" value="LOCAL-00010" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698306038383</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00011" summary="Include TaskScheduleStopResponse">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698306056254</created>
|
||||||
|
<option name="number" value="00011" />
|
||||||
|
<option name="presentableId" value="LOCAL-00011" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698306056254</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00012" summary="Fix starting schedule and returning active time of schedule after stop">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698306442833</created>
|
||||||
|
<option name="number" value="00012" />
|
||||||
|
<option name="presentableId" value="LOCAL-00012" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698306442833</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00013" summary="Remove update spamming in console">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698306889808</created>
|
||||||
|
<option name="number" value="00013" />
|
||||||
|
<option name="presentableId" value="LOCAL-00013" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698306889808</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00014" summary="Start task now from Taskoverview in Dashboard">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698500028161</created>
|
||||||
|
<option name="number" value="00014" />
|
||||||
|
<option name="presentableId" value="LOCAL-00014" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698500028161</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00015" summary="Load worked minutes when reloading dashboard">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698512058945</created>
|
||||||
|
<option name="number" value="00015" />
|
||||||
|
<option name="presentableId" value="LOCAL-00015" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698512058945</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00016" summary="Load worked minutes when reloading dashboard">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1698512069065</created>
|
||||||
|
<option name="number" value="00016" />
|
||||||
|
<option name="presentableId" value="LOCAL-00016" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1698512069065</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="17" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@ -182,7 +254,14 @@
|
|||||||
<MESSAGE value="Delete Schedules" />
|
<MESSAGE value="Delete Schedules" />
|
||||||
<MESSAGE value="Deliver Schedule Path Info when fetching Schedules" />
|
<MESSAGE value="Deliver Schedule Path Info when fetching Schedules" />
|
||||||
<MESSAGE value="Removed unused TaskgroupShortInfo.java" />
|
<MESSAGE value="Removed unused TaskgroupShortInfo.java" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Removed unused TaskgroupShortInfo.java" />
|
<MESSAGE value="Fix Foreign Key Constraint Fail when deleting Taskgroups" />
|
||||||
|
<MESSAGE value="Stop and Finish TaskSchedules" />
|
||||||
|
<MESSAGE value="Include TaskScheduleStopResponse" />
|
||||||
|
<MESSAGE value="Fix starting schedule and returning active time of schedule after stop" />
|
||||||
|
<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" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
@ -192,11 +271,6 @@
|
|||||||
<line>52</line>
|
<line>52</line>
|
||||||
<option name="timeStamp" value="1" />
|
<option name="timeStamp" value="1" />
|
||||||
</line-breakpoint>
|
</line-breakpoint>
|
||||||
<line-breakpoint enabled="true" type="java-line">
|
|
||||||
<url>file://$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java</url>
|
|
||||||
<line>41</line>
|
|
||||||
<option name="timeStamp" value="2" />
|
|
||||||
</line-breakpoint>
|
|
||||||
</breakpoints>
|
</breakpoints>
|
||||||
</breakpoint-manager>
|
</breakpoint-manager>
|
||||||
</component>
|
</component>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package core.api.controller;
|
package core.api.controller;
|
||||||
|
|
||||||
import core.api.models.auth.SimpleStatusResponse;
|
import core.api.models.auth.SimpleStatusResponse;
|
||||||
import core.api.models.timemanager.taskSchedule.BasicTaskScheduleEntityInfo;
|
import core.api.models.timemanager.taskSchedule.*;
|
||||||
import core.api.models.timemanager.taskSchedule.BasicTaskScheduleFieldInfo;
|
import core.entities.User;
|
||||||
import core.api.models.timemanager.taskSchedule.ScheduleInfo;
|
|
||||||
import core.entities.timemanager.BasicTaskSchedule;
|
import core.entities.timemanager.BasicTaskSchedule;
|
||||||
import core.entities.timemanager.ScheduleType;
|
import core.entities.timemanager.ScheduleType;
|
||||||
import core.entities.timemanager.Task;
|
import core.entities.timemanager.Task;
|
||||||
|
import core.repositories.UserRepository;
|
||||||
import core.repositories.timemanager.BasicTaskScheduleRepository;
|
import core.repositories.timemanager.BasicTaskScheduleRepository;
|
||||||
import core.services.*;
|
import core.services.*;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -15,9 +15,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@CrossOrigin(origins = "*", maxAge = 3600)
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
@RestController
|
@RestController
|
||||||
@ -27,12 +25,15 @@ public class ScheduleController {
|
|||||||
private final TaskService taskService;
|
private final TaskService taskService;
|
||||||
private final TaskScheduleService taskScheduleService;
|
private final TaskScheduleService taskScheduleService;
|
||||||
private final BasicTaskScheduleRepository basicTaskScheduleRepository;
|
private final BasicTaskScheduleRepository basicTaskScheduleRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
public ScheduleController(@Autowired TaskService taskService, @Autowired TaskScheduleService taskScheduleService,
|
public ScheduleController(@Autowired TaskService taskService, @Autowired TaskScheduleService taskScheduleService,
|
||||||
BasicTaskScheduleRepository basicTaskScheduleRepository) {
|
BasicTaskScheduleRepository basicTaskScheduleRepository,
|
||||||
|
UserRepository userRepository) {
|
||||||
this.taskService = taskService;
|
this.taskService = taskService;
|
||||||
this.taskScheduleService = taskScheduleService;
|
this.taskScheduleService = taskScheduleService;
|
||||||
this.basicTaskScheduleRepository = basicTaskScheduleRepository;
|
this.basicTaskScheduleRepository = basicTaskScheduleRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/schedules/{taskID}/{scheduleType}")
|
@GetMapping("/schedules/{taskID}/{scheduleType}")
|
||||||
@ -102,9 +103,9 @@ public class ScheduleController {
|
|||||||
return ResponseEntity.ok(new SimpleStatusResponse("success"));
|
return ResponseEntity.ok(new SimpleStatusResponse("success"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/schedules/today")
|
@GetMapping("/schedules/today/{activateable}")
|
||||||
public ResponseEntity<?> loadTodaysSchedules() {
|
public ResponseEntity<?> loadTodaysSchedules(@PathVariable boolean activateable) {
|
||||||
ServiceResult<List<BasicTaskSchedule>> todaysSchedules = taskScheduleService.loadTodaysSchedule(SecurityContextHolder.getContext().getAuthentication().getName());
|
ServiceResult<List<BasicTaskSchedule>> todaysSchedules = taskScheduleService.loadTodaysSchedule(SecurityContextHolder.getContext().getAuthentication().getName(), activateable);
|
||||||
if(todaysSchedules.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
if(todaysSchedules.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
||||||
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
|
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
|
||||||
} else {
|
} else {
|
||||||
@ -136,7 +137,68 @@ public class ScheduleController {
|
|||||||
if(scheduleResult.getExitCode() == ServiceExitCode.ENTITY_ALREADY_EXIST) {
|
if(scheduleResult.getExitCode() == ServiceExitCode.ENTITY_ALREADY_EXIST) {
|
||||||
return ResponseEntity.status(409).body(new SimpleStatusResponse("failed"));
|
return ResponseEntity.status(409).body(new SimpleStatusResponse("failed"));
|
||||||
} else {
|
} else {
|
||||||
return ResponseEntity.ok(new BasicTaskScheduleEntityInfo(scheduleResult.getResult()));
|
return ResponseEntity.ok(new ScheduleInfo(scheduleResult.getResult()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/schedules/active")
|
||||||
|
public ResponseEntity<?> getActiveSchedule() {
|
||||||
|
ServiceResult<BasicTaskSchedule> activeScheduleResult = taskScheduleService.getActiveSchedule(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
if(activeScheduleResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
||||||
|
return ResponseEntity.ok(new ScheduleInfo());
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.ok(new ScheduleInfo(activeScheduleResult.getResult()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/schedules/{scheduleID}/activate")
|
||||||
|
public ResponseEntity<?> activateSchedule(@PathVariable long scheduleID) {
|
||||||
|
PermissionResult<BasicTaskSchedule> schedulePermissionResult = taskScheduleService.getSchedulePermissions(scheduleID, SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
if(!schedulePermissionResult.isHasPermissions()) {
|
||||||
|
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(schedulePermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
||||||
|
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceResult<BasicTaskSchedule> scheduleActivateResult = taskScheduleService.activateSchedule(schedulePermissionResult.getResult());
|
||||||
|
if(scheduleActivateResult.getExitCode() == ServiceExitCode.ENTITY_ALREADY_EXIST) {
|
||||||
|
return ResponseEntity.status(409).body(new SimpleStatusResponse("failed"));
|
||||||
|
} else if(scheduleActivateResult.getExitCode() == ServiceExitCode.OK){
|
||||||
|
return ResponseEntity.ok(new ScheduleActivateInfo(scheduleActivateResult.getResult().getStartTime()));
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/schedules/{scheduleID}/stop/{finish}")
|
||||||
|
public ResponseEntity<?> stopSchedule(@PathVariable long scheduleID, @PathVariable boolean finish) {
|
||||||
|
PermissionResult<BasicTaskSchedule> schedulePermissionResult = taskScheduleService.getSchedulePermissions(scheduleID, SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
if(!schedulePermissionResult.isHasPermissions()) {
|
||||||
|
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(schedulePermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
||||||
|
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceResult<Integer> stopResult = taskScheduleService.stopSchedule(schedulePermissionResult.getResult(), finish);
|
||||||
|
if(stopResult.getExitCode() == ServiceExitCode.INVALID_OPERATION) {
|
||||||
|
return ResponseEntity.status(400).body(new SimpleStatusResponse("failed"));
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.ok(new TaskScheduleStopResponse(stopResult.getResult()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/schedules/status/today")
|
||||||
|
public ResponseEntity<?> getWorkedMinutesToday() {
|
||||||
|
Optional<User> user = userRepository.findByUsername(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
if(user.isEmpty()) {
|
||||||
|
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int workedMinutes = taskScheduleService.getWorkedMinutes(user.get());
|
||||||
|
return ResponseEntity.ok(new ScheduleStatus(workedMinutes, taskScheduleService.isAnyScheduleMissed(user.get())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package core.api.controller;
|
package core.api.controller;
|
||||||
|
|
||||||
import core.api.models.auth.SimpleStatusResponse;
|
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.TaskEntityInfo;
|
||||||
import core.api.models.timemanager.tasks.TaskFieldInfo;
|
import core.api.models.timemanager.tasks.TaskFieldInfo;
|
||||||
import core.entities.timemanager.Task;
|
import core.entities.timemanager.Task;
|
||||||
@ -110,4 +111,19 @@ public class TaskController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(new TaskEntityInfo(taskPermissionResult.getResult()));
|
return ResponseEntity.ok(new TaskEntityInfo(taskPermissionResult.getResult()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tasks/{taskID}/finish")
|
||||||
|
public ResponseEntity<?> finishTask(@PathVariable long taskID) {
|
||||||
|
PermissionResult<Task> taskPermissionResult = taskService.getTaskPermissions(taskID, SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
if (!taskPermissionResult.isHasPermissions()) {
|
||||||
|
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
||||||
|
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
taskService.finishTask(taskPermissionResult.getResult());
|
||||||
|
return ResponseEntity.ok(new SimpleStatusResponse("success"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package core.api.controller;
|
package core.api.controller;
|
||||||
|
|
||||||
import core.api.models.auth.SimpleStatusResponse;
|
import core.api.models.auth.SimpleStatusResponse;
|
||||||
|
import core.api.models.timemanager.taskgroup.RecursiveTaskgroupInfo;
|
||||||
import core.api.models.timemanager.taskgroup.TaskgroupDetailInfo;
|
import core.api.models.timemanager.taskgroup.TaskgroupDetailInfo;
|
||||||
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
|
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
|
||||||
import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo;
|
import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo;
|
||||||
@ -80,6 +81,13 @@ public class TaskgroupController {
|
|||||||
return ResponseEntity.ok(taskgroupEntityInfos);
|
return ResponseEntity.ok(taskgroupEntityInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/taskgroups/tree")
|
||||||
|
public ResponseEntity<List<RecursiveTaskgroupInfo>> listTaskgroupsOfUserAsTree() {
|
||||||
|
List<Taskgroup> taskgroups = taskgroupService.getTopTaskgroupsByUser(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
List<RecursiveTaskgroupInfo> taskgroupEntityInfos = taskgroups.stream().map(RecursiveTaskgroupInfo::new).toList();
|
||||||
|
return ResponseEntity.ok(taskgroupEntityInfos);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/taskgroups/{taskgroupID}")
|
@GetMapping("/taskgroups/{taskgroupID}")
|
||||||
public ResponseEntity<?> getDetails(@PathVariable long taskgroupID) {
|
public ResponseEntity<?> getDetails(@PathVariable long taskgroupID) {
|
||||||
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());
|
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package core.api.models.timemanager.taskSchedule;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class ScheduleActivateInfo {
|
||||||
|
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
public ScheduleActivateInfo(LocalDateTime startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTime(LocalDateTime startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ public class ScheduleInfo {
|
|||||||
this.finishTime = basicTaskSchedule.getFinishedTime();
|
this.finishTime = basicTaskSchedule.getFinishedTime();
|
||||||
|
|
||||||
if(this.finishTime == null && this.startTime != null) {
|
if(this.finishTime == null && this.startTime != null) {
|
||||||
this.activeMinutes = (int) Duration.between(basicTaskSchedule.getStartTime(), LocalDate.now()).toMinutes();
|
this.activeMinutes = (int) Duration.between(basicTaskSchedule.getStartTime(), LocalDateTime.now()).toMinutes();
|
||||||
} else if(this.startTime != null){
|
} else if(this.startTime != null){
|
||||||
this.activeMinutes = (int) Duration.between(basicTaskSchedule.getStartTime(), basicTaskSchedule.getFinishedTime()).toMinutes();
|
this.activeMinutes = (int) Duration.between(basicTaskSchedule.getStartTime(), basicTaskSchedule.getFinishedTime()).toMinutes();
|
||||||
}
|
}
|
||||||
@ -44,6 +44,10 @@ public class ScheduleInfo {
|
|||||||
this.taskgroupPath = taskgroupPath.stream().map(TaskgroupEntityInfo::new).toList();
|
this.taskgroupPath = taskgroupPath.stream().map(TaskgroupEntityInfo::new).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ScheduleInfo() {
|
||||||
|
this.scheduleID = -1;
|
||||||
|
}
|
||||||
|
|
||||||
public long getScheduleID() {
|
public long getScheduleID() {
|
||||||
return scheduleID;
|
return scheduleID;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package core.api.models.timemanager.taskSchedule;
|
||||||
|
|
||||||
|
public class ScheduleStatus {
|
||||||
|
|
||||||
|
private int activeMinutes;
|
||||||
|
private boolean missedSchedules;
|
||||||
|
|
||||||
|
public ScheduleStatus(int activeMinutes, boolean missedSchedules) {
|
||||||
|
this.activeMinutes = activeMinutes;
|
||||||
|
this.missedSchedules = missedSchedules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getActiveMinutes() {
|
||||||
|
return activeMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveMinutes(int activeMinutes) {
|
||||||
|
this.activeMinutes = activeMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMissedSchedules() {
|
||||||
|
return missedSchedules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMissedSchedules(boolean missedSchedules) {
|
||||||
|
this.missedSchedules = missedSchedules;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package core.api.models.timemanager.taskSchedule;
|
||||||
|
|
||||||
|
public class TaskScheduleStopResponse {
|
||||||
|
|
||||||
|
private int workTime;
|
||||||
|
|
||||||
|
public TaskScheduleStopResponse(int workTime) {
|
||||||
|
this.workTime = workTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWorkTime() {
|
||||||
|
return workTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorkTime(int workTime) {
|
||||||
|
this.workTime = workTime;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package core.api.models.timemanager.taskgroup;
|
||||||
|
|
||||||
|
import core.api.models.timemanager.tasks.TaskOverviewInfo;
|
||||||
|
import core.entities.timemanager.Task;
|
||||||
|
import core.entities.timemanager.Taskgroup;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RecursiveTaskgroupInfo {
|
||||||
|
|
||||||
|
private long taskgroupID;
|
||||||
|
private String taskgroupName;
|
||||||
|
|
||||||
|
private boolean hasOverdueTask;
|
||||||
|
|
||||||
|
private int amountActiveTasks;
|
||||||
|
|
||||||
|
private List<RecursiveTaskgroupInfo> childTaskgroups = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<TaskOverviewInfo> activeTasks = new LinkedList<>();
|
||||||
|
|
||||||
|
public RecursiveTaskgroupInfo(Taskgroup taskgroup) {
|
||||||
|
this.taskgroupID = taskgroup.getTaskgroupID();
|
||||||
|
this.taskgroupName = taskgroup.getTaskgroupName();
|
||||||
|
|
||||||
|
for(Taskgroup child : taskgroup.getChildren()) {
|
||||||
|
this.childTaskgroups.add(new RecursiveTaskgroupInfo(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Task task : taskgroup.getActiveTasks()) {
|
||||||
|
this.activeTasks.add(new TaskOverviewInfo(task));
|
||||||
|
if(task.getDeadline().isBefore(LocalDate.now())) {
|
||||||
|
this.hasOverdueTask = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.amountActiveTasks = taskgroup.getAmountOfActiveTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTaskgroupID() {
|
||||||
|
return taskgroupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTaskgroupID(long taskgroupID) {
|
||||||
|
this.taskgroupID = taskgroupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTaskgroupName() {
|
||||||
|
return taskgroupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTaskgroupName(String taskgroupName) {
|
||||||
|
this.taskgroupName = taskgroupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RecursiveTaskgroupInfo> getChildTaskgroups() {
|
||||||
|
return childTaskgroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildTaskgroups(List<RecursiveTaskgroupInfo> childTaskgroups) {
|
||||||
|
this.childTaskgroups = childTaskgroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TaskOverviewInfo> getActiveTasks() {
|
||||||
|
return activeTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveTasks(List<TaskOverviewInfo> activeTasks) {
|
||||||
|
this.activeTasks = activeTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHasOverdueTask() {
|
||||||
|
return hasOverdueTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasOverdueTask(boolean hasOverdueTask) {
|
||||||
|
this.hasOverdueTask = hasOverdueTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAmountActiveTasks() {
|
||||||
|
return amountActiveTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmountActiveTasks(int amountActiveTasks) {
|
||||||
|
this.amountActiveTasks = amountActiveTasks;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package core.api.models.timemanager.tasks;
|
||||||
|
|
||||||
|
import core.entities.timemanager.Task;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class TaskOverviewInfo {
|
||||||
|
|
||||||
|
private long taskID;
|
||||||
|
private String taskName;
|
||||||
|
private int activeMinutes;
|
||||||
|
private int eta;
|
||||||
|
private LocalDate limit;
|
||||||
|
private boolean overdue;
|
||||||
|
|
||||||
|
public TaskOverviewInfo(Task task) {
|
||||||
|
this.taskID = task.getTaskID();
|
||||||
|
this.taskName = task.getTaskName();
|
||||||
|
this.activeMinutes = task.getWorkTime();
|
||||||
|
this.eta = task.getEta();
|
||||||
|
this.limit = task.getDeadline();
|
||||||
|
this.overdue = LocalDate.now().isAfter(task.getDeadline());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTaskID() {
|
||||||
|
return taskID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTaskID(long taskID) {
|
||||||
|
this.taskID = taskID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTaskName() {
|
||||||
|
return taskName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTaskName(String taskName) {
|
||||||
|
this.taskName = taskName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getActiveMinutes() {
|
||||||
|
return activeMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveMinutes(int activeMinutes) {
|
||||||
|
this.activeMinutes = activeMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEta() {
|
||||||
|
return eta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEta(int eta) {
|
||||||
|
this.eta = eta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLimit(LocalDate limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOverdue() {
|
||||||
|
return overdue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOverdue(boolean overdue) {
|
||||||
|
this.overdue = overdue;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package core.entities.timemanager;
|
|||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "basic_schedules")
|
@Table(name = "basic_schedules")
|
||||||
@ -75,4 +76,21 @@ public class BasicTaskSchedule {
|
|||||||
public void setFinishedTime(LocalDateTime finishedTime) {
|
public void setFinishedTime(LocalDateTime finishedTime) {
|
||||||
this.finishedTime = finishedTime;
|
this.finishedTime = finishedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isActivateAble() {
|
||||||
|
return startTime == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
BasicTaskSchedule that = (BasicTaskSchedule) o;
|
||||||
|
return scheduleID == that.scheduleID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(scheduleID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package core.entities.timemanager;
|
|||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -27,7 +29,7 @@ public class Task {
|
|||||||
private boolean finished;
|
private boolean finished;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "task", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
@OneToMany(mappedBy = "task", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
private Set<BasicTaskSchedule> basicTaskSchedules;
|
private List<BasicTaskSchedule> basicTaskSchedules;
|
||||||
|
|
||||||
private int workTime;
|
private int workTime;
|
||||||
|
|
||||||
@ -128,11 +130,11 @@ public class Task {
|
|||||||
this.taskID = taskID;
|
this.taskID = taskID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<BasicTaskSchedule> getBasicTaskSchedules() {
|
public List<BasicTaskSchedule> getBasicTaskSchedules() {
|
||||||
return basicTaskSchedules;
|
return basicTaskSchedules;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBasicTaskSchedules(Set<BasicTaskSchedule> basicTaskSchedules) {
|
public void setBasicTaskSchedules(List<BasicTaskSchedule> basicTaskSchedules) {
|
||||||
this.basicTaskSchedules = basicTaskSchedules;
|
this.basicTaskSchedules = basicTaskSchedules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,4 +97,22 @@ public class Taskgroup {
|
|||||||
}
|
}
|
||||||
return ancestors;
|
return ancestors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Task> getActiveTasks() {
|
||||||
|
List<Task> tasks = new LinkedList<>();
|
||||||
|
for(Task task : this.tasks) {
|
||||||
|
if(!task.isFinished()) {
|
||||||
|
tasks.add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAmountOfActiveTasks() {
|
||||||
|
int activeAmountTasks = getActiveTasks().size();
|
||||||
|
for(Taskgroup taskgroup : children) {
|
||||||
|
activeAmountTasks += taskgroup.getAmountOfActiveTasks();
|
||||||
|
}
|
||||||
|
return activeAmountTasks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import org.springframework.stereotype.Repository;
|
|||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface BasicTaskScheduleRepository extends CrudRepository<BasicTaskSchedule, Long> {
|
public interface BasicTaskScheduleRepository extends CrudRepository<BasicTaskSchedule, Long> {
|
||||||
@ -30,4 +31,9 @@ public interface BasicTaskScheduleRepository extends CrudRepository<BasicTaskSch
|
|||||||
|
|
||||||
@Query(value = "SELECT bts FROM BasicTaskSchedule bts WHERE bts.task.taskgroup.user = ?1 AND bts.scheduleDate = ?2")
|
@Query(value = "SELECT bts FROM BasicTaskSchedule bts WHERE bts.task.taskgroup.user = ?1 AND bts.scheduleDate = ?2")
|
||||||
List<BasicTaskSchedule> findAllByUserAndDate(User user, LocalDate localDate);
|
List<BasicTaskSchedule> findAllByUserAndDate(User user, LocalDate localDate);
|
||||||
|
|
||||||
|
@Query(value = "SELECT bts FROM BasicTaskSchedule bts WHERE bts.task.taskgroup.user.username = ?1 AND bts.startTime is not null and bts.finishedTime is null")
|
||||||
|
Optional<BasicTaskSchedule> findActiveTaskSchedule(String username);
|
||||||
|
|
||||||
|
List<BasicTaskSchedule> findAllByStartTimeIsNull();
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,6 @@ public enum ServiceExitCode {
|
|||||||
|
|
||||||
OK,
|
OK,
|
||||||
ENTITY_ALREADY_EXIST,
|
ENTITY_ALREADY_EXIST,
|
||||||
MISSING_ENTITY;
|
MISSING_ENTITY,
|
||||||
|
INVALID_OPERATION;
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,13 @@ import core.repositories.UserRepository;
|
|||||||
import core.repositories.timemanager.BasicTaskScheduleRepository;
|
import core.repositories.timemanager.BasicTaskScheduleRepository;
|
||||||
import core.repositories.timemanager.TaskRepository;
|
import core.repositories.timemanager.TaskRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -52,10 +56,22 @@ public class TaskScheduleService {
|
|||||||
basicTaskScheduleRepository.deleteBasicTaskScheduleByID(basicTaskSchedule.getScheduleID());
|
basicTaskScheduleRepository.deleteBasicTaskScheduleByID(basicTaskSchedule.getScheduleID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceResult<List<BasicTaskSchedule>> loadTodaysSchedule(String username) {
|
public ServiceResult<List<BasicTaskSchedule>> loadTodaysSchedule(String username, boolean onlyActivateable) {
|
||||||
Optional<User> user = userRepository.findByUsername(username);
|
Optional<User> user = userRepository.findByUsername(username);
|
||||||
return user.map(value -> new ServiceResult<>(basicTaskScheduleRepository.findAllByUserAndDate(value, LocalDate.now()))).orElseGet(() ->
|
if(user.isEmpty()) {
|
||||||
new ServiceResult<>(ServiceExitCode.MISSING_ENTITY));
|
return new ServiceResult<>(ServiceExitCode.MISSING_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BasicTaskSchedule> basicTaskSchedules = basicTaskScheduleRepository.findAllByUserAndDate(user.get(), LocalDate.now());
|
||||||
|
List<BasicTaskSchedule> activatableSchedules = new LinkedList<>();
|
||||||
|
if(onlyActivateable) {
|
||||||
|
for (BasicTaskSchedule basicTaskSchedule : basicTaskSchedules) {
|
||||||
|
if (basicTaskSchedule.isActivateAble()) {
|
||||||
|
activatableSchedules.add(basicTaskSchedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ServiceResult<>(activatableSchedules);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceResult<List<BasicTaskSchedule>> loadSchedules(String username) {
|
public ServiceResult<List<BasicTaskSchedule>> loadSchedules(String username) {
|
||||||
@ -70,6 +86,7 @@ public class TaskScheduleService {
|
|||||||
return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST);
|
return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST);
|
||||||
} else {
|
} else {
|
||||||
BasicTaskSchedule basicTaskSchedule = new BasicTaskSchedule(task, LocalDate.now());
|
BasicTaskSchedule basicTaskSchedule = new BasicTaskSchedule(task, LocalDate.now());
|
||||||
|
basicTaskSchedule.setStartTime(LocalDateTime.now());
|
||||||
task.getBasicTaskSchedules().add(basicTaskSchedule);
|
task.getBasicTaskSchedules().add(basicTaskSchedule);
|
||||||
basicTaskScheduleRepository.save(basicTaskSchedule);
|
basicTaskScheduleRepository.save(basicTaskSchedule);
|
||||||
taskRepository.save(task);
|
taskRepository.save(task);
|
||||||
@ -80,4 +97,63 @@ public class TaskScheduleService {
|
|||||||
public void deleteScheduleByTask(Task task) {
|
public void deleteScheduleByTask(Task task) {
|
||||||
basicTaskScheduleRepository.deleteAllByTask(task);
|
basicTaskScheduleRepository.deleteAllByTask(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceResult<BasicTaskSchedule> getActiveSchedule(String username) {
|
||||||
|
Optional<BasicTaskSchedule> activeTaskScheduleResult = basicTaskScheduleRepository.findActiveTaskSchedule(username);
|
||||||
|
return activeTaskScheduleResult.map(ServiceResult::new).orElseGet(() -> new ServiceResult<>(ServiceExitCode.MISSING_ENTITY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceResult<BasicTaskSchedule> activateSchedule(BasicTaskSchedule taskSchedule) {
|
||||||
|
//Check if taskSchedule can be started
|
||||||
|
if(taskSchedule.getStartTime() != null) {
|
||||||
|
return new ServiceResult<>(ServiceExitCode.INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getActiveSchedule(taskSchedule.getTask().getTaskgroup().getUser().getUsername()).getExitCode() == ServiceExitCode.MISSING_ENTITY) {
|
||||||
|
taskSchedule.setStartTime(LocalDateTime.now());
|
||||||
|
basicTaskScheduleRepository.save(taskSchedule);
|
||||||
|
return new ServiceResult<>(taskSchedule);
|
||||||
|
} else {
|
||||||
|
return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceResult<Integer> stopSchedule(BasicTaskSchedule taskSchedule, boolean finish) {
|
||||||
|
if(taskSchedule.getStartTime() == null || taskSchedule.getFinishedTime() != null) {
|
||||||
|
return new ServiceResult<>(ServiceExitCode.INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
taskSchedule.setFinishedTime(LocalDateTime.now());
|
||||||
|
long activeTime = Duration.between(taskSchedule.getStartTime(), taskSchedule.getFinishedTime()).toMinutes();
|
||||||
|
long workTime = taskSchedule.getTask().getWorkTime() + activeTime;
|
||||||
|
taskSchedule.getTask().setWorkTime((int) workTime);
|
||||||
|
if(finish) {
|
||||||
|
taskSchedule.getTask().setFinished(true);
|
||||||
|
}
|
||||||
|
basicTaskScheduleRepository.save(taskSchedule);
|
||||||
|
taskRepository.save(taskSchedule.getTask());
|
||||||
|
return new ServiceResult<>((int) activeTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWorkedMinutes(User user) {
|
||||||
|
List<BasicTaskSchedule> basicTaskSchedules = basicTaskScheduleRepository.findAllByUser(user);
|
||||||
|
long workedMinutes = 0;
|
||||||
|
for(BasicTaskSchedule basicTaskSchedule : basicTaskSchedules) {
|
||||||
|
if(basicTaskSchedule.getFinishedTime() != null && basicTaskSchedule.getFinishedTime().toLocalDate().isEqual(LocalDate.now())) {
|
||||||
|
workedMinutes += Duration.between(basicTaskSchedule.getStartTime(), basicTaskSchedule.getFinishedTime()).toMinutes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (int) workedMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnyScheduleMissed(User user) {
|
||||||
|
List<BasicTaskSchedule> unstartedSchedules = basicTaskScheduleRepository.findAllByStartTimeIsNull();
|
||||||
|
LocalDate dateReference = LocalDate.now();
|
||||||
|
for(BasicTaskSchedule schedule : unstartedSchedules) {
|
||||||
|
if(schedule.getScheduleDate().isBefore(dateReference)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package core.services;
|
package core.services;
|
||||||
|
|
||||||
import core.api.models.timemanager.tasks.TaskFieldInfo;
|
import core.api.models.timemanager.tasks.TaskFieldInfo;
|
||||||
|
import core.entities.timemanager.BasicTaskSchedule;
|
||||||
import core.entities.timemanager.Task;
|
import core.entities.timemanager.Task;
|
||||||
import core.entities.timemanager.Taskgroup;
|
import core.entities.timemanager.Taskgroup;
|
||||||
import core.repositories.timemanager.BasicTaskScheduleRepository;
|
import core.repositories.timemanager.BasicTaskScheduleRepository;
|
||||||
@ -9,8 +10,8 @@ import core.repositories.timemanager.TaskgroupRepository;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Optional;
|
import java.util.*;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class TaskService {
|
public class TaskService {
|
||||||
@ -75,4 +76,26 @@ public class TaskService {
|
|||||||
public void clearTasks(Taskgroup taskgroup) {
|
public void clearTasks(Taskgroup taskgroup) {
|
||||||
taskRepository.deleteAllByTaskgroup(taskgroup);
|
taskRepository.deleteAllByTaskgroup(taskgroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void finishTask(Task task) {
|
||||||
|
//Mark task as finished and remove all unstarted schedules as well as finish all started schedules
|
||||||
|
task.finish();
|
||||||
|
taskRepository.save(task);
|
||||||
|
|
||||||
|
List<BasicTaskSchedule> removedBasicTaskSchedules = new LinkedList<>();
|
||||||
|
for(BasicTaskSchedule basicTaskSchedule : task.getBasicTaskSchedules()) {
|
||||||
|
if(basicTaskSchedule.getStartTime() == null) {
|
||||||
|
removedBasicTaskSchedules.add(basicTaskSchedule);
|
||||||
|
} else if(basicTaskSchedule.getFinishedTime() == null) {
|
||||||
|
ServiceResult<?> result = taskScheduleService.stopSchedule(basicTaskSchedule, true);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(BasicTaskSchedule deletedTaskSchedule: removedBasicTaskSchedules) {
|
||||||
|
task.getBasicTaskSchedules().remove(deletedTaskSchedule);
|
||||||
|
taskRepository.save(task);
|
||||||
|
taskScheduleService.deleteBasicSchedule(deletedTaskSchedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,16 @@ model/passwordChangeRequest.ts
|
|||||||
model/propertiesInfo.ts
|
model/propertiesInfo.ts
|
||||||
model/propertyInfo.ts
|
model/propertyInfo.ts
|
||||||
model/propertyUpdateRequest.ts
|
model/propertyUpdateRequest.ts
|
||||||
|
model/recursiveTaskgroupInfo.ts
|
||||||
|
model/scheduleActivateInfo.ts
|
||||||
model/scheduleInfo.ts
|
model/scheduleInfo.ts
|
||||||
|
model/scheduleStatus.ts
|
||||||
model/signUpRequest.ts
|
model/signUpRequest.ts
|
||||||
model/simpleStatusResponse.ts
|
model/simpleStatusResponse.ts
|
||||||
model/taskEntityInfo.ts
|
model/taskEntityInfo.ts
|
||||||
model/taskFieldInfo.ts
|
model/taskFieldInfo.ts
|
||||||
|
model/taskOverviewInfo.ts
|
||||||
|
model/taskScheduleStopResponse.ts
|
||||||
model/taskShortInfo.ts
|
model/taskShortInfo.ts
|
||||||
model/taskgroupDetailInfo.ts
|
model/taskgroupDetailInfo.ts
|
||||||
model/taskgroupEntityInfo.ts
|
model/taskgroupEntityInfo.ts
|
||||||
|
@ -20,8 +20,11 @@ import { Observable } from 'rxjs';
|
|||||||
|
|
||||||
import { BasicScheduleEntityInfo } from '../model/models';
|
import { BasicScheduleEntityInfo } from '../model/models';
|
||||||
import { BasicScheduleFieldInfo } from '../model/models';
|
import { BasicScheduleFieldInfo } from '../model/models';
|
||||||
|
import { ScheduleActivateInfo } from '../model/models';
|
||||||
import { ScheduleInfo } from '../model/models';
|
import { ScheduleInfo } from '../model/models';
|
||||||
|
import { ScheduleStatus } from '../model/models';
|
||||||
import { SimpleStatusResponse } from '../model/models';
|
import { SimpleStatusResponse } from '../model/models';
|
||||||
|
import { TaskScheduleStopResponse } from '../model/models';
|
||||||
|
|
||||||
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||||
import { Configuration } from '../configuration';
|
import { Configuration } from '../configuration';
|
||||||
@ -88,6 +91,61 @@ export class ScheduleService {
|
|||||||
return httpParams;
|
return httpParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get active schedule
|
||||||
|
* get active schedule
|
||||||
|
* @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 schedulesActiveGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<ScheduleInfo>;
|
||||||
|
public schedulesActiveGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<ScheduleInfo>>;
|
||||||
|
public schedulesActiveGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<ScheduleInfo>>;
|
||||||
|
public schedulesActiveGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
|
||||||
|
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<ScheduleInfo>(`${this.configuration.basePath}/schedules/active`,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets all schedules of user
|
* gets all schedules of user
|
||||||
* gets all schedules of user
|
* gets all schedules of user
|
||||||
@ -143,6 +201,66 @@ export class ScheduleService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* activates schedule
|
||||||
|
* activates schedule
|
||||||
|
* @param scheduleID internal id of schedule
|
||||||
|
* @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 schedulesScheduleIDActivatePost(scheduleID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<ScheduleActivateInfo>;
|
||||||
|
public schedulesScheduleIDActivatePost(scheduleID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<ScheduleActivateInfo>>;
|
||||||
|
public schedulesScheduleIDActivatePost(scheduleID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<ScheduleActivateInfo>>;
|
||||||
|
public schedulesScheduleIDActivatePost(scheduleID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
if (scheduleID === null || scheduleID === undefined) {
|
||||||
|
throw new Error('Required parameter scheduleID was null or undefined when calling schedulesScheduleIDActivatePost.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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.post<ScheduleActivateInfo>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(scheduleID))}/activate`,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reschedules task
|
* reschedules task
|
||||||
* reschedules a task
|
* reschedules a task
|
||||||
@ -276,6 +394,123 @@ export class ScheduleService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param scheduleID internal id of schedule
|
||||||
|
* @param finish internal id of schedule
|
||||||
|
* @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 schedulesScheduleIDStopFinishPost(scheduleID: number, finish: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<TaskScheduleStopResponse>;
|
||||||
|
public schedulesScheduleIDStopFinishPost(scheduleID: number, finish: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<TaskScheduleStopResponse>>;
|
||||||
|
public schedulesScheduleIDStopFinishPost(scheduleID: number, finish: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<TaskScheduleStopResponse>>;
|
||||||
|
public schedulesScheduleIDStopFinishPost(scheduleID: number, finish: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
if (scheduleID === null || scheduleID === undefined) {
|
||||||
|
throw new Error('Required parameter scheduleID was null or undefined when calling schedulesScheduleIDStopFinishPost.');
|
||||||
|
}
|
||||||
|
if (finish === null || finish === undefined) {
|
||||||
|
throw new Error('Required parameter finish was null or undefined when calling schedulesScheduleIDStopFinishPost.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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.post<TaskScheduleStopResponse>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(scheduleID))}/stop/${encodeURIComponent(String(finish))}`,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get number of active minutes
|
||||||
|
* get number of worked minutes today
|
||||||
|
* @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 schedulesStatusTodayGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<ScheduleStatus>;
|
||||||
|
public schedulesStatusTodayGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<ScheduleStatus>>;
|
||||||
|
public schedulesStatusTodayGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<ScheduleStatus>>;
|
||||||
|
public schedulesStatusTodayGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
|
||||||
|
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<ScheduleStatus>(`${this.configuration.basePath}/schedules/status/today`,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* creates basic schedule for task
|
* creates basic schedule for task
|
||||||
* creates a basic schedule for a task
|
* creates a basic schedule for a task
|
||||||
@ -353,9 +588,9 @@ export class ScheduleService {
|
|||||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
* @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.
|
* @param reportProgress flag to report request and response progress.
|
||||||
*/
|
*/
|
||||||
public schedulesTaskIDNowPost(taskID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<BasicScheduleEntityInfo>;
|
public schedulesTaskIDNowPost(taskID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<ScheduleInfo>;
|
||||||
public schedulesTaskIDNowPost(taskID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<BasicScheduleEntityInfo>>;
|
public schedulesTaskIDNowPost(taskID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<ScheduleInfo>>;
|
||||||
public schedulesTaskIDNowPost(taskID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<BasicScheduleEntityInfo>>;
|
public schedulesTaskIDNowPost(taskID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<ScheduleInfo>>;
|
||||||
public schedulesTaskIDNowPost(taskID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
public schedulesTaskIDNowPost(taskID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
if (taskID === null || taskID === undefined) {
|
if (taskID === null || taskID === undefined) {
|
||||||
throw new Error('Required parameter taskID was null or undefined when calling schedulesTaskIDNowPost.');
|
throw new Error('Required parameter taskID was null or undefined when calling schedulesTaskIDNowPost.');
|
||||||
@ -393,7 +628,7 @@ export class ScheduleService {
|
|||||||
responseType_ = 'text';
|
responseType_ = 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.httpClient.post<BasicScheduleEntityInfo>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(taskID))}/now`,
|
return this.httpClient.post<ScheduleInfo>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(taskID))}/now`,
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
context: localVarHttpContext,
|
context: localVarHttpContext,
|
||||||
@ -472,13 +707,17 @@ export class ScheduleService {
|
|||||||
/**
|
/**
|
||||||
* get today\'s schedules
|
* get today\'s schedules
|
||||||
* get all schedules of today
|
* get all schedules of today
|
||||||
|
* @param activateable determines whether only schedules that can be started should be included or all schedules of today
|
||||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
* @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.
|
* @param reportProgress flag to report request and response progress.
|
||||||
*/
|
*/
|
||||||
public schedulesTodayGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<ScheduleInfo>>;
|
public schedulesTodayActivateableGet(activateable: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<ScheduleInfo>>;
|
||||||
public schedulesTodayGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<ScheduleInfo>>>;
|
public schedulesTodayActivateableGet(activateable: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<ScheduleInfo>>>;
|
||||||
public schedulesTodayGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<ScheduleInfo>>>;
|
public schedulesTodayActivateableGet(activateable: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<ScheduleInfo>>>;
|
||||||
public schedulesTodayGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
public schedulesTodayActivateableGet(activateable: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
if (activateable === null || activateable === undefined) {
|
||||||
|
throw new Error('Required parameter activateable was null or undefined when calling schedulesTodayActivateableGet.');
|
||||||
|
}
|
||||||
|
|
||||||
let localVarHeaders = this.defaultHeaders;
|
let localVarHeaders = this.defaultHeaders;
|
||||||
|
|
||||||
@ -512,7 +751,7 @@ export class ScheduleService {
|
|||||||
responseType_ = 'text';
|
responseType_ = 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.httpClient.get<Array<ScheduleInfo>>(`${this.configuration.basePath}/schedules/today`,
|
return this.httpClient.get<Array<ScheduleInfo>>(`${this.configuration.basePath}/schedules/today/${encodeURIComponent(String(activateable))}`,
|
||||||
{
|
{
|
||||||
context: localVarHttpContext,
|
context: localVarHttpContext,
|
||||||
responseType: <any>responseType_,
|
responseType: <any>responseType_,
|
||||||
|
144
frontend/src/api/api/schedules.service.ts
Normal file
144
frontend/src/api/api/schedules.service.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/* tslint:disable:no-unused-variable member-ordering */
|
||||||
|
|
||||||
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders, HttpParams,
|
||||||
|
HttpResponse, HttpEvent, HttpParameterCodec, HttpContext
|
||||||
|
} from '@angular/common/http';
|
||||||
|
import { CustomHttpParameterCodec } from '../encoder';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { ActiveMinutesInformation } from '../model/models';
|
||||||
|
import { SimpleStatusResponse } from '../model/models';
|
||||||
|
|
||||||
|
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||||
|
import { Configuration } from '../configuration';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SchedulesService {
|
||||||
|
|
||||||
|
protected basePath = 'http://localhost:8080/api';
|
||||||
|
public defaultHeaders = new HttpHeaders();
|
||||||
|
public configuration = new Configuration();
|
||||||
|
public encoder: HttpParameterCodec;
|
||||||
|
|
||||||
|
constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
|
||||||
|
if (configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
if (typeof this.configuration.basePath !== 'string') {
|
||||||
|
if (typeof basePath !== 'string') {
|
||||||
|
basePath = this.basePath;
|
||||||
|
}
|
||||||
|
this.configuration.basePath = basePath;
|
||||||
|
}
|
||||||
|
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
|
||||||
|
if (typeof value === "object" && value instanceof Date === false) {
|
||||||
|
httpParams = this.addToHttpParamsRecursive(httpParams, value);
|
||||||
|
} else {
|
||||||
|
httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
|
||||||
|
}
|
||||||
|
return httpParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
|
||||||
|
if (value == null) {
|
||||||
|
return httpParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "object") {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
(value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
|
||||||
|
} else if (value instanceof Date) {
|
||||||
|
if (key != null) {
|
||||||
|
httpParams = httpParams.append(key,
|
||||||
|
(value as Date).toISOString().substr(0, 10));
|
||||||
|
} else {
|
||||||
|
throw Error("key may not be null if value is Date");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive(
|
||||||
|
httpParams, value[k], key != null ? `${key}.${k}` : k));
|
||||||
|
}
|
||||||
|
} else if (key != null) {
|
||||||
|
httpParams = httpParams.append(key, value);
|
||||||
|
} else {
|
||||||
|
throw Error("key may not be null if value is not object or array");
|
||||||
|
}
|
||||||
|
return httpParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get number of active minutes
|
||||||
|
* get number of worked minutes today
|
||||||
|
* @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 schedulesWorkedMinutesTodayGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<ActiveMinutesInformation>;
|
||||||
|
public schedulesWorkedMinutesTodayGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<ActiveMinutesInformation>>;
|
||||||
|
public schedulesWorkedMinutesTodayGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<ActiveMinutesInformation>>;
|
||||||
|
public schedulesWorkedMinutesTodayGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
|
||||||
|
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<ActiveMinutesInformation>(`${this.configuration.basePath}/schedules/workedMinutesToday`,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -147,6 +147,66 @@ export class TaskService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finishs task
|
||||||
|
* finish tasks
|
||||||
|
* @param taskID internal id of task
|
||||||
|
* @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 tasksTaskIDFinishPost(taskID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<SimpleStatusResponse>;
|
||||||
|
public tasksTaskIDFinishPost(taskID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<SimpleStatusResponse>>;
|
||||||
|
public tasksTaskIDFinishPost(taskID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<SimpleStatusResponse>>;
|
||||||
|
public tasksTaskIDFinishPost(taskID: number, 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 tasksTaskIDFinishPost.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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.post<SimpleStatusResponse>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskID))}/finish`,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets taskdetails
|
* gets taskdetails
|
||||||
* loads information about a given task
|
* loads information about a given task
|
||||||
|
@ -20,6 +20,7 @@ import { Observable } from 'rxjs';
|
|||||||
|
|
||||||
import { InlineResponse200 } from '../model/models';
|
import { InlineResponse200 } from '../model/models';
|
||||||
import { InlineResponse403 } from '../model/models';
|
import { InlineResponse403 } from '../model/models';
|
||||||
|
import { RecursiveTaskgroupInfo } from '../model/models';
|
||||||
import { SimpleStatusResponse } from '../model/models';
|
import { SimpleStatusResponse } from '../model/models';
|
||||||
import { TaskgroupDetailInfo } from '../model/models';
|
import { TaskgroupDetailInfo } from '../model/models';
|
||||||
import { TaskgroupEntityInfo } from '../model/models';
|
import { TaskgroupEntityInfo } from '../model/models';
|
||||||
@ -513,4 +514,59 @@ export class TaskgroupService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list all top level taskgroups of authorized user
|
||||||
|
* list all taskgroups of authorized user
|
||||||
|
* @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 taskgroupsTreeGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<RecursiveTaskgroupInfo>>;
|
||||||
|
public taskgroupsTreeGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<RecursiveTaskgroupInfo>>>;
|
||||||
|
public taskgroupsTreeGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<RecursiveTaskgroupInfo>>>;
|
||||||
|
public taskgroupsTreeGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||||
|
|
||||||
|
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<RecursiveTaskgroupInfo>>(`${this.configuration.basePath}/taskgroups/tree`,
|
||||||
|
{
|
||||||
|
context: localVarHttpContext,
|
||||||
|
responseType: <any>responseType_,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: localVarHeaders,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
20
frontend/src/api/model/activeMinutesInformation.ts
Normal file
20
frontend/src/api/model/activeMinutesInformation.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 ActiveMinutesInformation {
|
||||||
|
/**
|
||||||
|
* number of minutes it was worked today
|
||||||
|
*/
|
||||||
|
activeMinutes: number;
|
||||||
|
}
|
||||||
|
|
@ -12,11 +12,16 @@ export * from './passwordChangeRequest';
|
|||||||
export * from './propertiesInfo';
|
export * from './propertiesInfo';
|
||||||
export * from './propertyInfo';
|
export * from './propertyInfo';
|
||||||
export * from './propertyUpdateRequest';
|
export * from './propertyUpdateRequest';
|
||||||
|
export * from './recursiveTaskgroupInfo';
|
||||||
|
export * from './scheduleActivateInfo';
|
||||||
export * from './scheduleInfo';
|
export * from './scheduleInfo';
|
||||||
|
export * from './scheduleStatus';
|
||||||
export * from './signUpRequest';
|
export * from './signUpRequest';
|
||||||
export * from './simpleStatusResponse';
|
export * from './simpleStatusResponse';
|
||||||
export * from './taskEntityInfo';
|
export * from './taskEntityInfo';
|
||||||
export * from './taskFieldInfo';
|
export * from './taskFieldInfo';
|
||||||
|
export * from './taskOverviewInfo';
|
||||||
|
export * from './taskScheduleStopResponse';
|
||||||
export * from './taskShortInfo';
|
export * from './taskShortInfo';
|
||||||
export * from './taskgroupDetailInfo';
|
export * from './taskgroupDetailInfo';
|
||||||
export * from './taskgroupEntityInfo';
|
export * from './taskgroupEntityInfo';
|
||||||
|
35
frontend/src/api/model/recursiveTaskgroupInfo.ts
Normal file
35
frontend/src/api/model/recursiveTaskgroupInfo.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* API Title
|
||||||
|
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
import { TaskOverviewInfo } from './taskOverviewInfo';
|
||||||
|
|
||||||
|
|
||||||
|
export interface RecursiveTaskgroupInfo {
|
||||||
|
/**
|
||||||
|
* internal id of taskgroup
|
||||||
|
*/
|
||||||
|
taskgroupID: number;
|
||||||
|
/**
|
||||||
|
* name of taskgroup
|
||||||
|
*/
|
||||||
|
taskgroupName: string;
|
||||||
|
childTaskgroups: Array<RecursiveTaskgroupInfo>;
|
||||||
|
activeTasks: Array<TaskOverviewInfo>;
|
||||||
|
/**
|
||||||
|
* determines whether the taskgroup has an overdue task or not
|
||||||
|
*/
|
||||||
|
hasOverdueTask: boolean;
|
||||||
|
/**
|
||||||
|
* determines the number of active tasks that can be scheduled
|
||||||
|
*/
|
||||||
|
amountActiveTasks: number;
|
||||||
|
}
|
||||||
|
|
20
frontend/src/api/model/scheduleActivateInfo.ts
Normal file
20
frontend/src/api/model/scheduleActivateInfo.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 ScheduleActivateInfo {
|
||||||
|
/**
|
||||||
|
* point in time at which the schedule was started
|
||||||
|
*/
|
||||||
|
startTime: string;
|
||||||
|
}
|
||||||
|
|
24
frontend/src/api/model/scheduleStatus.ts
Normal file
24
frontend/src/api/model/scheduleStatus.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* API Title
|
||||||
|
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export interface ScheduleStatus {
|
||||||
|
/**
|
||||||
|
* number of minutes it was worked today
|
||||||
|
*/
|
||||||
|
activeMinutes: number;
|
||||||
|
/**
|
||||||
|
* states whether a schedule was missed or not
|
||||||
|
*/
|
||||||
|
missedSchedules: boolean;
|
||||||
|
}
|
||||||
|
|
40
frontend/src/api/model/taskOverviewInfo.ts
Normal file
40
frontend/src/api/model/taskOverviewInfo.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* 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 TaskOverviewInfo {
|
||||||
|
/**
|
||||||
|
* internal id of task
|
||||||
|
*/
|
||||||
|
taskID: number;
|
||||||
|
/**
|
||||||
|
* name of task
|
||||||
|
*/
|
||||||
|
taskName: string;
|
||||||
|
/**
|
||||||
|
* expected time to finish task
|
||||||
|
*/
|
||||||
|
eta: number;
|
||||||
|
/**
|
||||||
|
* date until the task has to be finished
|
||||||
|
*/
|
||||||
|
limit: string;
|
||||||
|
/**
|
||||||
|
* determines whether the task is overdue
|
||||||
|
*/
|
||||||
|
overdue: boolean;
|
||||||
|
/**
|
||||||
|
* number in minutes that was already worked on this task
|
||||||
|
*/
|
||||||
|
activeTime?: number;
|
||||||
|
}
|
||||||
|
|
20
frontend/src/api/model/taskScheduleStopResponse.ts
Normal file
20
frontend/src/api/model/taskScheduleStopResponse.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 TaskScheduleStopResponse {
|
||||||
|
/**
|
||||||
|
* time where the schedule was active
|
||||||
|
*/
|
||||||
|
workTime: number;
|
||||||
|
}
|
||||||
|
|
@ -62,6 +62,11 @@ import { adapterFactory } from 'angular-calendar/date-adapters/date-fns';
|
|||||||
import {MatSelectModule} from "@angular/material/select";
|
import {MatSelectModule} from "@angular/material/select";
|
||||||
import { BasicSchedulerComponent } from './schedules/basic-scheduler/basic-scheduler.component';
|
import { BasicSchedulerComponent } from './schedules/basic-scheduler/basic-scheduler.component';
|
||||||
import { ScheduleDashboardComponent } from './schedules/schedule-dashboard/schedule-dashboard.component';
|
import { ScheduleDashboardComponent } from './schedules/schedule-dashboard/schedule-dashboard.component';
|
||||||
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
|
import { ActiveScheduleComponent } from './dashboard/active-schedule/active-schedule.component';
|
||||||
|
import {MatTreeModule} from "@angular/material/tree";
|
||||||
|
import { TaskgroupOverviewComponent } from './dashboard/taskgroup-overview/taskgroup-overview.component';
|
||||||
|
import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.component';
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
@ -88,7 +93,11 @@ import { ScheduleDashboardComponent } from './schedules/schedule-dashboard/sched
|
|||||||
TaskDetailOverviewComponent,
|
TaskDetailOverviewComponent,
|
||||||
SchedulerComponent,
|
SchedulerComponent,
|
||||||
BasicSchedulerComponent,
|
BasicSchedulerComponent,
|
||||||
ScheduleDashboardComponent
|
ScheduleDashboardComponent,
|
||||||
|
DashboardComponent,
|
||||||
|
ActiveScheduleComponent,
|
||||||
|
TaskgroupOverviewComponent,
|
||||||
|
TaskOverviewComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -123,7 +132,8 @@ import { ScheduleDashboardComponent } from './schedules/schedule-dashboard/sched
|
|||||||
MatProgressBarModule,
|
MatProgressBarModule,
|
||||||
MatExpansionModule,
|
MatExpansionModule,
|
||||||
CalendarModule.forRoot({provide: DateAdapter, useFactory: adapterFactory}),
|
CalendarModule.forRoot({provide: DateAdapter, useFactory: adapterFactory}),
|
||||||
MatSelectModule
|
MatSelectModule,
|
||||||
|
MatTreeModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import {ScheduleInfo} from "../../../api";
|
||||||
|
|
||||||
|
export interface StopActiveScheduleInfo {
|
||||||
|
schedule: ScheduleInfo
|
||||||
|
workedMinutes: number
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
.container {
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 70%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
margin-bottom: 2.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.today-worked-info {
|
||||||
|
margin-left: 20px;
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-card {
|
||||||
|
background-color: #e54c3c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-card {
|
||||||
|
background-color: #00bc8c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
width: 45%;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: white;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-heading {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.today-worked-info {
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.lightBlueBtn {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayBtn {
|
||||||
|
background-color: #444444;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-list-base {
|
||||||
|
--mdc-list-list-item-label-text-color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
::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
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-overview {
|
||||||
|
width: 25%;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-btn {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border: 0 solid #000000;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-last-btn {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-no-deco {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-no-deco:hover{
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray-text {
|
||||||
|
color: #6e6e6e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellowBtn {
|
||||||
|
background-color: #f39c12;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryBtn {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-card-header-text {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-del-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-actions {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-actions {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greenBtn {
|
||||||
|
background-color: #00bc8c;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<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>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card *ngIf="activeSchedule != undefined">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title><a routerLink="/" class="link-no-deco">{{activeSchedule!.task.taskName}}</a></mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<span *ngFor="let taskgroupPath of activeSchedule!.taskgroupPath">{{taskgroupPath.taskgroupName}} /</span>
|
||||||
|
<p class="gray-text" *ngIf="activeSchedule!.scheduleType==='BASIC'">Running for {{displayTime}}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-raised-button class="grayBtn" (click)="stopTask(false)">Stop</button>
|
||||||
|
<button mat-raised-button class="greenBtn" (click)="stopTask(true)">Finish</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ActiveScheduleComponent } from './active-schedule.component';
|
||||||
|
|
||||||
|
describe('ActiveScheduleComponent', () => {
|
||||||
|
let component: ActiveScheduleComponent;
|
||||||
|
let fixture: ComponentFixture<ActiveScheduleComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ActiveScheduleComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(ActiveScheduleComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,77 @@
|
|||||||
|
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||||
|
import {ScheduleInfo, ScheduleService, TaskOverviewInfo} from "../../../api";
|
||||||
|
import {StopActiveScheduleInfo} from "./StopActiveScheduleInfo";
|
||||||
|
import {TaskOverviewComponent} from "../task-overview/task-overview.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-active-schedule',
|
||||||
|
templateUrl: './active-schedule.component.html',
|
||||||
|
styleUrls: ['./active-schedule.component.css']
|
||||||
|
})
|
||||||
|
export class ActiveScheduleComponent implements OnInit{
|
||||||
|
activeSchedule: ScheduleInfo | undefined
|
||||||
|
|
||||||
|
startTime: number = 0;
|
||||||
|
currentTime: number = 0;
|
||||||
|
displayTime: string = "00:00:00"
|
||||||
|
|
||||||
|
@Output('onStopTask') scheduleStopEmitter = new EventEmitter<StopActiveScheduleInfo>;
|
||||||
|
|
||||||
|
constructor(private scheduleService: ScheduleService) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateTime() {
|
||||||
|
const now = Date.now();
|
||||||
|
const elapsed = this.activeSchedule != undefined ? now - this.startTime + this.currentTime : this.currentTime;
|
||||||
|
this.displayTime = this.formatTime(elapsed);
|
||||||
|
requestAnimationFrame(() => this.updateTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTime(milliseconds: number): string {
|
||||||
|
const seconds = Math.floor((milliseconds / 1000) % 60);
|
||||||
|
const minutes = Math.floor((milliseconds / 1000 / 60) % 60);
|
||||||
|
const hours = Math.floor(milliseconds / 1000 / 60 / 60);
|
||||||
|
return `${this.padNumber(hours)}:${this.padNumber(minutes)}:${this.padNumber(seconds)}`;
|
||||||
|
}
|
||||||
|
padNumber(num: number): string {
|
||||||
|
return num < 10 ? `0${num}` : `${num}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.scheduleService.schedulesActiveGet().subscribe({
|
||||||
|
next: resp => {
|
||||||
|
if(resp.scheduleID >= 0) {
|
||||||
|
this.activateSchedule(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTask(finish: boolean) {
|
||||||
|
this.scheduleService.schedulesScheduleIDStopFinishPost(this.activeSchedule!.scheduleID, finish).subscribe({
|
||||||
|
next: resp => {
|
||||||
|
this.scheduleStopEmitter.emit({
|
||||||
|
schedule: this.activeSchedule!,
|
||||||
|
workedMinutes: resp.workTime
|
||||||
|
})
|
||||||
|
this.activeSchedule = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
activateSchedule(schedule: ScheduleInfo) {
|
||||||
|
this.activeSchedule = schedule;
|
||||||
|
this.startTime = new Date(this.activeSchedule.startTime).getTime();
|
||||||
|
this.updateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
finishTaskByOverview(task: TaskOverviewInfo) {
|
||||||
|
this.activeSchedule = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
143
frontend/src/app/dashboard/dashboard.component.css
Normal file
143
frontend/src/app/dashboard/dashboard.component.css
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
.container {
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 70%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
margin-bottom: 2.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.today-worked-info {
|
||||||
|
margin-left: 20px;
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-card {
|
||||||
|
background-color: #e54c3c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-card {
|
||||||
|
background-color: #00bc8c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
width: 45%;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: white;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-heading {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.today-worked-info {
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.lightBlueBtn {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayBtn {
|
||||||
|
background-color: #444444;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-list-base {
|
||||||
|
--mdc-list-list-item-label-text-color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
::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
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-overview {
|
||||||
|
width: 35%;
|
||||||
|
float: right;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-btn {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border: 0 solid #000000;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-last-btn {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-no-deco {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-no-deco:hover{
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray-text {
|
||||||
|
color: #6e6e6e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellowBtn {
|
||||||
|
background-color: #f39c12;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryBtn {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-card-header-text {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-del-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-actions {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-actions {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-first-child {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border: 0 solid #000000;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
51
frontend/src/app/dashboard/dashboard.component.html
Normal file
51
frontend/src/app/dashboard/dashboard.component.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="main-container">
|
||||||
|
<mat-card *ngIf="missedSchedules" class="red-card">
|
||||||
|
<mat-card-content>
|
||||||
|
<p>There are missed schedules. Please reschedule them.</p>
|
||||||
|
<a class="btn-link" routerLink="/">Reschedule</a>
|
||||||
|
</mat-card-content>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<h1 class="dashboard-heading">Scheduled for today</h1>
|
||||||
|
<mat-card class="green-card" *ngIf="schedules.length == 0">
|
||||||
|
<mat-card-content>
|
||||||
|
<p>There is no scheduled Task for today</p>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<mat-card *ngFor="let schedule of schedules">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>
|
||||||
|
<a routerLink="/" class="link-no-deco">{{schedule.task.taskName}}</a>
|
||||||
|
</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<span *ngFor="let taskgroupPath of schedule.taskgroupPath">{{taskgroupPath.taskgroupName}} /</span>
|
||||||
|
<p class="gray-text" *ngIf="schedule.scheduleType==='BASIC'">To be done sometime today</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-raised-button color="primary" class="primaryBtn" (click)="startSchedule(schedule)" [disabled]="activeScheduleComponent!.activeSchedule != undefined">Start now</button>
|
||||||
|
<button mat-raised-button class="yellowBtn">Reschedule</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="taskgroup-overview">
|
||||||
|
<app-task-overview [tasks]="tasks" (onStartNow)="onStartTaskNow($event)" [taskgroupID]="selectedTaskgroupID" (onFinished)="onFinishTask($event)"></app-task-overview>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="taskgroup-overview">
|
||||||
|
|
||||||
|
<app-taskgroup-overview (taskgroupSelected)="onSelectTaskgroup($event)"></app-taskgroup-overview>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
21
frontend/src/app/dashboard/dashboard.component.spec.ts
Normal file
21
frontend/src/app/dashboard/dashboard.component.spec.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DashboardComponent } from './dashboard.component';
|
||||||
|
|
||||||
|
describe('DashboardComponent', () => {
|
||||||
|
let component: DashboardComponent;
|
||||||
|
let fixture: ComponentFixture<DashboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [DashboardComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(DashboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
75
frontend/src/app/dashboard/dashboard.component.ts
Normal file
75
frontend/src/app/dashboard/dashboard.component.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||||
|
import {BasicScheduleEntityInfo, ScheduleInfo, ScheduleService, TaskOverviewInfo} from "../../api";
|
||||||
|
import {ActiveScheduleComponent} from "./active-schedule/active-schedule.component";
|
||||||
|
import {StopActiveScheduleInfo} from "./active-schedule/StopActiveScheduleInfo";
|
||||||
|
import {TaskOverviewComponent} from "./task-overview/task-overview.component";
|
||||||
|
import {TaskOverviewData} from "./taskgroup-overview/taskgroup-overview.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard',
|
||||||
|
templateUrl: './dashboard.component.html',
|
||||||
|
styleUrls: ['./dashboard.component.css']
|
||||||
|
})
|
||||||
|
export class DashboardComponent implements OnInit{
|
||||||
|
|
||||||
|
missedSchedules: boolean = true
|
||||||
|
schedules: ScheduleInfo[] = []
|
||||||
|
workedMinutesToday: number = 0
|
||||||
|
|
||||||
|
tasks: TaskOverviewInfo[] = []
|
||||||
|
selectedTaskgroupID: number | undefined
|
||||||
|
|
||||||
|
@ViewChild('activeSchedule') activeScheduleComponent: ActiveScheduleComponent | undefined
|
||||||
|
constructor(private scheduleService: ScheduleService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.scheduleService.schedulesTodayActivateableGet(true).subscribe({
|
||||||
|
next: resp => {
|
||||||
|
this.schedules = resp;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.scheduleService.schedulesStatusTodayGet().subscribe({
|
||||||
|
next: resp => {
|
||||||
|
this.workedMinutesToday = resp.activeMinutes;
|
||||||
|
this.missedSchedules = resp.missedSchedules;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startSchedule(schedule: ScheduleInfo) {
|
||||||
|
this.scheduleService.schedulesScheduleIDActivatePost(schedule.scheduleID).subscribe({
|
||||||
|
next: resp => {
|
||||||
|
schedule.startTime = resp.startTime;
|
||||||
|
if(this.activeScheduleComponent != undefined) {
|
||||||
|
this.activeScheduleComponent.activateSchedule(schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stopedTask(stopActiveScheduleInfo: StopActiveScheduleInfo) {
|
||||||
|
this.workedMinutesToday += stopActiveScheduleInfo.workedMinutes;
|
||||||
|
this.schedules = this.schedules.filter(schedule => schedule.scheduleID !== stopActiveScheduleInfo.schedule.scheduleID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected readonly TaskOverviewComponent = TaskOverviewComponent;
|
||||||
|
|
||||||
|
onSelectTaskgroup(taskOverviewData: TaskOverviewData) {
|
||||||
|
this.tasks = taskOverviewData.tasks;
|
||||||
|
this.selectedTaskgroupID = taskOverviewData.taskgroupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStartTaskNow(schedule: ScheduleInfo) {
|
||||||
|
this.activeScheduleComponent?.activateSchedule(schedule)
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinishTask(task: TaskOverviewInfo) {
|
||||||
|
this.activeScheduleComponent?.finishTaskByOverview(task);
|
||||||
|
|
||||||
|
this.schedules = this.schedules.filter(schedule => schedule.task.taskID !== task.taskID)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
.greenBtn {
|
||||||
|
background-color: #00bc8c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-without-radius {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info {
|
||||||
|
line-height: .75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.long-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.yellowBtn {
|
||||||
|
background-color: #f39c12;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
<button mat-raised-button class="greenBtn long-btn" *ngIf="taskgroupID != undefined" (click)="openTaskCreation()">Add</button>
|
||||||
|
<mat-card *ngFor="let task of tasks">
|
||||||
|
<mat-card-content>
|
||||||
|
<h3><a class="task-link" [routerLink]="['/taskgroups', taskgroupID!, 'tasks', task.taskID]">{{task.taskName}}</a></h3>
|
||||||
|
<mat-progress-bar mode="determinate" value="{{task.activeTime}}" class="progress"></mat-progress-bar>
|
||||||
|
<p class="task-info"><i>ETA: </i>{{task.activeTime}} / {{task.eta}}</p>
|
||||||
|
<p class="task-info"><i>Limit: </i>{{task.limit}}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-raised-button color="primary" class="btn-without-radius" (click)="startTaskNow(task)">Start now</button>
|
||||||
|
<button *ngIf="taskgroupID != undefined" mat-raised-button class="yellowBtn btn-without-radius" [routerLink]="['/taskgroups', taskgroupID!, 'tasks', task.taskID, 'schedule']">Schedule</button>
|
||||||
|
<button mat-raised-button class="greenBtn btn-without-radius" (click)="finishTask(task)">Finish</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TaskOverviewComponent } from './task-overview.component';
|
||||||
|
|
||||||
|
describe('TaskOverviewComponent', () => {
|
||||||
|
let component: TaskOverviewComponent;
|
||||||
|
let fixture: ComponentFixture<TaskOverviewComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [TaskOverviewComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(TaskOverviewComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,82 @@
|
|||||||
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
import {BasicScheduleEntityInfo, ScheduleInfo, ScheduleService, TaskOverviewInfo, TaskService} from "../../../api";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {TaskEditorData} from "../../tasks/task-editor/TaskEditorData";
|
||||||
|
import {TaskEditorComponent} from "../../tasks/task-editor/task-editor.component";
|
||||||
|
import {MatDialog} from "@angular/material/dialog";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-task-overview',
|
||||||
|
templateUrl: './task-overview.component.html',
|
||||||
|
styleUrls: ['./task-overview.component.css']
|
||||||
|
})
|
||||||
|
export class TaskOverviewComponent {
|
||||||
|
|
||||||
|
@Input() tasks: TaskOverviewInfo[] = []
|
||||||
|
@Input() taskgroupID: number | undefined
|
||||||
|
@Output('onStartNow') startNowEmitter: EventEmitter<ScheduleInfo> = new EventEmitter<ScheduleInfo>();
|
||||||
|
@Output('onFinished') finishedEmitter: EventEmitter<TaskOverviewInfo> = new EventEmitter<TaskOverviewInfo>();
|
||||||
|
|
||||||
|
constructor(private scheduleService: ScheduleService,
|
||||||
|
private snackbar: MatSnackBar,
|
||||||
|
private taskService: TaskService,
|
||||||
|
private dialog: MatDialog) {
|
||||||
|
|
||||||
|
}
|
||||||
|
startTaskNow(task: TaskOverviewInfo) {
|
||||||
|
this.scheduleService.schedulesTaskIDNowPost(task.taskID).subscribe({
|
||||||
|
next: resp => {
|
||||||
|
this.startNowEmitter.emit(resp);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
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 if(err.status == 409) {
|
||||||
|
this.snackbar.open("Task is already running", "", {duration: 2000});
|
||||||
|
} else {
|
||||||
|
this.snackbar.open("Unexpected error", "", {duration: 2000});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
finishTask(task: TaskOverviewInfo) {
|
||||||
|
this.taskService.tasksTaskIDFinishPost(task.taskID).subscribe({
|
||||||
|
next: resp => {
|
||||||
|
this.finishedEmitter.emit(task);
|
||||||
|
this.tasks = this.tasks.filter(ct => ct.taskID !== task.taskID)
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
openTaskCreation() {
|
||||||
|
const editorData: TaskEditorData = {
|
||||||
|
task: undefined,
|
||||||
|
taskgroupID: this.taskgroupID!
|
||||||
|
}
|
||||||
|
const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"})
|
||||||
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
|
if(res != undefined) {
|
||||||
|
this.tasks.push({
|
||||||
|
taskID: res.taskID,
|
||||||
|
eta: res.eta,
|
||||||
|
limit: res.deadline,
|
||||||
|
taskName: res.taskName,
|
||||||
|
activeTime: 0,
|
||||||
|
overdue: res.overdue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
.container {
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.today-worked-info {
|
||||||
|
margin-left: 20px;
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-card {
|
||||||
|
background-color: #e54c3c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-card {
|
||||||
|
background-color: #00bc8c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
width: 45%;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: white;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-heading {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.today-worked-info {
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.lightBlueBtn {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayBtn {
|
||||||
|
background-color: #444444;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-list-base {
|
||||||
|
--mdc-list-list-item-label-text-color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
::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
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-overview {
|
||||||
|
width: 25%;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-btn {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border: 0 solid #000000;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-last-btn {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-no-deco {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-no-deco:hover{
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray-text {
|
||||||
|
color: #6e6e6e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellowBtn {
|
||||||
|
background-color: #f39c12;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryBtn {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-card-header-text {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-del-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-actions {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-actions {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskgroup-first-child {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border: 0 solid #000000;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-name {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-number{
|
||||||
|
color: white;
|
||||||
|
background-color: deepskyblue;
|
||||||
|
padding: 1px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.treenode-content-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overdue-taskgroup {
|
||||||
|
background-color: #ff695b;
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<mat-action-list style="padding: 0">
|
||||||
|
<button mat-list-item class="lightBlueBtn" [routerLink]="['/taskgroups']">Manage Taskgroups</button>
|
||||||
|
</mat-action-list>
|
||||||
|
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" >
|
||||||
|
<!-- This is the tree node template for leaf nodes -->
|
||||||
|
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding class="taskgroup-btn" matTreeNodePaddingIndent="10" [ngClass]="node.hasOverdueTask? 'overdue-taskgroup':''">
|
||||||
|
<!-- use a disabled button to provide padding for tree leaf -->
|
||||||
|
<button mat-icon-button disabled></button>
|
||||||
|
<div class="treenode-content-container">
|
||||||
|
<button mat-button class="node-name" (click)="onSelectTaskgroup(node.tasks, node.taskgroupID)">{{node.name}}</button>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<div class="task-number">{{node.activeTasks}}</div>
|
||||||
|
</div>
|
||||||
|
</mat-tree-node>
|
||||||
|
<!-- This is the tree node template for expandable nodes -->
|
||||||
|
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding class="taskgroup-btn" matTreeNodePaddingIndent="10" [ngClass]="node.hasOverdueTask? 'overdue-taskgroup':''">
|
||||||
|
<button mat-icon-button matTreeNodeToggle
|
||||||
|
[attr.aria-label]="'Toggle ' + node.name" style="padding-left: 10px">
|
||||||
|
<mat-icon class="mat-icon-rtl-mirror">
|
||||||
|
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
|
||||||
|
</mat-icon>
|
||||||
|
</button>
|
||||||
|
<div class="treenode-content-container">
|
||||||
|
<button mat-button class="node-name" (click)="onSelectTaskgroup(node.tasks, node.taskgroupID)">{{node.name}}</button>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<div class="task-number">{{node.activeTasks}}</div>
|
||||||
|
</div>
|
||||||
|
</mat-tree-node>
|
||||||
|
</mat-tree>
|
@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TaskgroupOverviewComponent } from './taskgroup-overview.component';
|
||||||
|
|
||||||
|
describe('TaskgroupOverviewComponent', () => {
|
||||||
|
let component: TaskgroupOverviewComponent;
|
||||||
|
let fixture: ComponentFixture<TaskgroupOverviewComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [TaskgroupOverviewComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(TaskgroupOverviewComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,81 @@
|
|||||||
|
import {Component, EventEmitter, Output} from '@angular/core';
|
||||||
|
import {MatIconModule} from "@angular/material/icon";
|
||||||
|
import {MatButtonModule} from "@angular/material/button";
|
||||||
|
import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from "@angular/material/tree";
|
||||||
|
import {FlatTreeControl} from "@angular/cdk/tree";
|
||||||
|
import {RecursiveTaskgroupInfo, TaskgroupService, TaskOverviewInfo} from "../../../api";
|
||||||
|
import {TaskOverviewComponent} from "../task-overview/task-overview.component";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Flat node with expandable and level information */
|
||||||
|
interface ExampleFlatNode {
|
||||||
|
expandable: boolean;
|
||||||
|
level: number;
|
||||||
|
name: string
|
||||||
|
activeTasks: number;
|
||||||
|
hasOverdueTask: boolean;
|
||||||
|
tasks: TaskOverviewInfo[];
|
||||||
|
taskgroupID: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskOverviewData {
|
||||||
|
tasks: TaskOverviewInfo[],
|
||||||
|
taskgroupID: number
|
||||||
|
}
|
||||||
|
@Component({
|
||||||
|
selector: 'app-taskgroup-overview',
|
||||||
|
templateUrl: './taskgroup-overview.component.html',
|
||||||
|
styleUrls: ['./taskgroup-overview.component.css']
|
||||||
|
})
|
||||||
|
export class TaskgroupOverviewComponent {
|
||||||
|
|
||||||
|
@Output('taskgroupSelected') taskgroupSelected: EventEmitter<TaskOverviewData> = new EventEmitter<TaskOverviewData>();
|
||||||
|
private _transformer = (node: RecursiveTaskgroupInfo, level: number) => {
|
||||||
|
return {
|
||||||
|
expandable: !!node.childTaskgroups && node.childTaskgroups.length > 0,
|
||||||
|
name: node.taskgroupName,
|
||||||
|
level: level,
|
||||||
|
activeTasks: node.amountActiveTasks,
|
||||||
|
hasOverdueTask: node.hasOverdueTask,
|
||||||
|
tasks: node.activeTasks,
|
||||||
|
taskgroupID: node.taskgroupID
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
treeControl = new FlatTreeControl<ExampleFlatNode>(
|
||||||
|
node => node.level,
|
||||||
|
node => node.expandable,
|
||||||
|
);
|
||||||
|
|
||||||
|
treeFlattener = new MatTreeFlattener(
|
||||||
|
this._transformer,
|
||||||
|
node => node.level,
|
||||||
|
node => node.expandable,
|
||||||
|
node => node.childTaskgroups,
|
||||||
|
);
|
||||||
|
|
||||||
|
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
||||||
|
|
||||||
|
constructor(private taskgroupService: TaskgroupService) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.taskgroupService.taskgroupsTreeGet().subscribe({
|
||||||
|
next: resp => {
|
||||||
|
this.dataSource.data = resp;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
|
||||||
|
|
||||||
|
onSelectTaskgroup(tasks: TaskOverviewInfo[], taskgroupID: number) {
|
||||||
|
this.taskgroupSelected.emit({
|
||||||
|
tasks: tasks,
|
||||||
|
taskgroupID: taskgroupID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<app-dashboard></app-dashboard>
|
@ -29,7 +29,7 @@
|
|||||||
<ng-container matColumnDef="finished">
|
<ng-container matColumnDef="finished">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Finished </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Finished </th>
|
||||||
<td mat-cell *matCellDef="let task">
|
<td mat-cell *matCellDef="let task">
|
||||||
<mat-checkbox [value]="task.finished" [contentEditable]="false" disableRipple="true" (click)="$event.preventDefault()"></mat-checkbox>
|
<mat-checkbox [checked]="task.finished" [contentEditable]="false" disableRipple="true" (click)="$event.preventDefault()"></mat-checkbox>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="status">
|
<ng-container matColumnDef="status">
|
||||||
|
@ -22,6 +22,8 @@ export class TaskDashboardComponent implements OnChanges{
|
|||||||
this.datasource = new MatTableDataSource<TaskEntityInfo>(resp);
|
this.datasource = new MatTableDataSource<TaskEntityInfo>(resp);
|
||||||
this.datasource.paginator = this.paginator!;
|
this.datasource.paginator = this.paginator!;
|
||||||
this.datasource.sort = this.sort!;
|
this.datasource.sort = this.sort!;
|
||||||
|
|
||||||
|
resp.forEach(task => console.log(task))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
311
openapi.yaml
311
openapi.yaml
@ -581,6 +581,23 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/TaskgroupEntityInfo'
|
$ref: '#/components/schemas/TaskgroupEntityInfo'
|
||||||
|
/taskgroups/tree:
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- API_TOKEN: []
|
||||||
|
tags:
|
||||||
|
- taskgroup
|
||||||
|
summary: list all top level taskgroups of authorized user
|
||||||
|
description: list all taskgroups of authorized user
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Anfrage erfolgreich
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/RecursiveTaskgroupInfo'
|
||||||
/taskgroups:
|
/taskgroups:
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
@ -1154,6 +1171,44 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
$ref: "#/components/schemas/SimpleStatusResponse"
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
|
/tasks/{taskID}/finish:
|
||||||
|
post:
|
||||||
|
security:
|
||||||
|
- API_TOKEN: []
|
||||||
|
tags:
|
||||||
|
- task
|
||||||
|
summary: finishs task
|
||||||
|
description: finish tasks
|
||||||
|
parameters:
|
||||||
|
- name: taskID
|
||||||
|
in: path
|
||||||
|
description: internal id of task
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Anfrage erfolgreich
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
|
403:
|
||||||
|
description: No permission
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
|
404:
|
||||||
|
description: Taskgroup does not exist
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
/schedules:
|
/schedules:
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
@ -1355,7 +1410,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
$ref: "#/components/schemas/SimpleStatusResponse"
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
/schedules/today:
|
/schedules/today/{activateable}:
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
- API_TOKEN: []
|
- API_TOKEN: []
|
||||||
@ -1363,6 +1418,15 @@ paths:
|
|||||||
- schedule
|
- schedule
|
||||||
description: get all schedules of today
|
description: get all schedules of today
|
||||||
summary: get today's schedules
|
summary: get today's schedules
|
||||||
|
parameters:
|
||||||
|
- name: activateable
|
||||||
|
in: path
|
||||||
|
description: determines whether only schedules that can be started should be included or all schedules of today
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Operation successfull
|
description: Operation successfull
|
||||||
@ -1395,7 +1459,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
$ref: '#/components/schemas/BasicScheduleEntityInfo'
|
$ref: '#/components/schemas/ScheduleInfo'
|
||||||
403:
|
403:
|
||||||
description: No permission
|
description: No permission
|
||||||
content:
|
content:
|
||||||
@ -1417,6 +1481,146 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
$ref: "#/components/schemas/SimpleStatusResponse"
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
|
/schedules/active:
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- API_TOKEN: []
|
||||||
|
tags:
|
||||||
|
- schedule
|
||||||
|
description: get active schedule
|
||||||
|
summary: get active schedule
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: operation successfull
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ScheduleInfo'
|
||||||
|
404:
|
||||||
|
description: there is no active schedule
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SimpleStatusResponse'
|
||||||
|
/schedules/{scheduleID}/activate:
|
||||||
|
post:
|
||||||
|
security:
|
||||||
|
- API_TOKEN: []
|
||||||
|
tags:
|
||||||
|
- schedule
|
||||||
|
description: activates schedule
|
||||||
|
summary: activates schedule
|
||||||
|
parameters:
|
||||||
|
- name: scheduleID
|
||||||
|
in: path
|
||||||
|
description: internal id of schedule
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Operation successfull
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$ref: '#/components/schemas/ScheduleActivateInfo'
|
||||||
|
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"
|
||||||
|
409:
|
||||||
|
description: Task is already running
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
|
/schedules/{scheduleID}/stop/{finish}:
|
||||||
|
post:
|
||||||
|
security:
|
||||||
|
- API_TOKEN: []
|
||||||
|
tags:
|
||||||
|
- schedule
|
||||||
|
parameters:
|
||||||
|
- name: scheduleID
|
||||||
|
in: path
|
||||||
|
description: internal id of schedule
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
- name: finish
|
||||||
|
in: path
|
||||||
|
description: internal id of schedule
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: True
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: No permission
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$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"
|
||||||
|
/schedules/status/today:
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- API_TOKEN: []
|
||||||
|
tags:
|
||||||
|
- schedule
|
||||||
|
description: get number of worked minutes today
|
||||||
|
summary: get number of active minutes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: operation successfull
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ScheduleStatus'
|
||||||
|
404:
|
||||||
|
description: User not found
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
$ref: "#/components/schemas/SimpleStatusResponse"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
components:
|
components:
|
||||||
@ -1834,3 +2038,106 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: name of task
|
description: name of task
|
||||||
example: "Vorlesung zusammenfassen"
|
example: "Vorlesung zusammenfassen"
|
||||||
|
ScheduleActivateInfo:
|
||||||
|
required:
|
||||||
|
- startTime
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
startTime:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: point in time at which the schedule was started
|
||||||
|
TaskScheduleStopResponse:
|
||||||
|
required:
|
||||||
|
- workTime
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
workTime:
|
||||||
|
type: number
|
||||||
|
description: time where the schedule was active
|
||||||
|
example: 10
|
||||||
|
RecursiveTaskgroupInfo:
|
||||||
|
required:
|
||||||
|
- taskgroupID
|
||||||
|
- taskgroupName
|
||||||
|
- childTaskgroups
|
||||||
|
- activeTasks
|
||||||
|
- hasOverdueTask
|
||||||
|
- amountActiveTasks
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
taskgroupID:
|
||||||
|
type: number
|
||||||
|
description: internal id of taskgroup
|
||||||
|
example: 1
|
||||||
|
taskgroupName:
|
||||||
|
type: string
|
||||||
|
description: name of taskgroup
|
||||||
|
example: Taskgroup 1
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
childTaskgroups:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/RecursiveTaskgroupInfo'
|
||||||
|
activeTasks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/TaskOverviewInfo'
|
||||||
|
hasOverdueTask:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
description: determines whether the taskgroup has an overdue task or not
|
||||||
|
amountActiveTasks:
|
||||||
|
type: number
|
||||||
|
example: 2
|
||||||
|
description: determines the number of active tasks that can be scheduled
|
||||||
|
TaskOverviewInfo:
|
||||||
|
required:
|
||||||
|
- taskID
|
||||||
|
- taskName
|
||||||
|
- activeMinutes
|
||||||
|
- eta
|
||||||
|
- limit
|
||||||
|
- overdue
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
taskID:
|
||||||
|
type: number
|
||||||
|
description: internal id of task
|
||||||
|
example: 1
|
||||||
|
taskName:
|
||||||
|
type: string
|
||||||
|
description: name of task
|
||||||
|
example: Vorlesung schauen
|
||||||
|
eta:
|
||||||
|
type: number
|
||||||
|
description: expected time to finish task
|
||||||
|
example: 10
|
||||||
|
minimum: 0
|
||||||
|
limit:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
description: date until the task has to be finished
|
||||||
|
overdue:
|
||||||
|
type: boolean
|
||||||
|
description: determines whether the task is overdue
|
||||||
|
example: True
|
||||||
|
activeTime:
|
||||||
|
type: number
|
||||||
|
description: number in minutes that was already worked on this task
|
||||||
|
example: 10
|
||||||
|
ScheduleStatus:
|
||||||
|
required:
|
||||||
|
- activeMinutes
|
||||||
|
- missedSchedules
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
activeMinutes:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
description: number of minutes it was worked today
|
||||||
|
missedSchedules:
|
||||||
|
type: boolean
|
||||||
|
description: states whether a schedule was missed or not
|
||||||
|
example: true
|
Loading…
Reference in New Issue
Block a user