issue-18 #28

Merged
sebastian merged 55 commits from issue-18 into master 2023-10-28 19:36:14 +02:00
13 changed files with 397 additions and 53 deletions
Showing only changes of commit 7394f9910a - Show all commits

View File

@ -5,10 +5,10 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Remove update spamming in console"> <list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Remove update spamming in console">
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskgroup/RecursiveTaskgroupInfo.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/tasks/TaskOverviewInfo.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/app.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/app.module.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$/../frontend/src/app/dashboard/dashboard.component.css" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/dashboard/dashboard.component.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/dashboard/dashboard.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/dashboard/dashboard.component.html" 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" />
@ -104,7 +104,7 @@
<workItem from="1698246651541" duration="5106000" /> <workItem from="1698246651541" duration="5106000" />
<workItem from="1698298897364" duration="4242000" /> <workItem from="1698298897364" duration="4242000" />
<workItem from="1698426430946" duration="665000" /> <workItem from="1698426430946" duration="665000" />
<workItem from="1698474363766" duration="612000" /> <workItem from="1698474363766" duration="1934000" />
</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" />

View File

@ -1,6 +1,7 @@
package core.api.controller; package core.api.controller;
import core.api.models.auth.SimpleStatusResponse; import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.taskgroup.RecursiveTaskgroupInfo;
import core.api.models.timemanager.taskgroup.TaskgroupDetailInfo; import core.api.models.timemanager.taskgroup.TaskgroupDetailInfo;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo; import core.api.models.timemanager.taskgroup.TaskgroupFieldInfo;
@ -80,6 +81,13 @@ public class TaskgroupController {
return ResponseEntity.ok(taskgroupEntityInfos); return ResponseEntity.ok(taskgroupEntityInfos);
} }
@GetMapping("/taskgroups/tree")
public ResponseEntity<List<RecursiveTaskgroupInfo>> listTaskgroupsOfUserAsTree() {
List<Taskgroup> taskgroups = taskgroupService.getTopTaskgroupsByUser(SecurityContextHolder.getContext().getAuthentication().getName());
List<RecursiveTaskgroupInfo> taskgroupEntityInfos = taskgroups.stream().map(RecursiveTaskgroupInfo::new).toList();
return ResponseEntity.ok(taskgroupEntityInfos);
}
@GetMapping("/taskgroups/{taskgroupID}") @GetMapping("/taskgroups/{taskgroupID}")
public ResponseEntity<?> getDetails(@PathVariable long taskgroupID) { public ResponseEntity<?> getDetails(@PathVariable long taskgroupID) {
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName()); PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());

View File

@ -0,0 +1,63 @@
package core.api.models.timemanager.taskgroup;
import core.api.models.timemanager.tasks.TaskOverviewInfo;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import java.util.ArrayList;
import java.util.List;
public class RecursiveTaskgroupInfo {
private long taskgroupID;
private String taskgroupName;
private List<RecursiveTaskgroupInfo> childTaskgroups = new ArrayList<>();
private List<TaskOverviewInfo> activeTasks = new ArrayList<>();
public RecursiveTaskgroupInfo(Taskgroup taskgroup) {
this.taskgroupID = taskgroup.getTaskgroupID();
this.taskgroupName = taskgroup.getTaskgroupName();
for(Taskgroup child : taskgroup.getChildren()) {
this.childTaskgroups.add(new RecursiveTaskgroupInfo(child));
}
for(Task task : taskgroup.getTasks()) {
this.activeTasks.add(new TaskOverviewInfo(task));
}
}
public long getTaskgroupID() {
return taskgroupID;
}
public void setTaskgroupID(long taskgroupID) {
this.taskgroupID = taskgroupID;
}
public String getTaskgroupName() {
return taskgroupName;
}
public void setTaskgroupName(String taskgroupName) {
this.taskgroupName = taskgroupName;
}
public List<RecursiveTaskgroupInfo> getChildTaskgroups() {
return childTaskgroups;
}
public void setChildTaskgroups(List<RecursiveTaskgroupInfo> childTaskgroups) {
this.childTaskgroups = childTaskgroups;
}
public List<TaskOverviewInfo> getActiveTasks() {
return activeTasks;
}
public void setActiveTasks(List<TaskOverviewInfo> activeTasks) {
this.activeTasks = activeTasks;
}
}

View File

@ -0,0 +1,72 @@
package core.api.models.timemanager.tasks;
import core.entities.timemanager.Task;
import java.time.LocalDate;
public class TaskOverviewInfo {
private long taskID;
private String taskName;
private int activeMinutes;
private int eta;
private LocalDate limit;
private boolean overdue;
public TaskOverviewInfo(Task task) {
this.taskID = task.getTaskID();
this.taskName = task.getTaskName();
this.activeMinutes = task.getWorkTime();
this.eta = task.getEta();
this.limit = task.getDeadline();
this.overdue = LocalDate.now().isAfter(task.getDeadline());
}
public long getTaskID() {
return taskID;
}
public void setTaskID(long taskID) {
this.taskID = taskID;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public int getActiveMinutes() {
return activeMinutes;
}
public void setActiveMinutes(int activeMinutes) {
this.activeMinutes = activeMinutes;
}
public int getEta() {
return eta;
}
public void setEta(int eta) {
this.eta = eta;
}
public LocalDate getLimit() {
return limit;
}
public void setLimit(LocalDate limit) {
this.limit = limit;
}
public boolean isOverdue() {
return overdue;
}
public void setOverdue(boolean overdue) {
this.overdue = overdue;
}
}

View File

@ -28,12 +28,14 @@ model/passwordChangeRequest.ts
model/propertiesInfo.ts model/propertiesInfo.ts
model/propertyInfo.ts model/propertyInfo.ts
model/propertyUpdateRequest.ts model/propertyUpdateRequest.ts
model/recursiveTaskgroupInfo.ts
model/scheduleActivateInfo.ts model/scheduleActivateInfo.ts
model/scheduleInfo.ts model/scheduleInfo.ts
model/signUpRequest.ts model/signUpRequest.ts
model/simpleStatusResponse.ts model/simpleStatusResponse.ts
model/taskEntityInfo.ts model/taskEntityInfo.ts
model/taskFieldInfo.ts model/taskFieldInfo.ts
model/taskOverviewInfo.ts
model/taskScheduleStopResponse.ts model/taskScheduleStopResponse.ts
model/taskShortInfo.ts model/taskShortInfo.ts
model/taskgroupDetailInfo.ts model/taskgroupDetailInfo.ts

View File

@ -20,6 +20,7 @@ import { Observable } from 'rxjs';
import { InlineResponse200 } from '../model/models'; import { InlineResponse200 } from '../model/models';
import { InlineResponse403 } from '../model/models'; import { InlineResponse403 } from '../model/models';
import { RecursiveTaskgroupInfo } from '../model/models';
import { SimpleStatusResponse } from '../model/models'; import { SimpleStatusResponse } from '../model/models';
import { TaskgroupDetailInfo } from '../model/models'; import { TaskgroupDetailInfo } from '../model/models';
import { TaskgroupEntityInfo } from '../model/models'; import { TaskgroupEntityInfo } from '../model/models';
@ -513,4 +514,59 @@ export class TaskgroupService {
); );
} }
/**
* list all top level taskgroups of authorized user
* list all taskgroups of authorized user
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public taskgroupsTreeGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<RecursiveTaskgroupInfo>>;
public taskgroupsTreeGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<RecursiveTaskgroupInfo>>>;
public taskgroupsTreeGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<RecursiveTaskgroupInfo>>>;
public taskgroupsTreeGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
let localVarHeaders = this.defaultHeaders;
let localVarCredential: string | undefined;
// authentication (API_TOKEN) required
localVarCredential = this.configuration.lookupCredential('API_TOKEN');
if (localVarCredential) {
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
}
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
let responseType_: 'text' | 'json' = 'json';
if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
}
return this.httpClient.get<Array<RecursiveTaskgroupInfo>>(`${this.configuration.basePath}/taskgroups/tree`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
} }

View File

@ -12,12 +12,14 @@ export * from './passwordChangeRequest';
export * from './propertiesInfo'; export * from './propertiesInfo';
export * from './propertyInfo'; export * from './propertyInfo';
export * from './propertyUpdateRequest'; export * from './propertyUpdateRequest';
export * from './recursiveTaskgroupInfo';
export * from './scheduleActivateInfo'; export * from './scheduleActivateInfo';
export * from './scheduleInfo'; export * from './scheduleInfo';
export * from './signUpRequest'; export * from './signUpRequest';
export * from './simpleStatusResponse'; export * from './simpleStatusResponse';
export * from './taskEntityInfo'; export * from './taskEntityInfo';
export * from './taskFieldInfo'; export * from './taskFieldInfo';
export * from './taskOverviewInfo';
export * from './taskScheduleStopResponse'; export * from './taskScheduleStopResponse';
export * from './taskShortInfo'; export * from './taskShortInfo';
export * from './taskgroupDetailInfo'; export * from './taskgroupDetailInfo';

View File

@ -0,0 +1,27 @@
/**
* API Title
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { TaskOverviewInfo } from './taskOverviewInfo';
export interface RecursiveTaskgroupInfo {
/**
* internal id of taskgroup
*/
taskgroupID: number;
/**
* name of taskgroup
*/
taskgroupName: string;
childTaskgroups: Array<RecursiveTaskgroupInfo>;
activeTasks: Array<TaskOverviewInfo>;
}

View File

@ -0,0 +1,40 @@
/**
* API Title
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface TaskOverviewInfo {
/**
* internal id of task
*/
taskID: number;
/**
* name of task
*/
taskName: string;
/**
* expected time to finish task
*/
eta: number;
/**
* date until the task has to be finished
*/
limit: string;
/**
* determines whether the task is overdue
*/
overdue: boolean;
/**
* number in minutes that was already worked on this task
*/
activeTime?: number;
}

View File

@ -1,11 +1,10 @@
.container { .container {
margin: 20px auto; margin: 20px auto;
width: 70%; width: 70%;
display: flex;
} }
.spacer { .spacer {
margin-bottom: 2.5%; flex: 1 1 auto;
} }
@ -146,3 +145,19 @@
font-weight: normal; font-weight: normal;
text-align: left; text-align: left;
} }
.task-number{
color: white;
background-color: deepskyblue;
padding: 1px 10px;
border-radius: 5px;
margin-right: 5px;
}
.treenode-content-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}

View File

@ -6,7 +6,11 @@
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding class="taskgroup-btn" matTreeNodePaddingIndent="10"> <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding class="taskgroup-btn" matTreeNodePaddingIndent="10">
<!-- use a disabled button to provide padding for tree leaf --> <!-- use a disabled button to provide padding for tree leaf -->
<button mat-icon-button disabled></button> <button mat-icon-button disabled></button>
<button mat-button class="node-name">{{node.name}}</button> <div class="treenode-content-container">
<button mat-button class="node-name">{{node.name}}</button>
<span class="spacer"></span>
<div class="task-number">{{node.activeTasks}}</div>
</div>
</mat-tree-node> </mat-tree-node>
<!-- This is the tree node template for expandable nodes --> <!-- This is the tree node template for expandable nodes -->
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding class="taskgroup-btn" matTreeNodePaddingIndent="10"> <mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding class="taskgroup-btn" matTreeNodePaddingIndent="10">
@ -16,6 +20,10 @@
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon> </mat-icon>
</button> </button>
<button mat-button class="node-name">{{node.name}}</button> <div class="treenode-content-container">
<button mat-button class="node-name">{{node.name}}</button>
<span class="spacer"></span>
<div class="task-number">{{node.activeTasks}}</div>
</div>
</mat-tree-node> </mat-tree-node>
</mat-tree> </mat-tree>

View File

@ -3,52 +3,17 @@ import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button"; import {MatButtonModule} from "@angular/material/button";
import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from "@angular/material/tree"; import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from "@angular/material/tree";
import {FlatTreeControl} from "@angular/cdk/tree"; import {FlatTreeControl} from "@angular/cdk/tree";
import {RecursiveTaskgroupInfo, TaskgroupService} from "../../../api";
interface TreeNode {
name: string
children?: TreeNode[]
}
const TREE_DATA: TreeNode[] = [
{
name: "KIT",
children: [
{
name: "WS23",
children: [
{
name: "Computergrafik",
children: [{name: "Vorlesungen"}, {name: "Globalübungen"}]
},
{
name: "Softwaretechnik II",
children: [{name: "Vorlesungen"}, {name: "Globalübungen"}]
},
{
name: "Algorithmen II",
children: [{name: "Vorlesungen"}, {name: "Globalübungen"}]
}
]
},
{
name: "SS24",
children: [
{
name: "Interaktive Computergrafik",
children: [{name: "Vorlesungen"}, {name: "Globalübungen"}]
}
]
}
]
}
]
/** Flat node with expandable and level information */ /** Flat node with expandable and level information */
interface ExampleFlatNode { interface ExampleFlatNode {
expandable: boolean; expandable: boolean;
name: string; name: string;
level: number; level: number;
activeTasks: number;
} }
@Component({ @Component({
selector: 'app-taskgroup-overview', selector: 'app-taskgroup-overview',
@ -56,12 +21,12 @@ interface ExampleFlatNode {
styleUrls: ['./taskgroup-overview.component.css'] styleUrls: ['./taskgroup-overview.component.css']
}) })
export class TaskgroupOverviewComponent { export class TaskgroupOverviewComponent {
private _transformer = (node: RecursiveTaskgroupInfo, level: number) => {
private _transformer = (node: TreeNode, level: number) => {
return { return {
expandable: !!node.children && node.children.length > 0, expandable: !!node.childTaskgroups && node.childTaskgroups.length > 0,
name: node.name, name: node.taskgroupName,
level: level, level: level,
activeTasks: node.activeTasks.length
}; };
}; };
@ -74,13 +39,21 @@ export class TaskgroupOverviewComponent {
this._transformer, this._transformer,
node => node.level, node => node.level,
node => node.expandable, node => node.expandable,
node => node.children, node => node.childTaskgroups,
); );
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
constructor() { constructor(private taskgroupService: TaskgroupService) {
this.dataSource.data = TREE_DATA;
}
ngOnInit() {
this.taskgroupService.taskgroupsTreeGet().subscribe({
next: resp => {
this.dataSource.data = resp;
}
})
} }
hasChild = (_: number, node: ExampleFlatNode) => node.expandable; hasChild = (_: number, node: ExampleFlatNode) => node.expandable;

View File

@ -581,6 +581,23 @@ paths:
type: array type: array
items: items:
$ref: '#/components/schemas/TaskgroupEntityInfo' $ref: '#/components/schemas/TaskgroupEntityInfo'
/taskgroups/tree:
get:
security:
- API_TOKEN: []
tags:
- taskgroup
summary: list all top level taskgroups of authorized user
description: list all taskgroups of authorized user
responses:
200:
description: Anfrage erfolgreich
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RecursiveTaskgroupInfo'
/taskgroups: /taskgroups:
get: get:
security: security:
@ -1979,3 +1996,64 @@ components:
type: number type: number
description: time where the schedule was active description: time where the schedule was active
example: 10 example: 10
RecursiveTaskgroupInfo:
required:
- taskgroupID
- taskgroupName
- childTaskgroups
- activeTasks
additionalProperties: false
properties:
taskgroupID:
type: number
description: internal id of taskgroup
example: 1
taskgroupName:
type: string
description: name of taskgroup
example: Taskgroup 1
maxLength: 255
minLength: 1
childTaskgroups:
type: array
items:
$ref: '#/components/schemas/RecursiveTaskgroupInfo'
activeTasks:
type: array
items:
$ref: '#/components/schemas/TaskOverviewInfo'
TaskOverviewInfo:
required:
- taskID
- taskName
- activeMinutes
- eta
- limit
- overdue
additionalProperties: false
properties:
taskID:
type: number
description: internal id of task
example: 1
taskName:
type: string
description: name of task
example: Vorlesung schauen
eta:
type: number
description: expected time to finish task
example: 10
minimum: 0
limit:
type: string
format: date
description: date until the task has to be finished
overdue:
type: boolean
description: determines whether the task is overdue
example: True
activeTime:
type: number
description: number in minutes that was already worked on this task
example: 10