From aafb5886db0f5d91d397628619c917b6cc0e0922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Sat, 16 Mar 2024 09:30:48 +0100 Subject: [PATCH] Frontend Form for generating repeating tasks weekly --- .../repeatinginfo/TaskRepeatWeekDayInfo.java | 10 --- .../repeatinginfo/TaskRepeatWeekInfo.java | 5 +- .../java/core/services/TaskSeriesService.java | 7 +- .../main/java/core/services/TaskService.java | 4 -- frontend/src/api/.openapi-generator/FILES | 2 + frontend/src/api/api/taskseries.service.ts | 67 +++++++++++++++++ frontend/src/api/model/models.ts | 2 + frontend/src/app/app.module.ts | 2 + .../task-dashboard.component.css | 2 +- .../task-dashboard.component.html | 20 +++++- .../task-dashboard.component.ts | 60 +++++++++++++--- .../task-series-creator.component.html | 19 +---- .../task-series-creator.component.ts | 26 ------- openapi.yaml | 72 ++++++++++++++++++- 14 files changed, 224 insertions(+), 74 deletions(-) diff --git a/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java b/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java index 0466638..4330b2b 100644 --- a/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java @@ -6,19 +6,9 @@ import core.entities.timemanager.Task; import java.time.DayOfWeek; public class TaskRepeatWeekDayInfo { - - private DayOfWeek dayOfWeek; private int offset; private long taskID; - public DayOfWeek getDayOfWeek() { - return dayOfWeek; - } - - public void setDayOfWeek(DayOfWeek dayOfWeek) { - this.dayOfWeek = dayOfWeek; - } - public int getOffset() { return offset; } diff --git a/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekInfo.java b/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekInfo.java index fc5301f..39b4b22 100644 --- a/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekInfo.java @@ -3,18 +3,17 @@ package core.api.models.timemanager.tasks.repeatinginfo; import com.fasterxml.jackson.annotation.JsonFormat; import org.hibernate.validator.constraints.Length; +import javax.validation.constraints.Size; import java.time.LocalDate; import java.util.List; public class TaskRepeatWeekInfo { - @Length(min = 1, max = 7) + @Size(min = 1, max = 7) private List weekDayInfos; private DeadlineStrategy deadlineStrategy; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") private LocalDate endDate; - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") - private LocalDate startDate; public List getWeekDayInfos() { return weekDayInfos; diff --git a/backend/src/main/java/core/services/TaskSeriesService.java b/backend/src/main/java/core/services/TaskSeriesService.java index 7541fd1..ac7bb54 100644 --- a/backend/src/main/java/core/services/TaskSeriesService.java +++ b/backend/src/main/java/core/services/TaskSeriesService.java @@ -36,6 +36,9 @@ public class TaskSeriesService { Optional task = taskRepository.findById(taskRepeatDayInfo.getTaskID()); if(task.isEmpty()) return ServiceExitCode.MISSING_ENTITY; + TaskSerieItem rootItem = taskSerie.addTask(task.get()); + task.get().setTaskSerieItem(rootItem); + LocalDate currentTaskDate = task.get().getStartDate().plusDays(taskRepeatDayInfo.getOffset()); while(currentTaskDate.isBefore(taskRepeatInfo.getEndDate())) { Task clonedTask = Task.cloneTask(task.get()); @@ -44,12 +47,14 @@ public class TaskSeriesService { TaskSerieItem taskSerieItem = taskSerie.addTask(clonedTask); clonedTask.setTaskSerieItem(taskSerieItem); createdTasks.add(clonedTask); + + currentTaskDate = currentTaskDate.plusDays(taskRepeatDayInfo.getOffset()); } } taskSerie.getTasks().sort(Comparator.comparing(o -> o.getTask().getStartDate())); for(int i=0; i createTask(Taskgroup taskgroup, TaskFieldInfo taskFieldInfo) { - if(existTaskByName(taskgroup.getTasks(), taskFieldInfo.getTaskName())) { - return new ServiceResult<>(ServiceExitCode.ENTITY_ALREADY_EXIST); - } - //Check for invalid date (deadline before start if(taskFieldInfo.getStartDate() != null && taskFieldInfo.getDeadline() != null && taskFieldInfo.getDeadline().isBefore(taskFieldInfo.getStartDate())) { diff --git a/frontend/src/api/.openapi-generator/FILES b/frontend/src/api/.openapi-generator/FILES index 3469f4b..8171b54 100644 --- a/frontend/src/api/.openapi-generator/FILES +++ b/frontend/src/api/.openapi-generator/FILES @@ -49,6 +49,8 @@ model/taskEntityInfo.ts model/taskFieldInfo.ts model/taskOverviewInfo.ts model/taskRepeatDayInfo.ts +model/taskRepeatWeekDayInfo.ts +model/taskRepeatWeekInfo.ts model/taskScheduleStopResponse.ts model/taskShortInfo.ts model/taskTaskgroupInfo.ts diff --git a/frontend/src/api/api/taskseries.service.ts b/frontend/src/api/api/taskseries.service.ts index 3ce54fc..722e542 100644 --- a/frontend/src/api/api/taskseries.service.ts +++ b/frontend/src/api/api/taskseries.service.ts @@ -20,6 +20,7 @@ import { Observable } from 'rxjs'; import { SimpleStatusResponse } from '../model/models'; import { TaskRepeatDayInfo } from '../model/models'; +import { TaskRepeatWeekInfo } from '../model/models'; import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; import { Configuration } from '../configuration'; @@ -156,4 +157,70 @@ export class TaskseriesService { ); } + /** + * daily repeating task creation + * Creates a daily repeating task + * @param taskRepeatWeekInfo + * @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 tasksTaskseriesWeeklyPost(taskRepeatWeekInfo?: TaskRepeatWeekInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable; + public tasksTaskseriesWeeklyPost(taskRepeatWeekInfo?: TaskRepeatWeekInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public tasksTaskseriesWeeklyPost(taskRepeatWeekInfo?: TaskRepeatWeekInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public tasksTaskseriesWeeklyPost(taskRepeatWeekInfo?: TaskRepeatWeekInfo, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + + 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(`${this.configuration.basePath}/tasks/taskseries/weekly`, + taskRepeatWeekInfo, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + } diff --git a/frontend/src/api/model/models.ts b/frontend/src/api/model/models.ts index b09b6a8..12edcd1 100644 --- a/frontend/src/api/model/models.ts +++ b/frontend/src/api/model/models.ts @@ -30,6 +30,8 @@ export * from './taskEntityInfo'; export * from './taskFieldInfo'; export * from './taskOverviewInfo'; export * from './taskRepeatDayInfo'; +export * from './taskRepeatWeekDayInfo'; +export * from './taskRepeatWeekInfo'; export * from './taskScheduleStopResponse'; export * from './taskShortInfo'; export * from './taskTaskgroupInfo'; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 080df50..71ac5f2 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -93,6 +93,7 @@ import { ConnectionSettingsComponent } from './user-settings/connection-settings import { NtfySettingsComponent } from './user-settings/connection-settings/ntfy-settings/ntfy-settings.component'; import { TaskSeriesCreatorComponent } from './tasks/task-series-creator/task-series-creator.component'; import {MatStepperModule} from "@angular/material/stepper"; +import { TaskWeeklySeriesCreatorComponent } from './tasks/task-weekly-series-creator/task-weekly-series-creator.component'; @NgModule({ declarations: [ AppComponent, @@ -141,6 +142,7 @@ import {MatStepperModule} from "@angular/material/stepper"; ConnectionSettingsComponent, NtfySettingsComponent, TaskSeriesCreatorComponent, + TaskWeeklySeriesCreatorComponent, ], imports: [ BrowserModule, diff --git a/frontend/src/app/tasks/task-dashboard/task-dashboard.component.css b/frontend/src/app/tasks/task-dashboard/task-dashboard.component.css index 532013d..542e3f2 100644 --- a/frontend/src/app/tasks/task-dashboard/task-dashboard.component.css +++ b/frontend/src/app/tasks/task-dashboard/task-dashboard.component.css @@ -20,7 +20,7 @@ td, th { margin: 0 auto; } -.mat-column-status, .mat-column-eta { +.mat-column-status, .mat-column-eta, .mat-column-select { width: 32px; text-align: left; } diff --git a/frontend/src/app/tasks/task-dashboard/task-dashboard.component.html b/frontend/src/app/tasks/task-dashboard/task-dashboard.component.html index d8d33bc..0358314 100644 --- a/frontend/src/app/tasks/task-dashboard/task-dashboard.component.html +++ b/frontend/src/app/tasks/task-dashboard/task-dashboard.component.html @@ -8,6 +8,22 @@
+ + + + + + - + diff --git a/frontend/src/app/tasks/task-dashboard/task-dashboard.component.ts b/frontend/src/app/tasks/task-dashboard/task-dashboard.component.ts index df32cb8..e842c3c 100644 --- a/frontend/src/app/tasks/task-dashboard/task-dashboard.component.ts +++ b/frontend/src/app/tasks/task-dashboard/task-dashboard.component.ts @@ -10,6 +10,8 @@ import {MatSnackBar} from "@angular/material/snack-bar"; import {ClearTaskDialogComponent, ClearTaskDialogData} from "../clear-task-dialog/clear-task-dialog.component"; import * as moment from "moment/moment"; import {TaskStatus, TaskStatusService} from "../task-status.service"; +import {SelectionModel} from "@angular/cdk/collections"; +import {TaskWeeklySeriesCreatorComponent} from "../task-weekly-series-creator/task-weekly-series-creator.component"; @Component({ selector: 'app-task-dashboard', @@ -19,15 +21,7 @@ import {TaskStatus, TaskStatusService} from "../task-status.service"; export class TaskDashboardComponent implements OnChanges{ ngOnChanges(): void { if(this.taskgroupID != undefined) { - this.taskService.tasksTaskgroupIDStatusGet(this.taskgroupID!, "all").subscribe({ - next: resp => { - this.datasource = new MatTableDataSource(resp); - this.datasource.paginator = this.paginator!; - this.datasource.sort = this.sort!; - - resp.forEach(task => console.log(task)) - } - }) + this.fetchTasks() } } @@ -35,9 +29,29 @@ export class TaskDashboardComponent implements OnChanges{ @ViewChild(MatPaginator) paginator: MatPaginator | undefined @ViewChild(MatSort) sort: MatSort | undefined - displayedColumns: string[] = ['status', 'name', 'eta', 'start', 'deadline', 'finished', 'edit', 'delete']; + displayedColumns: string[] = ['select', 'status', 'name', 'eta', 'start', 'deadline', 'finished', 'edit', 'delete']; datasource: MatTableDataSource = new MatTableDataSource(); + selection = new SelectionModel(true, []); + + /** Whether the number of selected elements matches the total number of rows. */ + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.datasource.data.length; + return numSelected === numRows; + } + + /** Selects all rows if they are not all selected; otherwise clear selection. */ + toggleAllRows() { + if (this.isAllSelected()) { + this.selection.clear(); + return; + } + + this.selection.select(...this.datasource.data); + } + + constructor(private taskService: TaskService, private dialog: MatDialog, private snackbar: MatSnackBar, @@ -107,4 +121,30 @@ export class TaskDashboardComponent implements OnChanges{ } protected readonly TaskStatus = TaskStatus; + + repeatSelectedTasks() { + const selectedTasks = this.selection.selected; + const dialogRef = this.dialog.open(TaskWeeklySeriesCreatorComponent, { + data: selectedTasks, + minWidth: "400px" + }); + dialogRef.afterClosed().subscribe(res => { + if(res) { + this.fetchTasks() + } + }) + + } + + fetchTasks() { + this.taskService.tasksTaskgroupIDStatusGet(this.taskgroupID!, "all").subscribe({ + next: resp => { + this.datasource = new MatTableDataSource(resp); + this.datasource.paginator = this.paginator!; + this.datasource.sort = this.sort!; + + resp.forEach(task => console.log(task)) + } + }) + } } diff --git a/frontend/src/app/tasks/task-series-creator/task-series-creator.component.html b/frontend/src/app/tasks/task-series-creator/task-series-creator.component.html index c9282b0..e000da5 100644 --- a/frontend/src/app/tasks/task-series-creator/task-series-creator.component.html +++ b/frontend/src/app/tasks/task-series-creator/task-series-creator.component.html @@ -1,22 +1,7 @@

Create Task-Series

- -
- Select Task Repeating Strategy - - Repeating Strategy - - Daily - Weekly - - -
- -
- -
- -
+ + Define Repeating Information Offset diff --git a/frontend/src/app/tasks/task-series-creator/task-series-creator.component.ts b/frontend/src/app/tasks/task-series-creator/task-series-creator.component.ts index 3779531..ed27ba5 100644 --- a/frontend/src/app/tasks/task-series-creator/task-series-creator.component.ts +++ b/frontend/src/app/tasks/task-series-creator/task-series-creator.component.ts @@ -13,25 +13,15 @@ import * as moment from "moment"; }) export class TaskSeriesCreatorComponent { - firstFormGroup = this._formBuilder.group({ - repeatingStrategyCtrl: ['', Validators.required] - }) - dailyFormGroup = this._formBuilder.group({ offsetCtrl: ['', Validators.required], deadlineStrategyCtrl: ['', Validators.required] }) - weeklyFormGroup = this._formBuilder.group({ - - }) - endDateFormGroup = this._formBuilder.group({ endDateCtrl: ['', Validators.required] }) - repeatingStrategy: "DAILY"|"WEEKLY"|undefined = undefined - constructor(private _formBuilder: FormBuilder, private taskSeriesService: TaskseriesService, @Inject(MAT_DIALOG_DATA) public task: TaskEntityInfo, @@ -39,23 +29,7 @@ export class TaskSeriesCreatorComponent { } - onSelectRepeatingStrategy() { - const selectedStrategy = this.firstFormGroup.get("repeatingStrategyCtrl")!.value! - if(selectedStrategy === "DAILY") { - this.repeatingStrategy = "DAILY"; - } else { - this.repeatingStrategy = "WEEKLY"; - } - console.log(this.repeatingStrategy) - } - save() { - if(this.repeatingStrategy === 'DAILY') { - this.saveDailyRepeating() - } - } - - saveDailyRepeating() { this.taskSeriesService.tasksTaskIDTaskseriesDailyPost(this.task.taskID,{ offset: Number(this.dailyFormGroup.get('offsetCtrl')!.value!), deadlineStrategy: this.convertDeadlineStrategyCtrlToDeadlineEnum(), diff --git a/openapi.yaml b/openapi.yaml index 17e44dc..4afa66e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2119,6 +2119,38 @@ paths: application/json: schema: $ref: '#/components/schemas/SimpleStatusResponse' + /tasks/taskseries/weekly: + post: + security: + - API_TOKEN: [] + tags: + - taskseries + description: Creates a daily repeating task + summary: daily repeating task creation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TaskRepeatWeekInfo' + responses: + 200: + description: Operation successfull + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' + 403: + description: No permission + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' + 404: + description: Task not found + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' components: @@ -2846,4 +2878,42 @@ components: endingDate: type: string format: date - description: Date until the tasks repeat \ No newline at end of file + description: Date until the tasks repeat + TaskRepeatWeekDayInfo: + required: + - offset + - taskID + additionalProperties: false + properties: + offset: + type: number + description: number repeating days + example: 7 + minimum: 1 + taskID: + type: number + description: internal identifier of task + example: 1 + TaskRepeatWeekInfo: + required: + - weekDayInfos + - deadlineStrategy + - endDate + additionalProperties: false + properties: + deadlineStrategy: + type: string + enum: + - DEADLINE_EQUAL_START + - DEADLINE_FIT_START + endDate: + type: string + format: date + description: Date until the tasks repeat + weekDayInfos: + type: array + items: + $ref: '#/components/schemas/TaskRepeatWeekDayInfo' + maxLength: 7 + minLength: 1 + \ No newline at end of file
+ + + + + + Name @@ -45,7 +61,9 @@ + +