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}&region_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