Refactor HomePage and more!
This commit is contained in:
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 && npx prettier . --check",
|
||||||
|
"pretty": "npx prettier . --write",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
import { Card, CardContent, CardHeader } from "../ui/card";
|
import { Card, CardContent } from "../ui/card";
|
||||||
import { Typography } from "../ui/typography";
|
import { Typography } from "../ui/typography";
|
||||||
import { BedDoubleIcon, BedSingleIcon, UserIcon } from "lucide-react";
|
import { BedDoubleIcon, BedSingleIcon, UserIcon } from "lucide-react";
|
||||||
import { RoomCategory, RoomCategoryPhoto } from "@/model/RoomCategory";
|
import { RoomCategory, RoomCategoryPhoto } from "@/model/RoomCategory";
|
||||||
import { IconWithTooltipNumber } from "../ui/icon-number-tooltip";
|
import { IconWithTooltip } from "../ui/icon-number-tooltip";
|
||||||
|
import roomImg from "@/res/SomniaRoomImage.svg";
|
||||||
|
|
||||||
interface RoomItemCardProps {
|
interface RoomItemCardProps {
|
||||||
room: RoomCategory;
|
room: RoomCategory;
|
||||||
@@ -34,50 +35,59 @@ export function RoomItemCard({ room = defaultRoom }: RoomItemCardProps) {
|
|||||||
bedTypeList.at(1) !== undefined && bedTypeList[1] > 0;
|
bedTypeList.at(1) !== undefined && bedTypeList[1] > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card id={room.id.toString()}>
|
<Card id={room.id.toString()} className="relative">
|
||||||
<CardHeader className="p-0 m-0 mb-5">
|
{/* <CardHeader className="p-0 m-0 mb-5">
|
||||||
<img
|
</CardHeader> */}
|
||||||
src={
|
<CardContent className="flex flex-col h-full w-full p-0">
|
||||||
mainPhoto?.path ??
|
<div className="">
|
||||||
"https://images.unsplash.com/photo-1618773928121-c32242e63f39"
|
<img
|
||||||
}
|
src={mainPhoto?.path ?? roomImg}
|
||||||
className="rounded-md"
|
className="rounded-t-md w-full aspect-[4/3] object-cover"
|
||||||
></img>
|
></img>
|
||||||
<Typography variant={"h2"} as="h1" className="mx-5 py-2">
|
<Separator className="my-0 border-2" />
|
||||||
{room.name}
|
</div>
|
||||||
</Typography>
|
<div>
|
||||||
</CardHeader>
|
<Typography variant={"h2"} as="h2" className="pt-4 px-4 border-none">
|
||||||
<CardContent>
|
{room.name}
|
||||||
<Typography variant={"p"} as="p">
|
</Typography>
|
||||||
{room.description}
|
</div>
|
||||||
</Typography>
|
<div>
|
||||||
<Separator className="my-4" />
|
<Typography variant={"p"} as="h2" className="px-5">
|
||||||
<div className="flex justify-around">
|
{room.description}
|
||||||
<div className="flex flex-row gap-2">
|
</Typography>
|
||||||
{hasSingleBed && (
|
</div>
|
||||||
<IconWithTooltipNumber
|
<div className="pb-4 mt-auto">
|
||||||
altText="Single bed"
|
<Separator className="my-4" />
|
||||||
amount={bedTypeList[0]}
|
<div className="flex justify-around">
|
||||||
>
|
<div className="flex flex-row gap-2">
|
||||||
<BedSingleIcon />
|
{hasSingleBed && (
|
||||||
</IconWithTooltipNumber>
|
<IconWithTooltip
|
||||||
)}
|
tooltipText="Single bed"
|
||||||
{hasDoubleBed && (
|
label={bedTypeList[0]?.toString()}
|
||||||
<>
|
|
||||||
<IconWithTooltipNumber
|
|
||||||
altText="Double bed"
|
|
||||||
amount={bedTypeList[1]}
|
|
||||||
>
|
>
|
||||||
<BedDoubleIcon />
|
<BedSingleIcon />
|
||||||
</IconWithTooltipNumber>
|
</IconWithTooltip>
|
||||||
</>
|
)}
|
||||||
)}
|
{hasDoubleBed && (
|
||||||
</div>
|
<>
|
||||||
<div className="flex flex-row">
|
<IconWithTooltip
|
||||||
<UserIcon />
|
tooltipText="Double bed"
|
||||||
<Typography variant={"p_inline"} className="ml-3">
|
label={bedTypeList[1]?.toString()}
|
||||||
{room.maxGuestNum}
|
>
|
||||||
</Typography>
|
<BedDoubleIcon />
|
||||||
|
</IconWithTooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<IconWithTooltip
|
||||||
|
tooltipText="Max. gests"
|
||||||
|
label={room.maxGuestNum?.toString()}
|
||||||
|
labelPosition={"right"}
|
||||||
|
>
|
||||||
|
<UserIcon />
|
||||||
|
</IconWithTooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import "react-day-picker/dist/style.css";
|
|||||||
import { DatePickerWithRange } from "../ui/dateRange-picker";
|
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 { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { SearchFilter } from "@/model/SearchFilter";
|
import { SearchFilter } from "@/model/SearchFilter";
|
||||||
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";
|
import { useAppStore } from "@/store";
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
|
|
||||||
type funOnSearchClick = (newFilter: SearchFilter) => void;
|
type funOnSearchClick = (newFilter: SearchFilter) => void;
|
||||||
|
|
||||||
@@ -29,10 +28,10 @@ export function SearchFilterCard({
|
|||||||
|
|
||||||
//On location change, try to load searchFilter from state.
|
//On location change, try to load searchFilter from state.
|
||||||
//FIXME: Si llegamos sin state, hay que borrar el state global...
|
//FIXME: Si llegamos sin state, hay que borrar el state global...
|
||||||
const location = useLocation();
|
// const location = useLocation();
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
setSearchFilter(location.state);
|
// setSearchFilter(location.state);
|
||||||
}, [location]);
|
// }, [location]);
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@@ -56,8 +55,9 @@ export function SearchFilterCard({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAppSearchFilter(searchFilter);
|
||||||
|
|
||||||
if (onSearchClick) {
|
if (onSearchClick) {
|
||||||
setAppSearchFilter(searchFilter);
|
|
||||||
onSearchClick(searchFilter);
|
onSearchClick(searchFilter);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -68,8 +68,6 @@ export function SearchFilterCard({
|
|||||||
...searchFilter,
|
...searchFilter,
|
||||||
dateRange: newRange,
|
dateRange: newRange,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("SearchFilterCard filter UPDATED");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const datePickerElement =
|
const datePickerElement =
|
||||||
|
|||||||
@@ -46,13 +46,10 @@ export function DatePickerWithRange({
|
|||||||
const handleDayClickFrom = (day: Date, mods: ActiveModifiers) => {
|
const handleDayClickFrom = (day: Date, mods: ActiveModifiers) => {
|
||||||
const newDateRange: DateRange = {
|
const newDateRange: DateRange = {
|
||||||
from: mods.selected ? undefined : day,
|
from: mods.selected ? undefined : day,
|
||||||
to: dateRange?.to,
|
to: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
setDateRange(newDateRange);
|
fnUpdateRange(newDateRange);
|
||||||
if (updateDateRange !== undefined) {
|
|
||||||
updateDateRange(newDateRange);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDayClickTo = (day: Date, mods: ActiveModifiers) => {
|
const handleDayClickTo = (day: Date, mods: ActiveModifiers) => {
|
||||||
@@ -61,10 +58,13 @@ export function DatePickerWithRange({
|
|||||||
to: mods.selected ? undefined : day,
|
to: mods.selected ? undefined : day,
|
||||||
};
|
};
|
||||||
|
|
||||||
setDateRange(newDateRange);
|
fnUpdateRange(newDateRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fnUpdateRange = (range: DateRange) => {
|
||||||
|
setDateRange(range);
|
||||||
if (updateDateRange !== undefined) {
|
if (updateDateRange !== undefined) {
|
||||||
updateDateRange(newDateRange);
|
updateDateRange(range);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ export function DatePickerWithRange({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="center">
|
||||||
<Calendar
|
<Calendar
|
||||||
initialFocus
|
initialFocus
|
||||||
mode="single"
|
mode="single"
|
||||||
@@ -137,11 +137,11 @@ export function DatePickerWithRange({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="center">
|
||||||
<Calendar
|
<Calendar
|
||||||
initialFocus
|
initialFocus
|
||||||
mode="single"
|
mode="single"
|
||||||
defaultMonth={dateRange?.to}
|
defaultMonth={dateRange?.to ?? dateRange?.from}
|
||||||
selected={dateRange?.to}
|
selected={dateRange?.to}
|
||||||
onDayClick={handleDayClickTo}
|
onDayClick={handleDayClickTo}
|
||||||
numberOfMonths={1}
|
numberOfMonths={1}
|
||||||
|
|||||||
@@ -8,21 +8,23 @@ import {
|
|||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface IconWithTooltipNumberProps {
|
interface IconWithTooltipNumberProps {
|
||||||
amount?: number | null | undefined;
|
label?: string | null | undefined;
|
||||||
altText?: string | null | undefined;
|
labelPosition?: "left" | "right" | null | undefined;
|
||||||
|
tooltipText?: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconWithTooltipNumber({
|
export function IconWithTooltip({
|
||||||
amount = undefined,
|
label = undefined,
|
||||||
altText = undefined,
|
labelPosition = "left",
|
||||||
|
tooltipText = undefined,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<IconWithTooltipNumberProps>) {
|
}: PropsWithChildren<IconWithTooltipNumberProps>) {
|
||||||
const iconElement = altText ? (
|
const iconElement = tooltipText ? (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{altText}</p>
|
<p>{tooltipText}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
@@ -30,10 +32,16 @@ export function IconWithTooltipNumber({
|
|||||||
<>{children}</>
|
<>{children}</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const labelElement = (
|
||||||
|
<Typography variant={"p_inline"} className="px-1">
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{amount && <Typography variant={"p_inline"}>{amount}</Typography>}
|
{label && labelPosition == "left" && labelElement}
|
||||||
{iconElement}
|
{iconElement}
|
||||||
|
{label && labelPosition == "right" && labelElement}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 133 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 96 KiB |
+1
-1
@@ -10,7 +10,7 @@ function Root() {
|
|||||||
<>
|
<>
|
||||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<div className="appPage bg-background text-foreground">
|
<div className="w-full sm:w-[90%] md:w-[80%] mx-auto p-8 text-center bg-background text-foreground">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.homePageRoomGrid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 3fr));
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ 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/SearchFilter";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { LoaderIcon } from "lucide-react";
|
||||||
|
import "./home-page.css";
|
||||||
|
|
||||||
function Homepage() {
|
function Homepage() {
|
||||||
const { roomCategoryList, isLoading, isError } = useAPIRoomCategories();
|
const { roomCategoryList, isLoading, isError } = useAPIRoomCategories();
|
||||||
@@ -27,13 +29,15 @@ function Homepage() {
|
|||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
<SearchFilterCard onSearchClick={onBtSearchClick} />
|
<SearchFilterCard onSearchClick={onBtSearchClick} />
|
||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
<div className="grid grid-cols-2 gap-4">{roomList}</div>
|
<div className="grid gap-4 homePageRoomGrid">{roomList}</div>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<>
|
<>
|
||||||
<Typography variant={"h3"} as="h2">
|
<Skeleton className="w-full h-[200px] mx-auto flex justify-center items-center flex-col">
|
||||||
Loading...
|
<LoaderIcon />
|
||||||
</Typography>
|
<Typography variant={"h3"} as="h2">
|
||||||
<Skeleton className="w-[400px] h-[200px] mx-auto" />
|
Loading...
|
||||||
|
</Typography>
|
||||||
|
</Skeleton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Separator className="my-10" />
|
<Separator className="my-10" />
|
||||||
|
|||||||
Reference in New Issue
Block a user