Skip to content
Snippets Groups Projects
Commit 290840e8 authored by migudel's avatar migudel :speech_balloon:
Browse files

Cambios para la búsqueda de habitaciones para fechas

parent 80f78a4e
No related branches found
No related tags found
2 merge requests!10Add ts types and json mocks, remove poblate scripts and fix the cascade...,!9Dev booking request
Showing
with 256 additions and 69 deletions
<!-- 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"> <div class="container">
<h3>Ejemplo angular. Gestión de reservas</h3> <h3>Ejemplo angular. Gestión de reservas</h3>
<router-outlet></router-outlet> <router-outlet></router-outlet>
... ...
......
...@@ -5,16 +5,16 @@ import { provideClientHydration } from '@angular/platform-browser'; ...@@ -5,16 +5,16 @@ import { provideClientHydration } from '@angular/platform-browser';
import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { ReactiveFormsModule } from '@angular/forms'; // Added import for ReactiveFormsModule import { ReactiveFormsModule } from '@angular/forms'; // Added import for ReactiveFormsModule
import { provideNativeDateAdapter } from '@angular/material/core';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideNativeDateAdapter(),
provideZoneChangeDetection({ eventCoalescing: true }), provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes), provideRouter(routes),
provideClientHydration(), provideClientHydration(),
provideHttpClient(withFetch()), provideHttpClient(withFetch()),
provideAnimationsAsync(), provideAnimationsAsync(),
ReactiveFormsModule ReactiveFormsModule,
], ],
}; };
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { HotelListComponent } from './hotel-list/hotel-list.component'; import { HotelListComponent } from './hotel-list/hotel-list.component';
import { BookingComponent } from './booking/booking.component'; // Asegúrate de ajustar la ruta import { BookingComponent } from './booking/booking.component'; // Asegúrate de ajustar la ruta
import { BookingListComponent } from './booking-list/booking-list.component';
export const routes: Routes = [ export const routes: Routes = [
{ {
...@@ -8,12 +9,16 @@ export const routes: Routes = [ ...@@ -8,12 +9,16 @@ export const routes: Routes = [
component: HotelListComponent, component: HotelListComponent,
}, },
{ {
path: 'booking', // Añade la ruta para el componente de reservas path: 'booking/search',
component: BookingComponent, component: BookingListComponent,
}, },
{ {
path: '**', // Mantiene la redirección para cualquier otra ruta no definida path: 'booking', // Añade la ruta para el componente de reservas
redirectTo: 'hotels', component: BookingComponent,
pathMatch: 'full',
}, },
// {
// path: '**', // Mantiene la redirección para cualquier otra ruta no definida
// redirectTo: 'hotels',
// pathMatch: 'full',
// },
]; ];
<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>
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();
});
});
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}`);
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { BookingComponent } from './booking.component'; import { BookingComponent } from './booking.component';
import { BookingService } from '../booking.service'; import { BookingService } from '../shared/booking.service';
import { of } from 'rxjs'; import { of } from 'rxjs';
class MockBookingService { class MockBookingService {
...@@ -19,7 +19,7 @@ describe('BookingComponent', () => { ...@@ -19,7 +19,7 @@ describe('BookingComponent', () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [BookingComponent], declarations: [BookingComponent],
imports: [ReactiveFormsModule], imports: [ReactiveFormsModule],
providers: [{ provide: BookingService, useClass: MockBookingService }] providers: [{ provide: BookingService, useClass: MockBookingService }],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(BookingComponent); fixture = TestBed.createComponent(BookingComponent);
... ...
......
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '@angular/forms'; import {
ReactiveFormsModule,
FormGroup,
FormBuilder,
Validators,
} from '@angular/forms';
interface BookingRequest { interface BookingRequest {
userId: number; // ID del usuario que realiza la reserva userId: number; // ID del usuario que realiza la reserva
hotelId: number; // ID del hotel en el que se realiza la reserva hotelId: number; // ID del hotel en el que se realiza la reserva
...@@ -7,16 +12,15 @@ interface BookingRequest { ...@@ -7,16 +12,15 @@ interface BookingRequest {
startDate: string; // Fecha de inicio de la reserva startDate: string; // Fecha de inicio de la reserva
endDate: string; // Fecha de fin de la reserva// Asegúrate de ajustar la ruta 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({ @Component({
standalone: true, standalone: true,
imports: [ReactiveFormsModule], imports: [ReactiveFormsModule],
selector: 'app-booking', selector: 'app-booking',
templateUrl: './booking.component.html', templateUrl: './booking.component.html',
styleUrls: ['./booking.component.css'] styleUrls: ['./booking.component.css'],
}) })
export class BookingComponent implements OnInit { export class BookingComponent implements OnInit {
bookingForm: FormGroup; bookingForm: FormGroup;
...@@ -27,7 +31,7 @@ export class BookingComponent implements OnInit { ...@@ -27,7 +31,7 @@ export class BookingComponent implements OnInit {
hotelId: ['', Validators.required], hotelId: ['', Validators.required],
roomType: ['', Validators.required], roomType: ['', Validators.required],
startDate: ['', Validators.required], startDate: ['', Validators.required],
endDate: ['', Validators.required] endDate: ['', Validators.required],
}); });
} }
...@@ -39,12 +43,12 @@ export class BookingComponent implements OnInit { ...@@ -39,12 +43,12 @@ export class BookingComponent implements OnInit {
// Llama al servicio para crear una nueva reserva // Llama al servicio para crear una nueva reserva
this.bookingService.createBooking(bookingRequest).subscribe( this.bookingService.createBooking(bookingRequest).subscribe(
response => { (response) => {
console.log('Reserva creada con éxito', response); console.log('Reserva creada con éxito', response);
// Aquí puedes redirigir al usuario o mostrar un mensaje de éxito // Aquí puedes redirigir al usuario o mostrar un mensaje de éxito
this.bookingForm.reset(); // Opcional: resetea el formulario después de una reserva exitosa this.bookingForm.reset(); // Opcional: resetea el formulario después de una reserva exitosa
}, },
error => { (error) => {
console.error('Error al crear la reserva', error); console.error('Error al crear la reserva', error);
// Manejo de errores // Manejo de errores
} }
... ...
......
...@@ -9,10 +9,10 @@ interface BookingRequest { ...@@ -9,10 +9,10 @@ interface BookingRequest {
startDate: string; // Fecha de inicio de la reserva startDate: string; // Fecha de inicio de la reserva
endDate: string; // Fecha de fin 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({ @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 { export class BookingService {
private apiUrl = 'http://localhost:8080/api/bookings'; private apiUrl = 'http://localhost:8080/api/bookings';
...@@ -23,8 +23,8 @@ export class BookingService { ...@@ -23,8 +23,8 @@ export class BookingService {
createBooking(bookingRequest: BookingRequest): Observable<Booking> { createBooking(bookingRequest: BookingRequest): Observable<Booking> {
return this.http.post<Booking>(this.apiUrl, bookingRequest, { return this.http.post<Booking>(this.apiUrl, bookingRequest, {
headers: new HttpHeaders({ headers: new HttpHeaders({
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}) }),
}); });
} }
... ...
......
...@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; ...@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import hotels from '../../mocks/hotels.json'; import hotels from '../../mocks/hotels.json';
import { Hotel, Booking } from '../../types'; import { Hotel, Booking, Room } from '../../types';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
...@@ -51,4 +51,11 @@ export class ClienteApiRestService { ...@@ -51,4 +51,11 @@ export class ClienteApiRestService {
return this.http.post('http://localhost:8080/bookings', bookingRequest); 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}`
);
}
} }
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<title>RestClient</title> <title>RestClient</title>
<base href="/"> <base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico"> <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
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
</head> 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> <body>
<app-root></app-root> <app-root></app-root>
</body> </body>
... ...
......
export type RoomType = 'SINGLE' | 'DOUBLE' | 'SUITE';
export interface Room { export interface Room {
id: number; id: number;
roomNumber: String; roomNumber: String;
// type: "single" | "double" | "suite"; type: RoomType;
type: 'SINGLE' | 'DOUBLE' | 'SUITE';
available: boolean; available: boolean;
} }
import { RoomType } from './Room';
export type * from './User'; export type * from './User';
export type * from './Address'; export type * from './Address';
export type * from './Hotel'; export type * from './Hotel';
export type * from './Room'; export type * from './Room';
export const roomTypeArray: RoomType[] = ['SINGLE', 'DOUBLE', 'SUITE'];
export type * from './Booking'; export type * from './Booking';
export type * from './User'; export type * from './User';
...@@ -61,15 +61,15 @@ public class HotelController { ...@@ -61,15 +61,15 @@ public class HotelController {
@GetMapping("/{hotelId}/rooms") @GetMapping("/{hotelId}/rooms")
public ResponseEntity<List<Room>> getRoomsFromHotel( public ResponseEntity<List<Room>> getRoomsFromHotel(
@PathVariable int hotelId, @PathVariable int hotelId,
@RequestParam(required = false) LocalDate date1, @RequestParam(required = false) LocalDate start,
@RequestParam(required = false) LocalDate date2) { @RequestParam(required = false) LocalDate end) {
List<Room> rooms; List<Room> rooms;
if (date1 != null && date2 != null) { if (start != null && end != null) {
if (!date1.isBefore(date2)) { if (!start.isBefore(end)) {
throw new InvalidDateRangeException("La fecha de inicio debe ser anterior a la fecha de fin"); 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 { } else {
rooms = roomRepository.findAllByHotelId(hotelId); rooms = roomRepository.findAllByHotelId(hotelId);
} }
... ...
......
...@@ -12,6 +12,7 @@ import org.springframework.data.repository.query.Param; ...@@ -12,6 +12,7 @@ import org.springframework.data.repository.query.Param;
public interface BookingRepository extends JpaRepository<Booking, Integer> { 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") @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); List<Booking> findByRoomIdAndDateRange(@Param("roomId") int roomId, @Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment