State Management - Improve state management for searchs.
CI / Docker Lint (push) Successful in 29s

This commit is contained in:
2025-09-08 14:36:50 +02:00
parent 4c21dc2764
commit 590d7f6fa1
15 changed files with 308 additions and 184 deletions
+32 -24
View File
@@ -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,9 +111,15 @@ 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.
let tableRows = null;
if (!isLoadingPrices) {
tableRows = roomPriceList?.map((el) => {
const selectedRoomAmount =
totalSelectAmount > 0 ? getSelectedRoomAmount(el.guestNumber) : 0;
return ( return (
<TableRow key={el.guestNumber}> <TableRow key={roomCategoryId + "-" + el.guestNumber}>
<TableCell> <TableCell>
<IconWithTooltip <IconWithTooltip
label={el.guestNumber?.toString()} label={el.guestNumber?.toString()}
@@ -130,13 +135,16 @@ function RoomBooking({
updateCount={(count) => updateCount={(count) =>
onSelectedAmountUpdate(el.guestNumber, count, el.price) onSelectedAmountUpdate(el.guestNumber, count, el.price)
} }
addEnabled={canAddMore} addEnabled={canAddMoreRooms}
initialValue={selectedRoomAmount}
></NumberSelector> ></NumberSelector>
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
}); });
}
//Element for
priceTable = ( priceTable = (
<Table className="overflow-hidden"> <Table className="overflow-hidden">
<TableHeader> <TableHeader>
-2
View File
@@ -35,7 +35,6 @@ 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}
@@ -43,7 +42,6 @@ export function RoomItemCard({
> >
<Icon icon={e.bedTypeIcon} height="auto" /> <Icon icon={e.bedTypeIcon} height="auto" />
</IconWithTooltip> </IconWithTooltip>
</div>
); );
}); });
+7 -18
View File
@@ -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);
} }
+1 -1
View File
@@ -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 = () => {
+3 -1
View File
@@ -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
View File
@@ -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";
+2 -2
View File
@@ -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>>({
+25 -2
View File
@@ -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 && (
+63 -62
View File
@@ -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));
} }
setAppSearchFilter(newFilter);
setAppBookingState(eBookingState.Searched);
initAppBooking(
makeDayUTCDate(newFilter.dateRange.from),
makeDayUTCDate(newFilter.dateRange.to),
);
}; };
// TODOME: Hay que dar una vuelta a esta solución
// - Mirar cómo trabajar con tiempos UTC desde el principio.
// - En cualquier caso, mover esta función a otro lugar.
const makeDayUTCDate = (date : Date) : Date => {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
}
const onBookingContinue = () => { const onBookingContinue = () => {
navigate("/book"); navigate("/book");
}; };
const roomList = roomAvailabilityList?.map((element) => { const roomList =
roomAvailabilityList?.map((element) => {
return ( return (
<RoomBooking <RoomBooking
key={element.roomCategory} key={element.roomCategory + "-" + appBookingState.lastSearchDate}
availableAmount={element.availability} availableAmount={element.availability}
roomCategoryId={element.roomCategory} 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>
+2 -3
View File
@@ -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
View File
@@ -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),
})), })),
})); }));
+27
View File
@@ -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;
}
+52
View File
@@ -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,
};
}