Merge pull request 'issue-10' (#17) from issue-10 into master

Reviewed-on: Sebastian/TimeManager#17
This commit is contained in:
Sebastian 2023-10-22 11:40:24 +02:00
commit 4cab32fae3
70 changed files with 8382 additions and 14003 deletions

View File

@ -9,4 +9,6 @@ npm install @openapitools/openapi-generator-cli -g
2. Generate Code 2. Generate Code
```bash ```bash
npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-angular -o frontend/src/api/ npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-angular -o frontend/src/api/
``` ```
Notiz: Angular Calendar https://www.npmjs.com/package/angular-calendar

View File

@ -1,2 +0,0 @@
#n:information_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -1,2 +0,0 @@
#n:mysql
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -1,2 +0,0 @@
#n:performance_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@ -1,2 +0,0 @@
#n:restservice
!<md> [1695647618000, 0, null, null, -2147483648, -2147483648]

View File

@ -9,4 +9,7 @@
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK" />
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project> </project>

View File

@ -4,19 +4,46 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Structure Taskgroups in Hierarchies"> <list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Delete and clear Tasks (Frontend)">
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupDetailInfo.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupShortInfo.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/core/api/controller/TaskgroupController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/controller/TaskgroupController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/entities/timemanager/Taskgroup.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/entities/timemanager/Taskgroup.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/.openapi-generator/FILES" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/.openapi-generator/FILES" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/api/taskgroup.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/api/taskgroup.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/api/model/models.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/api/model/models.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/app.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/app.module.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.css" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../openapi.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../openapi.yaml" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" /> <option name="LAST_RESOLUTION" value="IGNORE" />
</component> </component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Interface" />
<option value="Class" />
</list>
</option>
</component>
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$/.." value="master" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component> </component>
<component name="JpbToolWindowState">
<option name="isToolWindowVisible" value="false" />
</component>
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;, &quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 6 &quot;associatedIndex&quot;: 6
@ -35,11 +62,14 @@
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;, &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;, &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;git-widget-placeholder&quot;: &quot;issue-7&quot;, &quot;git-widget-placeholder&quot;: &quot;issue-11-angular-update&quot;,
&quot;last_directory_selection&quot;: &quot;D:/Programmierprojekte/TimeManager/backend/src/main/java/core/api/models/timemanager&quot;,
&quot;last_opened_file_path&quot;: &quot;D:/Programmierprojekte/Dicewars/client&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.lookFeel&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}, },
&quot;keyToStringList&quot;: { &quot;keyToStringList&quot;: {
@ -67,6 +97,12 @@
<updated>1695647243767</updated> <updated>1695647243767</updated>
<workItem from="1695647249321" duration="397000" /> <workItem from="1695647249321" duration="397000" />
<workItem from="1696183811713" duration="4994000" /> <workItem from="1696183811713" duration="4994000" />
<workItem from="1696399523081" duration="666000" />
<workItem from="1696517800445" duration="3887000" />
<workItem from="1696573678147" duration="111000" />
<workItem from="1697923629354" duration="3164000" />
<workItem from="1697958018921" duration="91000" />
<workItem from="1697958118995" duration="5749000" />
</task> </task>
<task id="LOCAL-00001" summary="Structure Taskgroups in Hierarchies"> <task id="LOCAL-00001" summary="Structure Taskgroups in Hierarchies">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -76,7 +112,39 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1696188127467</updated> <updated>1696188127467</updated>
</task> </task>
<option name="localTasksCounter" value="2" /> <task id="LOCAL-00002" summary="Datastructure for Tasks">
<option name="closed" value="true" />
<created>1696233406235</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1696233406235</updated>
</task>
<task id="LOCAL-00003" summary="Implementing edit route for tasks">
<option name="closed" value="true" />
<created>1697929392755</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1697929392755</updated>
</task>
<task id="LOCAL-00004" summary="Delete and clear Tasks">
<option name="closed" value="true" />
<created>1697962016013</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1697962016013</updated>
</task>
<task id="LOCAL-00005" summary="Delete and clear Tasks (Frontend)">
<option name="closed" value="true" />
<created>1697962125518</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1697962125518</updated>
</task>
<option name="localTasksCounter" value="6" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -84,7 +152,12 @@
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<MESSAGE value="Structure Taskgroups in Hierarchies" /> <MESSAGE value="Structure Taskgroups in Hierarchies" />
<option name="LAST_COMMIT_MESSAGE" value="Structure Taskgroups in Hierarchies" /> <MESSAGE value="Update gitignore for idea folder" />
<MESSAGE value="Datastructure for Tasks" />
<MESSAGE value="Implementing edit route for tasks" />
<MESSAGE value="Delete and clear Tasks" />
<MESSAGE value="Delete and clear Tasks (Frontend)" />
<option name="LAST_COMMIT_MESSAGE" value="Delete and clear Tasks (Frontend)" />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>

View File

@ -0,0 +1,99 @@
package core.api.controller;
import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.tasks.TaskEntityInfo;
import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import core.services.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api")
public class TaskController {
private final TaskService taskService;
private final TaskgroupService taskgroupService;
public TaskController(@Autowired TaskService taskService, @Autowired TaskgroupService taskgroupService) {
this.taskService = taskService;
this.taskgroupService = taskgroupService;
}
@GetMapping("/tasks/{taskgroupID}/{status}")
public ResponseEntity<?> listTasksOfTaskgroup(@PathVariable long taskgroupID, @PathVariable String status) {
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());
if(!taskgroupPermissionResult.isHasPermissions()) {
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
}
if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
}
return ResponseEntity.ok(taskgroupPermissionResult.getResult().getTasks().stream().map(TaskEntityInfo::new));
}
@PutMapping("/tasks/{taskgroupID}")
public ResponseEntity<?> createTask(@PathVariable long taskgroupID, @RequestBody @Valid TaskFieldInfo taskFieldInfo) {
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());
if(!taskgroupPermissionResult.isHasPermissions()) {
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
}
if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
}
ServiceResult<Task> creationResult = taskService.createTask(taskgroupPermissionResult.getResult(), taskFieldInfo);
if(creationResult.getExitCode() == ServiceExitCode.ENTITY_ALREADY_EXIST) {
return ResponseEntity.status(409).body(new SimpleStatusResponse("failed"));
} else {
return ResponseEntity.ok(new TaskEntityInfo(creationResult.getResult()));
}
}
@PostMapping("/tasks/{taskID}")
public ResponseEntity<?> editTask(@PathVariable long taskID, @RequestBody @Valid TaskFieldInfo taskFieldInfo) {
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"));
}
if(taskFieldInfo.getStartDate().isAfter(taskFieldInfo.getDeadline())) {
return ResponseEntity.status(400).body(new SimpleStatusResponse("failed"));
}
ServiceResult<Task> editorResult = taskService.editTask(taskPermissionResult.getResult(), taskFieldInfo);
if (editorResult.getExitCode() == ServiceExitCode.OK) {
return ResponseEntity.ok(new SimpleStatusResponse("success"));
} else {
return ResponseEntity.status(409).body(new SimpleStatusResponse("failed"));
}
}
@DeleteMapping("/tasks/{taskID}")
public ResponseEntity<?> deleteTask(@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.deleteTask(taskPermissionResult.getResult());
return ResponseEntity.ok(new SimpleStatusResponse("success"));
}
}

View File

@ -1,16 +1,13 @@
package core.api.controller; package core.api.controller;
import core.api.models.auth.SimpleStatusResponse; import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.taskgroup.TaskgroupDetailInfo;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo; import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo;
import core.entities.timemanager.Taskgroup; import core.entities.timemanager.Taskgroup;
import core.services.PermissionResult; import core.services.*;
import core.services.ServiceExitCode;
import core.services.ServiceResult;
import core.services.TaskgroupService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.config.Task;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -24,9 +21,11 @@ import java.util.Set;
public class TaskgroupController { public class TaskgroupController {
private final TaskgroupService taskgroupService; private final TaskgroupService taskgroupService;
private final TaskService taskService;
public TaskgroupController(@Autowired TaskgroupService taskgroupService) { public TaskgroupController(@Autowired TaskgroupService taskgroupService, @Autowired TaskService taskService) {
this.taskgroupService = taskgroupService; this.taskgroupService = taskgroupService;
this.taskService = taskService;
} }
@PutMapping("/taskgroups") @PutMapping("/taskgroups")
@ -92,7 +91,21 @@ public class TaskgroupController {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
} }
Set<Taskgroup> children = taskgroupPermissionResult.getResult().getChildren(); return ResponseEntity.ok(new TaskgroupDetailInfo(taskgroupPermissionResult.getResult()));
return ResponseEntity.ok(children.stream().map(TaskgroupEntityInfo::new)); }
@DeleteMapping("/taskgroups/{taskgroupID}/clear")
public ResponseEntity<?> clearTasks(@PathVariable long taskgroupID) {
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());
if (!taskgroupPermissionResult.isHasPermissions()) {
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
}
if (taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
}
taskService.clearTasks(taskgroupPermissionResult.getResult());
return ResponseEntity.ok(new SimpleStatusResponse("success"));
} }
} }

View File

@ -0,0 +1,45 @@
package core.api.models.timemanager.taskgroup;
import core.entities.timemanager.Taskgroup;
import org.springframework.scheduling.config.Task;
import java.util.ArrayList;
import java.util.List;
public class TaskgroupDetailInfo {
private List<TaskgroupEntityInfo> children;
private List<TaskgroupEntityInfo> ancestors;
private TaskgroupEntityInfo taskgroupInfo;
public TaskgroupDetailInfo(Taskgroup taskgroup) {
this.children = taskgroup.getChildren().stream().map(TaskgroupEntityInfo::new).toList();
List<Taskgroup> ancestorList = Taskgroup.getAncestorList(taskgroup);
this.ancestors = ancestorList.stream().map(TaskgroupEntityInfo::new).toList();
this.taskgroupInfo = new TaskgroupEntityInfo(taskgroup);
}
public List<TaskgroupEntityInfo> getChildren() {
return children;
}
public void setChildren(List<TaskgroupEntityInfo> children) {
this.children = children;
}
public List<TaskgroupEntityInfo> getAncestors() {
return ancestors;
}
public void setAncestors(List<TaskgroupEntityInfo> ancestors) {
this.ancestors = ancestors;
}
public TaskgroupEntityInfo getTaskgroupInfo() {
return taskgroupInfo;
}
public void setTaskgroupInfo(TaskgroupEntityInfo taskgroupInfo) {
this.taskgroupInfo = taskgroupInfo;
}
}

View File

@ -7,17 +7,9 @@ public class TaskgroupEntityInfo {
private long taskgroupID; private long taskgroupID;
private String taskgroupName; private String taskgroupName;
private TaskgroupEntityInfo parentTaskgroup;
public TaskgroupEntityInfo(Taskgroup taskgroup) { public TaskgroupEntityInfo(Taskgroup taskgroup) {
this.taskgroupID = taskgroup.getTaskgroupID(); this.taskgroupID = taskgroup.getTaskgroupID();
this.taskgroupName = taskgroup.getTaskgroupName(); this.taskgroupName = taskgroup.getTaskgroupName();
if(taskgroup.getParent() == null) {
this.parentTaskgroup = null;
} else {
this.parentTaskgroup = new TaskgroupEntityInfo(taskgroup.getParent());
}
} }
public long getTaskgroupID() { public long getTaskgroupID() {
@ -35,12 +27,4 @@ public class TaskgroupEntityInfo {
public void setTaskgroupName(String taskgroupName) { public void setTaskgroupName(String taskgroupName) {
this.taskgroupName = taskgroupName; this.taskgroupName = taskgroupName;
} }
public TaskgroupEntityInfo getParentTaskgroup() {
return parentTaskgroup;
}
public void setParentTaskgroup(TaskgroupEntityInfo parentTaskgroup) {
this.parentTaskgroup = parentTaskgroup;
}
} }

View File

@ -0,0 +1,7 @@
package core.api.models.timemanager.taskgroup;
public class TaskgroupShortInfo {
private long taskgroupID;
private String taskgroupName;
}

View File

@ -0,0 +1,88 @@
package core.api.models.timemanager.tasks;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.entities.timemanager.Task;
import java.time.LocalDate;
public class TaskEntityInfo {
private long taskID;
private String taskName;
private int eta;
private LocalDate startDate;
private LocalDate deadline;
private boolean overdue;
private boolean finished;
public TaskEntityInfo(Task task) {
this.taskID = task.getTaskID();
this.taskName = task.getTaskName();
this.eta = task.getEta();
this.startDate = task.getStartDate();
this.deadline = task.getDeadline();
this.overdue = LocalDate.now().isAfter(task.getDeadline());
this.finished = task.isFinished();
}
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 getEta() {
return eta;
}
public void setEta(int eta) {
this.eta = eta;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
this.deadline = deadline;
}
public boolean isOverdue() {
return overdue;
}
public void setOverdue(boolean overdue) {
this.overdue = overdue;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
}

View File

@ -0,0 +1,48 @@
package core.api.models.timemanager.tasks;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import java.time.LocalDate;
public class TaskFieldInfo {
@NotBlank
@Length(max = 255)
private String taskName;
private int eta;
private LocalDate startDate;
private LocalDate deadline;
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public int getEta() {
return eta;
}
public void setEta(int eta) {
this.eta = eta;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
this.deadline = deadline;
}
}

View File

@ -0,0 +1,108 @@
package core.entities.timemanager;
import javax.persistence.*;
import java.time.LocalDate;
import java.util.Objects;
@Entity
@Table(name = "tasks")
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long taskID;
@ManyToOne
@JoinColumn(name = "taskgroup_id")
private Taskgroup taskgroup;
private String taskName;
private LocalDate startDate;
private LocalDate deadline;
private int eta;
private boolean finished;
public Task(Taskgroup taskgroup, String taskName, LocalDate startDate, LocalDate deadline, int eta) {
this.taskgroup = taskgroup;
this.taskName = taskName;
this.startDate = startDate;
this.deadline = deadline;
this.eta = eta;
this.finished = false;
}
public Task() {
}
public long getTaskID() {
return taskID;
}
public Taskgroup getTaskgroup() {
return taskgroup;
}
public void setTaskgroup(Taskgroup taskgroup) {
this.taskgroup = taskgroup;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
this.deadline = deadline;
}
public int getEta() {
return eta;
}
public void setEta(int eta) {
this.eta = eta;
}
public void finish() {
this.finished = true;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task = (Task) o;
return taskID == task.taskID;
}
@Override
public int hashCode() {
return Objects.hash(taskID);
}
}

View File

@ -4,7 +4,7 @@ import core.entities.User;
import javax.persistence.*; import javax.persistence.*;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import java.util.Set; import java.util.*;
@Entity @Entity
@Table(name= "taskgroups") @Table(name= "taskgroups")
@ -29,6 +29,9 @@ public class Taskgroup {
@JoinColumn(name = "parent_id") @JoinColumn(name = "parent_id")
private Taskgroup parent; private Taskgroup parent;
@OneToMany(mappedBy = "taskgroup", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Task> tasks;
public Taskgroup(String taskgroupName, User user) { public Taskgroup(String taskgroupName, User user) {
this.taskgroupName = taskgroupName; this.taskgroupName = taskgroupName;
this.user = user; this.user = user;
@ -76,4 +79,22 @@ public class Taskgroup {
public void setParent(Taskgroup parent) { public void setParent(Taskgroup parent) {
this.parent = parent; this.parent = parent;
} }
public Set<Task> getTasks() {
return tasks;
}
public void setTasks(Set<Task> tasks) {
this.tasks = tasks;
}
public static List<Taskgroup> getAncestorList(Taskgroup taskgroup) {
List<Taskgroup> ancestors = new ArrayList<>();
Taskgroup currentTaskgroup = taskgroup;
while(currentTaskgroup.parent != null) {
ancestors.add(currentTaskgroup.parent);
currentTaskgroup = currentTaskgroup.parent;
}
return ancestors;
}
} }

View File

@ -0,0 +1,24 @@
package core.repositories.timemanager;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
@Repository
public interface TaskRepository extends CrudRepository<Task, Long> {
@Transactional
@Modifying
@Query(value = "DELETE FROM Task t WHERE t.taskgroup = ?1")
void deleteAllByTaskgroup(Taskgroup taskgroup);
@Transactional
@Modifying
@Query(value = "DELETE FROM Task t WHERE t.taskID = ?1")
void deleteByTaskID(long taskID);
}

View File

@ -0,0 +1,76 @@
package core.services;
import core.api.models.timemanager.tasks.TaskFieldInfo;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import core.repositories.timemanager.TaskRepository;
import core.repositories.timemanager.TaskgroupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@Service
public class TaskService {
private final TaskRepository taskRepository;
private final TaskgroupRepository taskgroupRepository;
public TaskService(@Autowired TaskRepository taskRepository,
TaskgroupRepository taskgroupRepository) {
this.taskRepository = taskRepository;
this.taskgroupRepository = taskgroupRepository;
}
public ServiceResult<Task> createTask(Taskgroup taskgroup, TaskFieldInfo taskFieldInfo) {
if(existTaskByName(taskgroup.getTasks(), taskFieldInfo.getTaskName())) {
return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST);
}
Task task = new Task(taskgroup, taskFieldInfo.getTaskName(), taskFieldInfo.getStartDate(), taskFieldInfo.getDeadline(), taskFieldInfo.getEta());
taskgroup.getTasks().add(task);
taskRepository.save(task);
return new ServiceResult<>(task);
}
private boolean existTaskByName(Collection<Task> tasks, String name) {
for(Task task : tasks) {
if(task.getTaskName().equals(name)) {
return true;
}
}
return false;
}
public PermissionResult<Task> getTaskPermissions(long taskID, String name) {
Optional<Task> task = taskRepository.findById(taskID);
return task.map(value -> new PermissionResult<>(value, value.getTaskgroup().getUser().getUsername().equals(name))).orElseGet(() ->
new PermissionResult<>(ServiceExitCode.MISSING_ENTITY));
}
public ServiceResult<Task> editTask(Task task, TaskFieldInfo taskFieldInfo) {
if(!task.getTaskName().equals(taskFieldInfo.getTaskName()) && !existTaskByName(task.getTaskgroup().getTasks(), taskFieldInfo.getTaskName())) {
task.setTaskName(taskFieldInfo.getTaskName());
} else if(!task.getTaskName().equals(taskFieldInfo.getTaskName()) && existTaskByName(task.getTaskgroup().getTasks(), taskFieldInfo.getTaskName())) {
return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST);
}
task.setEta(taskFieldInfo.getEta());
task.setStartDate(taskFieldInfo.getStartDate());
task.setDeadline(taskFieldInfo.getDeadline());
taskRepository.save(task);
return new ServiceResult<>(task);
}
public void deleteTask(Task task) {
taskRepository.deleteByTaskID(task.getTaskID());
}
public void clearTasks(Taskgroup taskgroup) {
taskRepository.deleteAllByTaskgroup(taskgroup);
}
}

View File

@ -1,16 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

View File

@ -103,6 +103,5 @@
} }
} }
} }
}, }
"defaultProject": "frontend"
} }

18182
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,24 +10,28 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~13.2.0", "@angular-material-components/datetime-picker": "^16.0.1",
"@angular/cdk": "^13.2.2", "@angular-material-components/moment-adapter": "^16.0.1",
"@angular/common": "~13.2.0", "@angular/animations": "^16.2.7",
"@angular/compiler": "~13.2.0", "@angular/cdk": "^16.2.6",
"@angular/core": "~13.2.0", "@angular/common": "^16.2.7",
"@angular/forms": "~13.2.0", "@angular/compiler": "^16.2.7",
"@angular/material": "^13.2.2", "@angular/core": "^16.2.7",
"@angular/platform-browser": "~13.2.0", "@angular/forms": "^16.2.7",
"@angular/platform-browser-dynamic": "~13.2.0", "@angular/material": "^16.2.6",
"@angular/router": "~13.2.0", "@angular/material-moment-adapter": "^16.2.7",
"@angular/platform-browser": "^16.2.7",
"@angular/platform-browser-dynamic": "^16.2.7",
"@angular/router": "^16.2.7",
"moment": "^2.29.4",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.11.4" "zone.js": "~0.13.3"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~13.2.2", "@angular-devkit/build-angular": "^16.2.4",
"@angular/cli": "~13.2.2", "@angular/cli": "^16.2.4",
"@angular/compiler-cli": "~13.2.0", "@angular/compiler-cli": "^16.2.7",
"@types/jasmine": "~3.10.0", "@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"jasmine-core": "~4.0.0", "jasmine-core": "~4.0.0",
@ -36,6 +40,6 @@
"karma-coverage": "~2.1.0", "karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.5.2" "typescript": "~5.1.6"
} }
} }

View File

@ -5,6 +5,7 @@ api/account.service.ts
api/api.ts api/api.ts
api/login.service.ts api/login.service.ts
api/properties.service.ts api/properties.service.ts
api/task.service.ts
api/taskgroup.service.ts api/taskgroup.service.ts
api/users.service.ts api/users.service.ts
configuration.ts configuration.ts
@ -25,6 +26,10 @@ model/propertiesInfo.ts
model/propertyInfo.ts model/propertyInfo.ts
model/propertyUpdateRequest.ts model/propertyUpdateRequest.ts
model/signUpRequest.ts model/signUpRequest.ts
model/simpleStatusResponse.ts
model/taskEntityInfo.ts
model/taskFieldInfo.ts
model/taskgroupDetailInfo.ts
model/taskgroupEntityInfo.ts model/taskgroupEntityInfo.ts
model/taskgroupFieldInfo.ts model/taskgroupFieldInfo.ts
model/userAddInfo.ts model/userAddInfo.ts

View File

@ -5,6 +5,7 @@ import { HttpClient } from '@angular/common/http';
import { AccountService } from './api/account.service'; import { AccountService } from './api/account.service';
import { LoginService } from './api/login.service'; import { LoginService } from './api/login.service';
import { PropertiesService } from './api/properties.service'; import { PropertiesService } from './api/properties.service';
import { TaskService } from './api/task.service';
import { TaskgroupService } from './api/taskgroup.service'; import { TaskgroupService } from './api/taskgroup.service';
import { UsersService } from './api/users.service'; import { UsersService } from './api/users.service';

View File

@ -4,8 +4,10 @@ export * from './login.service';
import { LoginService } from './login.service'; import { LoginService } from './login.service';
export * from './properties.service'; export * from './properties.service';
import { PropertiesService } from './properties.service'; import { PropertiesService } from './properties.service';
export * from './task.service';
import { TaskService } from './task.service';
export * from './taskgroup.service'; export * from './taskgroup.service';
import { TaskgroupService } from './taskgroup.service'; import { TaskgroupService } from './taskgroup.service';
export * from './users.service'; export * from './users.service';
import { UsersService } from './users.service'; import { UsersService } from './users.service';
export const APIS = [AccountService, LoginService, PropertiesService, TaskgroupService, UsersService]; export const APIS = [AccountService, LoginService, PropertiesService, TaskService, TaskgroupService, UsersService];

View File

@ -0,0 +1,363 @@
/**
* 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 { InlineResponse403 } from '../model/models';
import { SimpleStatusResponse } from '../model/models';
import { TaskEntityInfo } from '../model/models';
import { TaskFieldInfo } from '../model/models';
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration';
@Injectable({
providedIn: 'root'
})
export class TaskService {
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;
}
/**
* edits an existing task
* edits an existing task
* @param taskID internal id of task
* @param taskFieldInfo
* @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 tasksTaskIDDelete(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<SimpleStatusResponse>;
public tasksTaskIDDelete(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<SimpleStatusResponse>>;
public tasksTaskIDDelete(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<SimpleStatusResponse>>;
public tasksTaskIDDelete(taskID: number, taskFieldInfo?: TaskFieldInfo, 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 tasksTaskIDDelete.');
}
let localVarHeaders = this.defaultHeaders;
let localVarCredential: string | undefined;
// authentication (API_TOKEN) required
localVarCredential = this.configuration.lookupCredential('API_TOKEN');
if (localVarCredential) {
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
}
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
// to determine the Content-Type header
const consumes: string[] = [
'application/json'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
}
let responseType_: 'text' | 'json' = 'json';
if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
}
return this.httpClient.delete<SimpleStatusResponse>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskID))}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* edits an existing task
* edits an existing task
* @param taskID internal id of task
* @param taskFieldInfo
* @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 tasksTaskIDPost(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<TaskEntityInfo>;
public tasksTaskIDPost(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<TaskEntityInfo>>;
public tasksTaskIDPost(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<TaskEntityInfo>>;
public tasksTaskIDPost(taskID: number, taskFieldInfo?: TaskFieldInfo, 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 tasksTaskIDPost.');
}
let localVarHeaders = this.defaultHeaders;
let localVarCredential: string | undefined;
// authentication (API_TOKEN) required
localVarCredential = this.configuration.lookupCredential('API_TOKEN');
if (localVarCredential) {
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
}
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
// to determine the Content-Type header
const consumes: string[] = [
'application/json'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
}
let responseType_: 'text' | 'json' = 'json';
if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
}
return this.httpClient.post<TaskEntityInfo>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskID))}`,
taskFieldInfo,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* creates a new task
* creates tasks
* @param taskgroupID internal id of taskgroup
* @param taskFieldInfo
* @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 tasksTaskgroupIDPut(taskgroupID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<TaskEntityInfo>;
public tasksTaskgroupIDPut(taskgroupID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<TaskEntityInfo>>;
public tasksTaskgroupIDPut(taskgroupID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<TaskEntityInfo>>;
public tasksTaskgroupIDPut(taskgroupID: number, taskFieldInfo?: TaskFieldInfo, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (taskgroupID === null || taskgroupID === undefined) {
throw new Error('Required parameter taskgroupID was null or undefined when calling tasksTaskgroupIDPut.');
}
let localVarHeaders = this.defaultHeaders;
let localVarCredential: string | undefined;
// authentication (API_TOKEN) required
localVarCredential = this.configuration.lookupCredential('API_TOKEN');
if (localVarCredential) {
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
}
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
// to determine the Content-Type header
const consumes: string[] = [
'application/json'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
}
let responseType_: 'text' | 'json' = 'json';
if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
}
return this.httpClient.put<TaskEntityInfo>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskgroupID))}`,
taskFieldInfo,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* list tasks
* list tasks
* @param taskgroupID internal id of taskgroup
* @param status scope of listed tasks
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public tasksTaskgroupIDStatusGet(taskgroupID: number, status: 'all' | 'overdue' | 'upcoming', observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskEntityInfo>>;
public tasksTaskgroupIDStatusGet(taskgroupID: number, status: 'all' | 'overdue' | 'upcoming', observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskEntityInfo>>>;
public tasksTaskgroupIDStatusGet(taskgroupID: number, status: 'all' | 'overdue' | 'upcoming', observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskEntityInfo>>>;
public tasksTaskgroupIDStatusGet(taskgroupID: number, status: 'all' | 'overdue' | 'upcoming', observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (taskgroupID === null || taskgroupID === undefined) {
throw new Error('Required parameter taskgroupID was null or undefined when calling tasksTaskgroupIDStatusGet.');
}
if (status === null || status === undefined) {
throw new Error('Required parameter status was null or undefined when calling tasksTaskgroupIDStatusGet.');
}
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<TaskEntityInfo>>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskgroupID))}/${encodeURIComponent(String(status))}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
}

View File

@ -20,6 +20,8 @@ import { Observable } from 'rxjs';
import { InlineResponse200 } from '../model/models'; import { InlineResponse200 } from '../model/models';
import { InlineResponse403 } from '../model/models'; import { InlineResponse403 } from '../model/models';
import { SimpleStatusResponse } from '../model/models';
import { TaskgroupDetailInfo } from '../model/models';
import { TaskgroupEntityInfo } from '../model/models'; import { TaskgroupEntityInfo } from '../model/models';
import { TaskgroupFieldInfo } from '../model/models'; import { TaskgroupFieldInfo } from '../model/models';
@ -264,6 +266,65 @@ export class TaskgroupService {
); );
} }
/**
* clears tasks
* Deletes all tasks of that taskgroup
* @param taskgroupID internal id of taskgroup
* @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 taskgroupsTaskgroupIDClearDelete(taskgroupID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<SimpleStatusResponse>;
public taskgroupsTaskgroupIDClearDelete(taskgroupID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<SimpleStatusResponse>>;
public taskgroupsTaskgroupIDClearDelete(taskgroupID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<SimpleStatusResponse>>;
public taskgroupsTaskgroupIDClearDelete(taskgroupID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (taskgroupID === null || taskgroupID === undefined) {
throw new Error('Required parameter taskgroupID was null or undefined when calling taskgroupsTaskgroupIDClearDelete.');
}
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.delete<SimpleStatusResponse>(`${this.configuration.basePath}/taskgroups/${encodeURIComponent(String(taskgroupID))}/clear`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/** /**
* deletes taskgroup * deletes taskgroup
* deletes taskgroup * deletes taskgroup
@ -330,9 +391,9 @@ export class TaskgroupService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress. * @param reportProgress flag to report request and response progress.
*/ */
public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskgroupEntityInfo>>; public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<TaskgroupDetailInfo>;
public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskgroupEntityInfo>>>; public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<TaskgroupDetailInfo>>;
public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskgroupEntityInfo>>>; public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<TaskgroupDetailInfo>>;
public taskgroupsTaskgroupIDGet(taskgroupID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> { public taskgroupsTaskgroupIDGet(taskgroupID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (taskgroupID === null || taskgroupID === undefined) { if (taskgroupID === null || taskgroupID === undefined) {
throw new Error('Required parameter taskgroupID was null or undefined when calling taskgroupsTaskgroupIDGet.'); throw new Error('Required parameter taskgroupID was null or undefined when calling taskgroupsTaskgroupIDGet.');
@ -370,7 +431,7 @@ export class TaskgroupService {
responseType_ = 'text'; responseType_ = 'text';
} }
return this.httpClient.get<Array<TaskgroupEntityInfo>>(`${this.configuration.basePath}/taskgroups/${encodeURIComponent(String(taskgroupID))}`, return this.httpClient.get<TaskgroupDetailInfo>(`${this.configuration.basePath}/taskgroups/${encodeURIComponent(String(taskgroupID))}`,
{ {
context: localVarHttpContext, context: localVarHttpContext,
responseType: <any>responseType_, responseType: <any>responseType_,

View File

@ -11,6 +11,10 @@ export * from './propertiesInfo';
export * from './propertyInfo'; export * from './propertyInfo';
export * from './propertyUpdateRequest'; export * from './propertyUpdateRequest';
export * from './signUpRequest'; export * from './signUpRequest';
export * from './simpleStatusResponse';
export * from './taskEntityInfo';
export * from './taskFieldInfo';
export * from './taskgroupDetailInfo';
export * from './taskgroupEntityInfo'; export * from './taskgroupEntityInfo';
export * from './taskgroupFieldInfo'; export * from './taskgroupFieldInfo';
export * from './userAddInfo'; export * from './userAddInfo';

View File

@ -0,0 +1,28 @@
/**
* 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 SimpleStatusResponse {
/**
* Response Status der Request
*/
status: SimpleStatusResponse.StatusEnum;
}
export namespace SimpleStatusResponse {
export type StatusEnum = 'success' | 'failed';
export const StatusEnum = {
Success: 'success' as StatusEnum,
Failed: 'failed' as StatusEnum
};
}

View File

@ -0,0 +1,44 @@
/**
* 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 TaskEntityInfo {
/**
* internal id of task
*/
taskID: number;
/**
* name of task
*/
taskName: string;
/**
* expected time to finish task
*/
eta: number;
/**
* date from which the task can be started
*/
startDate: string;
/**
* date until the task has to be finished
*/
deadline: string;
/**
* determines whether the task is overdue
*/
overdue: boolean;
/**
* determines whether the task is finished
*/
finished: boolean;
}

View File

@ -0,0 +1,32 @@
/**
* 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 TaskFieldInfo {
/**
* name of task
*/
taskName: string;
/**
* expected time to finish task
*/
eta: number;
/**
* date from which the task can be started
*/
startDate: string;
/**
* date until the task has to be finished
*/
deadline: string;
}

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.
*/
import { TaskgroupEntityInfo } from './taskgroupEntityInfo';
export interface TaskgroupDetailInfo {
children: Array<TaskgroupEntityInfo>;
ancestors: Array<TaskgroupEntityInfo>;
taskgroupInfo: TaskgroupEntityInfo;
}

View File

@ -20,6 +20,5 @@ export interface TaskgroupEntityInfo {
* name of taskgroup * name of taskgroup
*/ */
taskgroupName: string; taskgroupName: string;
parentTaskgroup: TaskgroupEntityInfo;
} }

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {PropertiesInfo, PropertiesService, PropertyInfo, UserInfo} from "../../../api"; import {PropertiesInfo, PropertiesService, PropertyInfo, UserInfo} from "../../../api";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatLegacySnackBar as MatSnackBar} from "@angular/material/legacy-snack-bar";
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',

View File

@ -1,8 +1,8 @@
import {Component, Inject, OnInit, Output} from '@angular/core'; import {Component, Inject, OnInit, Output} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialogRef as MatDialogRef} from "@angular/material/legacy-dialog";
import {UserInfo, UsersService} from "../../../../api"; import {UserInfo, UsersService} from "../../../../api";
import {LoginInfoTypes} from "../../../auth/login/login.component"; import {LoginInfoTypes} from "../../../auth/login/login.component";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatLegacySnackBar as MatSnackBar} from "@angular/material/legacy-snack-bar";
@Component({ @Component({
selector: 'app-delete-confirmation', selector: 'app-delete-confirmation',

View File

@ -1,8 +1,8 @@
import {Component, Inject, OnInit} from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA as MAT_DIALOG_DATA, MatDialogRef as MatDialogRef} from "@angular/material/dialog";
import {UserInfo, UsersService} from "../../../../api"; import {UserInfo, UsersService} from "../../../../api";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
import {AbstractControl, FormControl, ValidationErrors, Validators} from "@angular/forms"; import {AbstractControl, UntypedFormControl, ValidationErrors, Validators} from "@angular/forms";
import {EditData} from "./edit-data"; import {EditData} from "./edit-data";
@ -24,22 +24,22 @@ export class EditComponent implements OnInit {
adminUpdateDisabled: boolean = false; adminUpdateDisabled: boolean = false;
usernameFormControl = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(32)]) usernameFormControl = new UntypedFormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(32)])
emailFormControl = new FormControl('', [Validators.required, Validators.email]) emailFormControl = new UntypedFormControl('', [Validators.required, Validators.email])
passwordEditControl = new FormControl('', [ passwordEditControl = new UntypedFormControl('', [
(control: AbstractControl): ValidationErrors | null => { (control: AbstractControl): ValidationErrors | null => {
return control.value.length > 0 && control.value.length < 6 ? {minlength: {value: control.value}} : null return control.value.length > 0 && control.value.length < 6 ? {minlength: {value: control.value}} : null
}, },
]) ])
passwordAddControl = new FormControl('', [Validators.required, Validators.minLength(6)]) passwordAddControl = new UntypedFormControl('', [Validators.required, Validators.minLength(6)])
passwordDuplicateEditControl = new FormControl('', [ passwordDuplicateEditControl = new UntypedFormControl('', [
(control: AbstractControl): ValidationErrors | null => { (control: AbstractControl): ValidationErrors | null => {
return control.value != this.password ? {duplicate: {value: control.value}} : null return control.value != this.password ? {duplicate: {value: control.value}} : null
} }
]) ])
passwordDuplicateAddControl = new FormControl('', [ passwordDuplicateAddControl = new UntypedFormControl('', [
Validators.required, Validators.required,
Validators.minLength(6), Validators.minLength(6),
(control: AbstractControl): ValidationErrors | null => { (control: AbstractControl): ValidationErrors | null => {

View File

@ -1,11 +1,11 @@
import {ChangeDetectorRef, Component, OnInit} from '@angular/core'; import {ChangeDetectorRef, Component, OnInit} from '@angular/core';
import {UserInfo, UsersService} from "../../../api"; import {UserInfo, UsersService} from "../../../api";
import {MatDialog} from "@angular/material/dialog"; import {MatDialog as MatDialog} from "@angular/material/dialog";
import {LoginComponent} from "../../auth/login/login.component"; import {LoginComponent} from "../../auth/login/login.component";
import {DeleteConfirmationComponent} from "./delete-confirmation/delete-confirmation.component"; import {DeleteConfirmationComponent} from "./delete-confirmation/delete-confirmation.component";
import {CollectionViewer, DataSource} from "@angular/cdk/collections"; import {CollectionViewer, DataSource} from "@angular/cdk/collections";
import {Observable} from "rxjs"; import {Observable} from "rxjs";
import {MatTableDataSource} from "@angular/material/table"; import {MatTableDataSource as MatTableDataSource} from "@angular/material/table";
import {EditComponent} from "./edit/edit.component"; import {EditComponent} from "./edit/edit.component";
import {EditData} from "./edit/edit-data"; import {EditData} from "./edit/edit-data";

View File

@ -9,25 +9,22 @@ import {HttpClient, HttpClientModule, HttpHandler} from "@angular/common/http";
import {ApiModule, Configuration} from "../api"; import {ApiModule, Configuration} from "../api";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MatToolbarModule} from "@angular/material/toolbar"; import {MatToolbarModule} from "@angular/material/toolbar";
import {MatButtonModule} from "@angular/material/button";
import {MatIconModule} from "@angular/material/icon"; import {MatIconModule} from "@angular/material/icon";
import {MatDialogModule} from "@angular/material/dialog"; import {MatDialogModule as MatDialogModule} from "@angular/material/dialog";
import {MatFormFieldModule} from "@angular/material/form-field"; import {MatFormFieldModule as MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input"; import {MatInputModule as MatInputModule} from "@angular/material/input";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; import {MatProgressSpinnerModule as MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MatSnackBar, MatSnackBarModule} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar, MatSnackBarModule as MatSnackBarModule} from "@angular/material/snack-bar";
import {MatMenuModule} from "@angular/material/menu";
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component'; import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';
import { MainComponent } from './main/main.component'; import { MainComponent } from './main/main.component';
import { UsermanagementComponent } from './admin-dashboard/usermanagement/usermanagement.component'; import { UsermanagementComponent } from './admin-dashboard/usermanagement/usermanagement.component';
import {MatTabsModule} from "@angular/material/tabs";
import {AuthService} from "./auth.service"; import {AuthService} from "./auth.service";
import {environment} from "../environments/environment"; import {environment} from "../environments/environment";
import {MatTableModule} from "@angular/material/table";
import {MatCheckboxModule} from "@angular/material/checkbox";
import { DeleteConfirmationComponent } from './admin-dashboard/usermanagement/delete-confirmation/delete-confirmation.component'; import { DeleteConfirmationComponent } from './admin-dashboard/usermanagement/delete-confirmation/delete-confirmation.component';
import { EditComponent } from './admin-dashboard/usermanagement/edit/edit.component'; import { EditComponent } from './admin-dashboard/usermanagement/edit/edit.component';
import {MatCardModule} from "@angular/material/card";
import {UserSettingsComponent} from "./user-settings/user-settings.component"; import {UserSettingsComponent} from "./user-settings/user-settings.component";
import {AccountSettingsComponent} from "./user-settings/account-settings/account-settings.component"; import {AccountSettingsComponent} from "./user-settings/account-settings/account-settings.component";
import { ChangePasswordComponent } from './user-settings/account-settings/change-password/change-password.component'; import { ChangePasswordComponent } from './user-settings/account-settings/change-password/change-password.component';
@ -35,10 +32,27 @@ import { ManageEmailComponent } from './user-settings/account-settings/manage-em
import { DeleteAccountComponent } from './user-settings/account-settings/delete-account/delete-account.component'; import { DeleteAccountComponent } from './user-settings/account-settings/delete-account/delete-account.component';
import { RegistrationComponent } from './auth/registration/registration.component'; import { RegistrationComponent } from './auth/registration/registration.component';
import { SettingsComponent } from './admin-dashboard/settings/settings.component'; import { SettingsComponent } from './admin-dashboard/settings/settings.component';
import {MatListModule} from "@angular/material/list";
import { TaskgroupDashboardComponent } from './taskgroups/taskgroup-dashboard/taskgroup-dashboard.component'; import { TaskgroupDashboardComponent } from './taskgroups/taskgroup-dashboard/taskgroup-dashboard.component';
import { TaskgroupCreationComponent } from './taskgroups/taskgroup-creation/taskgroup-creation.component'; import { TaskgroupCreationComponent } from './taskgroups/taskgroup-creation/taskgroup-creation.component';
import { TaskgroupDeletionComponent } from './taskgroups/taskgroup-deletion/taskgroup-deletion.component'; import { TaskgroupDeletionComponent } from './taskgroups/taskgroup-deletion/taskgroup-deletion.component';
import { TaskEditorComponent } from './tasks/task-editor/task-editor.component';
import {MatDatepickerModule} from "@angular/material/datepicker";
import {MatButtonModule} from "@angular/material/button";
import {MatMenuModule} from "@angular/material/menu";
import {MatTabsModule} from "@angular/material/tabs";
import {MatTableModule} from "@angular/material/table";
import {MatCheckboxModule} from "@angular/material/checkbox";
import {MatCardModule} from "@angular/material/card";
import {MatListModule} from "@angular/material/list";
import {DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatNativeDateModule} from "@angular/material/core";
import {MatMomentDateModule, MomentDateModule} from "@angular/material-moment-adapter";
import { TaskDashboardComponent } from './tasks/task-dashboard/task-dashboard.component';
import {MatSlideToggleModule} from "@angular/material/slide-toggle";
import {MatSortModule} from "@angular/material/sort";
import {MatPaginatorModule} from "@angular/material/paginator";
import { ClearTaskDialogComponent } from './tasks/clear-task-dialog/clear-task-dialog.component';
import { NavigationLinkListComponent } from './navigation-link-list/navigation-link-list.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -58,7 +72,11 @@ import { TaskgroupDeletionComponent } from './taskgroups/taskgroup-deletion/task
SettingsComponent, SettingsComponent,
TaskgroupDashboardComponent, TaskgroupDashboardComponent,
TaskgroupCreationComponent, TaskgroupCreationComponent,
TaskgroupDeletionComponent TaskgroupDeletionComponent,
TaskEditorComponent,
TaskDashboardComponent,
ClearTaskDialogComponent,
NavigationLinkListComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -81,7 +99,15 @@ import { TaskgroupDeletionComponent } from './taskgroups/taskgroup-deletion/task
MatCheckboxModule, MatCheckboxModule,
MatCardModule, MatCardModule,
ReactiveFormsModule, ReactiveFormsModule,
MatListModule MatListModule,
MatDatepickerModule,
MatInputModule,
MatDatepickerModule,
MatMomentDateModule,
FormsModule,
MatSlideToggleModule,
MatSortModule,
MatPaginatorModule
], ],
providers: [ providers: [
HttpClientModule, HttpClientModule,
@ -108,8 +134,7 @@ import { TaskgroupDeletionComponent } from './taskgroups/taskgroup-deletion/task
), ),
deps: [AuthService], deps: [AuthService],
multi: false multi: false
} },
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -1,8 +1,8 @@
import {Component, Inject, OnInit} from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import {LoginService, PropertiesService} from "../../../api"; import {LoginService, PropertiesService} from "../../../api";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA as MAT_DIALOG_DATA, MatDialog as MatDialog, MatDialogRef as MatDialogRef} from "@angular/material/dialog";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
import {AuthService} from "../../auth.service"; import {AuthService} from "../../auth.service";
import {RegistrationComponent} from "../registration/registration.component"; import {RegistrationComponent} from "../registration/registration.component";

View File

@ -1,12 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import {MatDialog as MatDialog, MatDialogRef as MatDialogRef} from "@angular/material/dialog";
import {LoginService} from "../../../api"; import {LoginService} from "../../../api";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
import {AuthService} from "../../auth.service"; import {AuthService} from "../../auth.service";
import {LoginComponent} from "../login/login.component"; import {LoginComponent} from "../login/login.component";
import {LoginDialogServiceService} from "../../login-dialog-service.service"; import {LoginDialogServiceService} from "../../login-dialog-service.service";
import {AbstractControl, FormControl, ValidationErrors, Validators} from "@angular/forms"; import {AbstractControl, UntypedFormControl, ValidationErrors, Validators} from "@angular/forms";
@Component({ @Component({
selector: 'app-registration', selector: 'app-registration',
@ -23,10 +23,10 @@ export class RegistrationComponent implements OnInit {
pending: boolean = false; pending: boolean = false;
isAuth: boolean = false; isAuth: boolean = false;
usernameControl = new FormControl('', [Validators.required, Validators.minLength(3)]); usernameControl = new UntypedFormControl('', [Validators.required, Validators.minLength(3)]);
emailControl = new FormControl('', [Validators.required, Validators.email]) emailControl = new UntypedFormControl('', [Validators.required, Validators.email])
passwordControl = new FormControl('', [Validators.required, Validators.minLength(6)]); passwordControl = new UntypedFormControl('', [Validators.required, Validators.minLength(6)]);
passwordDuplicateControl = new FormControl('', [ passwordDuplicateControl = new UntypedFormControl('', [
Validators.required, Validators.required,
Validators.minLength(6), Validators.minLength(6),
(control: AbstractControl): ValidationErrors | null => { (control: AbstractControl): ValidationErrors | null => {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {MatDialog} from "@angular/material/dialog"; import {MatDialog as MatDialog} from "@angular/material/dialog";
import {LoginComponent, LoginInfoTypes} from "./auth/login/login.component"; import {LoginComponent, LoginInfoTypes} from "./auth/login/login.component";
import {Observable} from "rxjs"; import {Observable} from "rxjs";

View File

@ -0,0 +1,23 @@
.navLink {
text-decoration: underline;
color: black;
margin-right: 5px;
margin-left: 3px;
}
.navLink-disabled {
text-decoration: none;
color: grey;
}
.navLink-bar {
margin-bottom: 20px;
}
.link-separation {
text-decoration: none;
padding-right: 5px;
}

View File

@ -0,0 +1,8 @@
<mat-card class="navLink-bar">
<mat-card-content>
<span *ngFor="let navigationLink of navigationLinks; index as i">
<span class="link-seperation" *ngIf="i !== 0">/</span>
<a class="navLink" [routerLink]="navigationLink.routerLink" [class.navLink-disabled]="i == navigationLinks.length-1">{{navigationLink.linkText}}</a>
</span>
</mat-card-content>
</mat-card>

View File

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

View File

@ -0,0 +1,40 @@
import {Component, Input, OnInit} from '@angular/core';
export interface NavigationLink {
routerLink: string[],
linkText: string
}
@Component({
selector: 'app-navigation-link-list',
templateUrl: './navigation-link-list.component.html',
styleUrls: ['./navigation-link-list.component.css']
})
export class NavigationLinkListComponent implements OnInit{
@Input() navigationLinks: NavigationLink[] = []
ngOnInit(): void {
}
addNavigationLink(linkText: string, link: string[]) {
const navigationLink: NavigationLink = {
linkText: linkText,
routerLink: link
}
if(this.navigationLinks.find(searchedLink => searchedLink.linkText === linkText) === undefined) {
this.navigationLinks.push(navigationLink);
}
}
removeLink(linkText: string) {
this.navigationLinks = this.navigationLinks.filter(link => link.linkText === linkText);
}
}

View File

@ -1,12 +1,11 @@
import {Component, Inject, OnInit} from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import {FormControl, Validators} from "@angular/forms"; import {UntypedFormControl, Validators} from "@angular/forms";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA as MAT_DIALOG_DATA, MatDialogRef as MatDialogRef} from "@angular/material/dialog";
import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; import {TaskgroupService} from "../../../api";
import {error} from "@angular/compiler/src/util"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
import {MatSnackBar} from "@angular/material/snack-bar";
import {ActivatedRoute} from "@angular/router";
import {TaskgroupCreationData} from "./TaskgroupCreationData"; import {TaskgroupCreationData} from "./TaskgroupCreationData";
@Component({ @Component({
selector: 'app-taskgroup-creation', selector: 'app-taskgroup-creation',
templateUrl: './taskgroup-creation.component.html', templateUrl: './taskgroup-creation.component.html',
@ -14,7 +13,7 @@ import {TaskgroupCreationData} from "./TaskgroupCreationData";
}) })
export class TaskgroupCreationComponent implements OnInit { export class TaskgroupCreationComponent implements OnInit {
nameCtrl = new FormControl('', [Validators.required, Validators.maxLength(255)]) nameCtrl = new UntypedFormControl('', [Validators.required, Validators.maxLength(255)])
pending: boolean = false pending: boolean = false
constructor(private dialogRef: MatDialogRef<TaskgroupCreationComponent>, constructor(private dialogRef: MatDialogRef<TaskgroupCreationComponent>,

View File

@ -1,6 +1,6 @@
.container { .container {
margin: 20px auto; margin: 20px auto;
width: 60%; width: 70%;
} }
.spacer { .spacer {
@ -15,27 +15,23 @@
} }
} }
.navLink {
text-decoration: underline;
color: black;
margin-right: 5px;
}
.navLink-disabled {
text-decoration: none;
color: grey;
}
.navLink-bar {
margin-bottom: 20px;
}
#addTaskgroupBtn { #addTaskgroupBtn {
margin-top: 20px; margin-top: 20px;
float: right;
}
#addTaskBtn {
margin-top: 20px;
float: left;
} }
.edit-btn { .edit-btn {
background-color: #6e6e6e; background-color: #6e6e6e;
color: white; color: white;
} }
#task-dashboard-container {
width: 100%;
margin-top: 20px;
}

View File

@ -1,10 +1,5 @@
<div class="container"> <div class="container">
<mat-card class="navLink-bar"> <app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<mat-card-content>
<a class="navLink" routerLink="/">Dashboard</a>
<a class="navLink-disabled">/ Taskgroups</a>
</mat-card-content>
</mat-card>
<mat-card *ngFor="let taskgroup of taskgroups"> <mat-card *ngFor="let taskgroup of taskgroups">
<mat-card-content> <mat-card-content>
@ -15,5 +10,16 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<button id="addTaskgroupBtn" mat-raised-button color="primary" (click)="openTaskgroupCreation()">Create new Taskgroup</button>
<mat-card style="width: 100%" *ngIf="taskgroupID >= 0">
<mat-card-content>
<app-task-dashboard #taskDashboard [taskgroupID]="taskgroupID"></app-task-dashboard>
</mat-card-content>
</mat-card>
<div style="width: 100%">
<button id="addTaskBtn" *ngIf="taskgroupID >= 0" mat-raised-button color="primary" (click)="openTaskCreation()">Create New Task</button>
<button id="addTaskgroupBtn" mat-raised-button color="primary" (click)="openTaskgroupCreation()">Create new Taskgroup</button>
</div>
</div> </div>

View File

@ -1,9 +1,13 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit, ViewChild} from '@angular/core';
import {MatDialog} from "@angular/material/dialog"; import {MatDialog as MatDialog} from "@angular/material/dialog";
import {TaskgroupCreationComponent} from "../taskgroup-creation/taskgroup-creation.component"; import {TaskgroupCreationComponent} from "../taskgroup-creation/taskgroup-creation.component";
import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; import {TaskgroupEntityInfo, TaskgroupService} from "../../../api";
import {TaskgroupDeletionComponent} from "../taskgroup-deletion/taskgroup-deletion.component"; import {TaskgroupDeletionComponent} from "../taskgroup-deletion/taskgroup-deletion.component";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
import {TaskEditorComponent} from "../../tasks/task-editor/task-editor.component";
import {TaskEditorData} from "../../tasks/task-editor/TaskEditorData";
import {TaskDashboardComponent} from "../../tasks/task-dashboard/task-dashboard.component";
import {NavigationLink, NavigationLinkListComponent} from "../../navigation-link-list/navigation-link-list.component";
@Component({ @Component({
selector: 'app-taskgroup-dashboard', selector: 'app-taskgroup-dashboard',
@ -17,7 +21,21 @@ export class TaskgroupDashboardComponent implements OnInit {
private activatedRoute: ActivatedRoute) { } private activatedRoute: ActivatedRoute) { }
taskgroups: TaskgroupEntityInfo[] = [] taskgroups: TaskgroupEntityInfo[] = []
taskgroup: TaskgroupEntityInfo | undefined
taskgroupPath: TaskgroupEntityInfo[] = []
taskgroupID: number = -1; taskgroupID: number = -1;
@ViewChild("taskDashboard") taskDashboard: TaskDashboardComponent | undefined
@ViewChild('navLinkList') navLinkListComponent: NavigationLinkListComponent | undefined
defaultNavigationLinkPath: NavigationLink[] = [
{
linkText: "Dashboard",
routerLink: ['/']
},
{
linkText: "Taskgroups",
routerLink: ["/taskgroups"]
}
]
ngOnInit(): void { ngOnInit(): void {
this.activatedRoute.paramMap.subscribe(params => { this.activatedRoute.paramMap.subscribe(params => {
@ -25,7 +43,13 @@ export class TaskgroupDashboardComponent implements OnInit {
this.taskgroupID = Number(params.get('taskgroupID')); this.taskgroupID = Number(params.get('taskgroupID'));
this.taskgroupService.taskgroupsTaskgroupIDGet(this.taskgroupID).subscribe({ this.taskgroupService.taskgroupsTaskgroupIDGet(this.taskgroupID).subscribe({
next: resp => { next: resp => {
this.taskgroups = resp this.taskgroups = resp.children
this.taskgroupPath = resp.ancestors
this.taskgroup = resp.taskgroupInfo;
this.navLinkListComponent!.addNavigationLink(this.taskgroup.taskgroupName, ['/taskgroups', this.taskgroup.taskgroupID.toString()])
this.taskgroupPath.forEach(taskgroupEntity => {
this.navLinkListComponent!.addNavigationLink(taskgroupEntity.taskgroupName, ['/taskgroups', taskgroupEntity.taskgroupID.toString()]);
})
} }
}) })
} else { } else {
@ -65,4 +89,17 @@ export class TaskgroupDashboardComponent implements OnInit {
} }
}) })
} }
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.taskDashboard!.addTask(res);
}
})
}
} }

View File

@ -1,7 +1,7 @@
import {Component, Inject, OnInit} from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA as MAT_DIALOG_DATA, MatDialogRef as MatDialogRef} from "@angular/material/dialog";
import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; import {TaskgroupEntityInfo, TaskgroupService} from "../../../api";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
@Component({ @Component({
selector: 'app-taskgroup-deletion', selector: 'app-taskgroup-deletion',

View File

@ -0,0 +1,3 @@
.deleteSetttings {
margin-right: 20px;
}

View File

@ -0,0 +1,13 @@
<h1 mat-dialog-title>Clear Tasks ({{editorData.taskgroupID}})</h1>
<div mat-dialog-content>
<p>Are you sure you want to clear the Tasks? This <b>cannot</b> be <b>undone!</b></p>
<mat-slide-toggle class="deleteSetttings">Finished Tasks <b>( {{finishedTasks}}Tasks)</b></mat-slide-toggle>
<mat-slide-toggle class="deleteSetttings">Overdue Tasks <b>({{overdueTasks}} Tasks)</b></mat-slide-toggle>
<mat-slide-toggle class="deleteSetttings">Unfinished Tasks <b>( {{unfinishedTasks}}Tasks)</b></mat-slide-toggle>
<mat-slide-toggle class="deleteSetttings">Upcoming Tasks <b>({{upcomingTasks}} Tasks)</b></mat-slide-toggle>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button (click)="cancel()">Cancel</button>
<button mat-raised-button color="warn" (click)="clearTasks()"><mat-icon>delete</mat-icon>Clear</button>
</div>

View File

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

View File

@ -0,0 +1,69 @@
import {Component, Inject, OnInit} from '@angular/core';
import {TaskEditorData} from "../task-editor/TaskEditorData";
import {TaskEntityInfo, TaskgroupService} from "../../../api";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MatSnackBar} from "@angular/material/snack-bar";
export interface ClearTaskDialogData {
taskgroupID: number,
tasks: TaskEntityInfo[]
}
@Component({
selector: 'app-clear-task-dialog',
templateUrl: './clear-task-dialog.component.html',
styleUrls: ['./clear-task-dialog.component.css']
})
export class ClearTaskDialogComponent implements OnInit{
finishedTasks: number = 0;
unfinishedTasks: number = 0;
overdueTasks: number = 0;
upcomingTasks: number = 0;
constructor(@Inject(MAT_DIALOG_DATA) public editorData: ClearTaskDialogData,
private taskgroupService: TaskgroupService,
private dialogRef: MatDialogRef<ClearTaskDialogComponent>,
private snackbar: MatSnackBar) {
}
ngOnInit(): void {
this.editorData.tasks.forEach(task => {
if(task.overdue) {
this.overdueTasks +=1;
} else {
this.upcomingTasks += 1;
}
if(task.finished) {
this.finishedTasks += 1;
} else {
this.unfinishedTasks += 1;
}
})
}
cancel() {
}
clearTasks() {
this.taskgroupService.taskgroupsTaskgroupIDClearDelete(this.editorData.taskgroupID).subscribe({
next: resp => {
if(resp.status == "success") {
const deletedTasks = this.editorData.tasks;
this.dialogRef.close(deletedTasks);
}
},
error: err => {
if(err.status == 403) {
this.snackbar.open("No permission", "", {duration: 2000});
} else if(err.status == 404) {
this.snackbar.open("Not found", "", {duration: 2000});
} else {
this.snackbar.open("Unexpected error", "", {duration: 2000});
}
}
})
}
}

View File

@ -0,0 +1,37 @@
mat-form-field {
width: 80%;
}
table {
width: 100%;
}
td, th {
width: 25%;
}
.status-indicator {
width: 20px;
height: 20px;
border-radius: 50%;
margin: 0 auto;
}
.mat-column-status, .mat-column-eta {
width: 32px;
text-align: left;
}
.mat-column-edit, .mat-column-delete, .mat-column-finished {
width: 32px;
text-align: center;
}
#clear-tasks-btn {
float: right;
margin-top: 10px;
}

View File

@ -0,0 +1,61 @@
<mat-form-field appearance="fill">
<mat-label>Search</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="Search for Taskname" #input>
</mat-form-field>
<!--<mat-slide-toggle style="margin-left: 20px">Show finished tasks</mat-slide-toggle>-->
<button id="clear-tasks-btn" (click)="clearTasks()" mat-raised-button color="warn"><mat-icon>delete</mat-icon>Clear Tasks</button>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="datasource" matSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let task"> {{task.taskName}} </td>
</ng-container>
<ng-container matColumnDef="eta">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ETA </th>
<td mat-cell *matCellDef="let task"> {{task.eta}} </td>
</ng-container>
<ng-container matColumnDef="start">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Start </th>
<td mat-cell *matCellDef="let task"> {{task.startDate}} </td>
</ng-container>
<ng-container matColumnDef="deadline">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Deadline </th>
<td mat-cell *matCellDef="let task"> {{task.deadline}} </td>
</ng-container>
<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>
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
<td mat-cell *matCellDef="let task">
<div class="status-indicator" [style.background-color]="getStatusOfTask(task)"></div>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef mat-sort-header></th>
<td mat-cell *matCellDef="let task">
<button mat-icon-button color="primary" (click)="editTask(task)"><mat-icon>edit</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef mat-sort-header></th>
<td mat-cell *matCellDef="let task">
<button mat-icon-button color="warn" (click)="deleteTask(task)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<!-- Row shown when there is no matching data. -->
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No data matching the filter "{{input.value}}"</td>
</tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" aria-label="Select page of users"></mat-paginator>
</div>

View File

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

View File

@ -0,0 +1,102 @@
import {Component, Input, OnChanges, ViewChild} from '@angular/core';
import {TaskEntityInfo, TaskService} from "../../../api";
import {MatPaginator} from "@angular/material/paginator";
import {MatSort} from "@angular/material/sort";
import {MatTableDataSource} from "@angular/material/table";
import {TaskEditorComponent} from "../task-editor/task-editor.component";
import {TaskEditorData} from "../task-editor/TaskEditorData";
import {MatDialog} from "@angular/material/dialog";
import {MatSnackBar} from "@angular/material/snack-bar";
import {ClearTaskDialogComponent, ClearTaskDialogData} from "../clear-task-dialog/clear-task-dialog.component";
@Component({
selector: 'app-task-dashboard',
templateUrl: './task-dashboard.component.html',
styleUrls: ['./task-dashboard.component.css']
})
export class TaskDashboardComponent implements OnChanges{
ngOnChanges(): void {
if(this.taskgroupID != undefined) {
this.taskService.tasksTaskgroupIDStatusGet(this.taskgroupID!, "all").subscribe({
next: resp => {
this.datasource = new MatTableDataSource<TaskEntityInfo>(resp);
this.datasource.paginator = this.paginator!;
this.datasource.sort = this.sort!;
}
})
}
}
@Input("taskgroupID") taskgroupID: number | undefined
@ViewChild(MatPaginator) paginator: MatPaginator | undefined
@ViewChild(MatSort) sort: MatSort | undefined
displayedColumns: string[] = ['status', 'name', 'eta', 'start', 'deadline', 'finished', 'edit', 'delete'];
datasource: MatTableDataSource<TaskEntityInfo> = new MatTableDataSource<TaskEntityInfo>();
constructor(private taskService: TaskService,
private dialog: MatDialog,
private snackbar: MatSnackBar) {
}
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.datasource.filter = filterValue.trim().toLowerCase();
if (this.datasource.paginator) {
this.datasource.paginator.firstPage();
}
}
getStatusOfTask(task: TaskEntityInfo) {
return "green";
}
deleteTask(deletedTask: TaskEntityInfo) {
this.taskService.tasksTaskIDDelete(deletedTask.taskID).subscribe({
next: resp => {
if(resp.status == "success") {
this.datasource.data = this.datasource.data.filter(task => task.taskID !== deletedTask.taskID);
} else {
this.snackbar.open("Unexpected behavior", "", {duration: 2000});
}
},
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});
}
}
})
}
editTask(task: TaskEntityInfo) {
const taskEditorInfo: TaskEditorData = {
task: task,
taskgroupID: this.taskgroupID!
};
const dialogRef = this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, minWidth: "400px"})
}
clearTasks() {
const clearTaskData: ClearTaskDialogData = {
tasks: this.datasource.data,
taskgroupID: this.taskgroupID!
}
const dialogRef = this.dialog.open(ClearTaskDialogComponent, {data: clearTaskData, width: "600px"});
dialogRef.afterClosed().subscribe(res => {
if(res != undefined) {
this.datasource.data = this.datasource.data.filter(task => !res.includes(task));
}
})
}
addTask(task: TaskEntityInfo) {
const data = this.datasource.data;
data.push(task)
this.datasource.data = data;
}
}

View File

@ -0,0 +1,6 @@
import {TaskEntityInfo} from "../../../api";
export interface TaskEditorData {
taskgroupID: number;
task: TaskEntityInfo | undefined
}

View File

@ -0,0 +1,15 @@
.long-form {
width: 100%;
}
.date-form {
width: 49%;
}
#startDate {
float: left;
}
#endDate {
float: right;
}

View File

@ -0,0 +1,32 @@
<h1 mat-dialog-title *ngIf="editorData.task != undefined">Edit Task ({{editorData.task!.taskName}})</h1>
<h1 mat-dialog-title *ngIf="editorData.task == undefined">Create New Task</h1>
<div mat-dialog-content>
<mat-form-field appearance="outline" class="long-form">
<mat-label>Name</mat-label>
<input matInput [formControl]="nameCtrl">
<mat-hint align="end">{{nameCtrl.value.length}} / 255</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline" class="long-form">
<mat-label>ETA</mat-label>
<input matInput type="number" [formControl]="etaCtrl" min="0">
</mat-form-field>
<mat-form-field class="date-form" id="startDate">
<mat-label>Startdate</mat-label>
<input matInput [matDatepicker]="startpicker" [formControl]="startDate">
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="startpicker"></mat-datepicker-toggle>
<mat-datepicker #startpicker></mat-datepicker>
</mat-form-field>
<mat-form-field class="date-form" id="endDate">
<mat-label>Deadline</mat-label>
<input matInput [matDatepicker]="deadlinepicker" [formControl]="endDate">
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="deadlinepicker"></mat-datepicker-toggle>
<mat-datepicker #deadlinepicker></mat-datepicker>
</mat-form-field>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button (click)="cancel()">Cancel</button>
<button mat-raised-button color="primary" (click)="submit()">{{editorData.task == undefined? 'Add Task': 'Edit Task'}}</button>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TaskEditorComponent } from './task-editor.component';
describe('TaskEditorComponent', () => {
let component: TaskEditorComponent;
let fixture: ComponentFixture<TaskEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TaskEditorComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,101 @@
import {Component, Inject, OnInit} from '@angular/core';
import {FormControl, UntypedFormControl, Validators} from "@angular/forms";
import * as _moment from 'moment';
import {DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE} from "@angular/material/core";
import {MomentDateAdapter} from "@angular/material-moment-adapter";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {TaskService} from "../../../api";
import {TaskEditorData} from "./TaskEditorData";
import {MatSnackBar} from "@angular/material/snack-bar";
@Component({
selector: 'app-task-editor',
templateUrl: './task-editor.component.html',
styleUrls: ['./task-editor.component.css'],
})
export class TaskEditorComponent implements OnInit {
nameCtrl: FormControl = new FormControl('', [Validators.required, Validators.maxLength(255)])
etaCtrl: FormControl = new FormControl(0, [Validators.required, Validators.min(0)])
startDate: FormControl = new FormControl(Date.now(), [Validators.required])
endDate: FormControl = new FormControl('');
constructor(private dialog: MatDialogRef<TaskEditorComponent>,
private taskService: TaskService,
@Inject(MAT_DIALOG_DATA) public editorData: TaskEditorData,
private snackbar: MatSnackBar) { }
ngOnInit(): void {
if(this.editorData.task != undefined) {
this.nameCtrl.setValue(this.editorData.task.taskName);
this.etaCtrl.setValue(this.editorData.task.eta)
this.startDate.setValue(this.editorData.task.startDate);
this.endDate.setValue(this.editorData.task.deadline);
}
}
cancel() {
this.dialog.close();
}
submit() {
if(this.editorData.task != undefined) {
this.editTask()
} else {
this.createTask();
}
}
createTask() {
this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, {
taskName: this.nameCtrl.value,
eta: this.etaCtrl.value,
startDate: this.startDate.value,
deadline: this.endDate.value
}).subscribe({
next: resp => {
this.dialog.close(resp);
},
error: err => {
if(err.status == 403) {
this.snackbar.open("No permission", "", {duration: 2000});
} else if(err.status == 404) {
this.snackbar.open("Taskgroup not found", "", {duration: 2000});
} else if(err.status == 409) {
this.snackbar.open("Task already exists", "", {duration: 2000});
} else {
this.snackbar.open("Unexpected error", "", {duration: 3000});
}
}
})
}
editTask() {
this.taskService.tasksTaskIDPost(this.editorData.task!.taskID, {
taskName: this.nameCtrl.value,
eta: this.etaCtrl.value,
startDate: this.startDate.value,
deadline: this.endDate.value
}).subscribe({
next: resp => {
this.editorData.task!.taskName = this.nameCtrl.value;
this.editorData.task!.eta = this.etaCtrl.value;
this.editorData.task!.startDate = this.startDate.value;
this.editorData.task!.deadline = this.endDate.value;
this.dialog.close(true);
},
error: err => {
if(err.status == 403) {
this.snackbar.open("No permission", "", {duration: 2000});
} else if(err.status == 404) {
this.snackbar.open("Taskgroup not found", "", {duration: 2000});
} else if(err.status == 409) {
this.snackbar.open("Task already exists", "", {duration: 2000});
} else {
this.snackbar.open("Unexpected error", "", {duration: 3000});
}
}
})
}
}

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {AbstractControl, FormControl, ValidationErrors, Validators} from "@angular/forms"; import {AbstractControl, UntypedFormControl, ValidationErrors, Validators} from "@angular/forms";
import {AccountService} from "../../../../api"; import {AccountService} from "../../../../api";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
@Component({ @Component({
selector: 'app-change-password', selector: 'app-change-password',
@ -14,9 +14,9 @@ export class ChangePasswordComponent implements OnInit {
hide: boolean = true; hide: boolean = true;
pending: boolean = false; pending: boolean = false;
oldPasswordControl = new FormControl('', [Validators.required]) oldPasswordControl = new UntypedFormControl('', [Validators.required])
newPasswordControl = new FormControl('', [Validators.required, Validators.minLength(6)]) newPasswordControl = new UntypedFormControl('', [Validators.required, Validators.minLength(6)])
duplicatePasswordControl = new FormControl('', [ duplicatePasswordControl = new UntypedFormControl('', [
Validators.required, Validators.required,
(control: AbstractControl): ValidationErrors | null => { (control: AbstractControl): ValidationErrors | null => {
return control.value != this.newPassword ? {duplicate: {value: control.value}} : null return control.value != this.newPassword ? {duplicate: {value: control.value}} : null

View File

@ -1,9 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {FormControl, Validators} from "@angular/forms"; import {UntypedFormControl, Validators} from "@angular/forms";
import {AccountService} from "../../../../api"; import {AccountService} from "../../../../api";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {AuthService} from "../../../auth.service"; import {AuthService} from "../../../auth.service";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
@Component({ @Component({
selector: 'app-delete-account', selector: 'app-delete-account',
@ -14,7 +14,7 @@ export class DeleteAccountComponent implements OnInit {
password: string = ""; password: string = "";
hide: boolean = true; hide: boolean = true;
passwordControl = new FormControl('', [Validators.required]); passwordControl = new UntypedFormControl('', [Validators.required]);
pending: boolean = false; pending: boolean = false;
constructor(private accountService: AccountService, private router: Router, private auth: AuthService, private snackbar: MatSnackBar) { } constructor(private accountService: AccountService, private router: Router, private auth: AuthService, private snackbar: MatSnackBar) { }

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {AccountService} from "../../../../api"; import {AccountService} from "../../../../api";
import {FormControl, Validators} from "@angular/forms"; import {UntypedFormControl, Validators} from "@angular/forms";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar as MatSnackBar} from "@angular/material/snack-bar";
@Component({ @Component({
selector: 'app-manage-email', selector: 'app-manage-email',
@ -12,8 +12,8 @@ export class ManageEmailComponent implements OnInit {
email : string = ""; email : string = "";
password: string = ""; password: string = "";
emailControl = new FormControl('', [Validators.required, Validators.email]); emailControl = new UntypedFormControl('', [Validators.required, Validators.email]);
passwordControl = new FormControl('', [Validators.required]) passwordControl = new UntypedFormControl('', [Validators.required])
hide: boolean = true; hide: boolean = true;
pending: boolean = false; pending: boolean = false;

View File

@ -7,20 +7,8 @@ import {
platformBrowserDynamicTesting platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
<T>(id: string): T;
keys(): string[];
};
};
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), platformBrowserDynamicTesting(),
); );
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -2,7 +2,7 @@
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "src",
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
@ -16,12 +16,13 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "es2017", "target": "ES2022",
"module": "es2020", "module": "es2020",
"lib": [ "lib": [
"es2020", "es2020",
"dom" "dom"
] ],
"useDefineForClassFields": false
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,

View File

@ -654,9 +654,8 @@ paths:
content: content:
'application/json': 'application/json':
schema: schema:
type: array type: object
items: $ref: '#/components/schemas/TaskgroupDetailInfo'
$ref: '#/components/schemas/TaskgroupEntityInfo'
403: 403:
description: No permission description: No permission
content: content:
@ -829,6 +828,303 @@ paths:
example: "failed" example: "failed"
enum: enum:
- "failed" - "failed"
/taskgroups/{taskgroupID}/clear:
delete:
security:
- API_TOKEN: []
tags:
- taskgroup
summary: clears tasks
description: Deletes all tasks of that taskgroup
parameters:
- name: taskgroupID
in: path
description: internal id of taskgroup
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"
/tasks/{taskgroupID}/{status}:
get:
security:
- API_TOKEN: []
tags:
- task
summary: list tasks
description: list tasks
parameters:
- name: taskgroupID
in: path
description: internal id of taskgroup
required: true
schema:
type: number
example: 1
- name: status
in: path
description: scope of listed tasks
required: true
schema:
type: string
enum:
- all
- overdue
- upcoming
example: all
responses:
200:
description: Anfrage erfolgreich
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/TaskEntityInfo'
403:
description: No permission
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
404:
description: Taskgroup does not exist
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
/tasks/{taskgroupID}:
put:
security:
- API_TOKEN: []
tags:
- task
summary: creates a new task
description: creates tasks
parameters:
- name: taskgroupID
in: path
description: internal id of taskgroup
required: true
schema:
type: number
example: 1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TaskFieldInfo'
responses:
200:
description: Anfrage erfolgreich
content:
'application/json':
schema:
type: object
$ref: '#/components/schemas/TaskEntityInfo'
403:
description: No permission
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
404:
description: Taskgroup does not exist
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
409:
description: Task already exist
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
/tasks/{taskID}:
post:
security:
- API_TOKEN: []
tags:
- task
summary: edits an existing task
description: edits an existing task
parameters:
- name: taskID
in: path
description: internal id of task
required: true
schema:
type: number
example: 1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TaskFieldInfo'
responses:
200:
description: Anfrage erfolgreich
content:
'application/json':
schema:
type: object
$ref: '#/components/schemas/TaskEntityInfo'
403:
description: No permission
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
404:
description: Taskgroup does not exist
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
409:
description: Task already exist
content:
'application/json':
schema:
type: object
required:
- status
properties:
status:
type: string
description: Status
example: "failed"
enum:
- "failed"
delete:
security:
- API_TOKEN: []
tags:
- task
summary: edits an existing task
description: edits an existing task
parameters:
- name: taskID
in: path
description: internal id of task
required: true
schema:
type: number
example: 1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TaskFieldInfo'
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"
components: components:
securitySchemes: securitySchemes:
API_TOKEN: API_TOKEN:
@ -836,6 +1132,18 @@ components:
scheme: bearer scheme: bearer
bearerFormat: JWT bearerFormat: JWT
schemas: schemas:
SimpleStatusResponse:
required:
- status
additionalProperties: false
properties:
status:
type: string
description: Response Status der Request
example: "failed"
enum:
- success
- failed
LoginRequest: LoginRequest:
required: required:
- username - username
@ -1019,7 +1327,6 @@ components:
required: required:
- taskgroupID - taskgroupID
- taskgroupName - taskgroupName
- parentTaskgroup
additionalProperties: false additionalProperties: false
properties: properties:
taskgroupID: taskgroupID:
@ -1032,9 +1339,6 @@ components:
example: Taskgroup 1 example: Taskgroup 1
maxLength: 255 maxLength: 255
minLength: 1 minLength: 1
parentTaskgroup:
type: object
$ref: '#/components/schemas/TaskgroupEntityInfo'
TaskgroupFieldInfo: TaskgroupFieldInfo:
required: required:
- name - name
@ -1051,4 +1355,86 @@ components:
type: number type: number
description: internal id of parent Taskgroup description: internal id of parent Taskgroup
example: 1 example: 1
TaskgroupDetailInfo:
required:
- children
- ancestors
- taskgroupInfo
additionalProperties: false
properties:
children:
type: array
items:
$ref: '#/components/schemas/TaskgroupEntityInfo'
ancestors:
type: array
items:
$ref: '#/components/schemas/TaskgroupEntityInfo'
taskgroupInfo:
type: object
$ref: '#/components/schemas/TaskgroupEntityInfo'
TaskEntityInfo:
required:
- taskID
- taskName
- eta
- startDate
- deadline
- overdue
- finished
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
startDate:
type: string
format: date
description: date from which the task can be started
deadline:
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
finished:
type: boolean
description: determines whether the task is finished
example: True
TaskFieldInfo:
required:
- taskName
- eta
- startDate
- deadline
additionalProperties: false
properties:
taskName:
type: string
description: name of task
example: Vorlesung schauen
eta:
type: number
description: expected time to finish task
example: 10
minimum: 0
startDate:
type: string
format: date
description: date from which the task can be started
deadline:
type: string
format: date
description: date until the task has to be finished