프로젝트/호텔 검색 앱

호텔 검색 앱 5 - 호텔 상세 페이지 구현하기

syleemomo 2021. 12. 19. 19:35
728x90

 

* 특정 호텔 선택시 상세페이지로 호텔 정보 전달하기

import React from 'react'
import { Link } from 'react-router-dom'

import { isArrayNull, handleNullObj } from 'lib'
import './HotelItem.css'

const HotelItem = ({ hotel }) => {
    const { id, name,  optimizedThumbUrls, starRating, address, landmarks, guestReviews, ratePlan, neighbourhood } = handleNullObj(hotel)
    const { srpDesktop } = handleNullObj(optimizedThumbUrls)
    const { streetAddress, locality, postalCode, countryName} = handleNullObj(address)
    const { rating, badgeText} = handleNullObj(guestReviews)
    const { price } = handleNullObj(ratePlan)
    const { old, current, info, summary, totalPricePerStay} = handleNullObj(price)
    const totalPrice = totalPricePerStay? totalPricePerStay.split(/[<>()]/) : []

    const hotelInfo = { id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary }

    return (<div className='HotelItem-container'>
                <Link className='HotelItem-thumbnail' to='/hotelInfo' state={{ hotelInfo }} >
                    <img className='HotelItem-thumbnail-img' src={srpDesktop} alt={name}/>
                </Link>
                <div className='HotelItem-info'>
                    <div className='HotelItem-name'>{name} <span>{starRating}성급</span></div>
                    <div className='HotelItem-address'>{streetAddress}, {locality}, {countryName}</div>
                    <div className='HotelItem-neighbourhood'>{neighbourhood}</div>
                    <div className='HotelItem-landmarks'>
                        {!isArrayNull(landmarks) && landmarks.map( (landmark, index) => {
                            return <div key={index}>* {landmark.label}까지 {landmark.distance}</div>
                        })}
                    </div>
                    <div className='HotelItem-rating'>
                        <div className='HotelItem-rating-badge'>{rating}</div>
                        <div className='HotelItem-rating-badgeText'> {badgeText}</div>
                    </div>
                </div>
                <div className='HotelItem-price'>
                    <div className='HotelItem-price-per-oneday'><span>{old}</span> {current}</div>
                    <div className='HotelItem-price-per-oneday-title'>{info}</div>
                    <div className='HotelItem-price-total'>{totalPrice[1]} {totalPrice[3]}</div>
                    <div className='HotelItem-price-summary'>{summary}</div>
                </div>
            </div>)
}
export default HotelItem

HotelItem.js 파일을 위와 같이 수정하자!

const hotelInfo = { id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary }

호텔 상세 페이지에서 필요한 데이터를 hotelInfo 라는 객체에 담는다.

<Link className='HotelItem-thumbnail' to='/hotelInfo' state={{ hotelInfo }} >

Link 컴포넌트의 state 속성에 전달할 호텔 정보를 설정한다. 

import React from 'react'
import { useLocation } from 'react-router-dom'
import { fetchHotelsCom, isArrayNull, handleNullObj } from 'lib'

import './HotelInfo.css'

const HotelInfo = () => {
    const location = useLocation()
    const { hotelInfo } = handleNullObj(location.state)
    const { id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary } = handleNullObj(hotelInfo)
    console.log(id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary)
    return (
        <div className='HotelInfo-container'>
            <div className='HotelInfo-header'>
                <div className='HotelInfo-hotel-name'>{name} <span>{starRating}성급</span></div>
                <div className='HotelInfo-hotel-price'>
                    <div className='HotelInfo-price-per-oneday'><span>{old}</span> {current}</div>
                    <div className='HotelInfo-price-per-oneday-title'>{info}</div>
                    <div className='HotelInfo-price-total'>{totalPrice[1]} {totalPrice[3]}</div>
                    <div className='HotelInfo-price-summary'>{summary}</div>
                </div>
            </div>
            <div className='HotelInfo-photos'>photo</div>
        </div>
    )
}

export default HotelInfo

HotelInfo.js 파일을 위와 같이 수정하자!

import { useLocation } from 'react-router-dom'

호텔 목록 페이지에서 Link 컴포넌트로부터 전달받은 데이터를 조회하기 위하여 useLocation 함수를 임포트한다.

import { fetchHotelsCom, isArrayNull, handleNullObj } from 'lib'

데이터 유효성 검증과 서버에서 데이터를 가져오기 위하여 위와 같은 함수들을 임포트한다.

import './HotelInfo.css'

 스타일을 적용하기 위하여 css 파일을 추가한다.

const location = useLocation()
const { hotelInfo } = handleNullObj(location.state)
const { id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary } = handleNullObj(hotelInfo)
console.log(id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary)

호텔 목록 페이지로부터 전달받은 데이터를 조회한다. 호텔의 이름, 숙박등급, 가격 정보를 조회한다. 

<div className='HotelInfo-container'>
    <div className='HotelInfo-header'>
        <div className='HotelInfo-hotel-name'>{name} <span>{starRating}성급</span></div>
        <div className='HotelInfo-hotel-price'>
            <div className='HotelInfo-price-per-oneday'><span>{old}</span> {current}</div>
            <div className='HotelInfo-price-per-oneday-title'>{info}</div>
            <div className='HotelInfo-price-total'>{totalPrice[1]} {totalPrice[3]}</div>
            <div className='HotelInfo-price-summary'>{summary}</div>
        </div>
    </div>
    <div className='HotelInfo-photos'>photo</div>
</div>

호텔의 이름, 숙박등급, 가격 정보를 화면에 보여준다. HotelItem 컴포넌트와 유사한 부분이 꽤 많다. 

.HotelInfo-container{
    width: 80%;
    margin: 0 auto;
    /* border: 1px solid red; */

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.HotelInfo-header{
    width: 100%;
    /* border: 1px solid orange; */

    display: flex;
    justify-content: center;
}
.HotelInfo-hotel-name{
    flex: 1;
    font-size: 1.3rem;
    font-weight: bold;
}
.HotelInfo-hotel-name span{
    font-size: 0.9rem;
    color: red;
}
.HotelInfo-hotel-price{
    width: 200px;
    /* border: 1px solid green; */
    text-align: right;
}
.HotelInfo-price-per-oneday{
    font-size: 1.5rem;
    font-weight: bold;
}
.HotelInfo-price-per-oneday span{
    font-size: 0.8rem;
    text-decoration: line-through;
}
.HotelInfo-price-per-oneday-title{
    font-size: 0.8rem;
    color: #525252;
}
.HotelInfo-price-total{
    font-weight: bold;
    margin-top: 10px;
}
.HotelInfo-price-summary{
    font-size: 0.8rem;
    color: #525252;
}

.HotelInfo-photos{
    width: 100%;
    /* border: 1px solid blue; */
}

pages > HotelInfo.css 파일을 위와 같이 생성하자!

 

* 호텔 사진 보여주기

호텔 사진을 보여주기 위하여 서버로부터 사진을 가져오는 대신 가짜 데이터를 가져온다.

const hotelPhotos = [
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/4a3dd0ec_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/4a3dd0ec_z.jpg",
        "imageId": 483876139,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/7f8a6645_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/7f8a6645_z.jpg",
        "imageId": 548447916,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/3ce3015e_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/3ce3015e_z.jpg",
        "imageId": 548447908,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/bc37ffaa_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/bc37ffaa_z.jpg",
        "imageId": 483876133,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/cf1fedaf_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/cf1fedaf_z.jpg",
        "imageId": 567957985,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/47fcdfc8_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/47fcdfc8_z.jpg",
        "imageId": 479840094,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/fa42f716_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/fa42f716_z.jpg",
        "imageId": 475189289,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/a2f9d54f_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/a2f9d54f_z.jpg",
        "imageId": 475189288,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/586c78dc_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/586c78dc_z.jpg",
        "imageId": 68537846,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    },
    {
        "baseUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/2e6bb8b3_{size}.jpg",
        "mainUrl": "https://exp.cdn-hotels.com/hotels/1000000/30000/22600/22529/2e6bb8b3_z.jpg",
        "imageId": 84089913,
        "sizes": [
            {
                "type": 15,
                "suffix": "z"
            },
            {
                "type": 17,
                "suffix": "w"
            },
            {
                "type": 14,
                "suffix": "y"
            },
            {
                "type": 3,
                "suffix": "b"
            },
            {
                "type": 11,
                "suffix": "n"
            },
            {
                "type": 13,
                "suffix": "d"
            },
            {
                "type": 16,
                "suffix": "e"
            },
            {
                "type": 12,
                "suffix": "g"
            },
            {
                "type": 9,
                "suffix": "l"
            },
            {
                "type": 2,
                "suffix": "s"
            },
            {
                "type": 1,
                "suffix": "t"
            }
        ]
    }
]

export default hotelPhotos

src > hotelPhotos.js 파일을 생성하자!

import React, { useState, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { fetchHotelsCom, isArrayNull, handleNullObj } from 'lib'
import hotelPhotos from '../hotelPhotos'

import './HotelInfo.css'

const HotelInfo = () => {
    const location = useLocation()
    const { hotelInfo } = handleNullObj(location.state)
    const { id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary } = handleNullObj(hotelInfo)
    console.log(id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary)

    const [photos, setPhotos] = useState([])
    const [index, setIndex] = useState(0)

    useEffect( async () => {
        const photos = await getHotelPhotos(`https://hotels-com-provider.p.rapidapi.com/v1/hotels/photos?hotel_id=${id}`)
        console.log(photos)
        setPhotos(photos)
    }, [])

    const getHotelPhotos = async (url) => {
        // const data = await fetchHotelsCom(url)
        // return data

        return hotelPhotos
    }

    const changePhoto = (index) => {
        setIndex(index)
    }

    return (
        <div className='HotelInfo-container'>
            <div className='HotelInfo-header'>
                <div className='HotelInfo-hotel-name'>{name} <span>{starRating}성급</span></div>
                <div className='HotelInfo-hotel-price'>
                    <div className='HotelInfo-price-per-oneday'><span>{old}</span> {current}</div>
                    <div className='HotelInfo-price-per-oneday-title'>{info}</div>
                    <div className='HotelInfo-price-total'>{totalPrice[1]} {totalPrice[3]}</div>
                    <div className='HotelInfo-price-summary'>{summary}</div>
                </div>
            </div>
            <div className='HotelInfo-photos'>
                <div className='HotelInfo-main-photo'>
                    <img src={!isArrayNull(photos)? photos[index].mainUrl : ''} alt="hotel-main-photo"/>
                </div>
                <div className='HotelInfo-sub-photos'>
                    {!isArrayNull(photos) && photos.map( (photo, index) => {
                        if(index < 4){
                            return (
                                <div className='HotelInfo-sub-photo' key={index} onClick={() => changePhoto(index)}>
                                    <img src={photo.mainUrl} alt='hotel-sub-photo'/>
                                </div>
                            )
                        }
                    })}
                </div>
            </div>
        </div>
    )
}

export default HotelInfo

HotelInfo.js 파일을 위와 같이 수정하자!

import React, { useState, useEffect } from 'react'

리액트 훅을 사용하기 위하여 useState, useEffect 함수를 임포트한다. 

import hotelPhotos from '../hotelPhotos'

 

사용자가 선택한 호텔에 대한 사진 데이터를 가져온다. 

const [photos, setPhotos] = useState([])

hotels.com API 서버로부터 가져온 사진 데이터를 저장하기 위하여 photos 상태를 선언한다. 

const [index, setIndex] = useState(0)

사용자 선택에 따라 호텔 메인 사진을 변경하기 위하여 index 상태를 선언한다. 

useEffect( async () => {
    const photos = await getHotelPhotos(`https://hotels-com-provider.p.rapidapi.com/v1/hotels/photos?hotel_id=${id}`)
    console.log(photos)
    setPhotos(photos)
}, [])

호텔 ID 값을 이용하여 서버로부터 사진 데이터를 가져온다. 그런 다음 photos 상태를 업데이트한다. 

const getHotelPhotos = async (url) => {
    // const data = await fetchHotelsCom(url)
    // return data

    return hotelPhotos
}

파라미터로 전달된 url 을 가지고 서버로부터 데이터를 조회한다. 

const changePhoto = (index) => {
    setIndex(index)
}

사용자가 선택한 사진으로 호텔 메인 사진을 변경하기 위하여 index 상태를 업데이트한다. 

<div className='HotelInfo-photos'>
    <div className='HotelInfo-main-photo'>
        <img src={!isArrayNull(photos)? photos[index].mainUrl : ''} alt="hotel-main-photo"/>
    </div>
    <div className='HotelInfo-sub-photos'>
        {!isArrayNull(photos) && photos.map( (photo, index) => {
            if(index < 4){
                return (
                    <div className='HotelInfo-sub-photo' key={index} onClick={() => changePhoto(index)}>
                        <img src={photo.mainUrl} alt='hotel-sub-photo'/>
                    </div>
                )
            }
        })}
    </div>
</div>

사용자가 선택한 특정 호텔에 대한 사진들을 화면에 보여준다. 

<div className='HotelInfo-main-photo'>
    <img src={!isArrayNull(photos)? photos[index].mainUrl : ''} alt="hotel-main-photo"/>
</div>

호텔 메인 사진을 화면에 보여준다. index 값을 이용하여 해당 사진을 조회한다. 

<div className='HotelInfo-sub-photos'>
    {!isArrayNull(photos) && photos.map( (photo, index) => {
        if(index < 4){
            return (
                <div className='HotelInfo-sub-photo' key={index} onClick={() => changePhoto(index)}>
                    <img src={photo.mainUrl} alt='hotel-sub-photo'/>
                </div>
            )
        }
    })}
</div>

호텔 서브 사진을 화면에 보여준다. 전체 사진 데이터로부터 4개의 사진만 보여준다. changePhoto 이벤트핸들러 함수는 사용자가 서브 사진을 클릭하면 index 값을 변경하여 해당 사진으로 메인 사진을 설정한다. 

.HotelInfo-container{
    width: 60%;
    margin: 0 auto;
    /* border: 1px solid red; */

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.HotelInfo-header{
    width: 100%;
    margin-top: 50px;
    /* border: 1px solid orange; */

    display: flex;
    justify-content: center;
}
.HotelInfo-hotel-name{
    flex: 1;
    font-size: 1.3rem;
    font-weight: bold;
}
.HotelInfo-hotel-name span{
    font-size: 0.9rem;
    color: red;
}
.HotelInfo-hotel-price{
    width: 200px;
    /* border: 1px solid green; */
    text-align: right;
}
.HotelInfo-price-per-oneday{
    font-size: 1.5rem;
    font-weight: bold;
}
.HotelInfo-price-per-oneday span{
    font-size: 0.8rem;
    text-decoration: line-through;
}
.HotelInfo-price-per-oneday-title{
    font-size: 0.8rem;
    color: #525252;
}
.HotelInfo-price-total{
    font-weight: bold;
    margin-top: 10px;
}
.HotelInfo-price-summary{
    font-size: 0.8rem;
    color: #525252;
}

.HotelInfo-photos{
    width: 100%;
    margin-top: 100px;
    /* border: 1px solid blue; */

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.HotelInfo-main-photo{
    width: 100%;
    height: 600px;
    border-radius: 5px;
    /* border: 1px solid red; */
    overflow: hidden;
}
.HotelInfo-main-photo img{
    width: 100%;
    height: 100%;
}
.HotelInfo-sub-photos{
    width: 100%;
    /* border: 1px solid green; */

    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
}
.HotelInfo-sub-photo{
    flex: 1;
    height: 200px;
    overflow: hidden;
    cursor: pointer;
    border-radius: 5px;
    margin-right: 2px;
}
.HotelInfo-sub-photo:hover{
    opacity: 0.8;
    transform: scale(1.01);
}
.HotelInfo-sub-photo img{
    width: 100%;
    height: 100%;
}

HotelInfo.css 파일을 위와 같이 수정하자!

호텔 사진을 디스플레이한 모습

 

* 호텔 리뷰 보여주기

호텔 리뷰에 대한 가짜 데이터를 추가한다. 

const hotelReviews = {
    "pagination": {
        "page": 1,
        "previousPage": false,
        "nextPage": true
    },
    "overview": {
        "totalCount": 686,
        "overall": 9.4,
        "formattedOverall": "9.4",
        "overallScoreBreakdown": [
            {
                "score": 10,
                "formattedScore": "10",
                "amount": 519
            },
            {
                "score": 8,
                "formattedScore": "8-9",
                "amount": 124
            },
            {
                "score": 6,
                "formattedScore": "6-7",
                "amount": 27
            },
            {
                "score": 4,
                "formattedScore": "4-5",
                "amount": 12
            },
            {
                "score": 1,
                "formattedScore": "1-3",
                "amount": 4
            }
        ],
        "qualitativeBadgeText": "Exceptional",
        "hotelService": 9.4,
        "roomComfort": 9.4,
        "hotelCondition": 9.2,
        "cleanliness": 9.4,
        "neighbourhood": 9.6,
        "formattedHotelService": "9.4",
        "formattedRoomComfort": "9.4",
        "formattedHotelCondition": "9.2",
        "formattedCleanliness": "9.4",
        "formattedNeighbourhood": "9.6"
    },
    "groupReview": [
        {
            "id": "SAME_LOCALE",
            "reviews": [
                {
                    "recommendedBy": "John",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1637971200000",
                    "title": "Perfect place to Propose",
                    "summary": "I got the Junior suite to propose to my girlfriend. The room is amazing and clean and beautiful. So many nice restaurants around our hotel. We also used the hotel lounge bar at night. My only negative review is the the bed linen. It wasn't too smooth. Overall, I recommend and would come again",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Edward",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1638748800000",
                    "title": "",
                    "summary": "The hotel and the staff members were outstanding and the underground connections to the mall and shops were very convenient. ",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Sean",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1636243200000",
                    "title": "",
                    "summary": "Excellent",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1636070400000",
                    "title": "No place like this hotel!",
                    "summary": "Location, location, location! They have the best location in Seoul. Directly connects to subway, a huge mall (called Coex) with a variety of eating and shopping options, and, most importantly, city airport where you can check in for your flight and drop off baggage. There’s no place like this hotel. \n\nThis was my 5th time staying at this hotel and its customer service, cleanliness, housekeeping, simply every aspect of the hotel is getting better each time. Had some errands to take care of that I needed the hotel’s help with. They were above and beyond in fulfilling all my requests. ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Yeeun",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1635552000000",
                    "title": "",
                    "summary": "Recently renovated, very clean, friendly staffs",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Sean",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1636243200000",
                    "title": "",
                    "summary": "Excellent ",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "min joo",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1634601600000",
                    "title": "",
                    "summary": "The staffs were super kind and the whole property was well maintained with a great effort to protect guests from the Covid pandemic.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "JI MIN",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1629763200000",
                    "title": "",
                    "summary": "very good",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1626825600000",
                    "title": "",
                    "summary": "friendly and helpful staff",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1625702400000",
                    "title": "",
                    "summary": "Extremely well maintained high quality hotel. The parking areas are also very nice. Very big parking space per car, not seen anywhere else in Korea. Pricey but worth the cost. ",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1624406400000",
                    "title": "",
                    "summary": "Amazing!",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Jacqueline Siew Imm",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1622246400000",
                    "title": "Excellent service at the Club Lounge ",
                    "summary": "Club Lounge facilities were excellent and the staff at the club were friendly and professional. Dominque Bang, who served us at the Club was exceptional. \nWill definitely recommend.\n\n\n",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1621728000000",
                    "title": "",
                    "summary": "great food and easy access in the middle of the city",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1620950400000",
                    "title": "",
                    "summary": "Convenient location, nice remodeling",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1615593600000",
                    "title": "One of the best business hotel in Gangnam",
                    "summary": "Stayed in a jr suite. Rm condition was excellent, the view was unblocked open overlooking Teheranro. Liked the fragrance of Joe Malone amenities. Compared to before remodeling the interior was more modern and brighter.   ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1613779200000",
                    "title": "",
                    "summary": "It was clean, the toiletries were Jo Malone, and the bed was so comfortable ",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1574899200000",
                    "title": "",
                    "summary": "Very convenient to reach nearby facilities within 3o minutes",
                    "reviewType": null
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1574726400000",
                    "title": "",
                    "summary": "I am very satisfied with all crews and services. All facilities are wonderful and good service, too.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 6,
                    "formattedRating": "6.0",
                    "qualitativeBadgeText": "Good",
                    "postedOn": "1573171200000",
                    "title": "",
                    "summary": "1, kind of old\n2, foods were too expensive\n3, no individual menu, all are course menu cost $150. 00 per course.\n 4, in lounge, 1st floor, felt like I had to order something while waiting to \n     meet someone\n5, actually I meant reserve Coex intercontinental hotel",
                    "reviewType": null
                },
                {
                    "recommendedBy": "",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1572048000000",
                    "title": "",
                    "summary": "It was a nice hotel in a great location. The staff was okay, not as helpful as expected. Hope they treat everyone the same. ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Jade",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1571961600000",
                    "title": "",
                    "summary": "It was in good location close to subway entrance and airport bus terminal. Staffs were very kind. Food was excellent   ",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1571443200000",
                    "title": "",
                    "summary": "Excellent reception & breakfast coffee is not so great.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1569196800000",
                    "title": "One of the best hotels I recommend. ",
                    "summary": "It was pleasure to stay at Grand InterContinental Seoul \nParnas for couple of days in 9, 2019. Location and customer services were fabulous!!! Mr. Woody was very helpful and supportive during checking out. I slept very well because the hotel was very clean and quiet. ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "WS",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1568592000000",
                    "title": "",
                    "summary": "Great location, easy to catch a bus to from airport & right on the Samseong subway stop.  Fitness center was really nice.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Young",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1572739200000",
                    "title": "Vacation",
                    "summary": "Awesome!!",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Louis",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1566777600000",
                    "title": "",
                    "summary": "Great location and great service!",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "JL",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1563753600000",
                    "title": "",
                    "summary": "Other than my kids, 10 years and 12 years old boys, were not allowed to swim at the pool because they are under 14 years, the rest all excellent.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "WuXiaoJie",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1562284800000",
                    "title": "",
                    "summary": "The toilet do not flush well, most of the time you have to flush 2 times.\nI recommend to use fully automatic toilet with washing devise (Washlet),\nfrom my experience TOTO brand flush better than any other toilet.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Patrick",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1561593600000",
                    "title": "",
                    "summary": "All staff should be taught to smile. Half had good smile and attitude but the other staff was either robotic or uncaring. ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1563840000000",
                    "title": "Bathroom odor",
                    "summary": "Been staying here since it opened in late 80's.  Hotel needs some refurnishment as the bathroom smelled BAD like mold and human waste.  That said best breakfast buffet and attentive staff.",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Soochul",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1560816000000",
                    "title": "Quiet!",
                    "summary": "",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Carlos",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1560816000000",
                    "title": "",
                    "summary": "It's always nice to return to this hotel. Great location!",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1559001600000",
                    "title": "",
                    "summary": "very good for using city airport by walking from the hotel!",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Stuart",
                    "rating": 6,
                    "formattedRating": "6.0",
                    "qualitativeBadgeText": "Good",
                    "postedOn": "1558051200000",
                    "title": "Simple things to adjust - that ruin the stay",
                    "summary": "Excellent standard hotel with great facilities.  Really let down by unfriendly and aloof staff (especially the Lobby bar) and the poor breakfast selection in the Club lounge (breakfast on Sunday before the Brunch starts is an absolute shocker).",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 2,
                    "formattedRating": "2.0",
                    "qualitativeBadgeText": "Poor",
                    "postedOn": "1559952000000",
                    "title": "",
                    "summary": "I have stayed at this hotel multiple times in many years. The service, attentive staff...this remains outstanding as it should in a 5 start hotel in SamsungDong. However, after a day of stay, due to further business commitments, I inquired about additional nights at front desk. I made a comment that Expedia rates were less expensive and the rates should be matched. The reply I received was that I should make the reservation through Expedia. I am dismayed and surprised that there is no sense of empowered sense of service to take care of the customer over a few dollars. \nI proceeded to check out to stay at a nearby Sheraton/Westin hotel chain.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1555891200000",
                    "title": "",
                    "summary": "Outstanding location. Rooms are dated and worn.  But overall a great stay.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Purev",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1555545600000",
                    "title": "",
                    "summary": "Always stay in this hotel when visit Seoul. Very dedicated, nice, polite staffs!",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Chan",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1555200000000",
                    "title": "",
                    "summary": "Super friendly staffs and clean room. Fitness club was also great with many equipments. The digital scale in the room was useful to monitor my weight during the stay.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "Purev",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1555200000000",
                    "title": "",
                    "summary": "Good location with nice shopping, food. Very dedicated and friendly  staffs, I enjoy my every stay in this hotel.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "SPH",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1554681600000",
                    "title": "",
                    "summary": "The room was so clean and spacious.  The staff is also excellent.  Everyone that helped us was extremely gracious, friendly, and wanting to go out of their way to make sure we were taken care of.  I think the only thing that I thought were not good were the plumbing fixtures.  It was so tough to get the water to come out just right and our toilet was clogged when we checked in.  They should actually put the handheld shower in the shower staff and stick with the downspout for the tub.  It was not comfortable to use.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1554336000000",
                    "title": "",
                    "summary": "We absolutely loved our stay here. We stayed for seven nights and all of the staff were so kind. Going to a country where you don’t speak their language or know a lot about their customs can be scary. The hotel staff were so helpful.  They helped with taxis, good dinner spots for special diets, exchanged money for us. Reviewed our itenary once and made helpful suggestions. Don’t hesitate to stay here. It is clean, great staff and good location.",
                    "reviewType": "er"
                },
                {
                    "recommendedBy": "TaiHyun",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1553040000000",
                    "title": "Club room is good for staying.",
                    "summary": "Hotel room is little old, but everything is good for staying. Breakfast is so delicious. Club room is excellent. I will recommend club room to my colleague and friends.",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Bong Ho",
                    "rating": 4,
                    "formattedRating": "4.0",
                    "qualitativeBadgeText": "Fair",
                    "postedOn": "1552348800000",
                    "title": "",
                    "summary": "friendly,and Quick service.",
                    "reviewType": "hr"
                }
            ]
        },
        {
            "id": "SAME_LANGUAGE",
            "reviews": [
                {
                    "recommendedBy": "Yousun",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1637625600000",
                    "title": "",
                    "summary": "so nice. best in Seoul",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "Lily",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1634428800000",
                    "title": "At the perfect location",
                    "summary": "Renovated with white marble bathroom at the perfect location in Gangnam. Satisfied with stay. Thanks! ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "andrew",
                    "rating": 8,
                    "formattedRating": "8.0",
                    "qualitativeBadgeText": "Very Good",
                    "postedOn": "1633737600000",
                    "title": "Lovely, expensive extras!",
                    "summary": "Very nice hotel,  clean, lovely rooms, nice staff,but be jesus is it expensive to buy things like coffee and breakfast!",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "S",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1633132800000",
                    "title": "Great quality of all service! ",
                    "summary": "Great quality of all service! ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "dongsoo",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1631664000000",
                    "title": "the most comfort hotel / best location. ",
                    "summary": "As it has the name, one of the best hotels in Seoul. \nbreakfast is also one of best you shouldn't miss. ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1629244800000",
                    "title": "",
                    "summary": "First night and first stay at the hotel, my mind was blown away how clean the room was! ",
                    "reviewType": "hr"
                },
                {
                    "recommendedBy": "",
                    "rating": 10,
                    "formattedRating": "10.0",
                    "qualitativeBadgeText": "Exceptional",
                    "postedOn": "1627603200000",
                    "title": "Decent clean and professional ",
                    "summary": "It is a very tidy business hotel. They were a little unprepared with the rush during check in and took them a while to figure it out. ",
                    "reviewType": "hr"
                }
            ]
        }
    ]
}

export default hotelReviews

hotelReviews.js 파일을 src 폴더 하위에 추가한다.

 

components 폴더 하위에 아래 파일들을 추가한다.

import React from 'react'
import './Review.css'

const Review = ({ review }) => {
    console.log(review)
    const { formattedRating, qualitativeBadgeText, title, summary, recommendedBy } = review
    return (
        <div className='Review-container'>
            <div className='Review-rating'>
                <div className={`Review-badge ${parseInt(formattedRating) < 8 ? 'Review-badge-gray' : ''}`}>{formattedRating}</div>
                <div className='Review-badgeText'>{qualitativeBadgeText}</div>
            </div>
            <div className='Review-text'>
                <p className='Review-text-title'>{title}</p>
                <p>{summary}</p>
                <div className='Review-user'>{recommendedBy? `- ${recommendedBy} -` : ''}</div>
            </div>
        </div>
    )
}
export default Review

Review.js 파일을 생성하고 위와 같이 작성하자!

const { formattedRating, qualitativeBadgeText, title, summary, recommendedBy } = review

review 데이터로부터 필요한 프로퍼티를 추출한다. 사용자 평점, 사용자 평가, 리뷰 제목, 리뷰 요약, 추천인 성명 등이다.

<div className='Review-rating'>
    <div className={`Review-badge ${parseInt(formattedRating) < 8 ? 'Review-badge-gray' : ''}`}>{formattedRating}</div>
    <div className='Review-badgeText'>{qualitativeBadgeText}</div>
</div>

사용자 평점과 평가를 화면에 보여준다. 평점이 8점 미만이면 회색 뱃지로 표시한다. 

<div className='Review-text'>
    <p className='Review-text-title'>{title}</p>
    <p>{summary}</p>
    <div className='Review-user'>{recommendedBy? `- ${recommendedBy} -` : ''}</div>
</div>

사용자 리뷰의 제목, 요약, 추천인 성명을 화면에 보여준다. 

.Review-container{
    border-bottom: 1px solid lightgray;
    padding-bottom: 15px;
    margin-bottom: 15px;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.Review-rating{
    width: 100%;
    margin-bottom: 15px;
    /* border: 1px solid red; */

    display: flex;
    justify-content: flex-start;
    align-items: center;
}
.Review-badge{
    background-color: #218242;
    color: white;
    padding-left: 5px;
    padding-right: 5px;
    padding-bottom: 2px;
    border-radius: 2px;
}
.Review-badge-gray{
    background-color: #a9a9a9;
    color: black;
}
.Review-badgeText{
    font-size: 0.9rem;
    font-weight: bold;
    margin-left: 10px;
}
.Review-text{
    width: 100%;
    margin-bottom: 15px;    
    /* border: 1px solid blue; */
    color: #333333;
}
.Review-text-title{
    font-weight: bold;
    font-size: 1.1rem;
}
.Review-user{
    font-weight: bold;
    font-size: 0.8rem;
}

Review.css 파일을 생성하고 위와 같이 작성하자!

export { default as Input } from './Input'
export { default as Button } from './Button'
export { default as Caption } from './Caption'
export { default as HotelItem } from './HotelItem'
export { default as Accordion } from './Accordion'
export { default as AccordionItem } from './AccordionItem'
export { default as StarRatingFilter } from './StarRatingFilter'
export { default as Review } from './Review'

components > index.js 파일에 Review 컴포넌트를 추가로 내보낸다. 

 

import React, { useState, useEffect } from 'react'
import { useLocation } from 'react-router-dom'

import { Review } from 'components'
import { fetchHotelsCom, isArrayNull, handleNullObj } from 'lib'
import hotelPhotos from '../hotelPhotos'
import hotelReviews from '../hotelReviews'

import './HotelInfo.css'

const HotelInfo = () => {
    const location = useLocation()
    const { hotelInfo } = handleNullObj(location.state)
    const { id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary } = handleNullObj(hotelInfo)
    console.log(id, name, starRating, rating, badgeText, old, current, info, totalPrice, summary)

    const [photos, setPhotos] = useState([])
    const [index, setIndex] = useState(0)
    const [reviews, setReviews] = useState([])

    useEffect( async () => {
        const photos = await getHotelPhotos(`https://hotels-com-provider.p.rapidapi.com/v1/hotels/photos?hotel_id=${id}`)
        const reviews = await getReviews(`https://hotels-com-provider.p.rapidapi.com/v1/hotels/reviews?locale=en_US&hotel_id=${id}`)
        setPhotos(photos)
        setReviews(reviews)
    }, [])

    const getHotelPhotos = async (url) => {
        // const data = await fetchHotelsCom(url)
        // return data

        return hotelPhotos
    }

    const changePhoto = (index) => {
        setIndex(index)
    }

    const getReviews = async (url) => {
        // const data = await fetchHotelsCom(url)
        // return data

        const { groupReview } = handleNullObj(hotelReviews)
        const { reviews } = !isArrayNull(groupReview) ? handleNullObj(groupReview[0]) : []

        return reviews
    }

    return (
        <div className='HotelInfo-container'>
            {/* 호텔 정보 보여주기 */}
            <div className='HotelInfo-header'>
                <div className='HotelInfo-hotel-name'>{name} <span>{starRating}성급</span></div>
                <div className='HotelInfo-hotel-price'>
                    <div className='HotelInfo-price-per-oneday'><span>{old}</span> {current}</div>
                    <div className='HotelInfo-price-per-oneday-title'>{info}</div>
                    <div className='HotelInfo-price-total'>{totalPrice[1]} {totalPrice[3]}</div>
                    <div className='HotelInfo-price-summary'>{summary}</div>
                </div>
            </div>

            {/* 호텔 사진 보여주기 */}
            <div className='HotelInfo-photos'>
                <div className='HotelInfo-main-photo'>
                    <img src={!isArrayNull(photos)? photos[index].mainUrl : ''} alt="hotel-main-photo"/>
                </div>
                <div className='HotelInfo-sub-photos'>
                    {!isArrayNull(photos) && photos.map( (photo, index) => {
                        if(index < 4){
                            return (
                                <div className='HotelInfo-sub-photo' key={index} onClick={() => changePhoto(index)}>
                                    <img src={photo.mainUrl} alt='hotel-sub-photo'/>
                                </div>
                            )
                        }
                    })}
                </div>
            </div>

            {/* 호텔 리뷰 보여주기 */}
            <div className='HotelInfo-reviews'>
                <div className='HotelInfo-total-review'>
                    <div className={`HotelInfo-rating-badge ${parseInt(rating) < 8 ? 'HotelInfo-rating-badge-gray' : ''}`}>{rating}</div>
                    <div className='HotelInfo-rating-badgeText'> {badgeText}</div>
                </div>
                <div className='HotelInfo-user-reviews'>{!isArrayNull(reviews) && reviews.map( (review, index) => {
                    return (
                        <Review key={index} review={review}/>
                    )
                })}</div>
            </div>
        </div>
    )
}

export default HotelInfo

HotelInfo.js 파일을 위와 같이 수정하자!

import { Review } from 'components'

Review 컴포넌트를 사용하기 위하여 임포트한다.

import hotelReviews from '../hotelReviews'

호텔 리뷰 데이터를 사용하기 위하여 임포트한다.

 const [reviews, setReviews] = useState([])

hotels.com API 서버로부터 가져온 리뷰 데이터를 저장하기 위하여 reviews 상태를 선언한다. 

 const reviews = await getReviews(`https://hotels-com-provider.p.rapidapi.com/v1/hotels/reviews?locale=en_US&hotel_id=${id}`)

호텔 ID 값을 이용하여 서버로부터 호텔 리뷰 데이터를 가져온다.

setReviews(reviews)

서버로부터 가져온 데이터로 reviews 상태를 업데이트한다.

const getReviews = async (url) => {
    // const data = await fetchHotelsCom(url)
    // return data

    const { groupReview } = handleNullObj(hotelReviews)
    const { reviews } = !isArrayNull(groupReview) ? handleNullObj(groupReview[0]) : []

    return reviews
}

파라미터로 전달된 url 로부터 필요한 데이터를 서버로부터 가져온다. 여기서는 reviews 배열을 반환한다. isArrayNull, handleNullObj 함수를 사용하여 데이터 유효성 검증을 수행한다. 

{/* 호텔 리뷰 보여주기 */}
<div className='HotelInfo-reviews'>
    <div className='HotelInfo-total-review'>
        <div className={`HotelInfo-rating-badge ${parseInt(rating) < 8 ? 'HotelInfo-rating-badge-gray' : ''}`}>{rating}</div>
        <div className='HotelInfo-rating-badgeText'> {badgeText}</div>
    </div>
    <div className='HotelInfo-user-reviews'>{!isArrayNull(reviews) && reviews.map( (review, index) => {
        return (
            <Review key={index} review={review}/>
        )
    })}</div>
</div>

Review 컴포넌트와 reviews 상태를 사용하여 화면에 사용자 리뷰를 보여준다. 

.HotelInfo-container{
    width: 60%;
    margin: 0 auto;
    /* border: 1px solid red; */

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.HotelInfo-header{
    width: 100%;
    margin-top: 50px;
    /* border: 1px solid orange; */

    display: flex;
    justify-content: center;
}
.HotelInfo-hotel-name{
    flex: 1;
    font-size: 1.3rem;
    font-weight: bold;
}
.HotelInfo-hotel-name span{
    font-size: 0.9rem;
    color: red;
}
.HotelInfo-hotel-price{
    width: 200px;
    /* border: 1px solid green; */
    text-align: right;
}
.HotelInfo-price-per-oneday{
    font-size: 1.5rem;
    font-weight: bold;
}
.HotelInfo-price-per-oneday span{
    font-size: 0.8rem;
    text-decoration: line-through;
}
.HotelInfo-price-per-oneday-title{
    font-size: 0.8rem;
    color: #525252;
}
.HotelInfo-price-total{
    font-weight: bold;
    margin-top: 10px;
}
.HotelInfo-price-summary{
    font-size: 0.8rem;
    color: #525252;
}

.HotelInfo-photos{
    width: 100%;
    margin-top: 100px;
    /* border: 1px solid blue; */

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.HotelInfo-main-photo{
    width: 100%;
    height: 600px;
    border-radius: 5px;
    /* border: 1px solid red; */
    overflow: hidden;
}
.HotelInfo-main-photo img{
    width: 100%;
    height: 100%;
}
.HotelInfo-sub-photos{
    width: 100%;
    /* border: 1px solid green; */

    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
}
.HotelInfo-sub-photo{
    flex: 1;
    height: 200px;
    overflow: hidden;
    cursor: pointer;
    border-radius: 5px;
    margin: 2px;
}
.HotelInfo-sub-photo:hover{
    opacity: 0.8;
    transform: scale(1.01);
}
.HotelInfo-sub-photo img{
    width: 100%;
    height: 100%;
}

/* 호텔 리뷰 보여주기 */
.HotelInfo-reviews{
    width: 100%;
    margin-top: 50px;
    border-top: 1px solid lightgray;
    padding-top: 20px;

    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-start;
}
.HotelInfo-total-review{
    width: 250px;
    flex-shrink: 0;

    display: flex;
    align-items: flex-end;
}
.HotelInfo-rating-badge{
    background-color: #218242;
    color: white;
    padding-left: 5px;
    padding-right: 5px;
    padding-bottom: 2px;
    border-radius: 2px;
}
.HotelInfo-rating-badge-gray{
    background-color: #a9a9a9;
}
.HotelInfo-rating-badgeText{
    font-size: 1rem;
    font-weight: bold;
    margin-left: 10px;
}
.HotelInfo-user-reviews{
    flex: 1;
    /* border: 1px solid blue; */
}

HotelInfo.css 파일을 위와 같이 수정하자!

사용자 리뷰 목록을 출력한 화면

 

728x90