From f94b924971976500038645854a1308c83f1e6a0c Mon Sep 17 00:00:00 2001 From: Matti Lupari Date: Thu, 16 Nov 2023 14:59:08 +0200 Subject: [PATCH] CSCEXAM-1195 Change some dropdowns to typeaheads with reservation searchy - also refactor the admin/teacher reservation components into one --- app/controllers/ReservationController.java | 28 ++- app/controllers/base/BaseController.java | 2 +- .../impl/CollaborationController.java | 13 +- conf/routes | 6 +- .../staff/admin/admin-dashboard.component.ts | 2 +- .../staff/admin/admin-dashboard.module.ts | 1 + .../dashboard/staff/staff-routing.module.ts | 6 +- .../admin/admin-reservations.component.html | 118 ---------- .../admin/admin-reservations.component.ts | 22 -- .../reservation-details.component.ts | 2 +- ui/src/app/reservation/reservation.module.ts | 8 +- .../reservation/reservations.component.html | 165 ++++++++++++++ ...component.ts => reservations.component.ts} | 212 ++++++++++-------- .../teacher-reservations.component.html | 57 ----- .../teacher/teacher-reservations.component.ts | 22 -- .../select/dropdown-select.component.ts | 26 ++- 16 files changed, 335 insertions(+), 355 deletions(-) delete mode 100644 ui/src/app/reservation/admin/admin-reservations.component.html delete mode 100644 ui/src/app/reservation/admin/admin-reservations.component.ts create mode 100644 ui/src/app/reservation/reservations.component.html rename ui/src/app/reservation/{reservation-base.component.ts => reservations.component.ts} (78%) delete mode 100644 ui/src/app/reservation/teacher/teacher-reservations.component.html delete mode 100644 ui/src/app/reservation/teacher/teacher-reservations.component.ts diff --git a/app/controllers/ReservationController.java b/app/controllers/ReservationController.java index 41720d3f82..6a46340f1f 100644 --- a/app/controllers/ReservationController.java +++ b/app/controllers/ReservationController.java @@ -71,7 +71,7 @@ public class ReservationController extends BaseController { @Authenticated @Restrict({ @Group("ADMIN"), @Group("TEACHER") }) - public Result getExams(Http.Request request) { + public Result getExams(Http.Request request, Optional filter) { User user = request.attrs().get(Attrs.AUTHENTICATED_USER); PathProperties props = PathProperties.parse("(id, name)"); Query q = DB.createQuery(Exam.class); @@ -80,6 +80,9 @@ public Result getExams(Http.Request request) { .where() .isNull("parent") // only Exam prototypes .eq("state", Exam.State.PUBLISHED); + if (filter.isPresent()) { + el = el.ilike("name", String.format("%%%s%%", filter.get())); + } if (user.hasRole(Role.Name.TEACHER)) { el = el @@ -91,7 +94,8 @@ public Result getExams(Http.Request request) { .eq("shared", true) .endJunction(); } - return ok(el.findList(), props); + List exams = el.findList(); + return ok(exams, props); } @Restrict({ @Group("ADMIN") }) @@ -119,15 +123,23 @@ private ArrayNode asJson(List users) { } @Restrict({ @Group("ADMIN"), @Group("TEACHER") }) - public Result getStudents() { - List students = DB.find(User.class).where().eq("roles.name", "STUDENT").findList(); + public Result getStudents(Optional filter) { + ExpressionList el = DB.find(User.class).where().eq("roles.name", "STUDENT"); + if (filter.isPresent()) { + el = el.or().ilike("userIdentifier", String.format("%%%s%%", filter.get())); + el = applyUserFilter(null, el, filter.get()).endOr(); + } + List students = el.findList(); return ok(Json.toJson(asJson(students))); } @Restrict({ @Group("ADMIN") }) - public Result getTeachers() { - List teachers = DB.find(User.class).where().eq("roles.name", "TEACHER").findList(); - + public Result getTeachers(Optional filter) { + ExpressionList el = DB.find(User.class).where().eq("roles.name", "TEACHER"); + if (filter.isPresent()) { + el = applyUserFilter(null, el.or(), filter.get()).endOr(); + } + List teachers = el.findList(); return ok(Json.toJson(asJson(teachers))); } @@ -155,7 +167,7 @@ public CompletionStage removeReservation(long id, Http.Request request) } Reservation reservation = enrolment.getReservation(); - // Lets not send emails about historical reservations + // Let's not send emails about historical reservations if (reservation.getEndAt().isAfter(DateTime.now())) { User student = enrolment.getUser(); emailComposer.composeReservationCancellationNotification(student, reservation, msg, false, enrolment); diff --git a/app/controllers/base/BaseController.java b/app/controllers/base/BaseController.java index c16b79b125..c8e50779c0 100644 --- a/app/controllers/base/BaseController.java +++ b/app/controllers/base/BaseController.java @@ -95,7 +95,7 @@ protected ExpressionList applyUserFilter(String prefix, ExpressionList String fnField = prefix == null ? "firstName" : String.format("%s.firstName", prefix); String lnField = prefix == null ? "lastName" : String.format("%s.lastName", prefix); if (rawFilter.contains(" ")) { - // Possible that user provided us two names. Lets try out some combinations of first and last names + // Possible that user provided us two names. Let's try out some combinations of first and last names String name1 = rawFilter.split(" ")[0]; String name2 = rawFilter.split(" ")[1]; result = diff --git a/app/controllers/iop/collaboration/impl/CollaborationController.java b/app/controllers/iop/collaboration/impl/CollaborationController.java index 1d981d8689..c3526e4b04 100644 --- a/app/controllers/iop/collaboration/impl/CollaborationController.java +++ b/app/controllers/iop/collaboration/impl/CollaborationController.java @@ -13,6 +13,8 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; import java.util.Optional; @@ -71,10 +73,13 @@ Optional parseUrlWithSearchParam(String filter, boolean anonymous) { if (filter == null) { return Optional.empty(); } - - String paramStr = String.format("?filter=%s&anonymous=%s", filter, anonymous); - String url = String.format("%s/api/exams/search%s", configReader.getIopHost(), paramStr); - return Optional.of(URI.create(url).toURL()); + String paramStr = String.format( + "filter=%s&anonymous=%s", + URLEncoder.encode(filter, StandardCharsets.UTF_8), + anonymous + ); + URI uri = URI.create(String.format("%s/api/exams/search?%s", configReader.getIopHost(), paramStr)); + return Optional.of(uri.toURL()); } catch (MalformedURLException e) { logger.error("Malformed URL", e); return Optional.empty(); diff --git a/conf/routes b/conf/routes index a20e7c42bd..f2ad8db502 100644 --- a/conf/routes +++ b/conf/routes @@ -221,10 +221,10 @@ GET /app/accessibility controlle GET /app/draft/rooms controllers.RoomController.createExamRoomDraft -GET /app/reservations/students controllers.ReservationController.getStudents -GET /app/reservations/teachers controllers.ReservationController.getTeachers +GET /app/reservations/students controllers.ReservationController.getStudents(filter: java.util.Optional[String]) +GET /app/reservations/teachers controllers.ReservationController.getTeachers(filter: java.util.Optional[String]) GET /app/reservations/examrooms controllers.ReservationController.getExamRooms -GET /app/reservations/exams controllers.ReservationController.getExams(request: Request) +GET /app/reservations/exams controllers.ReservationController.getExams(request: Request, filter: java.util.Optional[String]) GET /app/reservations controllers.ReservationController.getReservations(state: java.util.Optional[String], ownerId: java.util.Optional[java.lang.Long], studentId: java.util.Optional[java.lang.Long], roomId: java.util.Optional[java.lang.Long], machineId: java.util.Optional[java.lang.Long], examId: java.util.Optional[java.lang.Long], start: java.util.Optional[String], end: java.util.Optional[String], externalRef: java.util.Optional[String], request: Request) GET /app/events controllers.ReservationController.getExaminationEvents(state: java.util.Optional[String], ownerId: java.util.Optional[java.lang.Long], studentId: java.util.Optional[java.lang.Long], examId: java.util.Optional[java.lang.Long], start: java.util.Optional[String], end: java.util.Optional[String], request: Request) DELETE /app/reservations/:id controllers.ReservationController.removeReservation(id: Long, request: Request) diff --git a/ui/src/app/dashboard/staff/admin/admin-dashboard.component.ts b/ui/src/app/dashboard/staff/admin/admin-dashboard.component.ts index a366b79a70..c67acc31c5 100644 --- a/ui/src/app/dashboard/staff/admin/admin-dashboard.component.ts +++ b/ui/src/app/dashboard/staff/admin/admin-dashboard.component.ts @@ -16,6 +16,6 @@ import { Component } from '@angular/core'; @Component({ selector: 'xm-admin-dashboard', - template: '', + template: '', }) export class AdminDashboardComponent {} diff --git a/ui/src/app/dashboard/staff/admin/admin-dashboard.module.ts b/ui/src/app/dashboard/staff/admin/admin-dashboard.module.ts index 217c8a266f..96e110eaf2 100644 --- a/ui/src/app/dashboard/staff/admin/admin-dashboard.module.ts +++ b/ui/src/app/dashboard/staff/admin/admin-dashboard.module.ts @@ -12,6 +12,7 @@ * on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. */ +// TODO: maybe this module is redundant. Let's keep it anyway in case we want to lazy load admin stuff in the future import { NgModule } from '@angular/core'; import { ReservationModule } from '../../../reservation/reservation.module'; import { SharedModule } from '../../../shared/shared.module'; diff --git a/ui/src/app/dashboard/staff/staff-routing.module.ts b/ui/src/app/dashboard/staff/staff-routing.module.ts index 7cbf397093..442aaf60f8 100644 --- a/ui/src/app/dashboard/staff/staff-routing.module.ts +++ b/ui/src/app/dashboard/staff/staff-routing.module.ts @@ -34,7 +34,7 @@ import { LanguageInspectionsComponent } from '../../maturity/language-inspection import { MaturityReportingComponent } from '../../maturity/reporting/maturity-reporting.component'; import { QuestionComponent } from '../../question/basequestion/question.component'; import { LibraryComponent } from '../../question/library/library.component'; -import { TeacherReservationComponent } from '../../reservation/teacher/teacher-reservations.component'; +import { ReservationsComponent } from '../../reservation/reservations.component'; import { AssessmentComponent } from '../../review/assessment/assessment.component'; import { PrintedAssessmentComponent } from '../../review/assessment/print/printed-assessment.component'; import { SpeedReviewComponent } from '../../review/listing/speed-review.component'; @@ -152,8 +152,8 @@ const routes: Route[] = [ { path: 'inspections', component: LanguageInspectionsComponent }, { path: 'inspections/reports', component: MaturityReportingComponent }, { path: 'adminexams', component: ExamListingComponent }, - { path: 'reservations', component: TeacherReservationComponent }, - { path: 'reservations/:eid', component: TeacherReservationComponent }, + { path: 'reservations', component: ReservationsComponent }, + { path: 'reservations/:eid', component: ReservationsComponent }, { path: 'rooms', component: FacilityComponent }, { path: 'rooms/:id', component: RoomComponent }, { path: 'rooms/:id/availability', component: AvailabilityComponent }, diff --git a/ui/src/app/reservation/admin/admin-reservations.component.html b/ui/src/app/reservation/admin/admin-reservations.component.html deleted file mode 100644 index 701614e687..0000000000 --- a/ui/src/app/reservation/admin/admin-reservations.component.html +++ /dev/null @@ -1,118 +0,0 @@ -
-
-
-
-
{{ 'sitnet_reservations_administration' | translate }}
-
-
-
- -
-
-
- {{ 'sitnet_student' | translate }} - - -
-
- {{ 'sitnet_exam' | translate }} - - -
-
- {{ 'sitnet_begin' | translate }} - -
-
- {{ 'sitnet_end' | translate }} - -
-
-
-
- {{ 'sitnet_exam_owners' | translate }} - - -
-
- {{ 'sitnet_room' | translate }} - - -
-
- {{ 'sitnet_exam_machine' | translate }} - - -
-
- {{ 'sitnet_exam_state' | translate }} - - -
-
-
- -
-
- -
-
-
-
- - -
-
-
diff --git a/ui/src/app/reservation/admin/admin-reservations.component.ts b/ui/src/app/reservation/admin/admin-reservations.component.ts deleted file mode 100644 index 101db338bd..0000000000 --- a/ui/src/app/reservation/admin/admin-reservations.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2017 Exam Consortium - * - * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European Commission - subsequent - * versions of the EUPL (the "Licence"); - * You may not use this work except in compliance with the Licence. - * You may obtain a copy of the Licence at: - * - * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl - * - * Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed - * on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Licence for the specific language governing permissions and limitations under the Licence. - */ -import { Component } from '@angular/core'; -import { ReservationComponentBase } from '../reservation-base.component'; - -@Component({ - selector: 'xm-admin-reservations', - templateUrl: './admin-reservations.component.html', -}) -export class AdminReservationComponent extends ReservationComponentBase {} diff --git a/ui/src/app/reservation/reservation-details.component.ts b/ui/src/app/reservation/reservation-details.component.ts index 9799933750..993ebeae12 100644 --- a/ui/src/app/reservation/reservation-details.component.ts +++ b/ui/src/app/reservation/reservation-details.component.ts @@ -19,9 +19,9 @@ import { Component, Input, OnChanges } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { ToastrService } from 'ngx-toastr'; import { ExamEnrolment } from '../enrolment/enrolment.model'; -import { AnyReservation } from './reservation-base.component'; import type { Reservation } from './reservation.model'; import { ReservationService } from './reservation.service'; +import { AnyReservation } from './reservations.component'; type ReservationDetail = Reservation & { org: { name: string; code: string }; userAggregate: string }; diff --git a/ui/src/app/reservation/reservation.module.ts b/ui/src/app/reservation/reservation.module.ts index a9a97e249d..4d398b5cea 100644 --- a/ui/src/app/reservation/reservation.module.ts +++ b/ui/src/app/reservation/reservation.module.ts @@ -16,22 +16,20 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { SharedModule } from '../shared/shared.module'; -import { AdminReservationComponent } from './admin/admin-reservations.component'; import { ChangeMachineDialogComponent } from './admin/change-machine-dialog.component'; import { RemoveReservationDialogComponent } from './admin/remove-reservation-dialog.component'; import { ReservationDetailsComponent } from './reservation-details.component'; import { ReservationService } from './reservation.service'; -import { TeacherReservationComponent } from './teacher/teacher-reservations.component'; +import { ReservationsComponent } from './reservations.component'; @NgModule({ imports: [NgbModule, SharedModule, RouterModule], - exports: [AdminReservationComponent], + exports: [ReservationsComponent], declarations: [ ChangeMachineDialogComponent, RemoveReservationDialogComponent, ReservationDetailsComponent, - AdminReservationComponent, - TeacherReservationComponent, + ReservationsComponent, ], bootstrap: [ChangeMachineDialogComponent, RemoveReservationDialogComponent], providers: [ReservationService], diff --git a/ui/src/app/reservation/reservations.component.html b/ui/src/app/reservation/reservations.component.html new file mode 100644 index 0000000000..5f20ea8b87 --- /dev/null +++ b/ui/src/app/reservation/reservations.component.html @@ -0,0 +1,165 @@ +
+
+
+
+
+ {{ 'sitnet_reservations_administration' | translate }} +
+
+ {{ 'sitnet_reservations_new' | translate }} +
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + + +
+
+ +
+
+ +
+ + +
+
+
+ + + +
+
+ + + +
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
diff --git a/ui/src/app/reservation/reservation-base.component.ts b/ui/src/app/reservation/reservations.component.ts similarity index 78% rename from ui/src/app/reservation/reservation-base.component.ts rename to ui/src/app/reservation/reservations.component.ts index 2b8fb8e2cc..3cf1c04013 100644 --- a/ui/src/app/reservation/reservation-base.component.ts +++ b/ui/src/app/reservation/reservations.component.ts @@ -13,17 +13,18 @@ * See the Licence for the specific language governing permissions and limitations under the Licence. */ import { HttpClient } from '@angular/common/http'; -import { Directive, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { addMinutes, endOfDay, parseISO, startOfDay } from 'date-fns'; import { ToastrService } from 'ngx-toastr'; -import { forkJoin, Observable, of } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { forkJoin, from, Observable, of } from 'rxjs'; +import { debounceTime, distinctUntilChanged, exhaustMap, map } from 'rxjs/operators'; import type { ExamEnrolment } from '../enrolment/enrolment.model'; import type { CollaborativeExam, Exam, ExamImpl, Implementation } from '../exam/exam.model'; import type { User } from '../session/session.service'; import { SessionService } from '../session/session.service'; -import { isNumber, isObject } from '../shared/miscellaneous/helpers'; +import { isObject } from '../shared/miscellaneous/helpers'; import type { Option } from '../shared/select/dropdown-select.component'; import { OrderByPipe } from '../shared/sorting/order-by.pipe'; import type { ExamMachine, ExamRoom, Reservation } from './reservation.model'; @@ -64,12 +65,21 @@ export type AnyReservation = | RemoteTransferExamReservation | CollaborativeExamReservation; -@Directive() -export class ReservationComponentBase implements OnInit { +@Component({ + selector: 'xm-reservations', + templateUrl: './reservations.component.html', +}) +export class ReservationsComponent implements OnInit { + @ViewChild('studentInput') studentInput!: ElementRef; + @ViewChild('examInput') examInput!: ElementRef; + @ViewChild('ownerInput') ownerInput!: ElementRef; examId = ''; - user: User; + externalRef = ''; + student?: User; + owner?: User; startDate: Date | null = new Date(); endDate: Date | null = new Date(); + user: User; examStates = [ 'REVIEW', 'REVIEW_STARTED', @@ -84,11 +94,8 @@ export class ReservationComponentBase implements OnInit { ]; selection: Selection = {}; stateOptions: Option[] = []; - examOptions: Option[] = []; roomOptions: Option[] = []; machineOptions: Option[] = []; - studentOptions: Option[] = []; - teacherOptions: Option[] = []; rooms: ExamRoom[] = []; machines: ExamMachine[] = []; reservations: AnyReservation[] = []; @@ -114,7 +121,6 @@ export class ReservationComponentBase implements OnInit { ngOnInit() { this.examId = this.route.snapshot.params.eid; - this.selection = this.examId ? { examId: this.examId } : {}; this.initOptions(); this.query(); this.stateOptions = this.examStates.map((s) => { @@ -252,20 +258,46 @@ export class ReservationComponentBase implements OnInit { isAdminView = () => this.user.isAdmin; - getPlaceHolder = () => - this.examId ? this.examOptions.find((o) => (o.id ? o.id.toString() : o) === this.examId)?.label : '-'; + studentSelected(event: NgbTypeaheadSelectItemEvent) { + this.student = event.item; + this.query(); + } - roomChanged(event: Option | undefined) { - if (event?.value === undefined) { - delete this.selection.roomId; - this.machineOptions = this.machinesForRooms(this.rooms, this.machines); + clearStudent() { + delete this.student; + this.studentInput.nativeElement.value = ''; + this.query(); + } + + ownerSelected(event: NgbTypeaheadSelectItemEvent) { + this.owner = event.item; + this.query(); + } + + clearOwner() { + delete this.owner; + this.ownerInput.nativeElement.value = ''; + this.query(); + } + + examSelected(event: NgbTypeaheadSelectItemEvent) { + if (event.item.externalRef) { + this.externalRef = event.item.externalRef; + this.examId = ''; } else { - this.selection.roomId = event.value.id.toString(); - this.machineOptions = this.machinesForRoom(event.value, this.machines); + this.examId = event.item.id.toString(); + this.externalRef = ''; } this.query(); } + clearExam() { + this.examId = ''; + this.externalRef = ''; + this.examInput.nativeElement.value = ''; + this.query(); + } + startDateChanged(event: { date: Date | null }) { this.startDate = event.date; this.query(); @@ -276,11 +308,13 @@ export class ReservationComponentBase implements OnInit { this.query(); } - ownerChanged(event: Option | undefined) { - if (event?.value) { - this.selection.ownerId = event.value.id.toString(); + roomChanged(event: Option | undefined) { + if (event?.value === undefined) { + delete this.selection.roomId; + this.machineOptions = this.machinesForRooms(this.rooms, this.machines); } else { - delete this.selection.ownerId; + this.selection.roomId = event.value.id.toString(); + this.machineOptions = this.machinesForRoom(event.value, this.machines); } this.query(); } @@ -294,15 +328,6 @@ export class ReservationComponentBase implements OnInit { this.query(); } - studentChanged(event: Option | undefined) { - if (event?.value) { - this.selection.studentId = event.value.id.toString(); - } else { - delete this.selection.studentId; - } - this.query(); - } - machineChanged(event: Option | undefined) { if (event?.value) { this.selection.machineId = event.value.id.toString(); @@ -312,48 +337,64 @@ export class ReservationComponentBase implements OnInit { this.query(); } - examChanged(event: Option | undefined) { - if (event?.value) { - if (event.value.externalRef) { - this.selection.externalRef = event.value.externalRef || ''; - delete this.selection.examId; - } else { - this.selection.examId = event.value.id.toString(); - delete this.selection.externalRef; - } - } else { - delete this.selection.examId; - delete this.selection.externalRef; - } - this.query(); - } + protected searchStudents$ = (text$: Observable) => + text$.pipe( + debounceTime(300), + distinctUntilChanged(), + exhaustMap((term) => + term.length < 2 + ? from([]) + : this.http.get<(User & { name: string })[]>('/app/reservations/students', { + params: { filter: term }, + }), + ), + map((ss) => ss.sort((a, b) => a.firstName.localeCompare(b.firstName)).slice(0, 100)), + ); - updateQuery() { - this.query(); - } + protected searchOwners$ = (text$: Observable) => + text$.pipe( + debounceTime(300), + distinctUntilChanged(), + exhaustMap((term) => + term.length < 2 + ? from([]) + : this.http.get<(User & { name: string })[]>('/app/reservations/teachers', { + params: { filter: term }, + }), + ), + map((ss) => ss.sort((a, b) => a.lastName.localeCompare(b.lastName)).slice(0, 100)), + ); - protected initExamOptions = () => { - const examObservables: Observable[] = [ - this.http.get('/app/reservations/exams'), - ]; - if (this.isInteroperable && this.isAdminView()) { - examObservables.push(this.http.get('/app/iop/exams')); - } - forkJoin(examObservables) - .pipe( - map((exams) => exams.flat().flatMap((e) => ({ id: e.id, value: e, label: e.name }))), - tap((options) => (this.examOptions = this.orderPipe.transform(options, 'label'))), - ) - .subscribe(); + protected searchExams$ = (text$: Observable) => { + const listExams$ = (text: string) => { + const examObservables: Observable[] = [ + this.http.get('/app/reservations/exams', { params: { filter: text } }), + ]; + if (this.isInteroperable && this.isAdminView()) { + examObservables.push( + this.http.get('/app/iop/exams', { params: { filter: text } }), + ); + } + return forkJoin(examObservables).pipe(map((exams) => exams.flat())); + }; + return text$.pipe( + debounceTime(300), + distinctUntilChanged(), + exhaustMap((term) => (term.length < 2 ? from([]) : listExams$(term))), + map((es) => es.sort((a, b) => (a.name as string).localeCompare(b.name as string)).slice(0, 100)), + ); }; - // TODO: check this out + protected nameFormatter = (item: { name: string }) => item.name; + private createParams = (input: Selection) => { - const params: Selection = { ...input }; - if (params.examId && !isNumber(parseInt(params.examId as string))) { - params.externalRef = params.examId as string; - delete params.examId; - } + const extras = { + ...(this.student?.id && { studentId: this.student.id.toString() }), + ...(this.owner?.id && { ownerId: this.owner.id.toString() }), + ...(this.examId && { examId: this.examId }), + ...(this.externalRef && { externalRef: this.externalRef }), + }; + const params: Selection = { ...input, ...extras }; if (this.startDate) { params.start = startOfDay(this.startDate).toISOString(); } @@ -373,29 +414,11 @@ export class ReservationComponentBase implements OnInit { isObject(reservation.enrolment?.collaborativeExam); private initOptions() { - this.http.get<(User & { name: string })[]>('/app/reservations/students').subscribe({ - next: (resp) => { - const students: (User & { name: string })[] = this.orderPipe.transform(resp, 'lastName'); - this.studentOptions = students.map((s) => { - return { id: s.id, value: s, label: s.name }; - }); - }, - error: (err) => this.toast.error(err), - }); this.http.get<{ isExamVisitSupported: boolean }>('/app/settings/iop/examVisit').subscribe((resp) => { this.isInteroperable = resp.isExamVisitSupported; - this.initExamOptions(); }); if (this.isAdminView()) { - this.http.get<(User & { name: string })[]>('/app/reservations/teachers').subscribe({ - next: (resp) => { - const teachers = this.orderPipe.transform(resp, 'lastName'); - this.teacherOptions = teachers.map((t) => ({ id: t.id, value: t, label: t.name })); - }, - error: (err) => this.toast.error(err), - }); - this.http.get('/app/reservations/examrooms').subscribe({ next: (resp) => { this.rooms = this.orderPipe.transform(resp, 'name'); @@ -410,8 +433,6 @@ export class ReservationComponentBase implements OnInit { } } - private roomContains = (room: ExamRoom, machine: ExamMachine) => room.examMachines.some((m) => m.id === machine.id); - private machinesForRoom(room: ExamRoom, machines: ExamMachine[]): Option[] { if (room.examMachines.length < 1) { return []; @@ -422,7 +443,7 @@ export class ReservationComponentBase implements OnInit { isHeader: true, }; const machineData: Option[] = machines - .filter((m) => this.roomContains(room, m)) + .filter((m) => room.examMachines.some((rem) => m.id === rem.id)) .map((m) => { return { id: m.id, value: m, label: m.name == null ? '' : m.name }; }); @@ -434,14 +455,9 @@ export class ReservationComponentBase implements OnInit { rooms.map((r) => this.machinesForRoom(r, machines)).reduce((a, b) => a.concat(b), []); private somethingSelected(params: Selection) { - for (const k in params) { - if (!Object.prototype.hasOwnProperty.call(params, k)) { - continue; - } - if (params[k]) { - return true; - } - } + if (this.student || this.owner) return true; + if (this.examId || this.externalRef) return true; + if (Object.keys(params).length > 0) return true; return this.startDate || this.endDate; } } diff --git a/ui/src/app/reservation/teacher/teacher-reservations.component.html b/ui/src/app/reservation/teacher/teacher-reservations.component.html deleted file mode 100644 index 67283a4e1f..0000000000 --- a/ui/src/app/reservation/teacher/teacher-reservations.component.html +++ /dev/null @@ -1,57 +0,0 @@ -
-
-
-
-
{{ 'sitnet_reservations_new' | translate }}
-
-
-
- -
-
-
- {{ 'sitnet_begin' | translate }} - -
-
- {{ 'sitnet_end' | translate }} - -
-
- {{ 'sitnet_student' | translate }} - - -
-
- {{ 'sitnet_exam' | translate }} - - -
-
- {{ 'sitnet_exam_state' | translate }} - - -
-
- - -
-
diff --git a/ui/src/app/reservation/teacher/teacher-reservations.component.ts b/ui/src/app/reservation/teacher/teacher-reservations.component.ts deleted file mode 100644 index cd91dec2d4..0000000000 --- a/ui/src/app/reservation/teacher/teacher-reservations.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2017 Exam Consortium - * - * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European Commission - subsequent - * versions of the EUPL (the "Licence"); - * You may not use this work except in compliance with the Licence. - * You may obtain a copy of the Licence at: - * - * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl - * - * Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed - * on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Licence for the specific language governing permissions and limitations under the Licence. - */ -import { Component } from '@angular/core'; -import { ReservationComponentBase } from '../reservation-base.component'; - -@Component({ - selector: 'xm-teacher-reservations', - templateUrl: './teacher-reservations.component.html', -}) -export class TeacherReservationComponent extends ReservationComponentBase {} diff --git a/ui/src/app/shared/select/dropdown-select.component.ts b/ui/src/app/shared/select/dropdown-select.component.ts index 06a312f280..aa359cfc7c 100644 --- a/ui/src/app/shared/select/dropdown-select.component.ts +++ b/ui/src/app/shared/select/dropdown-select.component.ts @@ -39,18 +39,20 @@ export interface Option { {{ selected?.label || placeholder | translate }}