diff --git a/angular/RestClient/src/app/app.component.html b/angular/RestClient/src/app/app.component.html index 07855f6148867d68a9920ea6a65f4b450f1a6cb5..086c08469b743ad84e40ff603386f80cb56d4e88 100644 --- a/angular/RestClient/src/app/app.component.html +++ b/angular/RestClient/src/app/app.component.html @@ -1,12 +1,3 @@ -<!-- compiled and minified CSS --> -<link - rel="stylesheet" - href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css" -/> -<!-- jQuery library --> -<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> -<!-- compiled JavaScript --> -<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script> <div class="container"> <h3>Ejemplo angular. Gestión de reservas</h3> <router-outlet></router-outlet> diff --git a/angular/RestClient/src/app/app.config.ts b/angular/RestClient/src/app/app.config.ts index 5ceafb91855d50f5955a13713a496522f3b22146..c4c596d4c55739d0e1d45156b092e8665985d508 100644 --- a/angular/RestClient/src/app/app.config.ts +++ b/angular/RestClient/src/app/app.config.ts @@ -5,16 +5,16 @@ import { provideClientHydration } from '@angular/platform-browser'; import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { ReactiveFormsModule } from '@angular/forms'; // Added import for ReactiveFormsModule - +import { provideNativeDateAdapter } from '@angular/material/core'; export const appConfig: ApplicationConfig = { providers: [ + provideNativeDateAdapter(), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideClientHydration(), - provideHttpClient(withFetch()), + provideHttpClient(withFetch()), provideAnimationsAsync(), - ReactiveFormsModule - + ReactiveFormsModule, ], }; diff --git a/angular/RestClient/src/app/app.routes.ts b/angular/RestClient/src/app/app.routes.ts index 1787d715684c20bc00d416731fa0ecf6d59b4ab3..a45f6b56d94d29ba2ed93ecdd2dd8b055929593c 100644 --- a/angular/RestClient/src/app/app.routes.ts +++ b/angular/RestClient/src/app/app.routes.ts @@ -1,6 +1,7 @@ import { Routes } from '@angular/router'; import { HotelListComponent } from './hotel-list/hotel-list.component'; import { BookingComponent } from './booking/booking.component'; // Asegúrate de ajustar la ruta +import { BookingListComponent } from './booking-list/booking-list.component'; export const routes: Routes = [ { @@ -8,12 +9,16 @@ export const routes: Routes = [ component: HotelListComponent, }, { - path: 'booking', // Añade la ruta para el componente de reservas - component: BookingComponent, + path: 'booking/search', + component: BookingListComponent, }, { - path: '**', // Mantiene la redirección para cualquier otra ruta no definida - redirectTo: 'hotels', - pathMatch: 'full', + path: 'booking', // Añade la ruta para el componente de reservas + component: BookingComponent, }, + // { + // path: '**', // Mantiene la redirección para cualquier otra ruta no definida + // redirectTo: 'hotels', + // pathMatch: 'full', + // }, ]; diff --git a/angular/RestClient/src/app/booking-list/booking-list.component.css b/angular/RestClient/src/app/booking-list/booking-list.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/angular/RestClient/src/app/booking-list/booking-list.component.html b/angular/RestClient/src/app/booking-list/booking-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..653fb3a73614955a1d731766f9b0aaed7cad5665 --- /dev/null +++ b/angular/RestClient/src/app/booking-list/booking-list.component.html @@ -0,0 +1,51 @@ +<div> + <mat-form-field> + <mat-label>Enter a date range</mat-label> + <mat-date-range-input [rangePicker]="picker"> + <input + matStartDate + placeholder="Start date" + (dateInput)="updateStart($event)" + (dateChange)="updateStart($event)" + /> + <input + matEndDate + placeholder="End date" + (dateInput)="updateEnd($event)" + (dateChange)="updateEnd($event)" + /> + </mat-date-range-input> + <mat-hint>MM/DD/YYYY – MM/DD/YYYY</mat-hint> + <mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle> + <mat-date-range-picker #picker></mat-date-range-picker> + </mat-form-field> + <mat-form-field> + <mat-label>Hotel</mat-label> + <mat-select [(value)]="hotelSelected"> + @for (hotel of hotels; track hotel.id) { + <mat-option [value]="hotel.id">{{ hotel.name }}</mat-option> + } + </mat-select> + </mat-form-field> + <mat-form-field> + <mat-label>Room Type</mat-label> + <mat-select [(value)]="roomTypeSelected" (selectionChange)="updateRooms()"> + @for (type of roomTypes; track type) { + <mat-option [value]="type">{{ type }}</mat-option> + } + </mat-select> + </mat-form-field> + <button + [disabled]="!this.start || !this.end || this.hotelSelected == -1" + mat-raised-button + color="primary" + (click)="search()" + > + Search + </button> + @for (room of trateRooms; track $index) { + <button (click)="bookingRoom(room.id)"> + {{ room.id }} {{ room.type }} {{ room.roomNumber }} + </button> + } +</div> diff --git a/angular/RestClient/src/app/booking-list/booking-list.component.spec.ts b/angular/RestClient/src/app/booking-list/booking-list.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f6b1910a567d3ff00efa491436359519ee52632d --- /dev/null +++ b/angular/RestClient/src/app/booking-list/booking-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BookingListComponent } from './booking-list.component'; + +describe('BookingListComponent', () => { + let component: BookingListComponent; + let fixture: ComponentFixture<BookingListComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BookingListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BookingListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular/RestClient/src/app/booking-list/booking-list.component.ts b/angular/RestClient/src/app/booking-list/booking-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..41d5976b734d93abfa3d2e0457af80f6e2c09c5b --- /dev/null +++ b/angular/RestClient/src/app/booking-list/booking-list.component.ts @@ -0,0 +1,88 @@ +import { Component } from '@angular/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { + MatDatepickerInputEvent, + MatDatepickerModule, +} from '@angular/material/datepicker'; +import { FormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectChange, MatSelectModule } from '@angular/material/select'; +import { Hotel, Room, RoomType, roomTypeArray } from '../../types'; +import { ClienteApiRestService } from '../shared/cliente-api-rest.service'; +import { Router } from '@angular/router'; + +type SelectableRoomType = 'All' | RoomType; +const selectableRoomTypeArray: SelectableRoomType[] = ['All', ...roomTypeArray]; + +@Component({ + selector: 'app-booking-list', + standalone: true, + imports: [ + MatFormFieldModule, + MatDatepickerModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + FormsModule, + ], + templateUrl: './booking-list.component.html', + styleUrl: './booking-list.component.css', +}) +export class BookingListComponent { + start?: Date; + end?: Date; + hotels!: Hotel[]; + hotelSelected: number = -1; + roomTypeSelected?: SelectableRoomType; + roomTypes = selectableRoomTypeArray; + rooms: Room[] = []; + trateRooms: Room[] = []; + + constructor(private router: Router, private client: ClienteApiRestService) {} + + ngOnInit() { + this.getHotels(); + } + + getHotels() { + this.client.getAllHotels().subscribe({ + next: (resp) => { + if (resp.body != null) this.hotels = [...resp.body]; + }, + error(err) { + console.log('Error al traer la lista: ' + err.message); + throw err; + }, + }); + } + + updateStart(event: MatDatepickerInputEvent<Date>) { + this.start = event.value!; + } + + updateEnd(event: MatDatepickerInputEvent<Date>) { + this.end = event.value!; + } + + search() { + this.client + .getRoomsAvailableInDateRange(this.hotelSelected, this.start!, this.end!) + .subscribe({ + next: (resp) => { + this.rooms = resp; + this.updateRooms(); + }, + }); + } + + updateRooms() { + this.trateRooms = + this.roomTypeSelected && this.roomTypeSelected !== 'All' + ? this.rooms.filter((room) => room.type === this.roomTypeSelected) + : this.rooms; + } + + bookingRoom(roomId: number) { + this.router.navigateByUrl(`/booking?room=${roomId}`); + } +} diff --git a/angular/RestClient/src/app/booking/booking.component.spec.ts b/angular/RestClient/src/app/booking/booking.component.spec.ts index 0a8d9ea7b6fb3a520a401263b587ba7361b34989..486d21fb8c300bbcef700c5d53a5d3d84bcb68f9 100644 --- a/angular/RestClient/src/app/booking/booking.component.spec.ts +++ b/angular/RestClient/src/app/booking/booking.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { BookingComponent } from './booking.component'; -import { BookingService } from '../booking.service'; +import { BookingService } from '../shared/booking.service'; import { of } from 'rxjs'; class MockBookingService { @@ -19,7 +19,7 @@ describe('BookingComponent', () => { await TestBed.configureTestingModule({ declarations: [BookingComponent], imports: [ReactiveFormsModule], - providers: [{ provide: BookingService, useClass: MockBookingService }] + providers: [{ provide: BookingService, useClass: MockBookingService }], }).compileComponents(); fixture = TestBed.createComponent(BookingComponent); diff --git a/angular/RestClient/src/app/booking/booking.component.ts b/angular/RestClient/src/app/booking/booking.component.ts index b8bd104619cbddefeae2f0743471606049b76863..9636e65609448ce2df6cf698da2186627c937722 100644 --- a/angular/RestClient/src/app/booking/booking.component.ts +++ b/angular/RestClient/src/app/booking/booking.component.ts @@ -1,22 +1,26 @@ import { Component, OnInit } from '@angular/core'; -import { ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { + ReactiveFormsModule, + FormGroup, + FormBuilder, + Validators, +} from '@angular/forms'; interface BookingRequest { - userId: number; // ID del usuario que realiza la reserva - hotelId: number; // ID del hotel en el que se realiza la reserva - roomType: string; // Tipo de habitación (single, double, suite) - startDate: string; // Fecha de inicio de la reserva - endDate: string; // Fecha de fin de la reserva// Asegúrate de ajustar la ruta + userId: number; // ID del usuario que realiza la reserva + hotelId: number; // ID del hotel en el que se realiza la reserva + roomType: string; // Tipo de habitación (single, double, suite) + startDate: string; // Fecha de inicio de la reserva + endDate: string; // Fecha de fin de la reserva// Asegúrate de ajustar la ruta } -import { BookingService } from '../booking.service'; // Asegúrate de que el servicio exista +import { BookingService } from '../shared/booking.service'; // Asegúrate de que el servicio exista @Component({ - standalone : true, - imports : [ReactiveFormsModule], + standalone: true, + imports: [ReactiveFormsModule], selector: 'app-booking', templateUrl: './booking.component.html', - styleUrls: ['./booking.component.css'] + styleUrls: ['./booking.component.css'], }) - export class BookingComponent implements OnInit { bookingForm: FormGroup; @@ -27,7 +31,7 @@ export class BookingComponent implements OnInit { hotelId: ['', Validators.required], roomType: ['', Validators.required], startDate: ['', Validators.required], - endDate: ['', Validators.required] + endDate: ['', Validators.required], }); } @@ -39,12 +43,12 @@ export class BookingComponent implements OnInit { // Llama al servicio para crear una nueva reserva this.bookingService.createBooking(bookingRequest).subscribe( - response => { + (response) => { console.log('Reserva creada con éxito', response); // Aquí puedes redirigir al usuario o mostrar un mensaje de éxito this.bookingForm.reset(); // Opcional: resetea el formulario después de una reserva exitosa }, - error => { + (error) => { console.error('Error al crear la reserva', error); // Manejo de errores } diff --git a/angular/RestClient/src/app/booking.service.spec.ts b/angular/RestClient/src/app/shared/booking.service.spec.ts similarity index 100% rename from angular/RestClient/src/app/booking.service.spec.ts rename to angular/RestClient/src/app/shared/booking.service.spec.ts diff --git a/angular/RestClient/src/app/booking.service.ts b/angular/RestClient/src/app/shared/booking.service.ts similarity index 63% rename from angular/RestClient/src/app/booking.service.ts rename to angular/RestClient/src/app/shared/booking.service.ts index ce356423228f72ae1ddaecaf118f58d24c1d170f..3a4db3fc8c30952e5d240e43027138009b3ac000 100644 --- a/angular/RestClient/src/app/booking.service.ts +++ b/angular/RestClient/src/app/shared/booking.service.ts @@ -3,19 +3,19 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; interface BookingRequest { - userId: number; // ID del usuario que realiza la reserva - hotelId: number; // ID del hotel en el que se realiza la reserva - roomType: string; // Tipo de habitación (single, double, suite) - startDate: string; // Fecha de inicio de la reserva - endDate: string; // Fecha de fin de la reserva + userId: number; // ID del usuario que realiza la reserva + hotelId: number; // ID del hotel en el que se realiza la reserva + roomType: string; // Tipo de habitación (single, double, suite) + startDate: string; // Fecha de inicio de la reserva + endDate: string; // Fecha de fin de la reserva } -import { Booking } from '../types/Booking'; // Ajusta la ruta a tu modelo Booking +import { Booking } from '../../types/Booking'; // Ajusta la ruta a tu modelo Booking @Injectable({ - providedIn: 'root' // Esto hace que el servicio esté disponible en toda la aplicación + providedIn: 'root', // Esto hace que el servicio esté disponible en toda la aplicación }) export class BookingService { - private apiUrl = 'http://localhost:8080/api/bookings'; + private apiUrl = 'http://localhost:8080/api/bookings'; constructor(private http: HttpClient) {} @@ -23,8 +23,8 @@ export class BookingService { createBooking(bookingRequest: BookingRequest): Observable<Booking> { return this.http.post<Booking>(this.apiUrl, bookingRequest, { headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) + 'Content-Type': 'application/json', + }), }); } diff --git a/angular/RestClient/src/app/shared/cliente-api-rest.service.ts b/angular/RestClient/src/app/shared/cliente-api-rest.service.ts index 7cc9d40607f696d2c133f87aa027d5e872dc4ca8..853139cb669159005619ef680b3360e3e79b67f7 100644 --- a/angular/RestClient/src/app/shared/cliente-api-rest.service.ts +++ b/angular/RestClient/src/app/shared/cliente-api-rest.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import hotels from '../../mocks/hotels.json'; -import { Hotel, Booking } from '../../types'; +import { Hotel, Booking, Room } from '../../types'; @Injectable({ providedIn: 'root', @@ -50,5 +50,12 @@ export class ClienteApiRestService { createBooking(bookingRequest: Booking): Observable<any> { return this.http.post('http://localhost:8080/bookings', bookingRequest); } - + + getRoomsAvailableInDateRange(hotelId: number, start: Date, end: Date) { + const startStr = start.toISOString().split('T')[0]; + const endStr = end.toISOString().split('T')[0]; + return this.http.get<Room[]>( + `${ClienteApiRestService.HOTEL_URI}/${hotelId}/rooms?start=${startStr}&end=${endStr}` + ); + } } diff --git a/angular/RestClient/src/index.html b/angular/RestClient/src/index.html index 189053197993efde89b3e56b3debb06775363007..55c5530803842faa15de37607c2c916d5bd4f068 100644 --- a/angular/RestClient/src/index.html +++ b/angular/RestClient/src/index.html @@ -1,15 +1,29 @@ -<!doctype html> +<!DOCTYPE html> <html lang="en"> -<head> - <meta charset="utf-8"> - <title>RestClient</title> - <base href="/"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <link rel="icon" type="image/x-icon" href="favicon.ico"> - <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> - <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> -</head> -<body> - <app-root></app-root> -</body> + <head> + <meta charset="utf-8" /> + <title>RestClient</title> + <base href="/" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="icon" type="image/x-icon" href="favicon.ico" /> + <link + href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" + rel="stylesheet" + /> + <link + href="https://fonts.googleapis.com/icon?family=Material+Icons" + rel="stylesheet" + /> + <link + rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css" + /></head + ><!-- compiled and minified CSS --> + <!-- jQuery library --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> + <!-- compiled JavaScript --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script> + <body> + <app-root></app-root> + </body> </html> diff --git a/angular/RestClient/src/types/Room.d.ts b/angular/RestClient/src/types/Room.d.ts index d15b5148c018da01fb1d08d2cc0477c4dd66dbf8..0c54fdcbfee2f4da7aa34f3727dd21421acbb048 100644 --- a/angular/RestClient/src/types/Room.d.ts +++ b/angular/RestClient/src/types/Room.d.ts @@ -1,7 +1,7 @@ +export type RoomType = 'SINGLE' | 'DOUBLE' | 'SUITE'; export interface Room { id: number; roomNumber: String; - // type: "single" | "double" | "suite"; - type: 'SINGLE' | 'DOUBLE' | 'SUITE'; + type: RoomType; available: boolean; } diff --git a/angular/RestClient/src/types/index.ts b/angular/RestClient/src/types/index.ts index 958d57e8a39f47ec45f9a33857cf52f3aacfde34..b974a05f55ecc2a2b9a6e953882d7cc526689ab6 100644 --- a/angular/RestClient/src/types/index.ts +++ b/angular/RestClient/src/types/index.ts @@ -1,6 +1,9 @@ +import { RoomType } from './Room'; + export type * from './User'; export type * from './Address'; export type * from './Hotel'; export type * from './Room'; +export const roomTypeArray: RoomType[] = ['SINGLE', 'DOUBLE', 'SUITE']; export type * from './Booking'; export type * from './User'; diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java index eea12ebe70ad54c0095629223d90c5e459a82bcd..587c57014ea64272b3be49cbed0706800bef484b 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java @@ -61,15 +61,15 @@ public class HotelController { @GetMapping("/{hotelId}/rooms") public ResponseEntity<List<Room>> getRoomsFromHotel( @PathVariable int hotelId, - @RequestParam(required = false) LocalDate date1, - @RequestParam(required = false) LocalDate date2) { + @RequestParam(required = false) LocalDate start, + @RequestParam(required = false) LocalDate end) { List<Room> rooms; - if (date1 != null && date2 != null) { - if (!date1.isBefore(date2)) { + if (start != null && end != null) { + if (!start.isBefore(end)) { throw new InvalidDateRangeException("La fecha de inicio debe ser anterior a la fecha de fin"); } - rooms = roomRepository.findAvailableRoomsByHotelAndDates(hotelId, date1, date2); + rooms = roomRepository.findAvailableRoomsByHotelAndDates(hotelId, start, end); } else { rooms = roomRepository.findAllByHotelId(hotelId); } diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/BookingRepository.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/BookingRepository.java index f0a00276192f2ee34db29aa79b58936c1e6df0fa..a010a3a19ccfbb0fafd588c4fb7a52cb530897d9 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/BookingRepository.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/BookingRepository.java @@ -11,7 +11,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface BookingRepository extends JpaRepository<Booking, Integer> { - - @Query("SELECT b FROM Booking b WHERE b.room.id = :roomId AND b.startDate < :endDate AND b.endDate > :startDate") - List<Booking> findByRoomIdAndDateRange(@Param("roomId") int roomId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); + + @Query("SELECT b FROM Booking b WHERE b.roomId.id = ?1 AND b.startDate < ?2 AND b.endDate > ?3") + List<Booking> findByRoomIdAndDateRange(@Param("roomId") int roomId, @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); }