add location detail

This commit is contained in:
NCanggoro 2023-09-20 21:26:25 +07:00
parent 2c7f1cf7a0
commit e454c00847
13 changed files with 576 additions and 50 deletions

View File

@ -3,6 +3,9 @@ import { BrowserRouter as Router } from 'react-router-dom'
import './app.css'
import { DefaultLayout } from './layouts'
import routes from './routes'
import "yet-another-react-lightbox/styles.css";
export function App() {
return (
<>

View File

@ -7,12 +7,17 @@ const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
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`;
const GET_LOCATION_TAGS_URI = `${BASE_URL}/location/tags`
const GET_IMAGES_BY_LOCATION_URI = `${BASE_URL}/images/location`
export {
BASE_URL,
SIGNUP_URI,
GET_LIST_RECENT_LOCATIONS_RATING_URI,
GET_LIST_TOP_LOCATIONS,
GET_LIST_LOCATIONS_URI,
GET_LOCATION_URI,
SIGNUP_URI
GET_LOCATION_TAGS_URI,
GET_IMAGES_BY_LOCATION_URI,
}

View File

@ -3,6 +3,7 @@ import { getListTopLocationsService } from "../../services";
import { DefaultSeparator } from "../../components";
import { TargetedEvent } from "preact/compat";
import './style.css';
import { useNavigate } from "react-router-dom";
interface TopLocation {
@ -13,9 +14,9 @@ interface TopLocation {
address: String,
google_maps_link: string,
regency_name: string,
critic_score: NullValueRes<"Int32", Number>,
critic_score: Number,
critic_count: Number,
user_score: NullValueRes<"Int32", Number>,
user_score: Number,
user_count: Number,
critic_bayes: Number,
user_bayes: Number,
@ -56,6 +57,8 @@ function BestLocation() {
filterRegionType: 0,
})
const navigate = useNavigate()
async function getTopLocations() {
try {
const res = await getListTopLocationsService({ page: page, page_size: 20, order_by: pageState.filterScoreTypeidx, region_type: pageState.filterRegionType })
@ -76,6 +79,18 @@ function BestLocation() {
setPageState({ ...pageState, filterRegionTypeName: region_name, filterRegionType: type})
}
function onNavigateToDetail(
id: Number,
critic_count: Number,
critic_score: Number,
user_count: Number,
user_score: Number,
) {
navigate(`/location/${id}`, { state: { user_score: user_score, user_count: user_count, critic_score: critic_score, critic_count: critic_count }})
}
useEffect(() => {
getTopLocations()
}, [pageState])
@ -137,11 +152,11 @@ function BestLocation() {
</a>
</div> */}
<div className={'mb-2 best-locations-title'}>
<a className={'text-xl'} href={`/location/${x.id}`}>{x.row_number}.{x.name}</a>
<a className={'text-xl'} href={`/location/${x.id}`} onClick={() => onNavigateToDetail(x.id, x.critic_count, x.critic_score, x.user_count, x.user_score)}>{x.row_number}.{x.name}</a>
</div>
<div style={{ maxWidth: 200, maxHeight: 200, margin: '0 30px 30px 10px', float: 'left' }}>
<a href={`/location/${x.id}`}>
<img src={x.thumbnail.String.toString()} style={{ width: '100%', objectFit: 'cover', height: '100%', aspectRatio: '1/1' }} />
<a href={`/location/${x.id}`} onClick={() => onNavigateToDetail(x.id, x.critic_count, x.critic_score, x.user_count, x.user_score)}>
<img src={x.thumbnail.String.toString()} loading={'lazy'} style={{ width: '100%', objectFit: 'cover', height: '100%', aspectRatio: '1/1' }} />
</a>
</div>
<div className={'text-md font-bold'}>{x.regency_name}</div>
@ -153,9 +168,9 @@ function BestLocation() {
<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.Valid ? 'font-bold' : ''}`}>{x.critic_score.Valid ? Number(x.critic_score.Int32) / Number(x.critic_count) * 10 : "N/A"}</p>
<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_score.Valid ? Number(x.critic_score.Int32) / Number(x.critic_count) * 10 : 0}%`, backgroundColor: 'green' }} />
<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>
@ -165,9 +180,9 @@ function BestLocation() {
<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.Valid ? 'font-bold' : ''}`}>{x.user_score.Valid ? x.user_score.Int32 : "N/A" }</p>
<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.Valid ? x.user_score.Int32 : 0}%`, backgroundColor: 'green' }} />
<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>
@ -178,8 +193,8 @@ function BestLocation() {
</>
))}
</div>
<div className={'p-4 bg-secondary'}style={{ minWidth: 300}}>
<div className={'h-30 bg-primary p-4'}>
<div className={'p-4 bg-secondary'} style={{ minWidth: 300}}>
<div className={'h-30 bg-primary p-4'} style={{ position: 'sticky', alignSelf: 'flex-start', top: 10}}>
{REVIEWERS_TYPE.map((x, idx) => (
<a
onClick={(e) => onChangeReviewType(e, x.toLowerCase(), idx+1)}

View File

@ -6,12 +6,14 @@ import popular_user_review from '../../datas/popular_user_reviews.json';
import './style.css';
import { useEffect, useState } from 'preact/hooks';
import { getListRecentLocationsRatingsService } from '../../services';
import { useNavigate } from 'react-router-dom';
type NewPlaces = {
id: Number,
name: string,
thumbnail: NullValueRes<'String', string>,
regency_name: NullValueRes<'String', string>,
regency_name: String,
province_name: String,
critic_score: Number,
critic_count: Number,
user_score: Number,
@ -30,6 +32,9 @@ function Home() {
const [recentLocations, setRecentLocations] = useState<Array<NewPlaces>>([])
// const [isLoading, setIsLoading] = useState<boolean>(true)
const navigate = useNavigate()
async function getRecentLocations() {
try {
const locations = await getListRecentLocationsRatingsService(12)
@ -40,6 +45,18 @@ function Home() {
}
}
function onNavigateToDetail(
id: Number,
critic_count: Number,
critic_score: Number,
user_count: Number,
user_score: Number,
) {
navigate(`/location/${id}`, { state: { user_score: user_score, user_count: user_count, critic_score: critic_score, critic_count: critic_count } })
}
useEffect(() => {
getRecentLocations()
},[])
@ -52,30 +69,32 @@ function Home() {
<SeparatorWithAnchor pageLink='#' pageName='recently added' secondLink='#' />
{recentLocations.map((x) => (
<div className={"recently-added-section-card"}>
<div className={'border-secondary recently-img-container'}>
<img alt={x.name} src={x.thumbnail.String.toString()} loading="eager" style={{ width: '100%', height: '100%' }} />
</div>
<a onClick={() => onNavigateToDetail(x.id, x.critic_count, x.critic_score, x.user_count, x.user_score)}>
<div className={'border-secondary recently-img-container'}>
<img alt={x.name} src={x.thumbnail.String.toString()} loading="lazy" style={{ width: '100%', height: '100%' }} />
</div>
</a>
<div className={"border-primary pb-2 location-container text-sm mb-2 mt-2"}>
<p className={'location-title'}>{x.name}</p>
<p className={'text-xs mt-1'}>{x.regency_name.String}</p>
<p className={'text-xs mt-1'}>{x.regency_name}, {x.province_name}</p>
</div>
{ x.critic_score !== -1 &&
{ x.critic_count !== 0 &&
<div className={"flex flex-row items-center mb-3"}>
<div className={'mr-3 users-score-bar'}>
<p className={'text-sm text-center'}>{x.critic_score === -1 ? "NR" : x.critic_score}</p>
<p className={'text-sm text-center'}>{x.critic_score}</p>
<div style={{ height: 4, width: 30, backgroundColor: "#72767d"}}>
<div style={{ height: 4, width: `${x.critic_score === -1 ? 0 : x.critic_score}%`, backgroundColor: 'green' }} />
<div style={{ height: 4, width: `${x.critic_score}%`, backgroundColor: 'green' }} />
</div>
</div>
<p className={"users-score"}>critic score ({x.critic_count})</p>
</div>
}
{ x.user_score !== -1 &&
{ x.user_score !== 0 &&
<div className={"flex flex-row items-center"}>
<div className={'mr-3 users-score-bar'}>
<p className={'text-sm text-center'}>{x.user_score === -1 ? "NR" : x.user_score}</p>
<p className={'text-sm text-center'}>{x.user_score}</p>
<div style={{ height: 4, width: 30, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${x.user_score === -1 ? 0 : x.user_score}%`, backgroundColor: 'green' }} />
<div style={{ height: 4, width: ` ${x.user_score}%`, backgroundColor: 'green' }} />
</div>
</div>
<p className={'users-score'}>user score ({x.user_count})</p>
@ -95,7 +114,7 @@ function Home() {
{news.data.map((x: News) => (
<div class={"text-sm news-card"}>
<div className={"image-news-container"}>
<img src={x.thumbnail} className={"news-img"}/>
<img src={x.thumbnail} loading={'lazy'} className={"news-img"}/>
</div>
<a className={'news-link'} target="_blank" href={x.link}>{x.link.split("/")[2].replace(/www\./, '')}</a>
<p className={'mt-2 mb-2'}>{x.header}</p>
@ -120,7 +139,7 @@ function Home() {
{popular_user_review.data.map((x) => (
<div className={'text-sm reviews-container'}>
<div style={{ float: 'left', width: 120, margin: '0 12px 10px 0' }}>
<img src={x.thumbnail} style={{ width: '100%' }} />
<img src={x.thumbnail} loading={'lazy'} style={{ width: '100%' }} />
</div>
<p className={"text-sm location-titles"}>{x.place_name}</p>
<p className={'text-xs mb-2'}>{x.location}</p>
@ -169,7 +188,7 @@ function Home() {
{critics_users_pick.critics.map((x) => (
<div className={"pt-2 text-sm"}>
<div className={'mr-2 critics-users-image'}>
<img src={x.thumbnail} style={{ height: '100%', width: '100%', borderRadius: 3}} />
<img src={x.thumbnail} loading={'lazy'} style={{ height: '100%', width: '100%', borderRadius: 3}} />
</div>
<p className={'location-title'}>{x.name}</p>
<p className={'text-xs location-province location-title'}>{x.location}</p>

View File

@ -6,6 +6,10 @@
width: 16.6%;
}
.recently-added-section-card a:hover {
cursor: pointer;
}
.location-container {
text-align: left;
border-bottom-width: 1px;

View File

@ -0,0 +1,79 @@
.header-link{
font-size: 0.7em;
padding-bottom: 5px;
border-bottom: 1px solid #38444d;
}
.header-link a:hover{
color: white;
cursor: pointer;
}
.image-stack {
display: grid;
position: relative;
grid-template-columns: repeat(12, 1fr);
}
.image-stack__item--bottom {
grid-column: -3 / 1;
grid-row: 1;
}
.image-stack__item--middle {
margin-left: 10px;
grid-column: -2 / 1;
grid-row: 1;
padding-top: 2%;
z-index: 1;
}
.image-stack__item--top {
grid-row: 1;
grid-column: -1 / 2;
padding-top: 4%;
z-index: 2;
}
img {
width: 100%;
display: block;
}
.location-detail-container {
padding: 15px;
width: 35%;
vertical-align: top;
border: 1px solid #38444d
}
.location-detail-container div span {
font-size: 12px;
color: #a8adb3
}
.tags-box {
display: inline-block;
background-color: #484848;
border-radius: 3;
}
.tags-box a:hover {
color: white;
border-bottom: 1px solid white;
}
@media screen and (max-width: 380px) {
.header-link {
white-space: nowrap;
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: none;
padding: 0 10px;
}
.header-link::-webkit-scrollbar {
display: none;
}
}

View File

@ -1,9 +1,227 @@
import { useLocation, useParams } from 'react-router-dom';
import { getImagesByLocationService, getLocationService } from "../../services";
import { useEffect, useState } from 'preact/hooks';
import './index.css';
import Lightbox from 'yet-another-react-lightbox';
import useCallbackState from '../../types/state-callback';
import { EmptyLocationDetailResponse, LocationDetailResponse, LocationResponse, emptyLocationResponse } from './types';
function LocationDetail() {
return(
const [locationDetail, setLocationDetail] = useCallbackState<LocationDetailResponse>(EmptyLocationDetailResponse)
const [locationImages, setLocationImages] = useState<LocationResponse>(emptyLocationResponse())
const [lightboxOpen, setLightboxOpen] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState<boolean>(true)
const { state } = useLocation();
const { id } = useParams()
async function getLocationDetail() {
try {
const res = await getLocationService(Number(id))
setLocationDetail(res.data, (val) => {
getImage(val.detail.thumbnail.String.toString())
})
} catch (err) {
console.log(err)
}
}
async function getImage(thumbnail?: String) {
try {
const res = await getImagesByLocationService({ page: 1, page_size: 15, location_id: Number(id) })
res.data.images.push({ src: thumbnail })
setLocationImages(res.data)
} catch (error) {
console.log(error)
}
setIsLoading(false)
}
useEffect(() => {
getLocationDetail()
}, [])
return (
<>
<div>
LOCATION DETAIL
</div>
<div className={'content main-content mt-3'}>
<section name={"HEADER LINK"}>
<div className={'header-link text-tertiary'}>
<a style={{ display: 'inline-block' }}>OVERVIEW</a>
<a className={'ml-4'} style={{ display: 'inline-block' }}>USER REVIEWS</a>
<a className={'ml-4'} style={{ display: 'inline-block' }}>CRITIC REVIEWS</a>
<a className={'ml-4'} style={{ display: 'inline-block' }}>COMMENTS</a>
</div>
</section>
<section name={'LOCATION HEADER'}>
<div className={'pb-5'} style={{ borderBottom: '1px solid #38444d'}}>
<div className={'font-bold mt-5 text-2xl'}>
<h1>{locationDetail?.detail.name}</h1>
</div>
{isLoading ?
<div className={'mt-3'} style={{ width: 250, height: 250, backgroundColor: 'gray', float: 'left' }} />
:
<div className={'inline-block'} style={{ maxWidth: 320 }}>
<a
onClick={() => setLightboxOpen(true)}
className={'mt-3'}
style={{ display: 'grid', position: 'relative', gridTemplateColumns: 'repeat(12,1fr)', cursor: 'zoom-in' }}
>{Number(locationImages?.total_image) > 0 &&
<div class="image-stack__item image-stack__item--top">
<img src={locationDetail.detail.thumbnail.String.toString()} alt="" style={{ aspectRatio: '1/1' }} />
{locationImages?.images.length > 1 &&
<div className={'text-xs p-2 bg-primary'} style={{ position: 'absolute', bottom: 0, right: 0 }}>
Total images ({locationImages?.images.length})
</div>
}
</div>
}
{locationImages?.images.length > 1 &&
<div class="image-stack__item image-stack__item--middle">
<img src={locationImages?.images[0].src} alt="" style={{ aspectRatio: '1/1' }} />
</div>
}
<div class="image-stack__item image-stack__item--bottom" style={Number(locationImages?.total_image) > 1 ? {} : { gridColumn: '13/1' }}>
<img src={Number(locationImages?.total_image) > 1 ? locationImages?.images[1].src.toString() : locationDetail.detail.thumbnail.String.toString()} alt="" style={{ aspectRatio: '1/1' }} />
</div>
</a>
</div>
}
<div className={'inline-block'} style={{ verticalAlign: 'top', padding: '0 2%', width: '30%', minWidth: 310 }}>
<div className={'p-4 bg-secondary mt-3'} style={{ width: '100%', height: 120, borderTopLeftRadius: 10, borderTopRightRadius: 10 }}>
<div className={'font-bold ml-1 text-xs'}>CRITICS SCORE</div>
<div className={'text-4xl text-center mt-2 mr-4'} style={{ width: 95, float: 'left' }}>
{state.critic_count !== 0 ? state.critic_score : "NR"}
<div className={"items-center p-2"}>
<div className={'mr-3 users-score-bar'}>
<div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${state.critic_count !== 0 ? Number(state.critic_score) / Number(state.critic_count) * 10 : 0}%`, backgroundColor: 'green' }} />
</div>
</div>
</div>
</div>
{state.critic_count !== 0 &&
<div className={'ml-14 text-sm'}>
Based on {state.critic_count} reviews
</div>
}
</div>
<div className={'p-4 bg-secondary mt-1 inline-block'} style={{ width: '100%', height: 120, borderBottomLeftRadius: 10, borderBottomRightRadius: 10 }}>
<div className={'font-bold ml-2 text-xs'}>USERS SCORE</div>
<div className={'text-4xl text-center mt-2'} style={{ width: 95, float: 'left' }}>
{state.user_count !== 0 ? state.user_score : "NR"}
<div className={"items-center p-2"}>
<div className={'mr-3 users-score-bar'}>
<div className={"mt-1"} style={{ height: 4, width: 80, backgroundColor: "#72767d" }}>
<div style={{ height: 4, width: ` ${state.user_count !== 0 ? Number(state.user_score) / Number(state.user_score) * 10 : 0}%`, backgroundColor: 'green' }} />
</div>
</div>
</div>
</div>
{state.user_count !== 0 &&
<div className={'ml-4'}>
Based on {state.user_count} reviews
</div>
}
</div>
</div>
<div className={'inline-block mt-3 bg-primary text-sm location-detail-container'}>
<div className={'pb-1'} style={{ borderBottom: '1px solid #38444d' }}>
<h2 className={'inline-block font-bold text-xs'} style={{ letterSpacing: .9 }}>DETAILS</h2>
<div className={''} style={{ float: 'right', fontSize: 10, letterSpacing: .9 }}>
<a href="#">SUBMIT CORRECTION</a>
</div>
</div>
<div className={'mt-2 capitalize'}>
<span>address: </span> {locationDetail.detail.address} {locationDetail.detail.regency_name}
</div>
<div className={'mt-1 capitalize'}>
<span>province: </span> <a href={'#'}> {locationDetail.detail.province_name}</a>
</div>
<div className={'mt-1 capitalize'}>
<span>region: </span> <a href={'#'}> {locationDetail.detail.region_name}</a>
</div>
<div className={'mt-1 capitalize'}>
<span>average cost: </span> IDR 25.0000
</div>
<div className={'mt-1 text-md'}>
<a href={locationDetail.detail.google_maps_link.toString()} style={{ borderBottom: '1px solid white' }} target={'_'}> Maps Location</a>
</div>
<div className={'mt-1'}>
<span>Tags: </span>
</div>
{locationDetail.tags.map(x => (
<div className={'p-1 text-xs tags-box mr-1'}>
<a href={'#'}>{x}</a>
</div>
))
}
<div className={'p-1 text-xs tags-box'}>
<a href={'#'}>+ add tags</a>
</div>
</div>
</div>
</section>
<section>
<div className={'mt-5'} style={{ tableLayout: 'fixed', display: 'table', width: '100%'}}>
<div className={'wideLeft'} style={{ textAlign: 'left', paddingRight: 20, maxWidth: 1096, minWidth: 680, display: 'table-cell', position: 'relative', verticalAlign: 'top', width: '100%', boxSizing: 'border-box' }}>
<div className={'reviewContainer p-4'} style={{ backgroundColor: '#2f3136' }}>
<div className={'reviewBoxContent'} style={{ width: '75%', margin: '0 auto' }}>
<div className={'userImage mr-3'} style={{ width: 50, float: 'left' }}>
<a href={'#'}>
<img loading={'lazy'} src={'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'} />
</a>
</div>
<div style={{ display: 'block' }}>
<a href={'#'}>user</a>
</div>
<div style={{ margin: '5px 0 10px' }}>
<div style={{ float: 'left' }}>
<input
type={'text'}
pattern={"\d*"}
style={{ fontSize: 12, backgroundColor: '#40444b', textAlign: 'center', width: 40, height: 20, lineHeight: 18, border: '1px solid #38444d' }}
maxLength={3}
placeholder={"0-100"}
autoComplete={'off'}
/>
</div>
<div style={{ clear: 'both' }} />
</div>
<div className={'mt-3'} style={{ width: '100%' }}>
<textarea style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', backgroundColor: '#40444b' }}></textarea>
</div>
<div style={{ textAlign: 'right', width: "100%" }}>
<div style={{ display: 'inline-block', fontSize: 11, verticalAlign: 'middle', margin: '0 10px 0 0', letterSpacing: .5 }}>
<a>Review Guidelines</a>
</div>
<span className={'text-xxs p-1'} style={{ backgroundColor: 'gray', letterSpacing: 1 }}>
<a href={'#'}>
POST
</a>
</span>
</div>
</div>
</div>
</div>
<div style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 330, textAlign: 'left', padding: 15, boxSizing: 'border-box', backgroundColor: "gray", height: 1080 }}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reprehenderit cumque aliquam doloribus in reiciendis? Laborum, ea assumenda, tempora dolore placeat aspernatur, cumque totam sequi debitis dolor nam eligendi suscipit aliquid?
</div>
</div>
</section>
<Lightbox
open={lightboxOpen}
close={() => setLightboxOpen(false)}
slides={locationImages?.images}
/>
</div>
</>
)
}

View File

@ -0,0 +1,69 @@
import { SlideImage } from "yet-another-react-lightbox"
export interface ILocationDetail {
id: Number,
name: String,
address: String,
google_maps_link: String,
thumbnail: NullValueRes<"String", String>,
submitted_by: Number,
regency_name: String,
province_name: String,
region_name: String,
submitted_by_user: String
}
export function emptyLocationDetail(): ILocationDetail {
return {
id: 0,
address: '',
google_maps_link: '',
thumbnail: { String: '', Valid: false },
name: '',
province_name: '',
regency_name: '',
region_name: '',
submitted_by: 0,
submitted_by_user: ''
}
}
export interface LocationDetailResponse {
detail: ILocationDetail,
tags: Array<String>
}
export function EmptyLocationDetailResponse(): LocationDetailResponse {
return {
detail: emptyLocationDetail(),
tags: []
}
}
export interface LocationImage extends SlideImage {
id: Number,
src: string,
created_at: String,
uploaded_by: String
}
export function emptyLocationImage(): LocationImage {
return {
id: 0,
src: '',
created_at: '',
uploaded_by: ''
}
}
export interface LocationResponse {
total_image: Number,
images: Array<LocationImage>
}
export function emptyLocationResponse(): LocationResponse {
return {
total_image: 0,
images: [emptyLocationImage()]
}
}

36
src/services/images.ts Normal file
View File

@ -0,0 +1,36 @@
import { GET_IMAGES_BY_LOCATION_URI } from "../constants/api"
import { client } from "./config"
import statusCode from "./status-code"
const initialState: any = {
data: null,
error: null
}
interface getImagesReq extends GetRequestPagination {
location_id?: Number
}
async function getImagesByLocationService({ page, page_size, location_id }: getImagesReq) {
const newState = { ...initialState }
const url = `${GET_IMAGES_BY_LOCATION_URI}?location_id=${location_id}&page=${page}&page_size=${page_size}`
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(`GET IMAGE BY LOCATION SERVICE ERROR: ${error}`)
}
}
export {
getImagesByLocationService
}

View File

@ -1,11 +1,18 @@
import {
getListLocationsService,
getListRecentLocationsRatingsService,
getListTopLocationsService
getListTopLocationsService,
getLocationService,
getLocationTagsService,
} from "./locations";
import { getImagesByLocationService } from "./images"
export {
getListLocationsService,
getListRecentLocationsRatingsService,
getListTopLocationsService,
getLocationService,
getLocationTagsService,
getImagesByLocationService,
}

View File

@ -1,4 +1,4 @@
import { GET_LIST_LOCATIONS_URI, GET_LIST_RECENT_LOCATIONS_RATING_URI, GET_LIST_TOP_LOCATIONS } from "../constants/api";
import { GET_LIST_LOCATIONS_URI, GET_LIST_RECENT_LOCATIONS_RATING_URI, GET_LIST_TOP_LOCATIONS, GET_LOCATION_TAGS_URI, GET_LOCATION_URI } from "../constants/api";
import { client } from "./config";
import statusCode from "./status-code";
@ -7,18 +7,16 @@ const initialState: any = {
error: null
}
type getListLocationsArg = {
page: number,
page_size: number,
interface getListLocationsArg extends GetRequestPagination {
order_by?: number,
region_type?: number
}
async function getListLocationsService ({ page, page_size}: getListLocationsArg) {
const newState = {...initialState};
async function getListLocationsService({ page, page_size }: getListLocationsArg) {
const newState = { ...initialState };
const url = `${GET_LIST_LOCATIONS_URI}?page=${page}&page_size=${page_size}`
try {
const response = await client({method: 'GET', url: url})
try {
const response = await client({ method: 'GET', url: url })
switch (response.request.status) {
case statusCode.OK:
newState.data = response.data;
@ -30,13 +28,13 @@ async function getListLocationsService ({ page, page_size}: getListLocationsArg)
} catch (error) {
console.log(error)
}
}
}
async function getListRecentLocationsRatingsService (page_size: Number) {
const newState = {...initialState};
async function getListRecentLocationsRatingsService(page_size: Number) {
const newState = { ...initialState };
const url = `${GET_LIST_RECENT_LOCATIONS_RATING_URI}?page_size=${page_size}`
try {
const response = await client({method: 'GET', url: url})
try {
const response = await client({ method: 'GET', url: url })
switch (response.request.status) {
case statusCode.OK:
newState.data = response.data;
@ -48,13 +46,13 @@ async function getListRecentLocationsRatingsService (page_size: Number) {
} catch (error) {
console.log(error)
}
}
}
async function getListTopLocationsService({ page, page_size, order_by, region_type}: getListLocationsArg) {
const newState = {...initialState};
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})
try {
const response = await client({ method: 'GET', url: url })
switch (response.request.status) {
case statusCode.OK:
newState.data = response.data;
@ -68,8 +66,46 @@ async function getListTopLocationsService({ page, page_size, order_by, region_ty
}
}
async function getLocationService(id: Number) {
const newState = { ...initialState };
const url = `${GET_LOCATION_URI}/${id}`
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)
}
}
async function getLocationTagsService(id: Number) {
const newState = { ...initialState };
const url = `${GET_LOCATION_TAGS_URI}/${id}`
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 {
getListLocationsService,
getListRecentLocationsRatingsService,
getListTopLocationsService,
getLocationTagsService,
getLocationService
}

View File

@ -1,2 +1,7 @@
type BaseNullValueRes = { Valid: boolean };
type NullValueRes<Key extends string, _> = BaseNullValueRes & Record<Key, string | number>
type NullValueRes<Key extends string, _> = BaseNullValueRes & Record<Key, string | number>
interface GetRequestPagination {
page: number,
page_size: number,
}

View File

@ -0,0 +1,30 @@
// https://medium.com/geekculture/usecallbackstate-the-hook-that-let-you-run-code-after-a-setstate-operation-finished-25f40db56661
import { useEffect, useRef, useState } from "react";
type CallBackType<T> = (updatedValue: T) => void;
type SetStateType<T> = T | ((prev: T) => T);
type RetType = <T>(
initialValue: T | (() => T)
) => [T, (newValue: SetStateType<T>, callback?: CallBackType<T>) => void];
const useCallbackState: RetType = <T>(initialValue: T | (() => T)) => {
const [state, _setState] = useState<T>(initialValue);
const callbackQueue = useRef<CallBackType<T>[]>([]);
useEffect(() => {
callbackQueue.current.forEach((cb) => cb(state));
callbackQueue.current = [];
}, [state]);
const setState = (newValue: SetStateType<T>, callback?: CallBackType<T>) => {
_setState(newValue);
if (callback && typeof callback === "function") {
callbackQueue.current.push(callback);
}
};
return [state, setState];
};
export default useCallbackState;