Merge pull request 'issue-25' (#27) from issue-25 into issue-18

Reviewed-on: Sebastian/TimeManager#27
This commit is contained in:
Sebastian 2023-10-28 19:33:05 +02:00
commit 79d280b3c0
51 changed files with 2606 additions and 80 deletions

View File

@ -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 @@
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;swagger&quot;, &quot;settings.editor.selected.configurable&quot;: &quot;swagger&quot;,
&quot;ts.external.directory.path&quot;: &quot;/snap/intellij-idea-ultimate/459/plugins/javascript-impl/jsLanguageServicesImpl/external&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}, },
&quot;keyToStringList&quot;: { &quot;keyToStringList&quot;: {
@ -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>

View File

@ -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())));
}
} }

View File

@ -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"));
}
} }

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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;
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
} }

View File

@ -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;
} }

View File

@ -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;
}
} }

View File

@ -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();
} }

View File

@ -4,5 +4,6 @@ public enum ServiceExitCode {
OK, OK,
ENTITY_ALREADY_EXIST, ENTITY_ALREADY_EXIST,
MISSING_ENTITY; MISSING_ENTITY,
INVALID_OPERATION;
} }

View File

@ -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;
}
} }

View File

@ -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);
}
}
} }

View File

@ -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

View File

@ -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\&#39;s schedules * get today\&#39;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_,

View 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
}
);
}
}

View File

@ -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

View File

@ -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
}
);
}
} }

View 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;
}

View File

@ -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';

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@ -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,43 +93,48 @@ import { ScheduleDashboardComponent } from './schedules/schedule-dashboard/sched
TaskDetailOverviewComponent, TaskDetailOverviewComponent,
SchedulerComponent, SchedulerComponent,
BasicSchedulerComponent, BasicSchedulerComponent,
ScheduleDashboardComponent ScheduleDashboardComponent,
], DashboardComponent,
imports: [ ActiveScheduleComponent,
BrowserModule, TaskgroupOverviewComponent,
AppRoutingModule, TaskOverviewComponent
FormsModule,
ApiModule,
HttpClientModule,
BrowserAnimationsModule,
MatToolbarModule,
MatButtonModule,
MatIconModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatMenuModule,
MatTabsModule,
MatTableModule,
MatCheckboxModule,
MatCardModule,
ReactiveFormsModule,
MatListModule,
MatDatepickerModule,
MatInputModule,
MatDatepickerModule,
MatMomentDateModule,
FormsModule,
MatSlideToggleModule,
MatSortModule,
MatPaginatorModule,
MatProgressBarModule,
MatExpansionModule,
CalendarModule.forRoot({provide: DateAdapter, useFactory: adapterFactory}),
MatSelectModule
], ],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
ApiModule,
HttpClientModule,
BrowserAnimationsModule,
MatToolbarModule,
MatButtonModule,
MatIconModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatMenuModule,
MatTabsModule,
MatTableModule,
MatCheckboxModule,
MatCardModule,
ReactiveFormsModule,
MatListModule,
MatDatepickerModule,
MatInputModule,
MatDatepickerModule,
MatMomentDateModule,
FormsModule,
MatSlideToggleModule,
MatSortModule,
MatPaginatorModule,
MatProgressBarModule,
MatExpansionModule,
CalendarModule.forRoot({provide: DateAdapter, useFactory: adapterFactory}),
MatSelectModule,
MatTreeModule
],
providers: [ providers: [
HttpClientModule, HttpClientModule,
/* /*

View File

@ -0,0 +1,6 @@
import {ScheduleInfo} from "../../../api";
export interface StopActiveScheduleInfo {
schedule: ScheduleInfo
workedMinutes: number
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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
}
}

View 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;
}

View 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>

View 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();
});
});

View 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)
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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
})
}
})
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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
})
}
}

View File

@ -0,0 +1 @@
<app-dashboard></app-dashboard>

View File

@ -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">

View File

@ -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))
} }
}) })
} }

View File

@ -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:
@ -1833,4 +2037,107 @@ components:
taskName: taskName:
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