@@ -183,7 +186,13 @@ function Home() {
{topUsersLocations.map((x) => (
-
![{x.name}]()
+ {/*

+ /> */}
{x.name}
{x.regency_name}
diff --git a/src/pages/LocationDetail/index.tsx b/src/pages/LocationDetail/index.tsx
index cdd076b..e0ae219 100755
--- a/src/pages/LocationDetail/index.tsx
+++ b/src/pages/LocationDetail/index.tsx
@@ -12,6 +12,7 @@ import {
} from './types';
import { handleApiError, useAutosizeTextArea } from '../../utils';
import { getCurrentUserLocationReviewService, getImagesByLocationService, getLocationService, postReviewLocation, postReviewImages } from "../../services";
+import { recordLocationVisitService } from "../../services/locations";
import { DefaultSeparator, SeparatorWithAnchor, CustomInterweave, SpinnerLoading, ReviewCard, ReviewCardFull } from '../../components';
import RatingsCard from '../../components/Card/RatingsCard';
import { useSelector } from 'react-redux';
@@ -112,6 +113,11 @@ function LocationDetail() {
getImage(val.detail.thumbnail)
}
})
+ // Fire-and-forget visit hit. Only fires once per (browser tab × location);
+ // the backend additionally dedupes by client IP for 30 minutes via Redis,
+ // so refreshes / multiple tabs / bots cannot inflate the trending counts.
+ // Anything that fails here is silently swallowed inside the service.
+ recordLocationVisitService(Number(id))
} catch (error) {
const err = error as IHttpResponse;
if (err.status == 404) {
diff --git a/src/pages/LocationDetail/types.ts b/src/pages/LocationDetail/types.ts
index af4dffc..abb39da 100755
--- a/src/pages/LocationDetail/types.ts
+++ b/src/pages/LocationDetail/types.ts
@@ -7,6 +7,7 @@ export interface ILocationDetail {
address: String,
regency_name: String,
province_name: String,
+ location_type: String,
region_name: String,
google_maps_link: String,
thumbnail: string | null,
@@ -27,6 +28,7 @@ export function emptyLocationDetail(): ILocationDetail {
province_name: '',
regency_name: '',
region_name: '',
+ location_type: '',
submitted_by: 0,
critic_score: 0,
critic_count: 0,
diff --git a/src/services/locations.ts b/src/services/locations.ts
index 2ad3cbf..701ede6 100755
--- a/src/services/locations.ts
+++ b/src/services/locations.ts
@@ -4,10 +4,13 @@ import {
GET_LIST_LOCATIONS_URI,
GET_LIST_RECENT_LOCATIONS_RATING_URI,
GET_LIST_TOP_LOCATIONS,
+ GET_LIST_TRENDING_LOCATIONS_URI,
GET_LOCATION_TAGS_URI,
GET_LOCATION_URI,
GET_SEARCH_LOCATIONS_URI,
- POST_CREATE_LOCATION
+ POST_CREATE_LOCATION,
+ POST_LOCATION_VISIT_URI,
+ GET_MENU_ITEMS_URI,
} from "../constants/api";
import { client } from "./config";
@@ -170,6 +173,7 @@ async function getListTopLocationsService(params: GetListLocationsArg) {
async function getLocationService(params: GetLocationArg) {
try {
const data = await fetchLocation(params)
+ console.log(data)
return { data, error: null }
} catch (error) {
throw error
@@ -211,6 +215,77 @@ async function getSearchLocationService(arg: GetSearchLocations): Promise
{
+ const response = await client({ method: 'GET', url: `${GET_MENU_ITEMS_URI}?location_id=${locationId}` })
+ return response.data
+}
+
+interface GetTrendingLocationsArg extends GetRequestPagination {
+ /** week | month | 3month | semester | year */
+ window: string
+}
+
+const fetchTrendingLocations = async ({ window, page, page_size }: GetTrendingLocationsArg) => {
+ const url = `${GET_LIST_TRENDING_LOCATIONS_URI}?window=${encodeURIComponent(window)}&page=${page}&page_size=${page_size}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+
+export const useTrendingLocations = (
+ params: GetTrendingLocationsArg,
+ options?: Omit, 'queryKey' | 'queryFn'>,
+) => {
+ return useQuery({
+ queryKey: ['locations', 'trending', params],
+ queryFn: () => fetchTrendingLocations(params),
+ enabled: !!params.window,
+ ...options,
+ })
+}
+
+export const getTrendingLocationsService = async (params: GetTrendingLocationsArg) => {
+ try {
+ const data = await fetchTrendingLocations(params)
+ return { data, error: null }
+ } catch (error: any) {
+ return { data: null, error, status: error?.status }
+ }
+}
+
+/**
+ * Records a page-visit hit for a location. The backend already de-duplicates
+ * by client IP (one count per IP per location per 30 minutes via Redis), so
+ * this is intentionally fire-and-forget. We additionally suppress repeats
+ * within the same browser tab/session so React re-renders, StrictMode double
+ * effects, or back/forward nav don't even hit the wire.
+ */
+const visitedThisSession = new Set()
+
+export const recordLocationVisitService = async (locationId: number): Promise => {
+ if (!Number.isFinite(locationId) || locationId <= 0) return
+ if (visitedThisSession.has(locationId)) return
+ visitedThisSession.add(locationId)
+ try {
+ await client({
+ method: 'POST',
+ url: `${POST_LOCATION_VISIT_URI}/${locationId}/visit`,
+ })
+ } catch {
+ // Silent: visit counting must never disrupt the page render. The backend
+ // also responds 204 on dedup, so the only errors here are network/server.
+ visitedThisSession.delete(locationId) // allow a future retry on real failure
+ }
+}
+
+export const getMenuItemsService = async (locationId: number) => {
+ try {
+ const data = await fetchMenuItems(locationId)
+ return { data, error: null }
+ } catch (error: any) {
+ return { data: null, error, status: error.status }
+ }
+}
+
export {
getListLocationsService,
getSearchLocationService,