This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { useAPIRoomCategory } from "@/api/api-roomCategories";
|
import { useAPIRoomCategory } from "@/api/api-roomCategories";
|
||||||
import { Card, CardContent } from "../ui/card";
|
import { Card, CardContent } from "../ui/card";
|
||||||
import { useMemo } from "react";
|
|
||||||
import { Typography } from "../ui/typography";
|
import { Typography } from "../ui/typography";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -12,14 +11,14 @@ import {
|
|||||||
} from "../ui/table";
|
} from "../ui/table";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../ui/skeleton";
|
||||||
import { useAppStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
import { DateRange } from "react-day-picker";
|
|
||||||
import { FareFilter } from "@/model/RoomSearch";
|
|
||||||
import { useAPIRoomPrices } from "@/api/api-roomSearch";
|
import { useAPIRoomPrices } from "@/api/api-roomSearch";
|
||||||
import { NumberSelector } from "../ui/number-selector";
|
import { NumberSelector } from "../ui/number-selector";
|
||||||
import { NewBooking, NewBookingRoom } from "@/model/Bookings";
|
import { NewBooking, NewBookingRoom } from "@/model/Bookings";
|
||||||
import { RoomItemCard } from "./room-item-card";
|
import { RoomItemCard } from "./room-item-card";
|
||||||
import { IconWithTooltip } from "../ui/icon-number-tooltip";
|
import { IconWithTooltip } from "../ui/icon-number-tooltip";
|
||||||
import { UserIcon } from "lucide-react";
|
import { UserIcon } from "lucide-react";
|
||||||
|
import { makeFareFilterFromSearchFilter } from "@/utils/utils";
|
||||||
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
|
|
||||||
function RoomBooking({
|
function RoomBooking({
|
||||||
roomCategoryId,
|
roomCategoryId,
|
||||||
@@ -28,12 +27,12 @@ function RoomBooking({
|
|||||||
roomCategoryId: number;
|
roomCategoryId: number;
|
||||||
availableAmount: number;
|
availableAmount: number;
|
||||||
}) {
|
}) {
|
||||||
const currentDates: DateRange = useAppStore(
|
const appSearchFilter: SearchFilter = useAppStore(
|
||||||
(state) => state.searchFilter.dateRange,
|
(state) => state.searchFilter,
|
||||||
);
|
);
|
||||||
const addNewRoom = useAppStore((state) => state.addNewBookingRoom);
|
const addNewRoom = useAppStore((state) => state.currentBookingAddRoom);
|
||||||
const bookingRooms: NewBooking | null = useAppStore(
|
const bookingRooms: NewBooking | null = useAppStore(
|
||||||
(state) => state.newBookingData,
|
(state) => state.currentBooking,
|
||||||
);
|
);
|
||||||
|
|
||||||
function onSelectedAmountUpdate(
|
function onSelectedAmountUpdate(
|
||||||
@@ -59,29 +58,29 @@ function RoomBooking({
|
|||||||
addNewRoom(newRoom);
|
addNewRoom(newRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalSelected = () => {
|
const getSelectedRoomAmount = (guestNumber: number | null) => {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
if (bookingRooms) {
|
if (bookingRooms) {
|
||||||
|
//Rooms with same category and, if guestNumber is set, rooms with same guestNumber.
|
||||||
bookingRooms.rooms
|
bookingRooms.rooms
|
||||||
.filter((v) => v.roomCategory == roomCategoryId)
|
.filter((v) => {
|
||||||
|
const isSameCat = v.roomCategory == roomCategoryId;
|
||||||
|
const isSameGuestNum =
|
||||||
|
guestNumber != null ? v.guestNumber == guestNumber : true;
|
||||||
|
return isSameCat && isSameGuestNum;
|
||||||
|
})
|
||||||
.forEach((v) => (sum += v.amount));
|
.forEach((v) => (sum += v.amount));
|
||||||
}
|
}
|
||||||
return sum;
|
return sum;
|
||||||
};
|
};
|
||||||
const canAddMore: boolean = availableAmount > totalSelected();
|
const totalSelectAmount = getSelectedRoomAmount(null);
|
||||||
|
const canAddMoreRooms: boolean = availableAmount > totalSelectAmount;
|
||||||
|
|
||||||
//This seems a little bit overkill, but IDK.
|
//This seems a little bit overkill, but IDK.
|
||||||
const fareFilter: FareFilter | null = useMemo(() => {
|
const fareFilter = makeFareFilterFromSearchFilter(
|
||||||
if (currentDates && currentDates.from && currentDates.to) {
|
appSearchFilter,
|
||||||
const filter: FareFilter = {
|
roomCategoryId,
|
||||||
startDate: currentDates.from?.toDateString(),
|
);
|
||||||
endDate: currentDates.to?.toDateString(),
|
|
||||||
roomCategory: roomCategoryId,
|
|
||||||
};
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [currentDates, roomCategoryId]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomCategory,
|
roomCategory,
|
||||||
@@ -112,31 +111,40 @@ function RoomBooking({
|
|||||||
const hasPrices = roomPriceList != null && roomPriceList?.length > 0;
|
const hasPrices = roomPriceList != null && roomPriceList?.length > 0;
|
||||||
let priceTable = null;
|
let priceTable = null;
|
||||||
if (hasPrices) {
|
if (hasPrices) {
|
||||||
const tableRows = roomPriceList?.map((el) => {
|
//A row for each room guestNumber.
|
||||||
return (
|
let tableRows = null;
|
||||||
<TableRow key={el.guestNumber}>
|
if (!isLoadingPrices) {
|
||||||
<TableCell>
|
tableRows = roomPriceList?.map((el) => {
|
||||||
<IconWithTooltip
|
const selectedRoomAmount =
|
||||||
label={el.guestNumber?.toString()}
|
totalSelectAmount > 0 ? getSelectedRoomAmount(el.guestNumber) : 0;
|
||||||
tooltipText={"Guest Number"}
|
|
||||||
labelPosition={"right"}
|
|
||||||
>
|
|
||||||
<UserIcon></UserIcon>
|
|
||||||
</IconWithTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{el.price / 100} €</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<NumberSelector
|
|
||||||
updateCount={(count) =>
|
|
||||||
onSelectedAmountUpdate(el.guestNumber, count, el.price)
|
|
||||||
}
|
|
||||||
addEnabled={canAddMore}
|
|
||||||
></NumberSelector>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={roomCategoryId + "-" + el.guestNumber}>
|
||||||
|
<TableCell>
|
||||||
|
<IconWithTooltip
|
||||||
|
label={el.guestNumber?.toString()}
|
||||||
|
tooltipText={"Guest Number"}
|
||||||
|
labelPosition={"right"}
|
||||||
|
>
|
||||||
|
<UserIcon></UserIcon>
|
||||||
|
</IconWithTooltip>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{el.price / 100} €</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<NumberSelector
|
||||||
|
updateCount={(count) =>
|
||||||
|
onSelectedAmountUpdate(el.guestNumber, count, el.price)
|
||||||
|
}
|
||||||
|
addEnabled={canAddMoreRooms}
|
||||||
|
initialValue={selectedRoomAmount}
|
||||||
|
></NumberSelector>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Element for
|
||||||
priceTable = (
|
priceTable = (
|
||||||
<Table className="overflow-hidden">
|
<Table className="overflow-hidden">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
|||||||
@@ -35,15 +35,13 @@ export function RoomItemCard({
|
|||||||
|
|
||||||
const bedTypeIconList = room.roomCategoryBedTypes.map((e) => {
|
const bedTypeIconList = room.roomCategoryBedTypes.map((e) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<IconWithTooltip
|
||||||
<IconWithTooltip
|
key={room.id.toString() + e.bedTypeIcon}
|
||||||
key={room.id.toString() + e.bedTypeIcon}
|
tooltipText={e.bedTypeName}
|
||||||
tooltipText={e.bedTypeName}
|
label={e.amount.toString()}
|
||||||
label={e.amount.toString()}
|
>
|
||||||
>
|
<Icon icon={e.bedTypeIcon} height="auto" />
|
||||||
<Icon icon={e.bedTypeIcon} height="auto" />
|
</IconWithTooltip>
|
||||||
</IconWithTooltip>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,37 +6,28 @@ import { DatePickerWithRange } from "../ui/dateRange-picker";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { SearchIcon } from "lucide-react";
|
import { SearchIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SearchFilter } from "@/model/SearchFilter";
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
import { useAppStore } from "@/store";
|
|
||||||
|
|
||||||
type funOnSearchClick = (newFilter: SearchFilter) => void;
|
type funOnSearchClick = (newFilter: SearchFilter) => void;
|
||||||
|
|
||||||
export function SearchFilterCard({
|
export function SearchFilterCard({
|
||||||
onSearchClick,
|
onSearchClick,
|
||||||
|
initialsearchFilter,
|
||||||
}: {
|
}: {
|
||||||
onSearchClick?: funOnSearchClick;
|
onSearchClick?: funOnSearchClick;
|
||||||
|
initialsearchFilter?: SearchFilter;
|
||||||
}) {
|
}) {
|
||||||
//App filter. Updated on btSearchClick.
|
|
||||||
const appSearchFilter = useAppStore((state) => state.searchFilter);
|
|
||||||
const setAppSearchFilter = useAppStore((state) => state.setSearchFilter);
|
|
||||||
|
|
||||||
//Local Filter state.
|
//Local Filter state.
|
||||||
const [searchFilter, setSearchFilter] =
|
const [searchFilter, setSearchFilter] = useState<SearchFilter | null>(
|
||||||
useState<SearchFilter>(appSearchFilter);
|
initialsearchFilter ?? null,
|
||||||
|
);
|
||||||
//On location change, try to load searchFilter from state.
|
|
||||||
//FIXME: Si llegamos sin state, hay que borrar el state global...
|
|
||||||
// const location = useLocation();
|
|
||||||
// useEffect(() => {
|
|
||||||
// setSearchFilter(location.state);
|
|
||||||
// }, [location]);
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// Validate and call callback on btSearch click.
|
// Validate and call callback on btSearch click.
|
||||||
//TODOME: Add data validations and show messages.
|
//TODOME: Add data validations and show messages as form instead of toasts.
|
||||||
const btSearchClick = () => {
|
const btSearchClick = () => {
|
||||||
if (!searchFilter || !searchFilter.dateRange) {
|
if (!searchFilter || !searchFilter.dateRange) {
|
||||||
toast({
|
toast({
|
||||||
@@ -55,8 +46,6 @@ export function SearchFilterCard({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAppSearchFilter(searchFilter);
|
|
||||||
|
|
||||||
if (onSearchClick) {
|
if (onSearchClick) {
|
||||||
onSearchClick(searchFilter);
|
onSearchClick(searchFilter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function BookingDetails({
|
|||||||
onBookingConfirm?: OnBookingClickFunction;
|
onBookingConfirm?: OnBookingClickFunction;
|
||||||
mode: BookingDetailsModes;
|
mode: BookingDetailsModes;
|
||||||
}) {
|
}) {
|
||||||
const bookingData = useAppStore((state) => state.newBookingData);
|
const bookingData = useAppStore((state) => state.currentBooking);
|
||||||
const roomCategoryList = useAPIRoomCategoryList();
|
const roomCategoryList = useAPIRoomCategoryList();
|
||||||
|
|
||||||
const onBtBookingClick = () => {
|
const onBtBookingClick = () => {
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ export function NumberSelector({
|
|||||||
updateCount,
|
updateCount,
|
||||||
addEnabled = true,
|
addEnabled = true,
|
||||||
allowNegative = false,
|
allowNegative = false,
|
||||||
|
initialValue = 0,
|
||||||
}: {
|
}: {
|
||||||
updateCount: UpdateSelectedNumberFunction;
|
updateCount: UpdateSelectedNumberFunction;
|
||||||
addEnabled?: boolean;
|
addEnabled?: boolean;
|
||||||
allowNegative?: boolean;
|
allowNegative?: boolean;
|
||||||
|
initialValue?: number;
|
||||||
}) {
|
}) {
|
||||||
const [count, setCount] = useState<number>(0);
|
const [count, setCount] = useState<number>(initialValue);
|
||||||
|
|
||||||
const counterUpdated = (newAmount: number) => {
|
const counterUpdated = (newAmount: number) => {
|
||||||
setCount(newAmount);
|
setCount(newAmount);
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||||
import Root from "./routes/Root";
|
import Root from "./routes/Root";
|
||||||
import ErrorPage from "./error-page";
|
import ErrorPage from "./routes/error-page";
|
||||||
import Homepage from "./routes/home-page";
|
import Homepage from "./routes/home-page";
|
||||||
import SearchPage from "./routes/search-page";
|
import SearchPage from "./routes/search-page";
|
||||||
import BookPage from "./routes/book-page";
|
import BookPage from "./routes/book-page";
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ enum PageState {
|
|||||||
|
|
||||||
function BookPage() {
|
function BookPage() {
|
||||||
const [pageState, setPageState] = useState<PageState>(PageState.showForm);
|
const [pageState, setPageState] = useState<PageState>(PageState.showForm);
|
||||||
const bookingData = useAppStore((state) => state.newBookingData);
|
const bookingData = useAppStore((state) => state.currentBooking);
|
||||||
const setCustomerData = useAppStore((state) => state.setBookingCustomer);
|
const setCustomerData = useAppStore((state) => state.currentBookingSetCustomer);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const bookingForm = useForm<z.infer<typeof BookingFormSchema>>({
|
const bookingForm = useForm<z.infer<typeof BookingFormSchema>>({
|
||||||
|
|||||||
@@ -6,16 +6,36 @@ import { Separator } from "@/components/ui/separator";
|
|||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Typography } from "@/components/ui/typography";
|
import { Typography } from "@/components/ui/typography";
|
||||||
import { RoomCategory } from "@/model/RoomCategory";
|
import { RoomCategory } from "@/model/RoomCategory";
|
||||||
import { SearchFilter } from "@/model/SearchFilter";
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
|
import { eBookingState } from "@/utils/AppStateManager";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { LoaderIcon } from "lucide-react";
|
import { LoaderIcon } from "lucide-react";
|
||||||
import "./home-page.css";
|
import "./home-page.css";
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
import { isValidSearchFilter, makeDayUTCDate } from "@/utils/utils";
|
||||||
|
|
||||||
function Homepage() {
|
function Homepage() {
|
||||||
|
const appSearchFilter = useAppStore((state) => state.searchFilter);
|
||||||
|
const setAppSearchFilter = useAppStore((state) => state.setSearchFilter);
|
||||||
|
const setAppBookingState = useAppStore((state) => state.setBookingState);
|
||||||
|
const initAppBooking = useAppStore((state) => state.initCurrentBooking);
|
||||||
|
|
||||||
const { roomCategoryList, isLoading, isError } = useAPIRoomCategoryList();
|
const { roomCategoryList, isLoading, isError } = useAPIRoomCategoryList();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onBtSearchClick = (filter: SearchFilter) => {
|
const onBtSearchClick = (filter: SearchFilter) => {
|
||||||
|
if (!isValidSearchFilter(filter)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppSearchFilter(filter);
|
||||||
|
setAppBookingState(eBookingState.Searched);
|
||||||
|
|
||||||
|
initAppBooking(
|
||||||
|
makeDayUTCDate(filter.dateRange.from),
|
||||||
|
makeDayUTCDate(filter.dateRange.to),
|
||||||
|
);
|
||||||
|
|
||||||
navigate("/search", { state: filter });
|
navigate("/search", { state: filter });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,7 +46,10 @@ function Homepage() {
|
|||||||
<>
|
<>
|
||||||
<Typography variant={"h1"}>Somnia Rooms</Typography>
|
<Typography variant={"h1"}>Somnia Rooms</Typography>
|
||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
<SearchFilterCard onSearchClick={onBtSearchClick} />
|
<SearchFilterCard
|
||||||
|
onSearchClick={onBtSearchClick}
|
||||||
|
initialsearchFilter={appSearchFilter}
|
||||||
|
/>
|
||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
<div className="homePageRoomGrid grid gap-4">{roomList}</div>
|
<div className="homePageRoomGrid grid gap-4">{roomList}</div>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
|
|||||||
+69
-68
@@ -6,79 +6,68 @@ import { BookingDetails } from "@/components/booking/booking-details";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Typography } from "@/components/ui/typography";
|
import { Typography } from "@/components/ui/typography";
|
||||||
import { AvailabilityFilter } from "@/model/RoomSearch";
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
import { SearchFilter } from "@/model/SearchFilter";
|
import { eBookingState } from "@/utils/AppStateManager";
|
||||||
import { useAppStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
import { useMemo, useState } from "react";
|
import {
|
||||||
|
isValidSearchFilter,
|
||||||
|
makeAvailabilityFilterFromSearchFilter,
|
||||||
|
makeDayUTCDate,
|
||||||
|
} from "@/utils/utils";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
//TODOME: Igual podemos crear un StateMachine conjunta para los siguiente estados de la página: Sin Buscar, Sin disponibilidad, error, cargando.
|
//TODOME: Al pulsar el botón "buscar", si ya existen datos en la reserva (se han añadido habitaciones) mostrar un cuadro de aviso de que si se busca se borrará la selección actual.
|
||||||
|
//TODOME: Mediante los estados globales de la reserva, si ya está en estado "booking" añadiendo los datos, mostrar un mensaje y permitir redirigir directamente para que continue donde lo dejó.
|
||||||
function SearchPage() {
|
function SearchPage() {
|
||||||
//const [availabilityFilter, setAvailabilityFilter] = useState<AvailabilityFilter>();
|
const appBookingState = useAppStore((state) => state.bookingState);
|
||||||
const searchFilter = useAppStore((state) => state.searchFilter);
|
const setAppBookingState = useAppStore((state) => state.setBookingState);
|
||||||
const resetBooking = useAppStore((state) => state.resetBookingState);
|
const appSearchFilter = useAppStore((state) => state.searchFilter);
|
||||||
|
const setAppSearchFilter = useAppStore((state) => state.setSearchFilter);
|
||||||
|
const initAppBooking = useAppStore((state) => state.initCurrentBooking);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [isSearched, setIsSearched] = useState<boolean>(false);
|
|
||||||
|
|
||||||
//This seems a little bit overkill, but IDK.
|
|
||||||
const availabilityFilter: AvailabilityFilter | null = useMemo(() => {
|
|
||||||
if (
|
|
||||||
searchFilter &&
|
|
||||||
searchFilter.dateRange &&
|
|
||||||
searchFilter.dateRange.from &&
|
|
||||||
searchFilter.dateRange.to
|
|
||||||
) {
|
|
||||||
const newAvailabilityFilter: AvailabilityFilter = {
|
|
||||||
startDate: searchFilter.dateRange.from.toDateString(),
|
|
||||||
endDate: searchFilter.dateRange.to.toDateString(),
|
|
||||||
};
|
|
||||||
return newAvailabilityFilter;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [searchFilter]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomAvailabilityList,
|
roomAvailabilityList,
|
||||||
isLoading: isLoadingAvailability,
|
isLoading: isLoadingAvailability,
|
||||||
isError: isErrorAvailability,
|
isError: isErrorAvailability,
|
||||||
} = useAPIRoomAvailability(availabilityFilter ?? null);
|
} = useAPIRoomAvailability(
|
||||||
|
makeAvailabilityFilterFromSearchFilter(appSearchFilter) ?? null,
|
||||||
|
);
|
||||||
const isLoading = isLoadingAvailability;
|
const isLoading = isLoadingAvailability;
|
||||||
const isError = isErrorAvailability;
|
const isError = isErrorAvailability;
|
||||||
|
|
||||||
|
const hasRooms =
|
||||||
|
roomAvailabilityList?.length && roomAvailabilityList?.length > 0;
|
||||||
|
|
||||||
|
//TODOME: Hay que ver para mover esto a un método común.
|
||||||
const onBtSearchClick = (newFilter: SearchFilter) => {
|
const onBtSearchClick = (newFilter: SearchFilter) => {
|
||||||
if (
|
if (!isValidSearchFilter(newFilter)) {
|
||||||
newFilter.dateRange &&
|
return;
|
||||||
newFilter.dateRange.from &&
|
|
||||||
newFilter.dateRange.to
|
|
||||||
) {
|
|
||||||
setIsSearched(true);
|
|
||||||
|
|
||||||
resetBooking(makeDayUTCDate(newFilter.dateRange.from), makeDayUTCDate(newFilter.dateRange.to));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// TODOME: Hay que dar una vuelta a esta solución
|
setAppSearchFilter(newFilter);
|
||||||
// - Mirar cómo trabajar con tiempos UTC desde el principio.
|
setAppBookingState(eBookingState.Searched);
|
||||||
// - En cualquier caso, mover esta función a otro lugar.
|
|
||||||
const makeDayUTCDate = (date : Date) : Date => {
|
initAppBooking(
|
||||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
|
makeDayUTCDate(newFilter.dateRange.from),
|
||||||
}
|
makeDayUTCDate(newFilter.dateRange.to),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onBookingContinue = () => {
|
const onBookingContinue = () => {
|
||||||
navigate("/book");
|
navigate("/book");
|
||||||
};
|
};
|
||||||
|
|
||||||
const roomList = roomAvailabilityList?.map((element) => {
|
const roomList =
|
||||||
return (
|
roomAvailabilityList?.map((element) => {
|
||||||
<RoomBooking
|
return (
|
||||||
key={element.roomCategory}
|
<RoomBooking
|
||||||
availableAmount={element.availability}
|
key={element.roomCategory + "-" + appBookingState.lastSearchDate}
|
||||||
roomCategoryId={element.roomCategory}
|
availableAmount={element.availability}
|
||||||
/>
|
roomCategoryId={element.roomCategory}
|
||||||
);
|
/>
|
||||||
});
|
);
|
||||||
|
}) ?? null;
|
||||||
const noRoomsAvailable = (
|
const noRoomsAvailable = (
|
||||||
<>
|
<>
|
||||||
<Typography variant={"h3"} as="h3">
|
<Typography variant={"h3"} as="h3">
|
||||||
@@ -87,22 +76,23 @@ function SearchPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
const hasRooms =
|
|
||||||
roomAvailabilityList?.length && roomAvailabilityList?.length > 0;
|
|
||||||
console.log(hasRooms);
|
|
||||||
|
|
||||||
const pageContent = isLoading ? (
|
const pageContentSearch = (
|
||||||
|
<Typography>
|
||||||
|
Realiza una búsqueda para ver la disponibilidad y los precios.
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
const pageContentLoading = (
|
||||||
<>
|
<>
|
||||||
<Typography variant={"h3"} as="h2">
|
<Typography variant={"h3"} as="h2">
|
||||||
Loading...
|
Loading...
|
||||||
</Typography>
|
</Typography>
|
||||||
<Skeleton className="mx-auto h-[200px] w-full" />
|
<Skeleton className="mx-auto h-[200px] w-full" />
|
||||||
</>
|
</>
|
||||||
) : (
|
);
|
||||||
|
const pageContentLoaded = (
|
||||||
<div className="2xl:grid 2xl:grid-cols-60/40 2xl:gap-6">
|
<div className="2xl:grid 2xl:grid-cols-60/40 2xl:gap-6">
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">{roomList}</div>
|
||||||
{hasRooms ? roomList : isSearched && noRoomsAvailable}
|
|
||||||
</div>
|
|
||||||
<Separator className="my-10 2xl:hidden" />
|
<Separator className="my-10 2xl:hidden" />
|
||||||
<BookingDetails
|
<BookingDetails
|
||||||
onBookingConfirm={onBookingContinue}
|
onBookingConfirm={onBookingContinue}
|
||||||
@@ -111,17 +101,28 @@ function SearchPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getPageContent = () => {
|
||||||
|
const isSearched = appBookingState.state == eBookingState.Searched;
|
||||||
|
if (!isSearched) {
|
||||||
|
return pageContentSearch;
|
||||||
|
}
|
||||||
|
if (isLoading) {
|
||||||
|
return pageContentLoading;
|
||||||
|
}
|
||||||
|
if (!hasRooms) {
|
||||||
|
return noRoomsAvailable;
|
||||||
|
}
|
||||||
|
return pageContentLoaded;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-background text-foreground">
|
<div className="bg-background text-foreground">
|
||||||
<SearchFilterCard onSearchClick={onBtSearchClick} />
|
<SearchFilterCard
|
||||||
|
onSearchClick={onBtSearchClick}
|
||||||
|
initialsearchFilter={appSearchFilter}
|
||||||
|
/>
|
||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
{hasRooms ? (
|
{getPageContent()}
|
||||||
pageContent
|
|
||||||
) : (
|
|
||||||
<Typography>
|
|
||||||
Realiza una búsqueda para ver la disponibilidad y los precios.
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|||||||
import { Typography } from "@/components/ui/typography";
|
import { Typography } from "@/components/ui/typography";
|
||||||
import { NewBookingResponse } from "@/model/Bookings";
|
import { NewBookingResponse } from "@/model/Bookings";
|
||||||
import { useAppStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
import { addDays } from "date-fns";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
function SuccessPage() {
|
function SuccessPage() {
|
||||||
const [pageState, setPageState] = useState<NewBookingResponse | null>(null);
|
const [pageState, setPageState] = useState<NewBookingResponse | null>(null);
|
||||||
const resetBookingState = useAppStore((state) => state.resetBookingState);
|
const resetBookingState = useAppStore((state) => state.resetCurrentBooking);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -22,7 +21,7 @@ function SuccessPage() {
|
|||||||
}, [location, navigate]);
|
}, [location, navigate]);
|
||||||
|
|
||||||
const handleBtBackClick = () => {
|
const handleBtBackClick = () => {
|
||||||
resetBookingState(new Date(), addDays(new Date(), 2));
|
resetBookingState();
|
||||||
navigate("/home");
|
navigate("/home");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+59
-34
@@ -1,19 +1,31 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { SearchFilter } from "./model/SearchFilter";
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
|
import {
|
||||||
|
BookingState,
|
||||||
|
eBookingState,
|
||||||
|
SetBookingState,
|
||||||
|
} from "./utils/AppStateManager";
|
||||||
import {
|
import {
|
||||||
NewBooking,
|
NewBooking,
|
||||||
NewBookingCustomerDetails,
|
NewBookingCustomerDetails,
|
||||||
NewBookingRoom,
|
NewBookingRoom,
|
||||||
} from "./model/Bookings";
|
} from "./model/Bookings";
|
||||||
|
|
||||||
interface AppState {
|
interface State {
|
||||||
searchFilter: SearchFilter;
|
searchFilter: SearchFilter;
|
||||||
|
currentBooking: NewBooking | null;
|
||||||
|
bookingState: BookingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Actions {
|
||||||
setSearchFilter: (newFilter: SearchFilter) => void;
|
setSearchFilter: (newFilter: SearchFilter) => void;
|
||||||
newBookingData: NewBooking | null;
|
resetSearchFilter: () => void;
|
||||||
setNewBooking: (newBooking: NewBooking) => void;
|
setCurrentBooking: (newBooking: NewBooking) => void;
|
||||||
resetBookingState: (arrival: Date, departure: Date) => void;
|
resetCurrentBooking: () => void;
|
||||||
addNewBookingRoom: (newRoom: NewBookingRoom) => void;
|
initCurrentBooking: (arrival: Date, departure: Date) => void;
|
||||||
setBookingCustomer: (newCustomer: NewBookingCustomerDetails) => void;
|
currentBookingAddRoom: (newRoom: NewBookingRoom) => void;
|
||||||
|
currentBookingSetCustomer: (newCustomer: NewBookingCustomerDetails) => void;
|
||||||
|
setBookingState: (newState: eBookingState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSearchFilter: SearchFilter = {
|
const defaultSearchFilter: SearchFilter = {
|
||||||
@@ -32,7 +44,12 @@ const defaultNewBooking: NewBooking = {
|
|||||||
rooms: [],
|
rooms: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDefaultBookingData(arrivalDate: Date, departureDate: Date) {
|
const defaultBookingState: BookingState = {
|
||||||
|
state: eBookingState.None,
|
||||||
|
lastSearchDate: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDefaultBooking(arrivalDate: Date, departureDate: Date) {
|
||||||
return {
|
return {
|
||||||
...defaultNewBooking,
|
...defaultNewBooking,
|
||||||
arrivalDate: arrivalDate,
|
arrivalDate: arrivalDate,
|
||||||
@@ -40,19 +57,19 @@ function getDefaultBookingData(arrivalDate: Date, departureDate: Date) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewBookingRoomToCurrentState(
|
function addNewBookingRoom(
|
||||||
newRoom: NewBookingRoom,
|
newRoom: NewBookingRoom,
|
||||||
currentState: NewBooking | null,
|
currentBookingState: NewBooking | null,
|
||||||
): NewBooking | null {
|
): NewBooking | null {
|
||||||
if (currentState == null) {
|
if (currentBookingState == null) {
|
||||||
return currentState;
|
return currentBookingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Remove rooms with "same key".
|
//Remove rooms with "same key".
|
||||||
const filteredRooms = currentState.rooms.filter((room) => {
|
const filteredRooms = currentBookingState.rooms.filter((room) => {
|
||||||
const diffRoom = room.roomCategory != newRoom.roomCategory;
|
const isDiffRoom = room.roomCategory != newRoom.roomCategory;
|
||||||
const diffAmount = room.guestNumber != newRoom.guestNumber;
|
const isDiffAmount = room.guestNumber != newRoom.guestNumber;
|
||||||
if (diffRoom || (!diffRoom && diffAmount)) {
|
if (isDiffRoom || (!isDiffRoom && isDiffAmount)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -64,11 +81,11 @@ function addNewBookingRoomToCurrentState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newState: NewBooking = {
|
const newState: NewBooking = {
|
||||||
...currentState,
|
...currentBookingState,
|
||||||
rooms: filteredRooms ?? [],
|
rooms: filteredRooms ?? [],
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("AddRoomNewState:");
|
console.log("Updated 'currentBooking' state:");
|
||||||
console.log(newState);
|
console.log(newState);
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
@@ -76,35 +93,43 @@ function addNewBookingRoomToCurrentState(
|
|||||||
|
|
||||||
function setBookingCustomer(
|
function setBookingCustomer(
|
||||||
newCustomer: NewBookingCustomerDetails,
|
newCustomer: NewBookingCustomerDetails,
|
||||||
currentState: NewBooking | null,
|
currentBookingState: NewBooking | null,
|
||||||
) {
|
) {
|
||||||
if (currentState == null) {
|
if (currentBookingState == null) {
|
||||||
return currentState;
|
return currentBookingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newState: NewBooking = {
|
const newState: NewBooking = {
|
||||||
...currentState,
|
...currentBookingState,
|
||||||
customerDetails: newCustomer,
|
customerDetails: newCustomer,
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAppStore = create<AppState>()((set) => ({
|
export const useAppStore = create<State & Actions>()((set) => ({
|
||||||
searchFilter: defaultSearchFilter,
|
searchFilter: defaultSearchFilter,
|
||||||
|
currentBooking: null,
|
||||||
|
bookingState: defaultBookingState,
|
||||||
|
|
||||||
setSearchFilter: (newFilter) => set(() => ({ searchFilter: newFilter })),
|
setSearchFilter: (newFilter) => set(() => ({ searchFilter: newFilter })),
|
||||||
newBookingData: null,
|
resetSearchFilter: () => set(() => ({ searchFilter: defaultSearchFilter })),
|
||||||
setNewBooking: (newBooking) => set(() => ({ newBookingData: newBooking })),
|
|
||||||
resetBookingState: (arrival, departure) =>
|
setCurrentBooking: (newBooking) =>
|
||||||
set(() => ({ newBookingData: getDefaultBookingData(arrival, departure) })),
|
set(() => ({ currentBooking: newBooking })),
|
||||||
addNewBookingRoom: (newRoom: NewBookingRoom) =>
|
resetCurrentBooking: () => set(() => ({ currentBooking: null })),
|
||||||
|
initCurrentBooking: (arrival, departure) =>
|
||||||
|
set(() => ({ currentBooking: getDefaultBooking(arrival, departure) })),
|
||||||
|
currentBookingAddRoom: (newRoom) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
newBookingData: addNewBookingRoomToCurrentState(
|
currentBooking: addNewBookingRoom(newRoom, state.currentBooking),
|
||||||
newRoom,
|
|
||||||
state.newBookingData,
|
|
||||||
),
|
|
||||||
})),
|
})),
|
||||||
setBookingCustomer: (newCustomer: NewBookingCustomerDetails) =>
|
currentBookingSetCustomer: (newCustomer) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
newBookingData: setBookingCustomer(newCustomer, state.newBookingData),
|
currentBooking: setBookingCustomer(newCustomer, state.currentBooking),
|
||||||
|
})),
|
||||||
|
|
||||||
|
setBookingState: (newState) =>
|
||||||
|
set((state) => ({
|
||||||
|
bookingState: SetBookingState(newState, state.bookingState),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export type BookingState = {
|
||||||
|
state: eBookingState;
|
||||||
|
lastSearchDate: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum eBookingState {
|
||||||
|
None = "NONE",
|
||||||
|
Searched = "SEARCHED",
|
||||||
|
Booking = "BOOKING",
|
||||||
|
Booked = "BOOKED",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SetBookingState(
|
||||||
|
newState: eBookingState,
|
||||||
|
currentAppBookingState: BookingState,
|
||||||
|
) {
|
||||||
|
const newAppState: BookingState = {
|
||||||
|
...currentAppBookingState,
|
||||||
|
state: newState,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (newState == eBookingState.Searched) {
|
||||||
|
newAppState.lastSearchDate = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAppState;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { AvailabilityFilter, FareFilter } from "@/model/RoomSearch";
|
||||||
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
|
|
||||||
|
export function makeDayUTCDate(date: Date | undefined): Date {
|
||||||
|
if (!date) {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(
|
||||||
|
date.getFullYear(),
|
||||||
|
date.getMonth(),
|
||||||
|
date.getDate(),
|
||||||
|
23,
|
||||||
|
59,
|
||||||
|
59,
|
||||||
|
999,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidSearchFilter(sf: SearchFilter): boolean {
|
||||||
|
if (sf?.dateRange?.from && sf?.dateRange?.to) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeAvailabilityFilterFromSearchFilter(
|
||||||
|
sf: SearchFilter,
|
||||||
|
): AvailabilityFilter | null {
|
||||||
|
if (!isValidSearchFilter(sf)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
startDate: sf.dateRange.from!.toISOString(),
|
||||||
|
endDate: sf.dateRange.to!.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeFareFilterFromSearchFilter(
|
||||||
|
sf: SearchFilter,
|
||||||
|
roomCategoryId: number,
|
||||||
|
): FareFilter | null {
|
||||||
|
if (!isValidSearchFilter(sf) || roomCategoryId < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate: sf.dateRange.from!.toISOString(),
|
||||||
|
endDate: sf.dateRange.to!.toISOString(),
|
||||||
|
roomCategory: roomCategoryId,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user