Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions src/app/travel-place/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"use client";
import TravelPlaceItem, {
TravelPlaceProps,
} from "@/components/travel-place/TravelPlaceItem";
import TravelPlaceList from "@/components/travel-place/TravelPlaceList";
import React, { useState, useEffect } from "react";

export default function TravelPlacePage() {
const [travelPlaces, setTravelPlaces] = useState<TravelPlaceProps[]>([]);

useEffect(() => {
async function loadTravelPlaces() {
const data = await fetchPlaces();
setTravelPlaces(data);
}

loadTravelPlaces();
}, []);

return (
<div>
<TravelPlaceList travelPlaces={travelPlaces} />
</div>
);
}

function fetchPlaces(): Promise<TravelPlaceProps[]> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(travelPlaceDatas);
}, 1000);
});
}

const travelPlaceDatas = [
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2021%2F12%2F29%2F61cc26b285028.jpg&w=828&q=75",
types: ["호텔", "리조트"],
name: "럭셔리 호텔",
areas: ["해운대", "바다 전망"],
ratings: [4.7, 3200],
sleepSalePrice: 130000,
sleepCheckInTime: "15:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2020%2F06%2F12%2F5ee2ce314b98e.jpg&w=640&q=75",
types: ["게스트하우스"],
name: "코지 게스트하우스",
areas: ["광안리", "해변 근처"],
ratings: [4.3, 250],
borrowOriginalPrice: 70000,
borrowSalePrice: 65000,
borrowMaximumTime: "6시간",
sleepOriginalPrice: 80000,
sleepSalePrice: 75000,
sleepCheckInTime: "14:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2021%2F05%2F07%2F6094c1a7028c3.jpg&w=640&q=75",
types: ["펜션", "해변"],
name: "해변 펜션",
areas: ["송정", "해변가"],
ratings: [4.6, 1230],
sleepSalePrice: 95000,
sleepCheckInTime: "16:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Fadimg_new%2F74709%2F517878%2Fdfae1e96f6e96c3ba705b638ad6e2b18.jpg&w=640&q=75",
types: ["호스텔", "도심"],
name: "모던 호스텔",
areas: ["서면", "시내 중심"],
ratings: [4.1, 210],
borrowSalePrice: 45000,
borrowMaximumTime: "12시간",
sleepOriginalPrice: 60000,
sleepSalePrice: 55000,
sleepCheckInTime: "15:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2020%2F11%2F12%2F5facd1c4769bd.jpg&w=640&q=75",
types: ["리조트", "풀빌라"],
name: "오션뷰 리조트",
areas: ["기장", "오션뷰"],
ratings: [4.8, 340],
borrowOriginalPrice: 180000,
borrowSalePrice: 170000,
borrowMaximumTime: "8시간",
sleepOriginalPrice: 220000,
sleepSalePrice: 200000,
sleepCheckInTime: "14:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2016%2F05%2F17%2F573ade296aabc.jpg&w=640&q=75",
types: ["민박", "산촌"],
name: "산속의 작은 민박",
areas: ["강원도", "산속"],
ratings: [4.2, 150],
sleepOriginalPrice: 50000,
sleepSalePrice: 45000,
sleepCheckInTime: "14:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2023%2F03%2F31%2F6426a6b54e93f.jpg&w=640&q=75",
types: ["B&B", "전원"],
name: "전원 B&B",
areas: ["전주", "시골"],
ratings: [4.5, 300],
borrowOriginalPrice: 60000,
borrowSalePrice: 55000,
borrowMaximumTime: "4시간",
sleepOriginalPrice: 70000,
sleepSalePrice: 65000,
sleepCheckInTime: "15:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2021%2F08%2F12%2F611473fc22462.jpg&w=640&q=75",
types: ["아파트", "시티뷰"],
name: "도심 아파트 숙소",
areas: ["서울", "도심"],
ratings: [4.8, 500],
sleepOriginalPrice: 120000,
sleepSalePrice: 110000,
sleepCheckInTime: "16:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Fadimg_new%2F74000%2F23922%2F764c4e5c292f71cdb4b3d63595086807.jpg&w=640&q=75",
types: ["캠프장", "자연"],
name: "자연 속 캠프장",
areas: ["제주도", "해변가"],
ratings: [4.6, 220],
borrowOriginalPrice: 80000,
borrowSalePrice: 70000,
borrowMaximumTime: "10시간",
sleepOriginalPrice: 90000,
sleepSalePrice: 85000,
sleepCheckInTime: "17:00",
},
{
imgUrl:
"https://www.yeogi.com/_next/image?url=https%3A%2F%2Fimage.goodchoice.kr%2Faffiliate%2F2023%2F10%2F20%2F65314d7fb6dd3.jpg&w=640&q=75",
types: ["모텔", "도심 근처"],
name: "시내 모텔",
areas: ["부산", "시내 근처"],
ratings: [3.9, 100],
sleepOriginalPrice: 40000,
sleepSalePrice: 35000,
sleepCheckInTime: "13:00",
},
];
175 changes: 175 additions & 0 deletions src/components/travel-place/TravelPlaceItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import Image from "next/image";
import React from "react";
import { formatNumberWithCommas } from "../../util/string_util";

export interface TravelPlaceProps {
imgUrl: string;
types: string[];
name: string;
areas: string[];
ratings: number[];
borrowOriginalPrice?: number;
borrowSalePrice?: number;
borrowMaximumTime?: string;
sleepOriginalPrice?: number;
sleepSalePrice?: number;
sleepCheckInTime?: string;
}

export default function TravelPlaceItem({
imgUrl,
types,
name,
areas,
ratings,
borrowOriginalPrice,
borrowSalePrice,
borrowMaximumTime,
sleepOriginalPrice,
sleepSalePrice,
sleepCheckInTime,
}: TravelPlaceProps) {
return (
// 아이템 영역
<div className="flex flex-row w-full h-240pxr md:h-200pxr">
{/* 이미지 */}
<div className="relative w-1/3 overflow-hidden max-w-[300px] md:w-5/12 rounded-xl">
<Image
className="object-cover object-center"
src={imgUrl}
layout="fill"
alt="Picture of the author"
/>
</div>
{/* 세부정보 */}
<div className="flex flex-col justify-between flex-grow h-full ml-4">
{/* 기본정보 */}
<div>
{/* 종류 */}
<div className="text-xs text-color-112">
{types.map((item, index) => (
<span className="" key={item}>
{item}
{index < types.length - 1 && <span>•</span>}
</span>
))}
</div>

{/* 이름 */}
<p className="font-bold text-18pxr">{name}</p>

{/* 위치 */}
<div className="flex items-center text-xs text-color-112">
{areas.map((item, index) => (
<span key={item} className={`${index == 0 ? "font-bold" : ""}`}>
{item}
{index < areas.length - 1 && <span>•</span>}
</span>
))}
</div>

{/* 사용자 평가 */}
<div className="flex flex-row items-center mt-1">
{/* 평점 */}
<div className="flex flex-row p-1 text-xs rounded-md bg-yellow bg-color-orange">
<span>★</span>
<span className="pl-1 font-semibold">{ratings[0]}</span>
</div>
{/* 평가자 수 */}
<div className="pl-2 text-sm font-normal text-color-112">
<span>{formatNumberWithCommas(ratings[1])}</span>
<span className="pl-1">평가</span>
</div>
</div>
</div>

{/* 가격정보 */}
<div>
{/* 대실 */}
{borrowSalePrice !== undefined && (
<div className="flex flex-col w-full">
{/* 대실 체크인 시간 및 원가 */}
<div className="flex items-center justify-between w-full">
<div className="flex items-center">
<span className="font-semibold text-12pxr">대실</span>
<span className="font-semibold m-4pxr text-10pxr text-color-112">
최대 {borrowMaximumTime}
</span>
</div>
{borrowOriginalPrice !== undefined ? (
<div className="text-sm text-color-112">
<span className="line-through ">
{formatNumberWithCommas(borrowOriginalPrice as number)}
</span>
<span className="line-through">원</span>
</div>
) : (
<></>
)}
</div>
{/* 대실 판매가 */}
<div className="flex items-end justify-end">
<span className="font-bold text-22pxr ">
{formatNumberWithCommas(borrowSalePrice as number)}
</span>
<span className="font-semibold text-16pxr">원</span>
</div>
</div>
)}

{sleepSalePrice !== undefined && borrowSalePrice !== undefined ? (
<div className="h-[8px]"></div>
) : (
<></>
)}

{/* 숙박 */}
<div className="flex flex-col w-full">
{/* 숙박 체크인 시간 및 원가 */}
<div className="flex justify-between w-full">
<div>
<span
className={`${
borrowSalePrice === undefined ? "invisible" : ""
} text-12pxr font-semibold`}
>
숙박
</span>
{sleepCheckInTime !== undefined &&
borrowSalePrice !== undefined ? (
<span className="font-semibold m-4pxr text-10pxr text-color-112">
{sleepCheckInTime} 체크인
</span>
) : (
""
)}
</div>
{sleepOriginalPrice !== undefined ? (
<div className="text-sm text-color-112">
<span className="line-through ">
{formatNumberWithCommas(sleepOriginalPrice as number)}
</span>
<span className="line-through">원</span>
</div>
) : (
<></>
)}
</div>
{/* 숙박 판매가 */}
<div className="flex items-end justify-end">
<span className="font-bold text-22pxr ">
<span
className={`${
sleepOriginalPrice !== undefined ? "invisible" : ""
} text-color-112`}
></span>
{formatNumberWithCommas(sleepSalePrice as number)}
</span>
<span className="font-semibold text-16pxr">원</span>
</div>
</div>
</div>
</div>
</div>
);
}
23 changes: 23 additions & 0 deletions src/components/travel-place/TravelPlaceList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";
import React, { useState, useEffect } from "react";
import TravelPlaceItem, { TravelPlaceProps } from "./TravelPlaceItem";

interface TravelPlaceListProps {
travelPlaces: TravelPlaceProps[];
}

export default function TravelPlaceList({
travelPlaces,
}: TravelPlaceListProps) {
return (
<div className="flex flex-col w-full">
<hr />
{travelPlaces.map((travelPlace, index) => (
<div key={index} className="mt-12pxr">
<TravelPlaceItem {...travelPlace} />
<hr className="mt-12pxr" />
</div>
))}
</div>
);
}
7 changes: 7 additions & 0 deletions src/util/string_util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function formatNumberWithCommas(input: number | string) {
const number = typeof input === "string" ? parseFloat(input) : input;
if (isNaN(number)) {
throw new Error("Invalid input: not a number or a numeric string");
}
return new Intl.NumberFormat().format(number);
}
Loading