From 5cdf8624d6fb659a124481cfaa3c999894715432 Mon Sep 17 00:00:00 2001 From: NCanggoro <ansianggoro@gmail.com> Date: Sun, 17 Sep 2023 16:29:53 +0700 Subject: [PATCH] add best locationsA --- src/constants/api.ts | 4 +- src/pages/BestLocations/index.tsx | 198 ++++++++++++++++++++++++++++++ src/pages/BestLocations/style.css | 51 ++++++++ src/pages/Discovery/index.tsx | 10 ++ src/pages/NewsEvents/index.tsx | 10 ++ src/pages/Stories/index.tsx | 10 ++ src/pages/index.tsx | 10 +- src/services/index.ts | 11 +- src/services/locations.ts | 33 ++++- 9 files changed, 326 insertions(+), 11 deletions(-) create mode 100644 src/pages/BestLocations/index.tsx create mode 100644 src/pages/BestLocations/style.css create mode 100644 src/pages/Discovery/index.tsx create mode 100644 src/pages/NewsEvents/index.tsx create mode 100644 src/pages/Stories/index.tsx diff --git a/src/constants/api.ts b/src/constants/api.ts index 0053020..c60eec5 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -4,12 +4,14 @@ const SIGNUP_URI = `${BASE_URL}/user/signup` const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`; -const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/recent-locations/ratings` +const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings` +const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent` const GET_LOCATION_URI = `${BASE_URL}/location`; export { BASE_URL, GET_LIST_RECENT_LOCATIONS_RATING_URI, + GET_LIST_TOP_LOCATIONS, GET_LIST_LOCATIONS_URI, GET_LOCATION_URI, SIGNUP_URI diff --git a/src/pages/BestLocations/index.tsx b/src/pages/BestLocations/index.tsx new file mode 100644 index 0000000..fc75777 --- /dev/null +++ b/src/pages/BestLocations/index.tsx @@ -0,0 +1,198 @@ +import { useEffect, useState } from "preact/hooks"; +import { getListTopLocationsService } from "../../services"; +import { DefaultSeparator } from "../../components"; +import { TargetedEvent } from "preact/compat"; +import './style.css'; + + +interface TopLocation { + row_number: Number, + id: Number, + name: String, + thumbnail: NullValueRes<"String", String>, + address: String, + google_maps_link: string, + regency_name: string, + critic_score: NullValueRes<"Int32", Number>, + critic_count: Number, + user_score: NullValueRes<"Int32", Number>, + user_count: Number, + critic_bayes: Number, + user_bayes: Number, + avg_bayes: Number, +} + +const REGIONS = [ + 'All', + 'Sumatera', + 'Jawa', + 'Kalimantan', + 'Nusa Tenggara', + 'Maluku', + 'Papua', + 'Sulawesi', +]; + +const REVIEWERS_TYPE =[ + 'All', + 'Critics', + 'Users' +] + +const MIN_REVIEWS = [ + 2, + 5, + 7, + 11 +] + +function BestLocation() { + const [page, setPage] = useState<number>(1); + const [topLocations, setTopLocations] = useState<Array<TopLocation>>([]) + const [pageState, setPageState] = useState({ + filterScoreType: 'all', + filterScoreTypeidx: 1, + filterRegionTypeName: 'All', + filterRegionType: 0, + }) + + async function getTopLocations() { + try { + const res = await getListTopLocationsService({ page: page, page_size: 20, order_by: pageState.filterScoreTypeidx, region_type: pageState.filterRegionType }) + setTopLocations(res.data) + + } catch (err) { + console.log(err) + } + } + + function onChangeReviewType(e: TargetedEvent<HTMLAnchorElement>, reviewer_type: string, i: number) { + e.preventDefault() + setPageState({...pageState, filterScoreType: reviewer_type, filterScoreTypeidx: i }) + } + + function onChangeRegionType(e: TargetedEvent<HTMLAnchorElement>, region_name: string, type: number) { + e.preventDefault(); + setPageState({ ...pageState, filterRegionTypeName: region_name, filterRegionType: type}) + } + + useEffect(() => { + getTopLocations() + }, [pageState]) + + + return ( + <> + <div className={'content main-content mt-3'}> + <section name={"Top locations header"}> + <h1 className={'text-3xl mb-5 font-bold'}>Top Locations</h1> + <div className={'regions-dropdown text-xs pr-3 inline-block'}> + <p className={'inline-block'}>Regions</p> + <a style={{ cursor: 'pointer' }}> + <p className={'ml-2 inline-block'}>{pageState.filterRegionTypeName}</p> + <svg style={{ display: 'inline-block' }} fill='white' xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" /></svg> + </a> + <div className={'dropdown-content text-sm'}> + {REGIONS.map((x, index) => ( + <a onClick={(e) => onChangeRegionType(e, x, index) } className={'block pt-1'}>{x}</a> + ))} + </div> + </div> + <div className={'regions-dropdown text-xs pr-3 inline-block'}> + <p className={'inline-block'}>Min. Reviews</p> + <a style={{ cursor: 'pointer' }}> + <p className={'ml-2 inline-block'}>All</p> + <svg style={{ display: 'inline-block' }} fill='white' xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" /></svg> + </a> + <div className={'dropdown-content-min-reviews text-sm'}> + {MIN_REVIEWS.map(x => ( + <a className={'block pt-1'}>{x}</a> + ))} + </div> + </div> + <div className={'regions-dropdown text-xs pr-3 inline-block'}> + <p className={'inline-block'}>Tags</p> + <a style={{ cursor: 'pointer' }}> + <p className={'ml-2 inline-block'}>All</p> + <svg style={{ display: 'inline-block' }} fill='white' xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" /></svg> + </a> + <div className={'dropdown-content text-sm'}> + {/* {REGIONS.map(x => ( + <a className={'block pt-1'}>{x}</a> + ))} */} + </div> + </div> + <DefaultSeparator /> + </section> + + <section name={'Main content'}> + <div className={'flex flex-row pt-10 pb-10'}> + <div className={'mr-5'} style={{ flex: 1}}> + {topLocations.map(x => ( + <> + <div style={{ float: 'right', cursor: 'pointer' }}> + <a className={'text-xl'}> + ... + </a> + </div> + <div className={'mb-2'}> + <p className={'text-xl'}>{x.row_number}.{x.name}</p> + </div> + <div style={{ maxWidth: 200, maxHeight: 200, margin: '0 30px 30px 10px', float: 'left' }}> + <img src={x.thumbnail.String.toString()} style={{ width: '100%', objectFit: 'cover', height: 200 }} /> + </div> + <div>{x.address}</div> + <div>$$$ (IDR 1000-12000)</div> + <div><a href={x.google_maps_link} target={'_'}> Maps </a></div> + <div className={'mt-6'}> + <div className={'text-xs bg-secondary'} style={{ width: 160, display: 'inline-block', borderRadius: 7 }}> + <div className={'text-center p-1 bg-tertiary text-primary'} style={{ borderTopRightRadius: 7, borderTopLeftRadius: 7}}>CRITICS SCORE</div> + <div className={"flex flex-row items-center p-3"}> + <div className={'mr-3 users-score-bar'}> + <p className={`text-xl text-center ${x.critic_score.Valid ? 'font-bold' : ''}`}>{x.critic_score.Valid ? Number(x.critic_score.Int32) / Number(x.critic_count) * 10 : "N/A"}</p> + <div className={"mt-1"} style={{ height: 4, width: 40, backgroundColor: "#72767d" }}> + <div style={{ height: 4, width: ` ${x.critic_score.Valid ? Number(x.critic_score.Int32) / Number(x.critic_count) * 10 : 0}%`, backgroundColor: 'green' }} /> + </div> + </div> + <p className={'text-xs users-score'}>{x.critic_count} reviews</p> + </div> + </div> + <div className={'text-xs bg-secondary ml-3'} style={{ width: 160, display: 'inline-block', borderRadius: 7 }}> + <div className={'text-center p-1 bg-tertiary text-primary'} style={{ borderTopLeftRadius: 7, borderTopRightRadius: 7}}>USERS SCORE</div> + <div className={"flex flex-row items-center p-3"}> + <div className={'mr-3 users-score-bar'}> + <p className={`text-xl text-center ${x.user_score.Valid ? 'font-bold' : ''}`}>{x.user_score.Valid ? x.user_score.Int32 : "N/A" }</p> + <div className={"mt-1"} style={{ height: 4, width: 40, backgroundColor: "#72767d" }}> + <div style={{ height: 4, width: ` ${x.user_score.Valid ? x.user_score.Int32 : 0}%`, backgroundColor: 'green' }} /> + </div> + </div> + <p className={'text-xs users-score'}>{x.user_count} reviews</p> + </div> + </div> + </div> + <div style={{ clear: 'both'}}/> + </> + ))} + </div> + <div className={'p-4 bg-secondary'}style={{ minWidth: 300}}> + <div className={'h-30 bg-primary p-4'}> + {REVIEWERS_TYPE.map((x, idx) => ( + <a + onClick={(e) => onChangeReviewType(e, x.toLowerCase(), idx+1)} + href={'#'} + style={ pageState.filterScoreType == x.toLowerCase() ? { pointerEvents: 'none'} : ''} + > + <div className={`pt-1 pb-1 ${pageState.filterScoreType == x.toLowerCase() ? 'pl-1 bg-tertiary selected-reviewer-filter' : ''}`}>{x} Score</div> + </a> + ))} + </div> + </div> + </div> + </section> + </div> + </> + ) + +} + +export default BestLocation; \ No newline at end of file diff --git a/src/pages/BestLocations/style.css b/src/pages/BestLocations/style.css new file mode 100644 index 0000000..fe62f1f --- /dev/null +++ b/src/pages/BestLocations/style.css @@ -0,0 +1,51 @@ +.dropdown-content { + display: none; + position: absolute; + background-color: #202225; + min-width: 120px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +.dropdown-content-min-reviews { + margin-left: 60px; + display: none; + position: absolute; + background-color: #202225; + min-width: 70px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +.regions-dropdown:hover .dropdown-content { + display: block; +} + +.regions-dropdown:hover .dropdown-content-min-reviews { + display: block; +} + +.dropdown-content a { + cursor: pointer; + padding: 5px; +} + +.dropdown-content a:hover { + background-color: rgb(73, 73, 73); + color: white; +} + +.dropdown-content-min-reviews a { + cursor: pointer; + padding-left: 10px; +} + +.dropdown-content-min-reviews a:hover { + background-color: rgb(73, 73, 73); + color: white; +} + +a .selected-reviewer-filter:hover{ + color: white; + cursor: default; +} \ No newline at end of file diff --git a/src/pages/Discovery/index.tsx b/src/pages/Discovery/index.tsx new file mode 100644 index 0000000..187278a --- /dev/null +++ b/src/pages/Discovery/index.tsx @@ -0,0 +1,10 @@ +function Discovery() { + return( + <> + <h1>Best PLaces</h1> + </> + ) + +} + +export default Discovery; \ No newline at end of file diff --git a/src/pages/NewsEvents/index.tsx b/src/pages/NewsEvents/index.tsx new file mode 100644 index 0000000..86ae15a --- /dev/null +++ b/src/pages/NewsEvents/index.tsx @@ -0,0 +1,10 @@ +function NewsEvent() { + return( + <> + <h1>Best PLaces</h1> + </> + ) + +} + +export default NewsEvent; \ No newline at end of file diff --git a/src/pages/Stories/index.tsx b/src/pages/Stories/index.tsx new file mode 100644 index 0000000..9c6ec40 --- /dev/null +++ b/src/pages/Stories/index.tsx @@ -0,0 +1,10 @@ +function Story() { + return( + <> + <h1>Best PLaces</h1> + </> + ) + +} + +export default Story; \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index fcbca58..574a333 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,13 @@ import Home from "./Home"; +import BestLocation from "./BestLocations"; +import Discovery from "./Discovery"; +import Story from "./Stories"; +import NewsEvent from "./NewsEvents"; export { - Home + Home, + BestLocation, + Discovery, + Story, + NewsEvent } \ No newline at end of file diff --git a/src/services/index.ts b/src/services/index.ts index 984f465..3bc86df 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,6 +1,11 @@ -import { getListLocations, getListRecentLocationsRatings } from "./locations"; +import { + getListLocationsService, + getListRecentLocationsRatingsService, + getListTopLocationsService +} from "./locations"; export { - getListLocations, - getListRecentLocationsRatings + getListLocationsService, + getListRecentLocationsRatingsService, + getListTopLocationsService, } \ No newline at end of file diff --git a/src/services/locations.ts b/src/services/locations.ts index 8f13375..6f17fc8 100644 --- a/src/services/locations.ts +++ b/src/services/locations.ts @@ -1,4 +1,4 @@ -import { GET_LIST_LOCATIONS_URI, GET_LIST_RECENT_LOCATIONS_RATING_URI } from "../constants/api"; +import { GET_LIST_LOCATIONS_URI, GET_LIST_RECENT_LOCATIONS_RATING_URI, GET_LIST_TOP_LOCATIONS } from "../constants/api"; import { client } from "./config"; import statusCode from "./status-code"; @@ -9,10 +9,12 @@ const initialState: any = { type getListLocationsArg = { page: number, - page_size: number + page_size: number, + order_by?: number, + region_type?: number } -async function getListLocations ({ page, page_size}: getListLocationsArg) { +async function getListLocationsService ({ page, page_size}: getListLocationsArg) { const newState = {...initialState}; const url = `${GET_LIST_LOCATIONS_URI}?page=${page}&page_size=${page_size}` try { @@ -30,7 +32,7 @@ async function getListLocations ({ page, page_size}: getListLocationsArg) { } } -async function getListRecentLocationsRatings (page_size: Number) { +async function getListRecentLocationsRatingsService (page_size: Number) { const newState = {...initialState}; const url = `${GET_LIST_RECENT_LOCATIONS_RATING_URI}?page_size=${page_size}` try { @@ -48,7 +50,26 @@ async function getListRecentLocationsRatings (page_size: Number) { } } +async function getListTopLocationsService({ page, page_size, order_by, region_type}: getListLocationsArg) { + const newState = {...initialState}; + const url = `${GET_LIST_TOP_LOCATIONS}?page=${page}&page_size=${page_size}&order_by=${order_by}®ion_type=${region_type}` + try { + const response = await client({method: 'GET', url: url}) + switch (response.request.status) { + case statusCode.OK: + newState.data = response.data; + return newState; + default: + newState.error = response.data; + return newState + } + } catch (error) { + console.log(error) + } +} + export { - getListLocations, - getListRecentLocationsRatings + getListLocationsService, + getListRecentLocationsRatingsService, + getListTopLocationsService, } \ No newline at end of file