第二十四課:resveration 將optionsContext條件行為計算後,同步更新到各個需要的component內
前一天我們完成了相關的SearchItem搜尋欄,並讓真實資料得以被filter今天將來把剩下的hotelPage做結尾,包括完成room的實際訂房UI與資料等連動前置作業。
這邊我們來示範一般產品類別如何跳轉到該產品詳細頁面,一樣我們會利用到useLocation,而第一次使用useLocation是在homePage與hotelsListPage的搜尋連接上,利用到了useNavgate與useLocation.state的配合,而這次我們要來使用useLocation的其他項目
<Link to={`/hotels/${dataDetail._id}`}>
了解link可以這樣做後,最前面的homePage的熱門景點如果也想要直接點擊,就跳出該熱門飯店的詳細資料就可以這樣做。
並我們將使用useLocation.pathname來抓取我們傳在分頁url的hotelid
並抓取到hotelid後,我們要用它來繼續fetchdata,所以一樣使用到我們的useFetch加上我們的getHotel method Api,
const locationHotelUrl = useLocation()
const hotelid = locationHotelUrl.pathname.split("/")[2]
const { data, loading, error } = useFetch(`/hotels/find/${hotelid}`)
console.log(data) //可以來檢查一下是否抓到正確的資料
上述成功後,useFetch應該也會成功抓取到data,並我們就可以使用data來把我們的資料都替換掉。
將資料成功換上後
hotel Page div
接下來的目標,我們要將他製作空房情況,並先講room使用useFetch測試看能不能成功抓取我們該飯店的所有房型,但還未加入該時段確定有房間的情況,所以第一步我們也一樣先來完成UI設計,首先先製作並跳轉去這個空房情況,將把這個空房情況命名為reservation
const {user} =useContext(LoginContext)
const [openReservation, setOpenReservation] = useState(false);
const navgator = useNavigate()
const handleReservation = () => {
if (user) {
setOpenReservation(true)
} else {
navgator("/login")
}
}
並這個空房情況彈跳視窗到時候,不會歸類在哪一個div裡面就是直接顯示全螢幕,所以將他放在footer下面
<div className='Reservation'>
<div className="container">
<div className="wrapper">
<div className="title">
<h2>空房情況</h2>
<h3>幾號幾號幾晚 大人小孩 </h3>
<FontAwesomeIcon icon={faCircleXmark} />
</div>
<div className="body">
<div className="roomTitle">
<div>客房類型</div>
<div>適合人數</div>
<div>房型今日價格</div>
<div>選擇房型編號</div>
</div>
<div className='roomData'>
<div className='roomColumn'>
room資料到時候存放地點
</div>
<button className='reservationbtn'> 現在預訂</button>
</div>
</div >
</div>
</div>
</div >
reservation.scss連結,因篇幅關係這邊scss就先不花時間在講解。
大概styling後,我們要先來控制他的開關與讓Room資料可以被導入,所以要做到這兩件事,我們必須把控制開關的useState導入,並導入hotelid才能用這個id去把所有的hotel's rooms抓吃出來而用到的Api method是我們之前寫的getHotelRooms.
<Reservation openSetting={setOpenReservation} hotelid={hotelid} />
完成這邊後,大致就能展現出我們hotel與相關room的所有資料,
<div className='roomColumn'>
{loading && <>載入中</>}
{data.map((room, i) =>
(
<div className="roomInfo" key={i}>
<div >
{room.title}<br/><p>{room.desc}</p>
</div>
<div >{room.maxPeople}</div>
<div >TWD {room.price}</div>
<div >
{room.roomNumbers?.map((item, i) => (
<span key={i}>
<input type="checkbox" value={item._id} />
{item.number}<br/>
</span>
))}
</div>
</div>)
)}
</div>
上述的資料型態可以邊打邊與我們之前一開始所設置的room Model去做對照,並接下來在確定送出預定資料前,我們要先來補之前的缺,將我們的optionsContext做各個分頁的資料串連。
這邊將來處理optionsContext中最重要的dates並同時都串接到我們searchItem、hotelPage到我們的現在新的空房情況,來顯示這些訂房目前的條件選擇。
首先先來完成我們的hotelsList中的searchItem,讓他同時顯示,一共幾晚並會這樣得價格是多少。這邊會有些細節處理,如沒有選擇日期則先跳出飯店的推薦就不顯示價格,因為無法計算的細節或是針對這一開始的日期選擇,就只跳出時間上可以的飯店(目前我們是先以預訂時如果不行我們會把可以勾選的checkBox給取消,讓他不能預訂)但因為我們資料庫資料太少與篇幅關係,將先以如何計算與串接為主。
<SearchItem active={index==0 && "active"} key={item._id} dataDetail={item} conditions={options} dates={date} />
所以這邊我們會發現getTime會讓我們回傳的datesLength都是呈現毫秒的狀態,但我們想要的是顯示幾晚,所以我們可以直接讓他除以一天的毫秒數也就是86400*1000
export const ReservationDatesAndPrice = (startDate, endDate, hotelsPrice, roomsPrice)=>{
const MSecond_per_day=1000*86400;
const DatesLength = (Math.abs(endDate?.getTime()-startDate?.getTime())||0 )/MSecond_per_day
const totalHotelsPrice = DatesLength*hotelsPrice||0
const totalRoomsPrice = DatesLength*roomsPrice||0
return{DatesLength,totalHotelsPrice,totalRoomsPrice}
}
所以回到searchItem那邊把我們函數導入並使用
const hotelsPrice = dataDetail.cheapestPrice
const { DatesLength, totalHotelsPrice } = ReservationDatesAndPrice(dates[0]?.startDate, dates[0]?.endDate, hotelsPrice)
好了以後就可以替換成我們要的資訊,並特別處理一下0晚的情況,
所以在我們的detailRight中的optionDes來新增我們的條件,
<span className="optionDes">
{DatesLength== 0?<>請先選擇住宿日期</> : `總共 ${DatesLength} 晚`}
<br/>{conditions.adult} 位大人 {conditions.children != 0 && `、${conditions.children} 位小孩`}
</span>
<span className="price">
{DatesLength== 0 ? `TWD ${dataDetail.cheapestPrice} 價格`:`TWD ${totalHotelsPrice} 價格`}
</span>
並利用這個方式也來快速處理我們的hotel Page與顯示在我們的Reservaiton最終空房情況的預訂上。
hotel Page內容
const { city, date, options,dispatch } = useContext(OptionsContext)
const hotelsPrice = data.cheapestPrice
const { DatesLength, totalHotelsPrice } = ReservationDatesAndPrice(date[0]?.startDate, date[0]?.endDate, hotelsPrice)
<div className="hotelDesPrice">
<h2>住宿特色 </h2>
<h3> {format(date[0]?.startDate, "MM/dd/yyyy")} - {format(date[0]?.endDate, "MM/dd/yyyy")}</h3>
<p>入住 {DatesLength} 晚的最佳選擇!
此住宿位於台南評分最高的地區,地理位置評分高達 {data.rating} 分
深受獨行旅客歡迎</p>
<h2>TWD {totalHotelsPrice}</h2>
<button onClick={handleReservation} >現在就預訂</button>
</div>
並最後的Reservation Component,快速完成,這邊DatesLength可以選擇要在Reservation在計算一次還是直接用props從我們的hotelPage傳入到Reservation Component之中,而我選擇後者,畢竟不想在計算重複的東西一次。
最後附上day24的完整版
github Day24完成的連結
這邊其實已經快進入尾聲,後續都是熟能生巧的一些小功能補上,所以想在day24-25內趕快完成,篇幅上略有刪減但還是有些小多,這邊可以練習是站在使用者使用的角度去設想,不管在哪一個行業以全端或是自己創業者的角度,要如何與工程師溝通有概念,其實在完成時就會發現光是看似簡單的訂房卻包含著很多小巧思跟細節,本作者雖然是第一次開發訂房的專案,這次卻有學到很多,而這些經驗很難用口述傳達,反而會是自己在越來越深入時,覺得自己還有很多細節要去完成跟精進,如這個專案順利結案,預計下一個大專案會是想要做台灣本土Dcard的致敬,並到時候如果真的有做,未來我們大家在YT上見,並讓我先去學個剪接。