From 30cbe60d6d283f291a3ed1cd367e1f466ed33fdb Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 Oct 2023 10:16:08 +0100 Subject: [PATCH 1/6] List missed Schedules --- backend/.idea/workspace.xml | 21 +++++-- .../api/controller/ScheduleController.java | 11 ++++ .../BasicTaskScheduleRepository.java | 3 + .../core/services/TaskScheduleService.java | 12 ++++ frontend/src/api/api/schedule.service.ts | 55 +++++++++++++++++++ frontend/src/app/app.module.ts | 4 +- .../app/dashboard/dashboard.component.html | 2 +- openapi.yaml | 30 ++++++++-- 8 files changed, 128 insertions(+), 10 deletions(-) diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml index eea8ce0..8a49128 100644 --- a/backend/.idea/workspace.xml +++ b/backend/.idea/workspace.xml @@ -6,8 +6,13 @@ + + - + + + + diff --git a/backend/src/main/java/core/api/controller/ScheduleController.java b/backend/src/main/java/core/api/controller/ScheduleController.java index 7168f1a..34f0d79 100644 --- a/backend/src/main/java/core/api/controller/ScheduleController.java +++ b/backend/src/main/java/core/api/controller/ScheduleController.java @@ -220,4 +220,15 @@ public class ScheduleController { return ResponseEntity.ok(new TaskScheduleStopResponse(serviceResult.getResult())); } } + + @GetMapping("/schedules/missed") + public ResponseEntity loadMissedSchedules() { + Optional user = userRepository.findByUsername(SecurityContextHolder.getContext().getAuthentication().getName()); + if(user.isEmpty()) { + return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); + } + + List schedules = taskScheduleService.loadMissedSchedules(user.get()); + return ResponseEntity.ok(schedules.stream().map(ScheduleInfo::new).toList()); + } } diff --git a/backend/src/main/java/core/repositories/timemanager/BasicTaskScheduleRepository.java b/backend/src/main/java/core/repositories/timemanager/BasicTaskScheduleRepository.java index 5a1b24a..9395be1 100644 --- a/backend/src/main/java/core/repositories/timemanager/BasicTaskScheduleRepository.java +++ b/backend/src/main/java/core/repositories/timemanager/BasicTaskScheduleRepository.java @@ -39,4 +39,7 @@ public interface BasicTaskScheduleRepository extends CrudRepository findAllFinishedByUserAndDate(User user, LocalDate now); + + @Query(value = "SELECT bts FROM BasicTaskSchedule bts WHERE bts.task.taskgroup.user = ?1 AND bts.startTime IS NULL AND bts.finishedTime IS NULL") + List findAllUnstartedSchedulesOfUser(User user); } diff --git a/backend/src/main/java/core/services/TaskScheduleService.java b/backend/src/main/java/core/services/TaskScheduleService.java index b1fe601..bde5b23 100644 --- a/backend/src/main/java/core/services/TaskScheduleService.java +++ b/backend/src/main/java/core/services/TaskScheduleService.java @@ -213,4 +213,16 @@ public class TaskScheduleService { } } + + public List loadMissedSchedules(User user) { + List unstartedSchedules = basicTaskScheduleRepository.findAllUnstartedSchedulesOfUser(user); + LocalDate currentDate = LocalDate.now(); + List missedSchedules = new LinkedList<>(); + for(BasicTaskSchedule schedule : unstartedSchedules) { + if(schedule.getScheduleDate().isBefore(currentDate)) { + missedSchedules.add(schedule); + } + } + return missedSchedules; + } } diff --git a/frontend/src/api/api/schedule.service.ts b/frontend/src/api/api/schedule.service.ts index da226af..688885f 100644 --- a/frontend/src/api/api/schedule.service.ts +++ b/frontend/src/api/api/schedule.service.ts @@ -202,6 +202,61 @@ export class ScheduleService { ); } + /** + * registers forgotten schedule + * Registers forgotten schedule + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public schedulesMissedGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public schedulesMissedGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public schedulesMissedGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public schedulesMissedGet(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(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.get>(`${this.configuration.basePath}/schedules/missed`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + /** * activates schedule * activates schedule diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 468aec4..051dbee 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -69,6 +69,7 @@ import { TaskgroupOverviewComponent } from './dashboard/taskgroup-overview/taskg import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.component'; import { ForgottenTaskStartDialogComponent } from './dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component'; import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.component'; @NgModule({ declarations: [ AppComponent, @@ -100,7 +101,8 @@ import {MatAutocompleteModule} from "@angular/material/autocomplete"; ActiveScheduleComponent, TaskgroupOverviewComponent, TaskOverviewComponent, - ForgottenTaskStartDialogComponent + ForgottenTaskStartDialogComponent, + MissedSchedulesComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index 54db53e..1e110fb 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -3,7 +3,7 @@

There are missed schedules. Please reschedule them.

- Reschedule + Reschedule

NowToday: {{workedMinutesToday}} min

diff --git a/openapi.yaml b/openapi.yaml index 06c21fd..1ddeaca 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1694,9 +1694,30 @@ paths: schema: type: object $ref: "#/components/schemas/SimpleStatusResponse" - - - + /schedules/missed: + get: + security: + - API_TOKEN: [] + tags: + - schedule + description: Registers forgotten schedule + summary: registers forgotten schedule + responses: + 200: + description: Operation successfull + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduleInfo' + 403: + description: User not found/No permission + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' + components: securitySchemes: API_TOKEN: @@ -2231,4 +2252,5 @@ components: minutesSpent: type: number description: number of minutes spent on task - example: 10 \ No newline at end of file + example: 10 + \ No newline at end of file From 966c896ae03aaf144c0cbaca45644bc10ea7eed2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 Oct 2023 10:34:02 +0100 Subject: [PATCH 2/6] Design Missed Schedules Component and manage routing to it --- frontend/src/app/app-routing.module.ts | 4 +- frontend/src/app/app.component.html | 1 + .../missed-schedules.component.css | 37 +++++++++++++++++++ .../missed-schedules.component.html | 19 ++++++++++ .../missed-schedules.component.spec.ts | 21 +++++++++++ .../missed-schedules.component.ts | 33 +++++++++++++++++ 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/missed-schedules/missed-schedules.component.css create mode 100644 frontend/src/app/missed-schedules/missed-schedules.component.html create mode 100644 frontend/src/app/missed-schedules/missed-schedules.component.spec.ts create mode 100644 frontend/src/app/missed-schedules/missed-schedules.component.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 076bea8..66a658a 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -7,6 +7,7 @@ import {UserSettingsComponent} from "./user-settings/user-settings.component"; import {TaskgroupDashboardComponent} from "./taskgroups/taskgroup-dashboard/taskgroup-dashboard.component"; import {TaskDetailOverviewComponent} from "./tasks/task-detail-overview/task-detail-overview.component"; import {SchedulerComponent} from "./schedules/scheduler/scheduler.component"; +import {MissedSchedulesComponent} from "./missed-schedules/missed-schedules.component"; const routes: Routes = [ {path: '', component: MainComponent}, @@ -16,7 +17,8 @@ const routes: Routes = [ {path: 'taskgroups/:taskgroupID', component: TaskgroupDashboardComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID', component: TaskDetailOverviewComponent}, {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule', component: SchedulerComponent}, - {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule/:scheduleID', component: SchedulerComponent} + {path: 'taskgroups/:taskgroupID/tasks/:taskID/schedule/:scheduleID', component: SchedulerComponent}, + {path: 'reschedule', component: MissedSchedulesComponent} ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index b4f5177..e37af02 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -4,6 +4,7 @@ + diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.css b/frontend/src/app/missed-schedules/missed-schedules.component.css new file mode 100644 index 0000000..2b618a5 --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedules.component.css @@ -0,0 +1,37 @@ +.container { + margin: 20px auto; + width: 70%; +} + +.spacer { + margin-bottom: 2.5%; +} + + +@media screen and (max-width: 600px) { + .container { + width: 100%; + margin: 20px 10px; + } +} + +.originally-planned-container { + border-style: solid; + border-color: #e1e1e1; + border-width: 1px; + margin-top: 10px; + border-radius: 5px; + padding: 10px; + +} + +.reschedule-actions-container { + display: flex; + justify-content: space-between; + flex-direction: row; + text-align: center; +} + +.forget-all-btn { + margin-top: 20px; +} diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.html b/frontend/src/app/missed-schedules/missed-schedules.component.html new file mode 100644 index 0000000..39c5ec8 --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedules.component.html @@ -0,0 +1,19 @@ +
+ + + +

{{taskgroup.taskgroupName}} / {{schedule.task.taskName}}

+ +
+
+

Originally planned: {{schedule.schedule.scheduleDate}}

+
+
+ + +
+
+
+
+ +
diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.spec.ts b/frontend/src/app/missed-schedules/missed-schedules.component.spec.ts new file mode 100644 index 0000000..4547946 --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedules.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MissedSchedulesComponent } from './missed-schedules.component'; + +describe('MissedSchedulesComponent', () => { + let component: MissedSchedulesComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MissedSchedulesComponent] + }); + fixture = TestBed.createComponent(MissedSchedulesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.ts b/frontend/src/app/missed-schedules/missed-schedules.component.ts new file mode 100644 index 0000000..2e7ebba --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedules.component.ts @@ -0,0 +1,33 @@ +import {Component, OnInit} from '@angular/core'; +import {ScheduleInfo, ScheduleService} from "../../api"; +import {NavigationLink} from "../navigation-link-list/navigation-link-list.component"; + +@Component({ + selector: 'app-missed-schedules', + templateUrl: './missed-schedules.component.html', + styleUrls: ['./missed-schedules.component.css'] +}) +export class MissedSchedulesComponent implements OnInit{ + + missedSchedules: ScheduleInfo[] = [] + defaultNavigationLinkPath: NavigationLink[] = [ + { + linkText: "Dashboard", + routerLink: ['/'] + }, + { + linkText: "Missed Schedules", + routerLink: ["/reschedule"] + } + ] + constructor(private scheduleService: ScheduleService) { + } + + ngOnInit() { + this.scheduleService.schedulesMissedGet().subscribe({ + next: resp => { + this.missedSchedules = resp; + } + }) + } +} From 38d27c3dac212b2830735fa8bad9659bdba289de Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 Oct 2023 10:56:08 +0100 Subject: [PATCH 3/6] Forget single schedule --- backend/.idea/workspace.xml | 25 +++++++++++-------- .../missed-schedules.component.html | 2 +- .../missed-schedules.component.ts | 21 +++++++++++++++- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml index 8a49128..5f433da 100644 --- a/backend/.idea/workspace.xml +++ b/backend/.idea/workspace.xml @@ -4,15 +4,9 @@
- - - - - - - - - + + + @@ -274,7 +276,8 @@ - diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.html b/frontend/src/app/missed-schedules/missed-schedules.component.html index 39c5ec8..d4d7741 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.html +++ b/frontend/src/app/missed-schedules/missed-schedules.component.html @@ -10,7 +10,7 @@
- +
diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.ts b/frontend/src/app/missed-schedules/missed-schedules.component.ts index 2e7ebba..0501267 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.ts +++ b/frontend/src/app/missed-schedules/missed-schedules.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {ScheduleInfo, ScheduleService} from "../../api"; import {NavigationLink} from "../navigation-link-list/navigation-link-list.component"; +import {MatSnackBar} from "@angular/material/snack-bar"; @Component({ selector: 'app-missed-schedules', @@ -20,7 +21,8 @@ export class MissedSchedulesComponent implements OnInit{ routerLink: ["/reschedule"] } ] - constructor(private scheduleService: ScheduleService) { + constructor(private scheduleService: ScheduleService, + private snackbar: MatSnackBar) { } ngOnInit() { @@ -30,4 +32,21 @@ export class MissedSchedulesComponent implements OnInit{ } }) } + + forgetSchedule(scheduleInfo: ScheduleInfo) { + this.scheduleService.schedulesScheduleIDScheduleTypeDelete(scheduleInfo.scheduleID, "BASIC").subscribe({ + next: resp => { + this.missedSchedules = this.missedSchedules.filter(schedule => schedule.scheduleID !== scheduleInfo.scheduleID) + }, + error: err => { + if(err.status == 403) { + this.snackbar.open("No permission", "", {duration: 2000}) + } else if(err.status == 404) { + this.snackbar.open("Schedule not found", "", {duration: 2000}); + } else { + this.snackbar.open("Unexpected error", "", {duration: 2000}); + } + } + }) + } } From 9de0e1a119d0aacf1b4f34805c574689fb181a47 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 Oct 2023 11:16:48 +0100 Subject: [PATCH 4/6] Forget all missed schedules --- backend/.idea/workspace.xml | 18 ++++-- .../api/controller/ScheduleController.java | 21 +++++++ frontend/src/api/api/schedule.service.ts | 59 +++++++++++++++++++ frontend/src/app/app.module.ts | 4 +- ...e-forget-confirmation-dialog.component.css | 0 ...-forget-confirmation-dialog.component.html | 9 +++ ...rget-confirmation-dialog.component.spec.ts | 21 +++++++ ...le-forget-confirmation-dialog.component.ts | 40 +++++++++++++ .../missed-schedules.component.html | 2 +- .../missed-schedules.component.ts | 16 ++++- openapi.yaml | 39 +++++++++++- 11 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.css create mode 100644 frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html create mode 100644 frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts create mode 100644 frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml index 5f433da..e461dea 100644 --- a/backend/.idea/workspace.xml +++ b/backend/.idea/workspace.xml @@ -4,9 +4,8 @@
- - - + + @@ -277,7 +284,8 @@ - diff --git a/backend/src/main/java/core/api/controller/ScheduleController.java b/backend/src/main/java/core/api/controller/ScheduleController.java index 34f0d79..f47b2c4 100644 --- a/backend/src/main/java/core/api/controller/ScheduleController.java +++ b/backend/src/main/java/core/api/controller/ScheduleController.java @@ -231,4 +231,25 @@ public class ScheduleController { List schedules = taskScheduleService.loadMissedSchedules(user.get()); return ResponseEntity.ok(schedules.stream().map(ScheduleInfo::new).toList()); } + + @DeleteMapping("/schedules/{scheduleIDs}/all") + public ResponseEntity deleteSchedules(@PathVariable long[] scheduleIDs) { + List schedulesToDelete = new LinkedList<>(); + for(long scheduleID: scheduleIDs) { + PermissionResult permissionResult = taskScheduleService.getSchedulePermissions(scheduleID, SecurityContextHolder.getContext().getAuthentication().getName()); + if(!permissionResult.isHasPermissions()) { + return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); + } + + if(permissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) { + return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); + } + schedulesToDelete.add(permissionResult.getResult()); + } + + for(BasicTaskSchedule schedule : schedulesToDelete) { + this.taskScheduleService.deleteBasicSchedule(schedule); + } + return ResponseEntity.ok(new SimpleStatusResponse("success")); + } } diff --git a/frontend/src/api/api/schedule.service.ts b/frontend/src/api/api/schedule.service.ts index 688885f..e19c1ef 100644 --- a/frontend/src/api/api/schedule.service.ts +++ b/frontend/src/api/api/schedule.service.ts @@ -512,6 +512,65 @@ export class ScheduleService { ); } + /** + * deletes multiple schedules + * deletes multiple schedules at once + * @param scheduleIDs internal ids of schedules to delete + * @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 schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public schedulesScheduleIDsAllDelete(scheduleIDs: Array, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (scheduleIDs === null || scheduleIDs === undefined) { + throw new Error('Required parameter scheduleIDs was null or undefined when calling schedulesScheduleIDsAllDelete.'); + } + + 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>(`${this.configuration.basePath}/schedules/${encodeURIComponent(String(scheduleIDs))}/all`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + /** * get number of active minutes * get number of worked minutes today diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 051dbee..8bf5716 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -70,6 +70,7 @@ import { TaskOverviewComponent } from './dashboard/task-overview/task-overview.c import { ForgottenTaskStartDialogComponent } from './dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component'; import {MatAutocompleteModule} from "@angular/material/autocomplete"; import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.component'; +import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component'; @NgModule({ declarations: [ AppComponent, @@ -102,7 +103,8 @@ import { MissedSchedulesComponent } from './missed-schedules/missed-schedules.co TaskgroupOverviewComponent, TaskOverviewComponent, ForgottenTaskStartDialogComponent, - MissedSchedulesComponent + MissedSchedulesComponent, + MissedScheduleForgetConfirmationDialogComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.css b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html new file mode 100644 index 0000000..e63b692 --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.html @@ -0,0 +1,9 @@ +

Forget All Missed Schedules

+
+

Are you sure you want to forget all missed schedules? This would delete {{this.missedSchedules.length}} schedules.

+

This cannot be undone!

+
+
+ + +
diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts new file mode 100644 index 0000000..547e00c --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MissedScheduleForgetConfirmationDialogComponent } from './missed-schedule-forget-confirmation-dialog.component'; + +describe('MissedScheduleForgetConfirmationDialogComponent', () => { + let component: MissedScheduleForgetConfirmationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MissedScheduleForgetConfirmationDialogComponent] + }); + fixture = TestBed.createComponent(MissedScheduleForgetConfirmationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts new file mode 100644 index 0000000..afe941c --- /dev/null +++ b/frontend/src/app/missed-schedules/missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component.ts @@ -0,0 +1,40 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ScheduleInfo, ScheduleService} from "../../../api"; +import {MatSnackBar} from "@angular/material/snack-bar"; + +@Component({ + selector: 'app-missed-schedule-forget-confirmation-dialog', + templateUrl: './missed-schedule-forget-confirmation-dialog.component.html', + styleUrls: ['./missed-schedule-forget-confirmation-dialog.component.css'] +}) +export class MissedScheduleForgetConfirmationDialogComponent { + + constructor(@Inject(MAT_DIALOG_DATA) public missedSchedules: ScheduleInfo[], + private dialogRef: MatDialogRef, + private scheduleService: ScheduleService, + private snackbar: MatSnackBar) { + } + + + cancel() { + this.dialogRef.close(); + } + + confirm() { + this.scheduleService.schedulesScheduleIDsAllDelete(this.missedSchedules.map(schedule => schedule.scheduleID)).subscribe({ + next: resp => { + this.dialogRef.close(resp); + }, + 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}); + } + } + }) + } +} diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.html b/frontend/src/app/missed-schedules/missed-schedules.component.html index d4d7741..b903459 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.html +++ b/frontend/src/app/missed-schedules/missed-schedules.component.html @@ -15,5 +15,5 @@ - + diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.ts b/frontend/src/app/missed-schedules/missed-schedules.component.ts index 0501267..3cf1baf 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.ts +++ b/frontend/src/app/missed-schedules/missed-schedules.component.ts @@ -2,6 +2,10 @@ import {Component, OnInit} from '@angular/core'; import {ScheduleInfo, ScheduleService} from "../../api"; import {NavigationLink} from "../navigation-link-list/navigation-link-list.component"; import {MatSnackBar} from "@angular/material/snack-bar"; +import {MatDialog} from "@angular/material/dialog"; +import { + MissedScheduleForgetConfirmationDialogComponent +} from "./missed-schedule-forget-confirmation-dialog/missed-schedule-forget-confirmation-dialog.component"; @Component({ selector: 'app-missed-schedules', @@ -22,7 +26,8 @@ export class MissedSchedulesComponent implements OnInit{ } ] constructor(private scheduleService: ScheduleService, - private snackbar: MatSnackBar) { + private snackbar: MatSnackBar, + private dialog: MatDialog) { } ngOnInit() { @@ -49,4 +54,13 @@ export class MissedSchedulesComponent implements OnInit{ } }) } + + forgetAllSchedules() { + const dialogRef = this.dialog.open(MissedScheduleForgetConfirmationDialogComponent, {data: this.missedSchedules, width: "400px"}); + dialogRef.afterClosed().subscribe(res => { + if(res != undefined) { + this.missedSchedules = []; + } + }) + } } diff --git a/openapi.yaml b/openapi.yaml index 1ddeaca..0f59d2e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1717,7 +1717,44 @@ paths: application/json: schema: $ref: '#/components/schemas/SimpleStatusResponse' - + /schedules/{scheduleIDs}/all: + delete: + security: + - API_TOKEN: [] + tags: + - schedule + description: deletes multiple schedules at once + summary: deletes multiple schedules + parameters: + - name: scheduleIDs + in: path + description: internal ids of schedules to delete + required: true + schema: + type: array + items: + type: number + responses: + 200: + description: Operation successfull + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SimpleStatusResponse' + 403: + description: No permission + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' + 404: + description: Schedule not found + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' components: securitySchemes: API_TOKEN: From c762d3d200b77920a2710be0e08a27a030192628 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 Oct 2023 11:21:40 +0100 Subject: [PATCH 5/6] Reschedule Missed Schedule --- .../src/app/missed-schedules/missed-schedules.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.html b/frontend/src/app/missed-schedules/missed-schedules.component.html index b903459..5307c05 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.html +++ b/frontend/src/app/missed-schedules/missed-schedules.component.html @@ -9,7 +9,8 @@

Originally planned: {{schedule.schedule.scheduleDate}}

- +
From 6eccb57a8c48bd7f8585c073d43df3db94ef0ab3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 Oct 2023 11:27:51 +0100 Subject: [PATCH 6/6] Include routing for task and taskgroup headlines --- .../app/missed-schedules/missed-schedules.component.css | 9 +++++++++ .../app/missed-schedules/missed-schedules.component.html | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.css b/frontend/src/app/missed-schedules/missed-schedules.component.css index 2b618a5..6c303ec 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.css +++ b/frontend/src/app/missed-schedules/missed-schedules.component.css @@ -35,3 +35,12 @@ .forget-all-btn { margin-top: 20px; } + +.undecorated-link { + text-decoration: none; + color: black; +} + +.undecorated-link:hover { + color: #3498db; +} diff --git a/frontend/src/app/missed-schedules/missed-schedules.component.html b/frontend/src/app/missed-schedules/missed-schedules.component.html index 5307c05..ad49ce8 100644 --- a/frontend/src/app/missed-schedules/missed-schedules.component.html +++ b/frontend/src/app/missed-schedules/missed-schedules.component.html @@ -2,7 +2,14 @@ -

{{taskgroup.taskgroupName}} / {{schedule.task.taskName}}

+

+ + {{taskgroup.taskgroupName}} + / + + + {{schedule.task.taskName}} +