issue-52 #64

Merged
sebastian merged 17 commits from issue-52 into docker-deployment 2023-11-13 22:48:50 +01:00
29 changed files with 562 additions and 47 deletions

View File

@ -4,14 +4,7 @@
<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="Fix parsing datetime"> <list default="true" id="3a869f59-290a-4ab2-b036-a878ce801bc4" name="Changes" comment="Edit and Delete in Draggable Scheduler" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskSchedule/scheduleInfos/AdvancedScheduleFieldInfo.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/api/models/timemanager/taskSchedule/scheduleInfos/AdvancedScheduleFieldInfo.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/TaskgroupRepository.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/repositories/timemanager/TaskgroupRepository.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/core/services/TaskgroupService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/core/services/TaskgroupService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/java/core/taskgroups/TaskgroupRepsitoryTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/java/core/taskgroups/TaskgroupRepsitoryTest.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/app/schedules/advanced-scheduler/advanced-scheduler.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/app/schedules/advanced-scheduler/advanced-scheduler.component.ts" afterDir="false" />
</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" />
@ -181,11 +174,11 @@
</configuration> </configuration>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="JUnit.ScheduleServiceTest.scheduleNow" />
<item itemvalue="JUnit.ScheduleServiceTest.editBasicSchedule" />
<item itemvalue="JUnit.ScheduleServiceTest.editscheduleAdvanced" />
<item itemvalue="JUnit.ScheduleServiceTest.scheduleAdvanced" />
<item itemvalue="JUnit.TaskServiceTest" /> <item itemvalue="JUnit.TaskServiceTest" />
<item itemvalue="JUnit.ScheduleServiceTest.scheduleNow" />
<item itemvalue="JUnit.ScheduleServiceTest.scheduleAdvanced" />
<item itemvalue="JUnit.ScheduleServiceTest.editscheduleAdvanced" />
<item itemvalue="JUnit.ScheduleServiceTest.editBasicSchedule" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>
@ -225,6 +218,7 @@
<workItem from="1699639316405" duration="9267000" /> <workItem from="1699639316405" duration="9267000" />
<workItem from="1699684493731" duration="1121000" /> <workItem from="1699684493731" duration="1121000" />
<workItem from="1699769541677" duration="7576000" /> <workItem from="1699769541677" duration="7576000" />
<workItem from="1699898375418" duration="325000" />
</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" />
@ -578,15 +572,29 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1699806194258</updated> <updated>1699806194258</updated>
</task> </task>
<option name="localTasksCounter" value="45" /> <task id="LOCAL-00045" summary="Fix parsing datetime (diesmal hoffentlich wirklich)">
<option name="closed" value="true" />
<created>1699809089060</created>
<option name="number" value="00045" />
<option name="presentableId" value="LOCAL-00045" />
<option name="project" value="LOCAL" />
<updated>1699809089060</updated>
</task>
<task id="LOCAL-00046" summary="Edit and Delete in Draggable Scheduler">
<option name="closed" value="true" />
<created>1699824923378</created>
<option name="number" value="00046" />
<option name="presentableId" value="LOCAL-00046" />
<option name="project" value="LOCAL" />
<updated>1699824923378</updated>
</task>
<option name="localTasksCounter" value="47" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<MESSAGE value="Write simple Testcase for ci/cd" />
<MESSAGE value="Deactivate Overall System test (temporarly)" />
<MESSAGE value="Fix failing test case" /> <MESSAGE value="Fix failing test case" />
<MESSAGE value="TaskgroupRepsitoryTest" /> <MESSAGE value="TaskgroupRepsitoryTest" />
<MESSAGE value="TaskgroupRepsitoryTest (+Delete)" /> <MESSAGE value="TaskgroupRepsitoryTest (+Delete)" />
@ -610,7 +618,9 @@
<MESSAGE value="Deploy on docker (not productive yet!)" /> <MESSAGE value="Deploy on docker (not productive yet!)" />
<MESSAGE value="Fix deleting tasks" /> <MESSAGE value="Fix deleting tasks" />
<MESSAGE value="Fix parsing datetime" /> <MESSAGE value="Fix parsing datetime" />
<option name="LAST_COMMIT_MESSAGE" value="Fix parsing datetime" /> <MESSAGE value="Fix parsing datetime (diesmal hoffentlich wirklich)" />
<MESSAGE value="Edit and Delete in Draggable Scheduler" />
<option name="LAST_COMMIT_MESSAGE" value="Edit and Delete in Draggable Scheduler" />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
@ -622,12 +632,7 @@
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" type="java-line"> <line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java</url> <url>file://$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java</url>
<line>68</line> <line>98</line>
<option name="timeStamp" value="14" />
</line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java</url>
<line>93</line>
<option name="timeStamp" value="16" /> <option name="timeStamp" value="16" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" type="java-line"> <line-breakpoint enabled="true" type="java-line">
@ -635,6 +640,21 @@
<line>93</line> <line>93</line>
<option name="timeStamp" value="35" /> <option name="timeStamp" value="35" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/api/controller/ScheduleController.java</url>
<line>83</line>
<option name="timeStamp" value="36" />
</line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/entities/timemanager/Taskgroup.java</url>
<line>99</line>
<option name="timeStamp" value="37" />
</line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/core/entities/timemanager/Taskgroup.java</url>
<line>100</line>
<option name="timeStamp" value="38" />
</line-breakpoint>
</breakpoints> </breakpoints>
</breakpoint-manager> </breakpoint-manager>
</component> </component>

View File

@ -1,9 +1,15 @@
FROM maven:3.8.1-openjdk-17-slim AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean test package
FROM openjdk:17-jdk-alpine FROM openjdk:17-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring USER spring:spring
ARG JAR_FILE=target/*.jar #ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar #COPY ${JAR_FILE} app.jar
COPY --from=build /home/app/target/*.jar app.jar
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"] ENTRYPOINT ["java","-jar","/app.jar"]

View File

@ -75,13 +75,21 @@ public class ScheduleController {
if(scheduleFieldInfo instanceof BasicScheduleFieldInfo) { if(scheduleFieldInfo instanceof BasicScheduleFieldInfo) {
ServiceResult<AbstractSchedule> scheduleResult = taskScheduleService.scheduleBasic(permissionResult.getResult(), (BasicScheduleFieldInfo) scheduleFieldInfo); ServiceResult<AbstractSchedule> scheduleResult = taskScheduleService.scheduleBasic(permissionResult.getResult(), (BasicScheduleFieldInfo) scheduleFieldInfo);
if(scheduleResult.getExitCode() == ServiceExitCode.INVALID_OPERATION) {
return ResponseEntity.status(400).body(new SimpleStatusResponse("failed"));
}
return ResponseEntity.ok(scheduleResult.getResult().toScheduleInfo()); return ResponseEntity.ok(scheduleResult.getResult().toScheduleInfo());
} else if(scheduleFieldInfo instanceof AdvancedScheduleFieldInfo) { } else if(scheduleFieldInfo instanceof AdvancedScheduleFieldInfo) {
ServiceResult<AbstractSchedule> scheduleResult = taskScheduleService.scheduleAdvanced(permissionResult.getResult(), (AdvancedScheduleFieldInfo) scheduleFieldInfo); ServiceResult<AbstractSchedule> scheduleResult = taskScheduleService.scheduleAdvanced(permissionResult.getResult(), (AdvancedScheduleFieldInfo) scheduleFieldInfo);
if(scheduleResult.getResult() != null) {
return ResponseEntity.ok(scheduleResult.getResult().toScheduleInfo()); return ResponseEntity.ok(scheduleResult.getResult().toScheduleInfo());
} else { } else {
return ResponseEntity.status(400).body(new SimpleStatusResponse("failed")); return ResponseEntity.status(400).body(new SimpleStatusResponse("failed"));
} }
} else {
return ResponseEntity.status(400).body(new SimpleStatusResponse("failed"));
}
} }
@PostMapping("/schedules/{scheduleID}/basic") @PostMapping("/schedules/{scheduleID}/basic")

View File

@ -2,17 +2,17 @@ package core.api.models.timemanager.taskSchedule;
public class TaskScheduleStopResponse { public class TaskScheduleStopResponse {
private int activeTime; private int workTime;
public TaskScheduleStopResponse(int activeTime) { public TaskScheduleStopResponse(int activeTime) {
this.activeTime = activeTime; this.workTime = activeTime;
} }
public int getActiveTime() { public int getWorkTime() {
return activeTime; return workTime;
} }
public void setActiveTime(int activeTime) { public void setWorkTime(int workTime) {
this.activeTime = activeTime; this.workTime = workTime;
} }
} }

View File

@ -8,6 +8,7 @@ import java.time.LocalDate;
public class BasicScheduleFieldInfo extends ScheduleFieldInfo{ public class BasicScheduleFieldInfo extends ScheduleFieldInfo{
@NotNull @NotNull
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
private LocalDate scheduleDate; private LocalDate scheduleDate;
public BasicScheduleFieldInfo(LocalDate localDate) { public BasicScheduleFieldInfo(LocalDate localDate) {

View File

@ -1,5 +1,6 @@
package core.api.models.timemanager.tasks; package core.api.models.timemanager.tasks;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
@ -11,7 +12,11 @@ public class TaskFieldInfo {
@Length(max = 255) @Length(max = 255)
private String taskName; private String taskName;
private int eta; private int eta;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
private LocalDate startDate; private LocalDate startDate;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
private LocalDate deadline; private LocalDate deadline;
public TaskFieldInfo() { public TaskFieldInfo() {

View File

@ -1,8 +1,11 @@
package core.api.models.timemanager.tasks; package core.api.models.timemanager.tasks;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.entities.timemanager.Task; import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
public class TaskOverviewInfo { public class TaskOverviewInfo {
@ -13,6 +16,7 @@ public class TaskOverviewInfo {
private LocalDate limit; private LocalDate limit;
private boolean overdue; private boolean overdue;
public TaskOverviewInfo(Task task) { public TaskOverviewInfo(Task task) {
this.taskID = task.getTaskID(); this.taskID = task.getTaskID();
this.taskName = task.getTaskName(); this.taskName = task.getTaskName();

View File

@ -92,11 +92,13 @@ public class Taskgroup {
public static List<Taskgroup> getAncestorList(Taskgroup taskgroup) { public static List<Taskgroup> getAncestorList(Taskgroup taskgroup) {
List<Taskgroup> ancestors = new ArrayList<>(); List<Taskgroup> ancestors = new ArrayList<>();
Taskgroup currentTaskgroup = taskgroup; Taskgroup currentTaskgroup = taskgroup;
ancestors.add(taskgroup);
while(currentTaskgroup.parent != null) { while(currentTaskgroup.parent != null) {
ancestors.add(currentTaskgroup.parent); ancestors.add(currentTaskgroup.parent);
currentTaskgroup = currentTaskgroup.parent; currentTaskgroup = currentTaskgroup.parent;
} }
//ancestors.add(taskgroup);
Collections.reverse(ancestors);
return ancestors; return ancestors;
} }

View File

@ -96,6 +96,9 @@ public class TaskScheduleService {
} }
public void deleteSchedule(AbstractSchedule schedule) { public void deleteSchedule(AbstractSchedule schedule) {
schedule.getTask().getBasicTaskSchedules().remove(schedule);
schedule.setTask(null);
scheduleRepository.save(schedule);
scheduleRepository.delete(schedule); scheduleRepository.delete(schedule);
} }

View File

@ -9,6 +9,7 @@
* https://openapi-generator.tech * https://openapi-generator.tech
* Do not edit the class manually. * Do not edit the class manually.
*/ */
import { TaskgroupEntityInfo } from './taskgroupEntityInfo';
export interface TaskOverviewInfo { export interface TaskOverviewInfo {

View File

@ -11,6 +11,7 @@ import {MissedSchedulesComponent} from "./missed-schedules/missed-schedules.comp
import {OverdueTaskOverviewComponent} from "./overdue-task-overview/overdue-task-overview.component"; import {OverdueTaskOverviewComponent} from "./overdue-task-overview/overdue-task-overview.component";
import {UpcomingTaskOverviewComponent} from "./upcoming-task-overview/upcoming-task-overview.component"; import {UpcomingTaskOverviewComponent} from "./upcoming-task-overview/upcoming-task-overview.component";
import {ActiveTaskOverviewComponent} from "./active-task-overview/active-task-overview.component"; import {ActiveTaskOverviewComponent} from "./active-task-overview/active-task-overview.component";
import {DraggableSchedulerComponent} from "./schedules/draggable-scheduler/draggable-scheduler.component";
const routes: Routes = [ const routes: Routes = [
{path: '', component: MainComponent}, {path: '', component: MainComponent},
@ -24,7 +25,8 @@ const routes: Routes = [
{path: 'reschedule', component: MissedSchedulesComponent}, {path: 'reschedule', component: MissedSchedulesComponent},
{path: 'overdue', component: OverdueTaskOverviewComponent}, {path: 'overdue', component: OverdueTaskOverviewComponent},
{path: 'upcoming', component: UpcomingTaskOverviewComponent}, {path: 'upcoming', component: UpcomingTaskOverviewComponent},
{path: 'active', component: ActiveTaskOverviewComponent} {path: 'active', component: ActiveTaskOverviewComponent},
{path: 'scheduler', component: DraggableSchedulerComponent}
]; ];
@NgModule({ @NgModule({

View File

@ -4,6 +4,7 @@
<button mat-button aria-label="Organize" *ngIf="authService.hasKey" [matMenuTriggerFor]="organizeMenu">Organize &#9662;</button> <button mat-button aria-label="Organize" *ngIf="authService.hasKey" [matMenuTriggerFor]="organizeMenu">Organize &#9662;</button>
<mat-menu #organizeMenu=matMenu> <mat-menu #organizeMenu=matMenu>
<button mat-menu-item routerLink="taskgroups/" aria-label="Task groups">Taskgroups</button> <button mat-menu-item routerLink="taskgroups/" aria-label="Task groups">Taskgroups</button>
<button mat-menu-item routerLink="scheduler/" aria-label="Task groups">Scheduler</button>
<button mat-menu-item routerLink="active/" aria-label="Missed Schedules">Active Tasks</button> <button mat-menu-item routerLink="active/" aria-label="Missed Schedules">Active Tasks</button>
<button mat-menu-item routerLink="upcoming/" aria-label="Upcoming Tasks">Upcoming Tasks</button> <button mat-menu-item routerLink="upcoming/" aria-label="Upcoming Tasks">Upcoming Tasks</button>
<button mat-menu-item routerLink="overdue/" aria-label="Overdue Tasks">Overdue Tasks</button> <button mat-menu-item routerLink="overdue/" aria-label="Overdue Tasks">Overdue Tasks</button>

View File

@ -79,6 +79,7 @@ import {NgxMaterialTimepickerModule} from "ngx-material-timepicker";
import { DateTimePickerComponent } from './date-time-picker/date-time-picker.component'; import { DateTimePickerComponent } from './date-time-picker/date-time-picker.component';
import {MatSliderModule} from "@angular/material/slider"; import {MatSliderModule} from "@angular/material/slider";
import {MatLegacySliderModule} from "@angular/material/legacy-slider"; import {MatLegacySliderModule} from "@angular/material/legacy-slider";
import { DraggableSchedulerComponent } from './schedules/draggable-scheduler/draggable-scheduler.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -117,7 +118,8 @@ import {MatLegacySliderModule} from "@angular/material/legacy-slider";
UpcomingTaskOverviewComponent, UpcomingTaskOverviewComponent,
ActiveTaskOverviewComponent, ActiveTaskOverviewComponent,
AdvancedSchedulerComponent, AdvancedSchedulerComponent,
DateTimePickerComponent DateTimePickerComponent,
DraggableSchedulerComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -10,7 +10,7 @@
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<span *ngFor="let taskgroupPath of activeSchedule!.taskgroupPath">{{taskgroupPath.taskgroupName}} /</span> <span *ngFor="let taskgroupPath of activeSchedule!.taskgroupPath">{{taskgroupPath.taskgroupName}} /</span>
<p class="gray-text" *ngIf="activeSchedule!.scheduleType==='BASIC'">Running for {{displayTime}}</p> <p class="gray-text">Running for {{displayTime}}</p>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button mat-raised-button class="grayBtn" (click)="stopTask(false)">Stop</button> <button mat-raised-button class="grayBtn" (click)="stopTask(false)">Stop</button>

View File

@ -63,6 +63,7 @@ export class ActiveScheduleComponent implements OnInit{
schedule: this.activeSchedule!, schedule: this.activeSchedule!,
workedMinutes: resp.workTime workedMinutes: resp.workTime
}) })
console.log(resp.workTime)
this.activeSchedule = undefined this.activeSchedule = undefined
} }
}) })

View File

@ -18,7 +18,7 @@ import {TaskOverviewData} from "./taskgroup-overview/taskgroup-overview.componen
}) })
export class DashboardComponent implements OnInit{ export class DashboardComponent implements OnInit{
missedSchedules: boolean = true missedSchedules: boolean = false
schedules: ScheduleInfo[] = [] schedules: ScheduleInfo[] = []
workedMinutesToday: number = 0 workedMinutesToday: number = 0

View File

@ -11,6 +11,11 @@ export interface NavigationLink {
styleUrls: ['./navigation-link-list.component.css'] styleUrls: ['./navigation-link-list.component.css']
}) })
export class NavigationLinkListComponent implements OnInit{ export class NavigationLinkListComponent implements OnInit{
resetComponent(defaultNavigationLinkPath: NavigationLink[]) {
this.navigationLinks = defaultNavigationLinkPath;
console.log("Navigation Links: ")
console.log(this.navigationLinks)
}
@ -27,7 +32,10 @@ export class NavigationLinkListComponent implements OnInit{
} }
if(this.navigationLinks.find(searchedLink => searchedLink.linkText === linkText) === undefined) { if(this.navigationLinks.find(searchedLink => searchedLink.linkText === linkText) === undefined) {
console.log("Test")
this.navigationLinks.push(navigationLink); this.navigationLinks.push(navigationLink);
} else {
console.log(linkText)
} }
} }

View File

@ -4,9 +4,9 @@
<mat-card-content> <mat-card-content>
<h3> <h3>
<span *ngFor="let taskgroup of task.taskgroups"> <span *ngFor="let taskgroup of task.taskgroups">
<a class="undecorated-link">{{taskgroup.taskgroupName}}</a>/ <a class="undecorated-link" [routerLink]="['/taskgroups', taskgroup.taskgroupID]">{{taskgroup.taskgroupName}}</a>/
</span> </span>
<a class="undecorated-link">{{task.taskName}}</a> <a class="undecorated-link" [routerLink]="['/taskgroups', task.taskgroups[task.taskgroups.length-1].taskgroupID, 'tasks', task.taskID]">{{task.taskName}}</a>
</h3> </h3>
<mat-progress-bar mode="determinate" [value]="calcProgress(task)"></mat-progress-bar> <mat-progress-bar mode="determinate" [value]="calcProgress(task)"></mat-progress-bar>
<div class="originally-planned-container"> <div class="originally-planned-container">

View File

@ -9,6 +9,7 @@ import {
} from "../../../api"; } from "../../../api";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar} from "@angular/material/snack-bar";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import * as moment from "moment";
@Component({ @Component({
selector: 'app-basic-scheduler', selector: 'app-basic-scheduler',
@ -36,7 +37,7 @@ export class BasicSchedulerComponent implements OnChanges{
if(this.task != undefined) { if(this.task != undefined) {
if(this.scheduleEntityInfo == undefined) { if(this.scheduleEntityInfo == undefined) {
this.scheduleService.schedulesTaskIDBasicPut(this.task.taskID, { this.scheduleService.schedulesTaskIDBasicPut(this.task.taskID, {
scheduleDate: this.dateCtrl.value scheduleDate: moment(this.dateCtrl.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ')
}).subscribe({ }).subscribe({
next: resp => { next: resp => {
this.scheduleEmitter.emit(resp as BasicScheduleInfo); this.scheduleEmitter.emit(resp as BasicScheduleInfo);
@ -44,7 +45,7 @@ export class BasicSchedulerComponent implements OnChanges{
}) })
} else { } else {
this.scheduleService.schedulesScheduleIDBasicPost(this.scheduleEntityInfo!.scheduleID, { this.scheduleService.schedulesScheduleIDBasicPost(this.scheduleEntityInfo!.scheduleID, {
scheduleDate: this.dateCtrl.value scheduleDate: moment(this.dateCtrl.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ')
}).subscribe({ }).subscribe({
next: resp => { next: resp => {
this.router.navigateByUrl("/taskgroups/" + this.taskgroup!.taskgroupID + "/tasks/" + this.task!.taskID); this.router.navigateByUrl("/taskgroups/" + this.taskgroup!.taskgroupID + "/tasks/" + this.task!.taskID);

View File

@ -0,0 +1,60 @@
.container {
margin: 20px auto;
width: 80%;
}
.spacer {
margin-bottom: 2.5%;
}
@media screen and (max-width: 600px) {
.container {
width: 100%;
margin: 20px 10px;
}
}
.schedule-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.long-form {
width: 100%;
}
.taskgroup-overview {
width: 90%;
display: inline-block;
}
app-task-overview {
margin-top: 20px;
}
::ng-deep .mat-mdc-list-base {
--mdc-list-list-item-label-text-color: black
}
::ng-deep .mat-mdc-list-base .taskgroup-btn, ::ng-deep .mat-mdc-list-base .taskgroup-last-btn {
--mdc-list-list-item-label-text-color: black
}
.task-card {
background-color: #f3f3f3;
border: 0;
line-height: 4em;
}
.lightBlueBtn {
background-color: #3498db;
color: white;
}
::ng-deep .cal-event-title {
}

View File

@ -0,0 +1,54 @@
<div class="container">
<app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<div class="schedule-header">
<h1>Monday, {{ viewDate | calendarDate:(view + 'ViewTitle'):'en':1 }}</h1>
<mat-form-field style="float:right;">
<mat-label>Schedule Strategy</mat-label>
<mat-select [(ngModel)]="scheduleStrategy">
<mat-option [value]="1">Basic</mat-option>
<!--<mat-option [value]="2">Moderate</mat-option>-->
<mat-option [value]="3">Advanced</mat-option>
</mat-select>
</mat-form-field>
</div>
<div style="display: flex; justify-content: space-between;">
<div style="width: 75%">
<mwl-calendar-week-view [viewDate]="viewDate" [daysInWeek]="daysInWeek" [dayStartHour]="7" [dayEndHour]="21" [refresh]="refresh"
[snapDraggedEvents]="false"
(eventTimesChanged)="eventDropped($event)" [events]="events" (eventClicked)="eventClicked('Click', $event.event)"
>
</mwl-calendar-week-view>
</div>
<div style="width: 23%;">
<div *ngIf="tasks.length > 0">
<mat-action-list style="padding: 0;">
<button mat-list-item class="lightBlueBtn" [routerLink]="['/scheduler']">Tasks</button>
</mat-action-list>
<div style="margin-bottom: 20px">
<div mwlDroppable
(drop)="externalDrop($event.dropData.event)"
dragOverClass="drag-over">
<div *ngFor="let event of tasks" mwlDraggable
[dropData]="{event: event}"
[touchStartLongPress]="{ delay: 300, delta: 30 }"
dragActiveClass="drag-active" class="task-card">
<div>
<a href="javascript:;" [style.color]="event.color" style="text-decoration: none; color: black; margin-left: 20px">
{{ event.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<app-taskgroup-overview (taskgroupSelected)="onSelectTaskgroup($event)"></app-taskgroup-overview>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,285 @@
import {Component, ViewChild} from '@angular/core';
import {NavigationLink, NavigationLinkListComponent} from "../../navigation-link-list/navigation-link-list.component";
import {
AdvancedScheduleInfo,
BasicScheduleInfo,
ScheduleInfo,
ScheduleService,
TaskEntityInfo,
TaskgroupEntityInfo,
TaskOverviewInfo, TaskShortInfo
} from "../../../api";
import {Subject} from "rxjs";
import {CalendarEvent, CalendarEventAction, CalendarEventTimesChangedEvent, CalendarView} from "angular-calendar";
import {TaskOverviewData} from "../../dashboard/taskgroup-overview/taskgroup-overview.component";
import {EventColor} from "calendar-utils";
import * as moment from "moment";
import {Router} from "@angular/router";
const colors: Record<string, EventColor> = {
red: {
primary: '#ad2121',
secondary: '#FAE3E3',
},
blue: {
primary: '#1e90ff',
secondary: '#D1E8FF',
},
yellow: {
primary: '#e3bc08',
secondary: '#FDF1BA',
},
};
@Component({
selector: 'app-draggable-scheduler',
templateUrl: './draggable-scheduler.component.html',
styleUrls: ['./draggable-scheduler.component.css'],
styles: [
`
h3 {
margin: 0 0 10px;
}
pre {
background-color: #f5f5f5;
padding: 15px;
}
`,
],
})
export class DraggableSchedulerComponent {
defaultNavigationLinkPath: NavigationLink[] = [
{
linkText: "Dashboard",
routerLink: ['/']
},
{
linkText: "Taskgroups",
routerLink: ["/taskgroups"]
}
]
taskgroups: TaskgroupEntityInfo[] = []
taskgroup: TaskgroupEntityInfo | undefined
taskgroupPath: TaskgroupEntityInfo[] = []
taskgroupID: number = -1;
scheduleID: number = -1;
editedSchedule: ScheduleInfo | undefined
@ViewChild('navLinkList') navLinkListComponent: NavigationLinkListComponent | undefined
task: TaskEntityInfo | undefined
scheduleStrategy: number = 1
/**************************************************/
//Calendar-Stuff
/**************************************************/
view: CalendarView = CalendarView.Week;
viewDate = new Date();
daysInWeek = 7;
refresh: Subject<void> = new Subject<void>()
events: CalendarEvent[] = []
actions: CalendarEventAction[] = [
{
label: '<i class="fas fa-fw fa-pencil-alt"></i>',
a11yLabel: 'Edit',
onClick: ({ event }: { event: CalendarEvent }): void => {
this.eventClicked('Edit', event)
},
},
{
label: '<i class="fas fa-fw fa-trash-alt"></i>',
a11yLabel: 'Delete',
onClick: ({ event }: { event: CalendarEvent }): void => {
this.eventClicked('Delete', event);
},
},
]
tasks: CalendarEvent[] = []
selectedTaskgroupID: number | undefined
constructor(private scheduleService: ScheduleService,
private router: Router) {
}
ngOnInit() {
this.scheduleService.schedulesGet().subscribe({
next: resp => {
resp.forEach(schedule => {
if(schedule.scheduleType == 'BASIC') {
const basicSchedule = schedule as BasicScheduleInfo
this.events.push({
title: this.computeTaskPath(schedule.taskgroupPath, schedule.task),
color: colors['red'],
start: new Date(basicSchedule.scheduleDate),
actions: this.actions,
allDay: true,
draggable: true,
resizable: {
beforeStart: true,
afterEnd: true
},
meta: {
taskID: schedule.task.taskID,
scheduleID: schedule.scheduleID,
taskgroupID: schedule.taskgroupPath[0].taskgroupID
},
})
} else {
const advancedSchedule = schedule as AdvancedScheduleInfo
this.events.push({
title: this.computeTaskPath(schedule.taskgroupPath, schedule.task),
color: colors['red'],
cssClass: 'test',
start: new Date(advancedSchedule.scheduleStartTime),
end: new Date(advancedSchedule.scheduleStopTime),
actions: this.actions,
draggable: true,
resizable: {
beforeStart: true,
afterEnd: true
},
meta: {
taskID: schedule.task.taskID,
scheduleID: schedule.scheduleID,
taskgroupID: schedule.taskgroupPath[0].taskgroupID
},
})
}
});
console.log(this.events)
this.refresh.next();
}
})
}
onSelectTaskgroup(taskOverviewData: TaskOverviewData) {
this.tasks = [];
taskOverviewData.tasks.forEach(task => {
this.tasks.push({
title: task.taskName,
color: colors['yellow'],
start: new Date(),
draggable: true,
cssClass: 'test',
resizable: {
beforeStart: true,
afterEnd: true
},
meta: {
taskID: task.taskID,
scheduleID: undefined,
taskgroupID: taskOverviewData.taskgroupID
},
actions: this.actions
})
})
this.selectedTaskgroupID = taskOverviewData.taskgroupID;
}
externalDrop(event: CalendarEvent) {
if (this.tasks.indexOf(event) === -1) {
this.events = this.events.filter((iEvent) => iEvent !== event);
this.tasks.push(event);
}
}
eventDropped({
event,
newStart,
newEnd,
allDay,
}: CalendarEventTimesChangedEvent): void {
const externalIndex = this.tasks.indexOf(event);
if (typeof allDay !== 'undefined') {
event.allDay = allDay;
}
if (externalIndex > -1) {
this.tasks.splice(externalIndex, 1);
this.events.push(event);
}
event.start = newStart;
if (newEnd) {
event.end = newEnd;
}
this.events = [...this.events];
if(externalIndex > -1) {
//Create new schedule as a new event was dropped
if(event.allDay) {
this.scheduleService.schedulesTaskIDBasicPut(event.meta.taskID,{
scheduleDate: moment(event.start).format('YYYY-MM-DDTHH:mm:ss.SSSZ')
}).subscribe({
next: resp => {
event.meta.scheduleID = resp.scheduleID;
event.color = colors['red'];
event.title = this.computeTaskPath(resp.taskgroupPath, resp.task)
}
})
} else {
console.log("Start: " + event.start);
console.log("End: " + event.end);
if(event.end == undefined) {
event.end = moment(event.start).add(30, 'm').toDate()
}
this.scheduleService.schedulesTaskIDAdvancedPut(event.meta.taskID, {
scheduleStartTime: moment(event.start).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
scheduleStopTime: moment(event.end).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
}).subscribe({
next: resp => {
event.meta.scheduleID = resp.scheduleID;
event.color = colors['red'];
event.title = this.computeTaskPath(resp.taskgroupPath, resp.task)
}
})
}
} else {
if(event.allDay) {
this.scheduleService.schedulesScheduleIDBasicPost(event.meta.scheduleID,{
scheduleDate: moment(event.start).format('YYYY-MM-DDTHH:mm:ss.SSSZ')
}).subscribe({
next: resp => {
console.log("Updated")
}
})
} else {
this.scheduleService.schedulesScheduleIDAdvancedPost(event.meta.scheduleID, {
scheduleStartTime: moment(event.start).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
scheduleStopTime: moment(event.end).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
}).subscribe({
next: resp => {
console.log("Updated")
}
})
}
}
}
computeTaskPath(taskgroupPath: TaskgroupEntityInfo[], task: TaskShortInfo | TaskEntityInfo) {
let result = "";
taskgroupPath.forEach(taskgroupPathPart => {
result += taskgroupPathPart.taskgroupName + "/"
});
result += task!.taskName
return result;
}
eventClicked(action: string, event: CalendarEvent): void {
if(action == 'Click') {
this.router.navigateByUrl("/taskgroups/" + event.meta.taskgroupID + "/tasks/" + event.meta.taskID);
} else if(action == 'Edit') {
this.router.navigateByUrl("/taskgroups/" + event.meta.taskgroupID + "/tasks/" + event.meta.taskID + "/schedule/" + event.meta.scheduleID);
} else if(action == 'Delete') {
this.scheduleService.schedulesScheduleIDDelete(event.meta.scheduleID).subscribe({
next: resp => {
this.events = this.events.filter(calendarEvent => calendarEvent.meta.scheduleID !== event.meta.scheduleID);
}
})
}
}
}

View File

@ -26,3 +26,6 @@
width: 100%; width: 100%;
} }
::ng-deep .cal-event-title {
white-space: normal;
}

View File

@ -40,13 +40,24 @@ export class TaskgroupDashboardComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.activatedRoute.paramMap.subscribe(params => { this.activatedRoute.paramMap.subscribe(params => {
if(params.has('taskgroupID')) { if(params.has('taskgroupID')) {
console.log("Update of taskgroup")
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.children this.taskgroups = resp.children
this.taskgroupPath = resp.ancestors this.taskgroupPath = resp.ancestors
this.taskgroup = resp.taskgroupInfo; this.taskgroup = resp.taskgroupInfo;
this.navLinkListComponent!.addNavigationLink(this.taskgroup.taskgroupName, ['/taskgroups', this.taskgroup.taskgroupID.toString()]) this.navLinkListComponent!.resetComponent([
{
linkText: "Dashboard",
routerLink: ['/']
},
{
linkText: "Taskgroups",
routerLink: ["/taskgroups"]
}
]);
console.log(this.taskgroups)
this.taskgroupPath.forEach(taskgroupEntity => { this.taskgroupPath.forEach(taskgroupEntity => {
this.navLinkListComponent!.addNavigationLink(taskgroupEntity.taskgroupName, ['/taskgroups', taskgroupEntity.taskgroupID.toString()]); this.navLinkListComponent!.addNavigationLink(taskgroupEntity.taskgroupName, ['/taskgroups', taskgroupEntity.taskgroupID.toString()]);
}) })

View File

@ -49,7 +49,16 @@ export class TaskDetailOverviewComponent implements OnInit {
this.taskgroups = resp.children this.taskgroups = resp.children
this.taskgroupPath = resp.ancestors this.taskgroupPath = resp.ancestors
this.taskgroup = resp.taskgroupInfo; this.taskgroup = resp.taskgroupInfo;
this.navLinkListComponent!.addNavigationLink(this.taskgroup.taskgroupName, ['/taskgroups', this.taskgroup.taskgroupID.toString()]) this.navLinkListComponent!.resetComponent([
{
linkText: "Dashboard",
routerLink: ['/']
},
{
linkText: "Taskgroups",
routerLink: ["/taskgroups"]
}
]);
this.taskgroupPath.forEach(taskgroupEntity => { this.taskgroupPath.forEach(taskgroupEntity => {
this.navLinkListComponent!.addNavigationLink(taskgroupEntity.taskgroupName, ['/taskgroups', taskgroupEntity.taskgroupID.toString()]); this.navLinkListComponent!.addNavigationLink(taskgroupEntity.taskgroupName, ['/taskgroups', taskgroupEntity.taskgroupID.toString()]);
}) })

View File

@ -7,6 +7,7 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {TaskService} from "../../../api"; import {TaskService} from "../../../api";
import {TaskEditorData} from "./TaskEditorData"; import {TaskEditorData} from "./TaskEditorData";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar} from "@angular/material/snack-bar";
import * as moment from "moment/moment";
@Component({ @Component({
@ -53,8 +54,8 @@ export class TaskEditorComponent implements OnInit {
this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, { this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, {
taskName: this.nameCtrl.value, taskName: this.nameCtrl.value,
eta: this.etaCtrl.value, eta: this.etaCtrl.value,
startDate: this.startDate.value, startDate: moment(this.startDate.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
deadline: this.endDate.value deadline: moment(this.endDate.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ')
}).subscribe({ }).subscribe({
next: resp => { next: resp => {
this.dialog.close(resp); this.dialog.close(resp);

View File

@ -11,6 +11,7 @@
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
</head> </head>
<body class="mat-typography"> <body class="mat-typography">
<app-root></app-root> <app-root></app-root>

View File

@ -2421,6 +2421,7 @@ components:
- eta - eta
- limit - limit
- overdue - overdue
- taskgroupPath
additionalProperties: false additionalProperties: false
properties: properties:
taskID: taskID:
@ -2448,6 +2449,10 @@ components:
type: number type: number
description: number in minutes that was already worked on this task description: number in minutes that was already worked on this task
example: 10 example: 10
taskgroupPath:
type: array
items:
$ref: '#/components/schemas/TaskgroupEntityInfo'
ScheduleStatus: ScheduleStatus:
required: required:
- activeMinutes - activeMinutes