204 lines
9.0 KiB
TypeScript
Executable File
204 lines
9.0 KiB
TypeScript
Executable File
import { TargetedEvent } from "preact/compat";
|
|
import { useEffect, useState } from "preact/hooks";
|
|
import { getListTopLocationsService } from "../../services";
|
|
import { DefaultSeparator } from "../../components";
|
|
import './style.css';
|
|
import { useClick, useFloating, useInteractions } from "@floating-ui/react";
|
|
import { Link } from "react-router-dom";
|
|
|
|
interface TopLocation {
|
|
row_number: Number,
|
|
id: Number,
|
|
name: String,
|
|
thumbnail: string | null,
|
|
address: String,
|
|
google_maps_link: string,
|
|
regency_name: string,
|
|
critic_score: Number,
|
|
critic_count: Number,
|
|
user_score: 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 => (
|
|
<>
|
|
{/* UNCOMMENT....IF THERES A PURPOSE FOR RIGHT THREE DOTS */}
|
|
{/* <div style={{ float: 'right', cursor: 'pointer' }}>
|
|
<a className={'text-xl'}>
|
|
...
|
|
</a>
|
|
</div> */}
|
|
<div className={'mb-2 best-locations-title'}>
|
|
<Link className={'text-xl'} to={`/location/${x.id}`}>{x.row_number}.{x.name}</Link>
|
|
</div>
|
|
<div style={{ maxWidth: 200, maxHeight: 200, margin: '0 30px 30px 10px', float: 'left' }}>
|
|
<Link to={`/location/${x.id}`} >
|
|
<img src={x.thumbnail ? x.thumbnail : ""} loading={'lazy'} style={{ width: '100%', objectFit: 'cover', height: '100%', aspectRatio: '1/1' }} />
|
|
</Link>
|
|
</div>
|
|
<div className={'text-md font-bold'}>{x.regency_name}</div>
|
|
<div className={'text-xs mb-2'}>{x.address}</div>
|
|
<div>$$$ (IDR 1000-12000)</div>
|
|
<a href={x.google_maps_link} target={'_'}><div className={'text-sm mt-2 items-center'}><svg style={{ display: 'inline-block', marginBottom: 3 }} xmlns="http://www.w3.org/2000/svg" height="12" fill={'white'} viewBox="0 -960 960 960" width="12 "><path d="M480-480q33 0 56.5-23.5T560-560q0-33-23.5-56.5T480-640q-33 0-56.5 23.5T400-560q0 33 23.5 56.5T480-480Zm0 294q122-112 181-203.5T720-552q0-109-69.5-178.5T480-800q-101 0-170.5 69.5T240-552q0 71 59 162.5T480-186Zm0 106Q319-217 239.5-334.5T160-552q0-150 96.5-239T480-880q127 0 223.5 89T800-552q0 100-79.5 217.5T480-80Zm0-480Z" /></svg>Maps Location</div></a>
|
|
<div className={'mt-4'}>
|
|
<div className={'text-xs bg-secondary'} style={{ width: 160, display: 'inline-block', borderRadius: 5 }}>
|
|
<div className={'text-center p-1 bg-tertiary text-primary'} style={{ borderTopRightRadius: 5, borderTopLeftRadius: 5 }}>CRITICS SCORE</div>
|
|
<div className={"flex flex-row items-center p-2"}>
|
|
<div className={'mr-3 users-score-bar'}>
|
|
<p className={`text-xl text-center ${x.critic_score !== 0 ? 'font-bold' : ''}`}>{x.critic_score !== 0 ? Number(x.critic_score) / 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_count !== 0 ? Number(x.critic_score) / 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: 5 }}>
|
|
<div className={'text-center p-1 bg-tertiary text-primary'} style={{ borderTopLeftRadius: 5, borderTopRightRadius: 5 }}>USERS SCORE</div>
|
|
<div className={"flex flex-row items-center p-2"}>
|
|
<div className={'mr-3 users-score-bar'}>
|
|
<p className={`text-xl text-center ${x.user_score !== 0 ? 'font-bold' : ''}`}>{x.user_score !== 0 ? x.user_score : "N/A"}</p>
|
|
<div className={"mt-1"} style={{ height: 4, width: 40, backgroundColor: "#72767d" }}>
|
|
<div style={{ height: 4, width: ` ${x.user_score !== 0 ? x.user_score : 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 right-filter'}>
|
|
{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; |