Homepage - Initial implementation.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { RoomCategory } from "@/model/RoomCategory";
|
||||
import { fetcherUnwraper } from "./backend";
|
||||
import useSWR from 'swr';
|
||||
|
||||
export function useAPIRoomCategories () {
|
||||
const { data, error, isLoading } = useSWR<RoomCategory[]>(`/api/room/category`, fetcherUnwraper)
|
||||
|
||||
return {
|
||||
roomCategoryList: data,
|
||||
isLoading,
|
||||
isError: error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
const BACKEND_HOST = import.meta.env.VITE_BACKEND_HOST;
|
||||
const BACKEND_PORT = import.meta.env.VITE_BACKEND_PORT;
|
||||
|
||||
const url = BACKEND_HOST + (BACKEND_PORT == 443 || BACKEND_PORT == 80 ? "" : ":" + BACKEND_PORT)
|
||||
|
||||
export function fetcher<T>(endpoint: string): Promise<T> {
|
||||
return fetch(url + endpoint, {
|
||||
method: "GET"
|
||||
})
|
||||
.then(r => {
|
||||
if (!r.ok) {
|
||||
throw new Error(r.statusText)
|
||||
}
|
||||
const data = r.json();
|
||||
return data as Promise<T>;
|
||||
})
|
||||
}
|
||||
|
||||
export function fetcherUnwraper<T>(endpoint: string): Promise<T> {
|
||||
return fetch(url + endpoint)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText)
|
||||
}
|
||||
return response.json() as Promise<{ results: T }>
|
||||
})
|
||||
.then(data => {
|
||||
return data.results
|
||||
})
|
||||
}
|
||||
@@ -2,30 +2,61 @@ import { Separator } from "../ui/separator"
|
||||
import { Card, CardContent, CardHeader } from "../ui/card"
|
||||
import { Typography } from "../ui/typography"
|
||||
import { BedDoubleIcon, BedSingleIcon, UserIcon } from "lucide-react"
|
||||
import { RoomCategory, RoomCategoryPhoto } from "@/model/RoomCategory"
|
||||
import { IconWithTooltipNumber } from "../ui/icon-number-tooltip"
|
||||
|
||||
export function RoomItemCard() {
|
||||
interface RoomItemCardProps {
|
||||
room: RoomCategory
|
||||
}
|
||||
|
||||
const defaultRoom: RoomCategory = {
|
||||
id: -1,
|
||||
name: "Sample Room",
|
||||
description: "Lorem Ipsum",
|
||||
bedType: "0,1",
|
||||
maxGuestNum: 2,
|
||||
roomCategoryDetails: [],
|
||||
roomCategoryPhotos: []
|
||||
}
|
||||
|
||||
export function RoomItemCard({ room = defaultRoom }: RoomItemCardProps) {
|
||||
|
||||
const mainPhoto: RoomCategoryPhoto | undefined = room.roomCategoryPhotos.find((element) => {
|
||||
return element.kind = "main"
|
||||
});
|
||||
const bedTypeList: number[] = room.bedType.split(",").map((e) => Number.parseInt(e));
|
||||
const hasSingleBed: boolean = (bedTypeList.at(0) !== undefined && bedTypeList[0] > 0);
|
||||
const hasDoubleBed: boolean = (bedTypeList.at(1) !== undefined && bedTypeList[1] > 0);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card id={room.id.toString()}>
|
||||
<CardHeader className="p-0 m-0 mb-5">
|
||||
<img src="https://images.unsplash.com/photo-1618773928121-c32242e63f39" className="rounded-md"></img>
|
||||
<Typography variant={"h2"} as="h1" className="mx-5 py-2">Doble con baño</Typography>
|
||||
<img src={mainPhoto?.path ?? "https://images.unsplash.com/photo-1618773928121-c32242e63f39"} className="rounded-md"></img>
|
||||
<Typography variant={"h2"} as="h1" className="mx-5 py-2">{room.name}</Typography>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Typography variant={"p"} as="p">
|
||||
Habitación doble con <strong>baño privado</strong>.
|
||||
{room.description}
|
||||
</Typography>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex justify-around">
|
||||
<div className="flex flex-row gap-2">
|
||||
<Typography variant={"p_inline"}>2</Typography>
|
||||
<BedSingleIcon />
|
||||
<Typography variant={"p_inline"}>1</Typography>
|
||||
<BedDoubleIcon />
|
||||
{hasSingleBed && (
|
||||
<IconWithTooltipNumber altText="Single bed" amount={bedTypeList[0]}>
|
||||
<BedSingleIcon />
|
||||
</IconWithTooltipNumber>
|
||||
)}
|
||||
{hasDoubleBed && (
|
||||
<>
|
||||
<IconWithTooltipNumber altText="Double bed" amount={bedTypeList[1]}>
|
||||
<BedDoubleIcon />
|
||||
</IconWithTooltipNumber>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<UserIcon />
|
||||
<Typography variant={"p_inline"} className="ml-3">3</Typography>
|
||||
<Typography variant={"p_inline"} className="ml-3">{room.maxGuestNum}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Separator } from "../ui/separator"
|
||||
import { Card, CardContent, CardHeader } from "../ui/card"
|
||||
import { Typography } from "../ui/typography"
|
||||
import 'react-day-picker/dist/style.css';
|
||||
import { DatePickerWithRange } from "../ui/DateRangePicker";
|
||||
import { DatePickerWithRange } from "../ui/dateRange-picker";
|
||||
import { Button } from "../ui/button";
|
||||
import { SearchIcon } from "lucide-react";
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ export function ThemeProvider({
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeProviderContext)
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Typography } from "./typography";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
interface IconWithTooltipNumberProps {
|
||||
amount: number | null |undefined,
|
||||
altText: string | null | undefined
|
||||
}
|
||||
|
||||
export function IconWithTooltipNumber({ amount = undefined, altText = undefined, children }: PropsWithChildren<IconWithTooltipNumberProps>) {
|
||||
|
||||
const iconElement = altText ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{altText}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{amount && <Typography variant={"p_inline"}>{amount}</Typography>}
|
||||
{iconElement}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -78,4 +78,5 @@ const Typography = React.forwardRef<HTMLElement, TypographyProps>(
|
||||
|
||||
Typography.displayName = 'Typography';
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { Typography, typographyVariants };
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
export type RoomCategory = {
|
||||
id: number,
|
||||
name: string,
|
||||
description: string,
|
||||
bedType: string,
|
||||
maxGuestNum: number,
|
||||
roomCategoryPhotos: RoomCategoryPhoto[],
|
||||
roomCategoryDetails: RoomCategoryDetails[],
|
||||
}
|
||||
|
||||
export type RoomCategoryDetails = {
|
||||
id: number,
|
||||
detailsSection: string,
|
||||
detailValue: string,
|
||||
}
|
||||
|
||||
export type RoomCategoryPhoto = {
|
||||
id: number,
|
||||
path: string,
|
||||
altText: string | undefined,
|
||||
kind: string,
|
||||
}
|
||||
@@ -1,17 +1,46 @@
|
||||
import { useAPIRoomCategories } from "@/api/api-roomCategories"
|
||||
import { RoomItemCard } from "@/components/Rooms/room-item-card"
|
||||
import { SearchFilterCard } from "@/components/Search/SearchFilterCard"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Typography } from "@/components/ui/typography"
|
||||
import { RoomCategory } from "@/model/RoomCategory"
|
||||
|
||||
function Homepage() {
|
||||
|
||||
const { roomCategoryList, isLoading, isError } = useAPIRoomCategories();
|
||||
|
||||
const BACKEND_HOST = import.meta.env.VITE_BACKEND_HOST;
|
||||
const BACKEND_PORT = import.meta.env.VITE_BACKEND_PORT;
|
||||
|
||||
const roomList = roomCategoryList?.map((element: RoomCategory) => {
|
||||
return (
|
||||
<RoomItemCard key={element.id} room={element}/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-background text-foreground">
|
||||
<Button>Test</Button>
|
||||
<Separator className="my-10"/>
|
||||
<Typography variant={"h1"}>Hostal del Val</Typography>
|
||||
<Separator className="my-10" />
|
||||
<SearchFilterCard />
|
||||
<Separator className="my-10"/>
|
||||
<RoomItemCard />
|
||||
<Separator className="my-10" />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{roomList}
|
||||
</div>
|
||||
{isLoading && (
|
||||
<>
|
||||
<Typography variant={"h3"} as="h2">Loading...</Typography>
|
||||
<Skeleton className="w-[400px] h-[200px] mx-auto"/>
|
||||
</>
|
||||
)}
|
||||
<Separator className="my-10" />
|
||||
<p>
|
||||
Conected to Back-End at: {BACKEND_HOST} - {BACKEND_PORT}
|
||||
</p>
|
||||
<p>
|
||||
Conection Status: {isError ? "ERROR" : (isLoading ? "LOADING" : "LOADED")}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user