diff --git a/frontend/src/app/coworking/coworking-home/coworking-home.component.ts b/frontend/src/app/coworking/coworking-home/coworking-home.component.ts
index f0c04056d..4ddaf957b 100644
--- a/frontend/src/app/coworking/coworking-home/coworking-home.component.ts
+++ b/frontend/src/app/coworking/coworking-home/coworking-home.component.ts
@@ -13,23 +13,19 @@ import { isAuthenticated } from 'src/app/gate/gate.guard';
import { profileResolver } from 'src/app/profile/profile.resolver';
import { CoworkingService } from '../coworking.service';
import { Profile } from 'src/app/models.module';
-import { ProfileService } from 'src/app/profile/profile.service';
import {
CoworkingStatus,
Reservation,
SeatAvailability
} from '../coworking.models';
import { Subscription, timer } from 'rxjs';
-import { RoomReservationService } from '../room-reservation/room-reservation.service';
-import { ReservationService } from '../reservation/reservation.service';
-import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
- selector: 'app-coworking-home',
- templateUrl: './coworking-home.component.html',
- styleUrls: ['./coworking-home.component.css'],
- standalone: false
+ selector: 'app-coworking-home',
+ templateUrl: './coworking-home.component.html',
+ styleUrls: ['./coworking-home.component.css'],
+ standalone: false
})
export class CoworkingPageComponent implements OnInit, OnDestroy {
public status: Signal
;
@@ -82,11 +78,7 @@ export class CoworkingPageComponent implements OnInit, OnDestroy {
public coworkingService: CoworkingService,
private router: Router,
private route: ActivatedRoute,
- private reservationService: ReservationService,
- protected snackBar: MatSnackBar,
- private roomReservationService: RoomReservationService,
- private profileService: ProfileService,
- private dialog: MatDialog
+ protected snackBar: MatSnackBar
) {
this.status = coworkingService.status;
@@ -118,11 +110,7 @@ export class CoworkingPageComponent implements OnInit, OnDestroy {
reserve(seatSelection: SeatAvailability[]) {
this.coworkingService.draftReservation(seatSelection).subscribe({
error: (response) => {
- this.snackBar.open(
- response.error.message,
- '',
- { duration: 8000 }
- );
+ this.snackBar.open(response.error.message, '', { duration: 8000 });
},
next: (reservation) => {
this.router.navigateByUrl(`/coworking/reservation/${reservation.id}`);
diff --git a/frontend/src/app/coworking/coworking-routing.module.ts b/frontend/src/app/coworking/coworking-routing.module.ts
index 36bb7827d..019851f18 100644
--- a/frontend/src/app/coworking/coworking-routing.module.ts
+++ b/frontend/src/app/coworking/coworking-routing.module.ts
@@ -2,14 +2,16 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoworkingPageComponent } from './coworking-home/coworking-home.component';
import { ReservationComponent } from './reservation/reservation.component';
-import { NewReservationPageComponent } from './room-reservation/new-reservation-page/new-reservation-page.component';
-import { ConfirmReservationComponent } from './room-reservation/confirm-reservation/confirm-reservation.component';
+import { NewRoomReservationComponent } from './room-reservation/room-reservation.component';
+import { OperatingHoursComponent } from './operating-hours/operating-hours.component';
+import { OperatingHoursEditorComponent } from './operating-hours/editor/operating-hours-editor.component';
const routes: Routes = [
CoworkingPageComponent.Route,
ReservationComponent.Route,
- NewReservationPageComponent.Route,
- ConfirmReservationComponent.Route,
+ NewRoomReservationComponent.Route,
+ OperatingHoursComponent.Route,
+ OperatingHoursEditorComponent.Route,
{
path: 'ambassador',
title: 'Ambassador',
diff --git a/frontend/src/app/coworking/coworking.models.ts b/frontend/src/app/coworking/coworking.models.ts
index be0ebfb48..e2b484bc4 100644
--- a/frontend/src/app/coworking/coworking.models.ts
+++ b/frontend/src/app/coworking/coworking.models.ts
@@ -112,7 +112,7 @@ export const parseCoworkingStatusJSON = (
};
export interface ReservationRequest extends TimeRange {
- users: Profile[] | null;
+ users: Profile[] | { id: number }[] | null;
seats: Seat[] | null;
room: { id: string };
}
@@ -180,3 +180,29 @@ export interface ReservationMapDetails {
operating_hours_end: string;
number_of_time_slots: number;
}
+
+/** New room reservation models */
+
+export type GetRoomAvailabilityResponse_RoomAvailability = {
+ state: 'AVAILABLE' | 'RESERVED' | 'YOUR_RESERVATION' | 'UNAVAILABLE';
+ description?: string | undefined;
+};
+
+export type GetRoomAvailabilityResponse_Slot = {
+ start_time: string;
+ end_time: string;
+};
+
+export type GetRoomAvailabilityResponse_Room = {
+ room: string;
+ capacity: number;
+ minimum_reservers: number;
+ availability: Record;
+};
+
+export type GetRoomAvailabilityResponse = {
+ is_instructor: boolean;
+ slot_labels: string[];
+ slots: Record;
+ rooms: GetRoomAvailabilityResponse_Room[];
+};
diff --git a/frontend/src/app/coworking/coworking.module.ts b/frontend/src/app/coworking/coworking.module.ts
index fe82bd99e..78e7a15c3 100644
--- a/frontend/src/app/coworking/coworking.module.ts
+++ b/frontend/src/app/coworking/coworking.module.ts
@@ -1,5 +1,6 @@
import { AsyncPipe, CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
import { CoworkingRoutingModule } from './coworking-routing.module';
import { CoworkingPageComponent } from './coworking-home/coworking-home.component';
@@ -14,16 +15,11 @@ import { ReservationComponent } from './reservation/reservation.component';
import { MatIconModule } from '@angular/material/icon';
import { MatDividerModule } from '@angular/material/divider';
import { MatButtonModule } from '@angular/material/button';
-import { NewReservationPageComponent } from './room-reservation/new-reservation-page/new-reservation-page.component';
-import { RoomReservationWidgetComponent } from './widgets/room-reservation-table/room-reservation-table.widget';
-import { ConfirmReservationComponent } from './room-reservation/confirm-reservation/confirm-reservation.component';
-import { DateSelector } from './widgets/date-selector/date-selector.widget';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { OperatingHoursDialog } from './widgets/operating-hours-dialog/operating-hours-dialog.widget';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
-import { ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { SharedModule } from '../shared/shared.module';
import { MatTabsModule } from '@angular/material/tabs';
@@ -31,13 +27,14 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { AmbassadorXLComponent } from './ambassador-home/ambassador-xl/ambassador-xl.component';
import { AmbassadorRoomComponent } from './ambassador-home/ambassador-room/ambassador-room.component';
import { ReservationFactsWidget } from './widgets/reservation-facts/reservation-facts.widget';
-import { DialogModule } from '@angular/cdk/dialog';
import { MatDialogModule } from '@angular/material/dialog';
+import { NewRoomReservationComponent } from './room-reservation/room-reservation.component';
+import { OperatingHoursComponent } from './operating-hours/operating-hours.component';
+import { OperatingHoursEditorComponent } from './operating-hours/editor/operating-hours-editor.component';
+import { MatTimepickerModule } from '@angular/material/timepicker';
@NgModule({
declarations: [
- NewReservationPageComponent,
- RoomReservationWidgetComponent,
CoworkingPageComponent,
ReservationComponent,
AmbassadorPageComponent,
@@ -45,14 +42,15 @@ import { MatDialogModule } from '@angular/material/dialog';
AmbassadorRoomComponent,
CoworkingDropInCard,
CoworkingReservationCard,
- ConfirmReservationComponent,
- NewReservationPageComponent,
- DateSelector,
OperatingHoursDialog,
- ReservationFactsWidget
+ ReservationFactsWidget,
+ NewRoomReservationComponent,
+ OperatingHoursComponent,
+ OperatingHoursEditorComponent
],
imports: [
CommonModule,
+ ReactiveFormsModule,
MatCardModule,
MatButtonModule,
MatDividerModule,
@@ -77,7 +75,8 @@ import { MatDialogModule } from '@angular/material/dialog';
MatFormFieldModule,
MatTooltipModule,
MatTabsModule,
- MatDialogModule
+ MatDialogModule,
+ MatTimepickerModule
]
})
export class CoworkingModule {}
diff --git a/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.css b/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.css
similarity index 100%
rename from frontend/src/app/coworking/widgets/date-selector/date-selector.widget.css
rename to frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.css
diff --git a/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.html b/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.html
new file mode 100644
index 000000000..6eff4818c
--- /dev/null
+++ b/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.html
@@ -0,0 +1,59 @@
+
diff --git a/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.ts b/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.ts
new file mode 100644
index 000000000..dc6a424e1
--- /dev/null
+++ b/frontend/src/app/coworking/operating-hours/editor/operating-hours-editor.component.ts
@@ -0,0 +1,95 @@
+import { DatePipe } from '@angular/common';
+import { Component } from '@angular/core';
+import { FormBuilder, FormControl, Validators } from '@angular/forms';
+import { permissionGuard } from 'src/app/permission.guard';
+import { TimeRange } from 'src/app/time-range';
+import { OperatingHoursService } from '../operating-hours.service';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-operating-hours-editor',
+ templateUrl: './operating-hours-editor.component.html',
+ styleUrl: './operating-hours-editor.component.css',
+ standalone: false
+})
+export class OperatingHoursEditorComponent {
+ public static Route = {
+ path: 'operating-hours/new',
+ component: OperatingHoursEditorComponent,
+ title: 'Operating Hours',
+ canActivate: [
+ permissionGuard(
+ 'coworking.operating_hours.*',
+ 'coworking/operating_hours'
+ )
+ ]
+ };
+
+ public form = this.formBuilder.group({
+ date: new FormControl(this.datePipe.transform(new Date(), 'yyyy-MM-dd'), [
+ Validators.required
+ ]),
+ start: new FormControl(
+ this.datePipe.transform(
+ new Date().setHours(9, 0, 0, 0),
+ 'yyyy-MM-ddTHH:mm'
+ ),
+ [Validators.required]
+ ),
+ end: new FormControl(
+ this.datePipe.transform(
+ new Date().setHours(18, 0, 0, 0),
+ 'yyyy-MM-ddTHH:mm'
+ ),
+ [Validators.required]
+ )
+ });
+
+ constructor(
+ protected formBuilder: FormBuilder,
+ private datePipe: DatePipe,
+ protected operatingHoursService: OperatingHoursService,
+ protected snackBar: MatSnackBar,
+ protected router: Router
+ ) {}
+
+ onSubmit() {
+ const selectedDate = new Date(this.form.value.date!);
+ const startTime = new Date(this.form.value.start!);
+ const endTime = new Date(this.form.value.end!);
+
+ // Combine the selected date with the start and end times
+ const startDateTime = new Date(
+ selectedDate.getFullYear(),
+ selectedDate.getMonth(),
+ selectedDate.getDate(),
+ startTime.getHours(),
+ startTime.getMinutes()
+ );
+
+ const endDateTime = new Date(
+ selectedDate.getFullYear(),
+ selectedDate.getMonth(),
+ selectedDate.getDate(),
+ endTime.getHours(),
+ endTime.getMinutes()
+ );
+
+ const range: TimeRange = {
+ start: startDateTime,
+ end: endDateTime
+ };
+
+ this.operatingHoursService.newOperatingHours(range).subscribe({
+ next: () => {
+ this.router.navigateByUrl(`/coworking/operating-hours`);
+ },
+ error: (err) => {
+ this.snackBar.open(`${err.error.message}`, '', {
+ duration: 2000
+ });
+ }
+ });
+ }
+}
diff --git a/frontend/src/app/coworking/operating-hours/operating-hours.component.css b/frontend/src/app/coworking/operating-hours/operating-hours.component.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/src/app/coworking/operating-hours/operating-hours.component.html b/frontend/src/app/coworking/operating-hours/operating-hours.component.html
new file mode 100644
index 000000000..4ccaf42be
--- /dev/null
+++ b/frontend/src/app/coworking/operating-hours/operating-hours.component.html
@@ -0,0 +1,23 @@
+
+
+ Operating Hours Administration
+
+
+
+ @for(hours of operatingHours; track hours.id) {
+
+
{{ hours.start | date: 'EEE' }}
+
{{ hours.start | date: 'MMM dd' }}
+
{{ hours.start | date: 'h:mma' }} - {{ hours.end | date: 'h:mma' }}
+
+
+
+
+ }
+
+
\ No newline at end of file
diff --git a/frontend/src/app/coworking/operating-hours/operating-hours.component.ts b/frontend/src/app/coworking/operating-hours/operating-hours.component.ts
new file mode 100644
index 000000000..5c1f92de8
--- /dev/null
+++ b/frontend/src/app/coworking/operating-hours/operating-hours.component.ts
@@ -0,0 +1,59 @@
+import { Component } from '@angular/core';
+import { permissionGuard } from 'src/app/permission.guard';
+import { OperatingHoursService } from './operating-hours.service';
+import { OperatingHours } from '../coworking.models';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-operating-hours',
+ templateUrl: './operating-hours.component.html',
+ styleUrl: './operating-hours.component.css',
+ standalone: false
+})
+export class OperatingHoursComponent {
+ public static Route = {
+ path: 'operating-hours',
+ component: OperatingHoursComponent,
+ title: 'Operating Hours',
+ canActivate: [
+ permissionGuard(
+ 'coworking.operating_hours.*',
+ 'coworking/operating_hours'
+ )
+ ]
+ };
+
+ operatingHours: OperatingHours[] = [];
+
+ constructor(
+ protected operatingHoursService: OperatingHoursService,
+ protected snackBar: MatSnackBar,
+ protected router: Router
+ ) {
+ this.fetchOperatingHours();
+ }
+
+ fetchOperatingHours() {
+ this.operatingHoursService.getOperatingHours().subscribe((result) => {
+ this.operatingHours = result;
+ });
+ }
+
+ newOperatingHours() {
+ this.router.navigateByUrl('/coworking/operating-hours/new');
+ }
+
+ deleteOperatingHours(id: number) {
+ this.operatingHoursService.deleteOperatingHours(id).subscribe({
+ next: () => {
+ this.fetchOperatingHours();
+ },
+ error: (err) => {
+ this.snackBar.open(`${err.error.message}`, '', {
+ duration: 2000
+ });
+ }
+ });
+ }
+}
diff --git a/frontend/src/app/coworking/operating-hours/operating-hours.service.ts b/frontend/src/app/coworking/operating-hours/operating-hours.service.ts
new file mode 100644
index 000000000..73e72ce2b
--- /dev/null
+++ b/frontend/src/app/coworking/operating-hours/operating-hours.service.ts
@@ -0,0 +1,35 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+import { OperatingHours } from '../coworking.models';
+import { Observable } from 'rxjs';
+import { TimeRange } from 'src/app/time-range';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class OperatingHoursService {
+ protected http = inject(HttpClient);
+
+ getOperatingHours(): Observable {
+ const start = new Date();
+ const end = new Date();
+ end.setFullYear(end.getFullYear() + 1); // 1 year in the future
+
+ const params = {
+ start: start.toISOString(),
+ end: end.toISOString()
+ };
+
+ return this.http.get('/api/coworking/operating_hours', {
+ params
+ });
+ }
+
+ newOperatingHours(timeRange: TimeRange) {
+ return this.http.post(`/api/coworking/operating_hours`, timeRange);
+ }
+
+ deleteOperatingHours(id: number) {
+ return this.http.delete(`/api/coworking/operating_hours/${id}`);
+ }
+}
diff --git a/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.css b/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.css
deleted file mode 100644
index 4c3fd726f..000000000
--- a/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.css
+++ /dev/null
@@ -1,34 +0,0 @@
-.card-container {
- margin: 16px;
-}
-
-.mat-mdc-card {
- max-width: 640px;
- margin: 0px;
-}
-
-.mat-mdc-card-header {
- margin-bottom: 16px;
-}
-
-.mat-mdc-card-actions {
- margin-top: 16px;
-}
-
-.mat-divider {
- margin: 1em 0;
-}
-
-h3 {
- font-size: 18px;
- margin-bottom: 0px;
-}
-
-h3 > label {
- width: 64px;
- display: inline-block;
-}
-
-p {
- margin-left: 64px;
-}
diff --git a/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.html b/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.html
deleted file mode 100644
index 2b1cdd2e6..000000000
--- a/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
- @if(reservation) {
-
-
- }
-
diff --git a/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.ts b/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.ts
deleted file mode 100644
index c06314eb3..000000000
--- a/frontend/src/app/coworking/room-reservation/confirm-reservation/confirm-reservation.component.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * @author John Schachte, Aarjav Jain, Nick Wherthey
- * @copyright 2023
- * @license MIT
- */
-
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { MatSnackBar } from '@angular/material/snack-bar';
-import { ActivatedRoute, Router } from '@angular/router';
-import { timer } from 'rxjs';
-import { isAuthenticated } from 'src/app/gate/gate.guard';
-import { profileResolver } from 'src/app/profile/profile.resolver';
-import { Reservation } from '../../coworking.models';
-import { RoomReservationService } from '../room-reservation.service';
-
-@Component({
- selector: 'app-confirm-reservation',
- templateUrl: './confirm-reservation.component.html',
- styleUrls: ['./confirm-reservation.component.css'],
- standalone: false
-})
-export class ConfirmReservationComponent implements OnInit, OnDestroy {
- public static Route = {
- path: 'confirm-reservation/:id',
- title: 'Confirm Reservation',
- component: ConfirmReservationComponent,
- canActivate: [isAuthenticated],
- resolve: { profile: profileResolver }
- };
-
- reservation: Reservation | null = null; // Declaration of the reservation property
- isConfirmed: boolean = false; // flag to see if reservation was confirmed
-
- public id: number;
-
- constructor(
- private roomReservationService: RoomReservationService,
- protected snackBar: MatSnackBar,
- private router: Router,
- public route: ActivatedRoute
- ) {
- this.id = parseInt(this.route.snapshot.params['id']);
- }
-
- ngOnDestroy(): void {
- if (this.isConfirmed) return;
- this.roomReservationService.cancel(this.reservation!).subscribe();
- }
-
- /**
- * A lifecycle hook that is called after Angular has initialized all data-bound properties of a directive.
- *
- * Use this hook to initialize the directive or component. This is the right place to fetch data from a server,
- * set up any local state, or perform operations that need to be executed only once when the component is instantiated.
- *
- * @returns {void} - This method does not return a value.
- */
- ngOnInit() {
- this.roomReservationService.getReservationObservable(this.id).subscribe({
- next: (response) => (this.reservation = response), // Assume only one draft per user
- error: (error) => {
- this.snackBar.open('Error while fetching draft reservation.', '', {
- duration: 8000
- });
- timer(3000).subscribe(() =>
- this.router.navigateByUrl('/coworking/new-reservation')
- );
- console.error(error.message);
- }
- });
- }
-
- setConfirmation(isConfirmed: boolean) {
- this.isConfirmed = isConfirmed;
- }
-}
diff --git a/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.css b/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.css
deleted file mode 100644
index a84731fc1..000000000
--- a/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.css
+++ /dev/null
@@ -1,78 +0,0 @@
-::ng-deep .mat-mdc-card-outlined {
- max-width: 100% !important;
- margin-right: 32px !important;
-}
-
-.mat-mdc-card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
-}
-
-.mat-mdc-card-title {
- padding-left: 10px;
-}
-
-.mat-mdc-card-subtitle {
- padding-left: 10px;
-}
-
-
-/* Add any additional styling for the room-reservation-table if needed */
-
-.date-selector {
- padding: 10px; /* Adjust the value as needed */
-}
-
-.upcoming-reservations-container {
- display: flex;
- flex-wrap: wrap;
- gap: 20px; /* Adjust the gap as needed */
-}
-
-.coworking-reservation-card {
- width: calc(50% - 10px); /* Adjust the width and margin as needed */
- margin-bottom: 20px;
- box-sizing: border-box; /* Include padding and border in the total width */
-}
-
-.heading {
- padding-top: 30px;
- padding-left: 10px;
-}
-
-.legend-container {
- display: flex;
- flex-wrap: wrap;
- margin-top: 20px; /* Adjust the margin as needed */
-}
-
-.legend-item {
- display: flex;
- align-items: center;
- margin-left: 10px; /* Adjust the margin between legend items */
- margin-top: 5px;
-}
-
-.legend-color {
- width: 20px;
- height: 20px;
- margin-right: 5px;
- border-radius: 6px;
-}
-
-.legend-text {
- font-size: 14px;
-}
-
-.reservation-limit {
- padding-left: 10px;
-}
-
-@media only screen and (max-width: 900px) {
- .mat-mdc-card-header {
- flex-direction: column;
- align-items: baseline;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.html b/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.html
deleted file mode 100644
index 7ebda32a4..000000000
--- a/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
- Reserve a Room
- Total Hours Remaining:
- {{ numHoursStudyRoomReservations$ | async }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.ts b/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.ts
deleted file mode 100644
index 0ff9b5b17..000000000
--- a/frontend/src/app/coworking/room-reservation/new-reservation-page/new-reservation-page.component.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * @author John Schachte, Aarjav Jain, Nick Wherthey
- * @copyright 2023
- * @license MIT
- */
-
-import { Component, OnInit } from '@angular/core';
-import { Router } from '@angular/router';
-import { Reservation } from 'src/app/coworking/coworking.models';
-import { isAuthenticated } from 'src/app/gate/gate.guard';
-import { profileResolver } from 'src/app/profile/profile.resolver';
-import { catchError, Observable, of } from 'rxjs';
-import { RoomReservationService } from '../room-reservation.service';
-import { MatSnackBar } from '@angular/material/snack-bar';
-
-@Component({
- selector: 'app-new-reservation-page',
- templateUrl: './new-reservation-page.component.html',
- styleUrls: ['./new-reservation-page.component.css'],
- standalone: false
-})
-export class NewReservationPageComponent implements OnInit {
- public static Route = {
- path: 'new-reservation',
- title: 'New Reservation',
- component: NewReservationPageComponent,
- canActivate: [isAuthenticated],
- resolve: { profile: profileResolver }
- };
-
- public numHoursStudyRoomReservations$!: Observable;
-
- constructor(
- private router: Router,
- private roomReservationService: RoomReservationService,
- protected snackBar: MatSnackBar
- ) {}
-
- /**
- * A lifecycle hook that is called after Angular has initialized all data-bound properties of a directive.
- *
- * Use this hook to initialize the directive or component. This is the right place to fetch data from a server,
- * set up any local state, or perform operations that need to be executed only once when the component is instantiated.
- *
- * @returns {void} - This method does not return a value.
- */
-
- ngOnInit() {
- this.getNumHoursStudyRoomReservations();
- }
-
- navigateToNewReservation() {
- this.router.navigateByUrl('/coworking/new-reservation');
- }
-
- getNumHoursStudyRoomReservations() {
- this.numHoursStudyRoomReservations$ =
- this.roomReservationService.getNumHoursStudyRoomReservations();
- }
-}
diff --git a/frontend/src/app/coworking/room-reservation/reservation-table.service.ts b/frontend/src/app/coworking/room-reservation/reservation-table.service.ts
deleted file mode 100644
index 74fb2994b..000000000
--- a/frontend/src/app/coworking/room-reservation/reservation-table.service.ts
+++ /dev/null
@@ -1,373 +0,0 @@
-/**
- * @author John Schachte, Aarjav Jain, Nick Wherthey
- * @copyright 2023
- * @license MIT
- */
-
-import { HttpClient, HttpParams } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { BehaviorSubject, Observable, Subscription } from 'rxjs';
-import {
- Reservation,
- ReservationMapDetails,
- ReservationRequest,
- TableCell,
- TablePropertyMap
-} from '../coworking.models';
-import { ProfileService } from '../../profile/profile.service';
-import { Profile } from '../../models.module';
-import { RoomReservationWidgetComponent } from '../widgets/room-reservation-table/room-reservation-table.widget';
-
-@Injectable({
- providedIn: 'root'
-})
-export class ReservationTableService {
- private selectedDateSubject = new BehaviorSubject('');
- selectedDate$ = this.selectedDateSubject.asObservable();
- private profile: Profile | undefined;
- private profileSubscription!: Subscription;
-
- private EndingOperationalHour: number = 18;
-
- static readonly MAX_RESERVATION_CELL_LENGTH: number = 4; // rule for how long a reservation can be consecutively
-
- static readonly CellEnum = {
- AVAILABLE: 0,
- BOOKED: 1,
- RESERVING: 2,
- UNAVAILABLE: 3,
- SUBJECT_RESERVATION: 4
- } as const;
-
- //Add table cell states here
- static readonly CellPropertyMap: TablePropertyMap = {
- [ReservationTableService.CellEnum.AVAILABLE]: {
- backgroundColor: '#03691e',
- isDisabled: false
- },
- [ReservationTableService.CellEnum.BOOKED]: {
- backgroundColor: '#B3261E',
- isDisabled: true
- },
- [ReservationTableService.CellEnum.RESERVING]: {
- backgroundColor: 'orange',
- isDisabled: false
- },
- [ReservationTableService.CellEnum.UNAVAILABLE]: {
- backgroundColor: '#4d4d4d',
- isDisabled: true
- },
- [ReservationTableService.CellEnum.SUBJECT_RESERVATION]: {
- backgroundColor: '#3479be',
- isDisabled: true
- }
- };
-
- constructor(
- private http: HttpClient,
- protected profileSvc: ProfileService
- ) {
- this.profileSubscription = this.profileSvc.profile$.subscribe(
- (profile) => (this.profile = profile)
- );
- }
-
- setSelectedDate(date: string) {
- this.selectedDateSubject.next(date);
- }
-
- //TODO Change route from ISO String to date object
- getReservationsForRoomsByDate(date: Date): Observable {
- let params = new HttpParams().set('date', date.toISOString());
- return this.http.get(
- `/api/coworking/room-reservation/`,
- { params }
- );
- }
-
- draftReservation(
- reservationsMap: Record,
- operationStart: Date
- ): Observable {
- const selectedRoom: { room: string; availability: number[] } | null =
- this._findSelectedRoom(reservationsMap);
-
- if (!selectedRoom) throw new Error('No room selected');
- const reservationRequest: ReservationRequest = this._makeReservationRequest(
- selectedRoom!,
- operationStart
- );
- return this.makeDraftReservation(reservationRequest);
- }
-
- //TODO Draft Reservation method
- makeDraftReservation(
- reservationRequest: ReservationRequest
- ): Observable {
- return this.http.post(
- `/api/coworking/reservation`,
- reservationRequest
- );
- }
-
- /**
- * Deselects a cell in the reservations map and updates selected cells.
- *
- * @param {string} key - The key representing the room in the reservations map.
- * @param {number} index - The index representing the time slot in the reservations map.
- * @returns {void} The method does not return a value.
- * @public This method is intended for internal use.
- */
- public deselectCell(
- key: string,
- index: number,
- tableWidget: RoomReservationWidgetComponent
- ): void {
- tableWidget.setSlotAvailable(key, index);
-
- tableWidget.selectedCells = tableWidget.selectedCells.filter(
- (cell) => !(cell.key === key && cell.index === index)
- );
-
- const isAllAdjacent = this._areAllAdjacent(tableWidget.selectedCells);
- if (!isAllAdjacent) {
- tableWidget.selectedCells = this._updateAdjacentCells(index, tableWidget);
- }
- }
-
- /**
- * Selects a cell in the reservations map and updates selected cells.
- *
- * @param {string} key - The key representing the room in the reservations map.
- * @param {number} index - The index representing the time slot in the reservations map.
- * @returns {void} The method does not return a value.
- * @public This method is intended for internal use.
- */
-
- public selectCell(
- key: string,
- index: number,
- tableWidget: RoomReservationWidgetComponent
- ): void {
- const isAdjacentToAny = tableWidget.selectedCells.some(
- (cell: TableCell) => {
- return Math.abs(index - cell.index) <= 1 && key === cell.key;
- }
- );
-
- if (
- isAdjacentToAny &&
- tableWidget.selectedCells.length <
- ReservationTableService.MAX_RESERVATION_CELL_LENGTH
- ) {
- // If adjacent and within the maximum reservation length, select the cell
- tableWidget.setSlotReserving(key, index);
-
- tableWidget.selectedCells.push({ key, index });
- } else if (
- tableWidget.selectedCells.length >=
- ReservationTableService.MAX_RESERVATION_CELL_LENGTH
- ) {
- // If the maximum reservation length is exceeded, deselect all cells
- this._setAllAvailable(tableWidget);
- tableWidget.selectedCells = [{ key, index }]; // resetting selected cells to lone cell
- tableWidget.setSlotReserving(key, index);
- } else {
- // If not adjacent to any selected cells, deselect all and select the new cell
- this._setAllAvailable(tableWidget);
- tableWidget.selectedCells = [{ key, index }]; // resetting selected cells to lone cell
- tableWidget.setSlotReserving(key, index);
- }
- }
-
- _findSelectedRoom(
- reservationsMap: Record
- ): { room: string; availability: number[] } | null {
- //- Finding the room with the selected cells (assuming only 1 row)
- const result = Object.entries(reservationsMap).find(
- ([id, availability]) => {
- return availability.includes(
- ReservationTableService.CellEnum.RESERVING
- );
- }
- );
- return result ? { room: result[0], availability: result[1] } : null;
- }
-
- _makeReservationRequest(
- selectedRoom: { room: string; availability: number[] },
- operationStart: Date
- ): ReservationRequest {
- const minIndex = selectedRoom?.availability.indexOf(
- ReservationTableService.CellEnum.RESERVING
- );
- const maxIndex = selectedRoom?.availability.lastIndexOf(
- ReservationTableService.CellEnum.RESERVING
- );
- const thirtyMinutes = 30 * 60 * 1000;
- const startDateTime = new Date(
- operationStart.getTime() + thirtyMinutes * minIndex
- );
-
- const endDateTime = new Date(
- operationStart.getTime() + thirtyMinutes * (maxIndex + 1)
- );
-
- return {
- users: [this.profile!],
- seats: [],
- room: { id: selectedRoom!.room },
- start: startDateTime,
- end: endDateTime
- };
- }
-
- /**
- * Makes all selected cells Available.
- * @param tableWidget RoomReservationWidgetComponent
- * @returns void
- * @private This method is intended for internal use.
- *
- */
- private _setAllAvailable(tableWidget: RoomReservationWidgetComponent): void {
- tableWidget.selectedCells.forEach((cell: TableCell) => {
- tableWidget.setSlotAvailable(cell.key, cell.index);
- });
- }
-
- /**
- * Checks if all currently selected cells are adjacent to each other.
- *
- * @returns {boolean} True if all selected cells are adjacent, false otherwise.
- * @private This method is intended for internal use.
- */
-
- private _areAllAdjacent(selectedCells: TableCell[]): boolean {
- return selectedCells.every((cell: TableCell, i) => {
- if (i < selectedCells.length - 1) {
- const nextCell = selectedCells[i + 1];
- return Math.abs(cell.index - nextCell.index) <= 1; // Check if the next cell is adjacent
- }
- return true; // Always return true for the last element
- });
- }
-
- /**
- * Updates adjacent cells based on the index of the selected cell.
- *
- * @param {number} index - The index representing the time slot in the reservations map.
- * @returns {void} The method does not return a value.
- * @private This method is intended for internal use.
- */
-
- private _updateAdjacentCells(
- index: number,
- tableWidget: RoomReservationWidgetComponent
- ): TableCell[] {
- // count if there are more cells on the left or on the right
- const leftFrom = this._countIfOnLeft(index, tableWidget.selectedCells);
- const rightFrom = tableWidget.selectedCells.length - leftFrom; // right and left counts are disjoint
- return this._filterCellsBasedOnIndex(
- tableWidget,
- index,
- leftFrom < rightFrom
- );
- }
-
- /**
- * Filters selected cells based on their index relative to a given index.
- *
- * @param tableWidget The RoomReservationWidgetComponent instance.
- * @param index The index to compare against.
- * @param filterBefore If true, filters out cells before the index; otherwise, filters out cells after the index.
- */
- private _filterCellsBasedOnIndex(
- tableWidget: RoomReservationWidgetComponent,
- index: number,
- filterBefore: boolean
- ): TableCell[] {
- return (
- tableWidget.selectedCells.filter((cell) => {
- if (filterBefore && cell.index < index) {
- tableWidget.setSlotAvailable(cell.key, cell.index);
- return false;
- } else if (!filterBefore && cell.index > index) {
- tableWidget.reservationsMap[cell.key][cell.index] =
- ReservationTableService.CellEnum.AVAILABLE;
- return false;
- }
- return true;
- }) ?? []
- );
- }
-
- /**
- * Counts the number of cells on the left of the selected cell.
- * @param index number
- * @param selectedCells TableCell[]
- * @returns number
- * @private This method is intended for internal use.
- */
-
- private _countIfOnLeft(index: number, selectedCells: TableCell[]): number {
- return selectedCells.reduce(
- (count, cell) => (cell.index < index ? (count += 1) : count),
- 0
- );
- }
-
- setMaxDate(): Date {
- let result = new Date();
- result.setDate(result.getDate() + 7);
- return result;
- }
-
- setMinDate(): Date {
- let result = new Date();
- if (result.getHours() >= this.EndingOperationalHour) {
- result.setDate(result.getDate() + 1);
- }
- return result;
- }
-
- /**
- * Formats a date object into a string of the format 'HH:MMAM/PM'.
- *
- * @private
- * @param {Date} date - The date object to be formatted.
- * @returns {string} The formatted time string in 'HH:MMAM/PM' format.
- */
- private formatAMPM(date: Date): string {
- let hours = date.getHours();
- let minutes = date.getMinutes();
- const ampm = hours >= 12 ? 'PM' : 'AM';
- hours = hours % 12;
- hours = hours ? hours : 12; // the hour '0' should be '12'
- const minutesStr = minutes < 10 ? '0' + minutes : minutes.toString();
- return `${hours}:${minutesStr}${ampm}`;
- }
-
- /**
- * Generates time slots between two dates in increments of thirty minutes, formatted as 'HH:MMA/PM
to
HH:MMPM'.
- *
- * @private
- * @param {Date} start - The start date and time for generating time slots.
- * @param {Date} end - The end date and time for the time slots.
- * @param {number} slots - The number of slots to generate.
- * @returns {string[]} An array of strings representing the time slots in 'HH:MMA/PM
to
HH:MMPM' format.
- */
- generateTimeSlots(start: Date, end: Date, slots: number): string[] {
- const timeSlots = [];
- const ThirtyMinutes = 30 * 60000; // Thirty minutes in milliseconds
- while (start < end) {
- let thirtyMinutesLater = new Date(start.getTime() + ThirtyMinutes);
- timeSlots.push(
- `${this.formatAMPM(start)}
to
${this.formatAMPM(
- thirtyMinutesLater
- )}`
- );
- start = thirtyMinutesLater;
- }
- return timeSlots;
- }
-}
diff --git a/frontend/src/app/coworking/room-reservation/room-reservation.component.css b/frontend/src/app/coworking/room-reservation/room-reservation.component.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/src/app/coworking/room-reservation/room-reservation.component.html b/frontend/src/app/coworking/room-reservation/room-reservation.component.html
new file mode 100644
index 000000000..ce1478711
--- /dev/null
+++ b/frontend/src/app/coworking/room-reservation/room-reservation.component.html
@@ -0,0 +1,121 @@
+
+
+ Reserve a Room
+
+
+
+
+
+
+
+
+
+
+ @if(availability) {
+
+
+
+
+
+ Available
+
+
+
+ Your Reservations
+
+
+
+ Reserved
+
+
+
+
+ @for(slot of availability.slot_labels; track slot; let onTheHour = $even) {
+ @if(onTheHour) {
+
+ {{ slot }}
+
+ }
+ }
+
+
+ @for(roomAvailability of availability.rooms; track roomAvailability.room) {
+
+
+
+
{{ roomAvailability.room }}
+
+ {{ roomAvailability.capacity }}
+ chair_alt
+
+
+
+ @for(slot of availability.slot_labels; track slot) {
+ @let slotAvailability = roomAvailability.availability[slot];
+ @switch (slotAvailability.state) {
+ @case('AVAILABLE') {
+
+ @if(isSlotSelected(roomAvailability, slot)) {
+
+ } @else {
+
+ }
+
+ }
+ @case('YOUR_RESERVATION') {
+
+ }
+ @case('RESERVED') {
+
+
+ }
+ @case('UNAVAILABLE') {
+
+ }
+ }
+
+ }
+
+ }
+
+ } @else {
+ You cannot reserve rooms for the selected date at this time.
+ }
+
+ @let room = selectedRoom();
+ @let selectedTimeslotRange = selectedSlotTimeRange();
+ @if(selectedSlots.length > 0 && !!room && !!selectedTimeslotRange) {
+
+
+
+
Selected: {{ room.room }} from {{ selectedTimeslotRange.start | date:'h:mma' }} to {{ selectedTimeslotRange.end | date:'h:mma' }}
+
Reserving For:
+
+
+
+ @let remainingNeededUsers = room.minimum_reservers - selectedUsers.length;
+
+
+
+
+
+ }
+
\ No newline at end of file
diff --git a/frontend/src/app/coworking/room-reservation/room-reservation.component.ts b/frontend/src/app/coworking/room-reservation/room-reservation.component.ts
new file mode 100644
index 000000000..0f792a392
--- /dev/null
+++ b/frontend/src/app/coworking/room-reservation/room-reservation.component.ts
@@ -0,0 +1,248 @@
+import { Component, signal } from '@angular/core';
+import { isAuthenticated } from 'src/app/gate/gate.guard';
+import { profileResolver } from 'src/app/profile/profile.resolver';
+import { NewRoomReservationService } from './room-reservation.service';
+import {
+ GetRoomAvailabilityResponse,
+ GetRoomAvailabilityResponse_Room
+} from '../coworking.models';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Profile } from 'src/app/models.module';
+import { PublicProfile } from 'src/app/profile/profile.service';
+import { TimeRange } from 'src/app/time-range';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { FormControl } from '@angular/forms';
+
+type SlotSelection = { room: string; slot: string };
+
+@Component({
+ selector: 'app-room-reservation',
+ templateUrl: './room-reservation.component.html',
+ styleUrl: './room-reservation.component.css',
+ standalone: false
+})
+export class NewRoomReservationComponent {
+ public static Route = {
+ path: 'new-reservation',
+ title: 'New Reservation',
+ component: NewRoomReservationComponent,
+ canActivate: [isAuthenticated],
+ resolve: { profile: profileResolver }
+ };
+
+ profile: PublicProfile;
+
+ availability: GetRoomAvailabilityResponse | undefined = undefined;
+
+ public selectedSlots: SlotSelection[] = [];
+ public selectedUsers: PublicProfile[] = [];
+
+ dateControl = new FormControl(new Date());
+
+ constructor(
+ protected route: ActivatedRoute,
+ protected router: Router,
+ protected snackBar: MatSnackBar,
+ private roomReservationService: NewRoomReservationService
+ ) {
+ /** Initialize data from resolvers. */
+ const data = this.route.snapshot.data as {
+ profile: Profile;
+ };
+ this.profile = data.profile as PublicProfile;
+ this.selectedUsers = [this.profile];
+
+ this.dateControl.valueChanges.subscribe((date) => {
+ if (date) {
+ // Refresh availability when date changes
+ this.roomReservationService
+ .getAvailability(date)
+ .subscribe((result) => {
+ this.availability =
+ result.slot_labels.length > 0 ? result : undefined;
+ this.selectedSlots = [];
+ this.selectedUsers = [this.profile];
+ });
+ }
+ });
+
+ // Initial load
+ this.roomReservationService.getAvailability().subscribe((result) => {
+ this.availability = result;
+ });
+ }
+
+ selectSlot(room: GetRoomAvailabilityResponse_Room, slot: string) {
+ // Validation
+ if (!this.availability) {
+ return;
+ }
+ // First, make sure that the slot is selectable.
+ if (room.availability[slot].state !== 'AVAILABLE') {
+ return;
+ }
+
+ // If there are no existing selections, select the slot.
+ if (this.selectedSlots.length === 0) {
+ this.selectedSlots.push({ room: room.room, slot });
+ return;
+ }
+
+ // If existing slots are for a different room, clear the selection and select.
+ if (this.selectedSlots[0].room !== room.room) {
+ this.selectedSlots = [{ room: room.room, slot }];
+ this.selectedUsers = this.selectedUsers.slice(0, room.capacity);
+ return;
+ }
+
+ // For later functionality, get the indexes of the selected slots within the
+ // list of slot labels.
+ const selectedSlotIndexes = Array.from(this.selectedSlots).map(
+ (selectedSlot) => {
+ return this.availability!.slot_labels.indexOf(selectedSlot.slot);
+ }
+ );
+ const earliestSlotIndex = Math.min(...selectedSlotIndexes);
+ const latestSlotIndex = Math.max(...selectedSlotIndexes);
+
+ const clickedSlotIndex = this.availability!.slot_labels.indexOf(slot);
+
+ // If the clicked slot is already selected:
+ // - If the slot is at the start or end, remove just the slot
+ // - Otherwise, start over selection
+ if (
+ this.selectedSlots.find((v) => v.room === room.room && v.slot === slot)
+ ) {
+ if (
+ clickedSlotIndex === earliestSlotIndex ||
+ clickedSlotIndex === latestSlotIndex
+ ) {
+ this.selectedSlots = this.selectedSlots.filter(
+ (v) => v.room !== room.room || v.slot !== slot
+ );
+ return;
+ } else {
+ this.selectedSlots = [{ room: room.room, slot }];
+ return;
+ }
+ }
+
+ // Otherwise, if a slot is before the earliest selection, we start over at
+ // the new selection
+ if (clickedSlotIndex < earliestSlotIndex) {
+ this.selectedSlots = [{ room: room.room, slot }];
+ return;
+ }
+
+ // Otherwise, the final case is that the clicked slot is some time after the
+ // currently selected range. We want to either:
+ // 1. Restart the range if there is an interruption of available slots for the room between
+ // the start and the end of the range, or:
+ // 2. Fill in the range up to the selected date.
+ let isInterruption: boolean = false;
+ let currentIndex = latestSlotIndex + 1;
+ let slotsToAdd: SlotSelection[] = [];
+ while (currentIndex <= clickedSlotIndex) {
+ if (
+ room.availability[this.availability.slot_labels[currentIndex]].state !==
+ 'AVAILABLE'
+ ) {
+ isInterruption = true;
+ }
+ slotsToAdd.push({
+ room: room.room,
+ slot: this.availability.slot_labels[currentIndex]
+ });
+ currentIndex += 1;
+ }
+
+ if (isInterruption) {
+ this.selectedSlots = [{ room: room.room, slot }];
+ return;
+ } else {
+ slotsToAdd.forEach((slot) => this.selectedSlots.push(slot));
+ return;
+ }
+ }
+
+ selectedRoom(): GetRoomAvailabilityResponse_Room | null {
+ if (!this.availability || this.selectedSlots.length === 0) return null;
+ const room = this.availability!.rooms.find(
+ (v) => v.room === this.selectedSlots[0].room
+ );
+ return room ? room : null;
+ }
+
+ isSlotSelected(room: GetRoomAvailabilityResponse_Room, slot: string) {
+ return !!this.selectedSlots.find(
+ (v) => v.room === room.room && v.slot === slot
+ );
+ }
+
+ clearSelections() {
+ this.selectedSlots = [];
+ }
+
+ canDraftReservation() {
+ if (this.selectedSlots.length === 0) return false;
+ const room = this.selectedRoom();
+ if (!room) return false;
+ const numSelectedUsers = this.selectedUsers.length;
+ return (
+ this.availability!.is_instructor ||
+ (numSelectedUsers >= room.minimum_reservers &&
+ numSelectedUsers <= room.capacity)
+ );
+ }
+
+ selectedSlotTimeRange(): TimeRange | null {
+ if (!this.availability || this.selectedSlots.length === 0) return null;
+ const selectedSlotIndexes = Array.from(this.selectedSlots).map(
+ (selectedSlot) => {
+ return this.availability!.slot_labels.indexOf(selectedSlot.slot);
+ }
+ );
+ const earliestSlotIndex = Math.min(...selectedSlotIndexes);
+ const latestSlotIndex = Math.max(...selectedSlotIndexes);
+ const earliestSlot = this.availability.slot_labels[earliestSlotIndex];
+ const latestSlot = this.availability.slot_labels[latestSlotIndex];
+ return {
+ start: new Date(this.availability!.slots[earliestSlot].start_time),
+ end: new Date(this.availability!.slots[latestSlot].end_time)
+ };
+ }
+
+ onUsersChanged(users: PublicProfile[]) {
+ this.selectedUsers = users;
+ }
+
+ draftReservation() {
+ const timeRange = this.selectedSlotTimeRange();
+ if (this.canDraftReservation() && timeRange) {
+ this.roomReservationService
+ .draftRoomReservation({
+ users: this.selectedUsers.map((publicProfile) => {
+ return {
+ id: publicProfile.id
+ };
+ }),
+ seats: [],
+ room: { id: this.selectedRoom()!.room },
+ start: timeRange.start,
+ end: timeRange.end
+ })
+ .subscribe({
+ next: (draftReservation) => {
+ this.router.navigateByUrl(
+ `/coworking/reservation/${draftReservation.id}`
+ );
+ },
+ error: (err) => {
+ this.snackBar.open(`${err.error.message}`, '', {
+ duration: 2000
+ });
+ }
+ });
+ }
+ }
+}
diff --git a/frontend/src/app/coworking/room-reservation/room-reservation.service.ts b/frontend/src/app/coworking/room-reservation/room-reservation.service.ts
index 9484d260a..d90bafba3 100644
--- a/frontend/src/app/coworking/room-reservation/room-reservation.service.ts
+++ b/frontend/src/app/coworking/room-reservation/room-reservation.service.ts
@@ -1,31 +1,30 @@
-/**
- * The Room Reservation Service abstracts HTTP requests to the backend
- * from the components.
- *
- * @author Aarjav Jain, John Schachte, Nick Wherthey, Yuvraj Jain
- * @copyright 2023
- * @license MIT
- */
-
-import { Injectable } from '@angular/core';
-import { HttpClient, HttpParams } from '@angular/common/http';
-import { map, Observable } from 'rxjs';
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
import {
- parseReservationJSON,
+ GetRoomAvailabilityResponse,
Reservation,
- ReservationJSON
+ ReservationRequest
} from '../coworking.models';
-import { ReservationService } from '../reservation/reservation.service';
+import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
-export class RoomReservationService extends ReservationService {
- constructor(http: HttpClient) {
- super(http);
+export class NewRoomReservationService {
+ protected http = inject(HttpClient);
+
+ getAvailability(date?: Date): Observable {
+ const params: { [key: string]: string } = {};
+ if (date) {
+ params['date'] = date.toISOString();
+ }
+ return this.http.get(
+ '/api/coworking/rooms/availability',
+ { params }
+ );
}
- getNumHoursStudyRoomReservations(): Observable {
- return this.http.get('/api/coworking/user-reservations/');
+ draftRoomReservation(request: ReservationRequest) {
+ return this.http.post(`/api/coworking/reservation`, request);
}
}
diff --git a/frontend/src/app/coworking/widgets/coworking-reservation-card/coworking-reservation-card.ts b/frontend/src/app/coworking/widgets/coworking-reservation-card/coworking-reservation-card.ts
index 563a46fb8..75893a4a2 100644
--- a/frontend/src/app/coworking/widgets/coworking-reservation-card/coworking-reservation-card.ts
+++ b/frontend/src/app/coworking/widgets/coworking-reservation-card/coworking-reservation-card.ts
@@ -8,15 +8,15 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Reservation } from 'src/app/coworking/coworking.models';
import { Observable, map, timer } from 'rxjs';
import { Router } from '@angular/router';
-import { RoomReservationService } from '../../room-reservation/room-reservation.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import { CoworkingService } from '../../coworking.service';
+import { ReservationService } from '../../reservation/reservation.service';
@Component({
- selector: 'coworking-reservation-card',
- templateUrl: './coworking-reservation-card.html',
- styleUrls: ['./coworking-reservation-card.css'],
- standalone: false
+ selector: 'coworking-reservation-card',
+ templateUrl: './coworking-reservation-card.html',
+ styleUrls: ['./coworking-reservation-card.css'],
+ standalone: false
})
export class CoworkingReservationCard implements OnInit {
@Input() reservation!: Reservation;
@@ -31,7 +31,7 @@ export class CoworkingReservationCard implements OnInit {
constructor(
public router: Router,
- public roomReservationService: RoomReservationService,
+ public reservationService: ReservationService,
protected snackBar: MatSnackBar,
public coworkingService: CoworkingService
) {
@@ -61,7 +61,7 @@ export class CoworkingReservationCard implements OnInit {
}
cancel() {
- this.roomReservationService.cancel(this.reservation).subscribe({
+ this.reservationService.cancel(this.reservation).subscribe({
next: () => {
this.refreshCoworkingHome();
},
@@ -78,7 +78,7 @@ export class CoworkingReservationCard implements OnInit {
confirm() {
this.isConfirmed.emit(true);
- this.roomReservationService.confirm(this.reservation).subscribe({
+ this.reservationService.confirm(this.reservation).subscribe({
next: () => {
this.refreshCoworkingHome();
// this.router.navigateByUrl('/coworking');
@@ -95,7 +95,7 @@ export class CoworkingReservationCard implements OnInit {
}
checkout() {
- this.roomReservationService.checkout(this.reservation).subscribe({
+ this.reservationService.checkout(this.reservation).subscribe({
next: () => this.refreshCoworkingHome(),
error: (error: Error) => {
this.snackBar.open(
@@ -109,7 +109,7 @@ export class CoworkingReservationCard implements OnInit {
}
checkin(): void {
- this.roomReservationService.checkin(this.reservation).subscribe({
+ this.reservationService.checkin(this.reservation).subscribe({
next: () => {
this.refreshCoworkingHome();
},
diff --git a/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.html b/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.html
deleted file mode 100644
index 42486efd6..000000000
--- a/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.ts b/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.ts
deleted file mode 100644
index a738227df..000000000
--- a/frontend/src/app/coworking/widgets/date-selector/date-selector.widget.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * The date selector widget that abstracts date selection.
- *
- * @author Aarjav Jain, John Schachte
- * @copyright 2023
- * @license MIT
- */
-
-import { Component, EventEmitter, Output } from '@angular/core';
-import { MatDatepickerInputEvent } from '@angular/material/datepicker';
-import { ReservationTableService } from '../../room-reservation/reservation-table.service';
-
-/**
- * @title Date Selector
- */
-@Component({
- selector: 'date-selector',
- templateUrl: './date-selector.widget.html',
- styleUrls: ['./date-selector.widget.css'],
- standalone: false
-})
-export class DateSelector {
- @Output() dateSelected = new EventEmitter();
- minDate: Date;
- maxDate: Date;
-
- constructor(private reservationTableService: ReservationTableService) {
- this.minDate = this.reservationTableService.setMinDate();
- this.maxDate = this.reservationTableService.setMaxDate();
- }
-
- onDateChange(event: MatDatepickerInputEvent) {
- const selectedDate: string = this.formatDate(event.value!);
- this.reservationTableService.setSelectedDate(selectedDate);
- }
-
- private formatDate(date: Date): string {
- // Format the date as needed, you might want to use a library like 'date-fns' or 'moment'
- // For simplicity, this example uses the default 'toLocaleDateString' method
- return date.toLocaleDateString(); // Adjust this based on your actual formatting requirements
- }
-}
diff --git a/frontend/src/app/coworking/widgets/reservation-facts/reservation-facts.widget.html b/frontend/src/app/coworking/widgets/reservation-facts/reservation-facts.widget.html
index ef491f23b..e83a26599 100644
--- a/frontend/src/app/coworking/widgets/reservation-facts/reservation-facts.widget.html
+++ b/frontend/src/app/coworking/widgets/reservation-facts/reservation-facts.widget.html
@@ -25,7 +25,6 @@
-
}
@@ -54,4 +53,14 @@
}
+
+
+ @if (reservation.users.length > 0) {
+