issue-18 #28

Merged
sebastian merged 55 commits from issue-18 into master 2023-10-28 19:36:14 +02:00
51 changed files with 2606 additions and 80 deletions
Showing only changes of commit 79d280b3c0 - Show all commits

View File

@ -4,11 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<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$/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/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>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -60,7 +62,9 @@
&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.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&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;keyToStringList&quot;: {
@ -99,7 +103,11 @@
<workItem from="1698067098771" duration="4770000" />
<workItem from="1698127431684" duration="2039000" />
<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 id="LOCAL-00001" summary="Structure Taskgroups in Hierarchies">
<option name="closed" value="true" />
@ -165,7 +173,71 @@
<option name="project" value="LOCAL" />
<updated>1698168406655</updated>
</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 />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -182,7 +254,14 @@
<MESSAGE value="Delete Schedules" />
<MESSAGE value="Deliver Schedule Path Info when fetching Schedules" />
<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 name="XDebuggerManager">
<breakpoint-manager>
@ -192,11 +271,6 @@
<line>52</line>
<option name="timeStamp" value="1" />
</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>
</breakpoint-manager>
</component>

View File

@ -1,12 +1,12 @@
package core.api.controller;
import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.taskSchedule.BasicTaskScheduleEntityInfo;
import core.api.models.timemanager.taskSchedule.BasicTaskScheduleFieldInfo;
import core.api.models.timemanager.taskSchedule.ScheduleInfo;
import core.api.models.timemanager.taskSchedule.*;
import core.entities.User;
import core.entities.timemanager.BasicTaskSchedule;
import core.entities.timemanager.ScheduleType;
import core.entities.timemanager.Task;
import core.repositories.UserRepository;
import core.repositories.timemanager.BasicTaskScheduleRepository;
import core.services.*;
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 javax.validation.Valid;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.*;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@ -27,12 +25,15 @@ public class ScheduleController {
private final TaskService taskService;
private final TaskScheduleService taskScheduleService;
private final BasicTaskScheduleRepository basicTaskScheduleRepository;
private final UserRepository userRepository;
public ScheduleController(@Autowired TaskService taskService, @Autowired TaskScheduleService taskScheduleService,
BasicTaskScheduleRepository basicTaskScheduleRepository) {
BasicTaskScheduleRepository basicTaskScheduleRepository,
UserRepository userRepository) {
this.taskService = taskService;
this.taskScheduleService = taskScheduleService;
this.basicTaskScheduleRepository = basicTaskScheduleRepository;
this.userRepository = userRepository;
}
@GetMapping("/schedules/{taskID}/{scheduleType}")
@ -102,9 +103,9 @@ public class ScheduleController {
return ResponseEntity.ok(new SimpleStatusResponse("success"));
}
@GetMapping("/schedules/today")
public ResponseEntity<?> loadTodaysSchedules() {
ServiceResult<List<BasicTaskSchedule>> todaysSchedules = taskScheduleService.loadTodaysSchedule(SecurityContextHolder.getContext().getAuthentication().getName());
@GetMapping("/schedules/today/{activateable}")
public ResponseEntity<?> loadTodaysSchedules(@PathVariable boolean activateable) {
ServiceResult<List<BasicTaskSchedule>> todaysSchedules = taskScheduleService.loadTodaysSchedule(SecurityContextHolder.getContext().getAuthentication().getName(), activateable);
if(todaysSchedules.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
} else {
@ -136,7 +137,68 @@ public class ScheduleController {
if(scheduleResult.getExitCode() == ServiceExitCode.ENTITY_ALREADY_EXIST) {
return ResponseEntity.status(409).body(new SimpleStatusResponse("failed"));
} 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;
import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.taskSchedule.ScheduleInfo;
import core.api.models.timemanager.tasks.TaskEntityInfo;
import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.entities.timemanager.Task;
@ -110,4 +111,19 @@ public class TaskController {
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;
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.TaskgroupEntityInfo;
import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo;
@ -80,6 +81,13 @@ public class TaskgroupController {
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}")
public ResponseEntity<?> getDetails(@PathVariable long taskgroupID) {
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();
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){
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();
}
public ScheduleInfo() {
this.scheduleID = -1;
}
public long getScheduleID() {
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 java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "basic_schedules")
@ -75,4 +76,21 @@ public class BasicTaskSchedule {
public void setFinishedTime(LocalDateTime 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 java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@ -27,7 +29,7 @@ public class Task {
private boolean finished;
@OneToMany(mappedBy = "task", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<BasicTaskSchedule> basicTaskSchedules;
private List<BasicTaskSchedule> basicTaskSchedules;
private int workTime;
@ -128,11 +130,11 @@ public class Task {
this.taskID = taskID;
}
public Set<BasicTaskSchedule> getBasicTaskSchedules() {
public List<BasicTaskSchedule> getBasicTaskSchedules() {
return basicTaskSchedules;
}
public void setBasicTaskSchedules(Set<BasicTaskSchedule> basicTaskSchedules) {
public void setBasicTaskSchedules(List<BasicTaskSchedule> basicTaskSchedules) {
this.basicTaskSchedules = basicTaskSchedules;
}

View File

@ -97,4 +97,22 @@ public class Taskgroup {
}
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 java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Repository
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")
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,
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.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@ -52,10 +56,22 @@ public class TaskScheduleService {
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);
return user.map(value -> new ServiceResult<>(basicTaskScheduleRepository.findAllByUserAndDate(value, LocalDate.now()))).orElseGet(() ->
new ServiceResult<>(ServiceExitCode.MISSING_ENTITY));
if(user.isEmpty()) {
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) {
@ -70,6 +86,7 @@ public class TaskScheduleService {
return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST);
} else {
BasicTaskSchedule basicTaskSchedule = new BasicTaskSchedule(task, LocalDate.now());
basicTaskSchedule.setStartTime(LocalDateTime.now());
task.getBasicTaskSchedules().add(basicTaskSchedule);
basicTaskScheduleRepository.save(basicTaskSchedule);
taskRepository.save(task);
@ -80,4 +97,63 @@ public class TaskScheduleService {
public void deleteScheduleByTask(Task 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;
import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.entities.timemanager.BasicTaskSchedule;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import core.repositories.timemanager.BasicTaskScheduleRepository;
@ -9,8 +10,8 @@ import core.repositories.timemanager.TaskgroupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Optional;
import java.time.LocalDateTime;
import java.util.*;
@Service
public class TaskService {
@ -75,4 +76,26 @@ public class TaskService {
public void clearTasks(Taskgroup 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/propertyInfo.ts
model/propertyUpdateRequest.ts
model/recursiveTaskgroupInfo.ts
model/scheduleActivateInfo.ts
model/scheduleInfo.ts
model/scheduleStatus.ts
model/signUpRequest.ts
model/simpleStatusResponse.ts
model/taskEntityInfo.ts
model/taskFieldInfo.ts
model/taskOverviewInfo.ts
model/taskScheduleStopResponse.ts
model/taskShortInfo.ts
model/taskgroupDetailInfo.ts
model/taskgroupEntityInfo.ts

View File

@ -20,8 +20,11 @@ import { Observable } from 'rxjs';
import { BasicScheduleEntityInfo } from '../model/models';
import { BasicScheduleFieldInfo } from '../model/models';
import { ScheduleActivateInfo } from '../model/models';
import { ScheduleInfo } from '../model/models';
import { ScheduleStatus } from '../model/models';
import { SimpleStatusResponse } from '../model/models';
import { TaskScheduleStopResponse } from '../model/models';
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration';
@ -88,6 +91,61 @@ export class ScheduleService {
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
@ -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 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 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 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?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<BasicScheduleEntityInfo>>;
public schedulesTaskIDNowPost(taskID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<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<ScheduleInfo>>;
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> {
if (taskID === null || taskID === undefined) {
throw new Error('Required parameter taskID was null or undefined when calling schedulesTaskIDNowPost.');
@ -393,7 +628,7 @@ export class ScheduleService {
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,
{
context: localVarHttpContext,
@ -472,13 +707,17 @@ export class ScheduleService {
/**
* get today\&#39;s schedules
* 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 reportProgress flag to report request and response progress.
*/
public schedulesTodayGet(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 schedulesTodayGet(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?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<ScheduleInfo>>;
public schedulesTodayActivateableGet(activateable: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<ScheduleInfo>>>;
public schedulesTodayActivateableGet(activateable: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<ScheduleInfo>>>;
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;
@ -512,7 +751,7 @@ export class ScheduleService {
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,
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
* loads information about a given task

View File

@ -20,6 +20,7 @@ import { Observable } from 'rxjs';
import { InlineResponse200 } from '../model/models';
import { InlineResponse403 } from '../model/models';
import { RecursiveTaskgroupInfo } from '../model/models';
import { SimpleStatusResponse } from '../model/models';
import { TaskgroupDetailInfo } 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 './propertyInfo';
export * from './propertyUpdateRequest';
export * from './recursiveTaskgroupInfo';
export * from './scheduleActivateInfo';
export * from './scheduleInfo';
export * from './scheduleStatus';
export * from './signUpRequest';
export * from './simpleStatusResponse';
export * from './taskEntityInfo';
export * from './taskFieldInfo';
export * from './taskOverviewInfo';
export * from './taskScheduleStopResponse';
export * from './taskShortInfo';
export * from './taskgroupDetailInfo';
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 { BasicSchedulerComponent } from './schedules/basic-scheduler/basic-scheduler.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({
declarations: [
AppComponent,
@ -88,43 +93,48 @@ import { ScheduleDashboardComponent } from './schedules/schedule-dashboard/sched
TaskDetailOverviewComponent,
SchedulerComponent,
BasicSchedulerComponent,
ScheduleDashboardComponent
],
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
ScheduleDashboardComponent,
DashboardComponent,
ActiveScheduleComponent,
TaskgroupOverviewComponent,
TaskOverviewComponent
],
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: [
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">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Finished </th>
<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>
</ng-container>
<ng-container matColumnDef="status">

View File

@ -22,6 +22,8 @@ export class TaskDashboardComponent implements OnChanges{
this.datasource = new MatTableDataSource<TaskEntityInfo>(resp);
this.datasource.paginator = this.paginator!;
this.datasource.sort = this.sort!;
resp.forEach(task => console.log(task))
}
})
}

View File

@ -581,6 +581,23 @@ paths:
type: array
items:
$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:
get:
security:
@ -1154,6 +1171,44 @@ paths:
schema:
type: object
$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:
get:
security:
@ -1355,7 +1410,7 @@ paths:
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
/schedules/today:
/schedules/today/{activateable}:
get:
security:
- API_TOKEN: []
@ -1363,6 +1418,15 @@ paths:
- schedule
description: get all schedules of today
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:
200:
description: Operation successfull
@ -1395,7 +1459,7 @@ paths:
application/json:
schema:
type: object
$ref: '#/components/schemas/BasicScheduleEntityInfo'
$ref: '#/components/schemas/ScheduleInfo'
403:
description: No permission
content:
@ -1417,6 +1481,146 @@ paths:
schema:
type: object
$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:
@ -1834,3 +2038,106 @@ components:
type: string
description: name of task
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