@@ -67,8 +67,7 @@ type RestaurantProps = { restaurant: Restaurant };
type RestaurantFeature = Supercluster . PointFeature < RestaurantProps > ;
function useSupercluster ( restaurants : Restaurant [ ] ) {
const indexRef = useRef < Supercluster < { restaurant : Restaurant } > | null > ( null ) ;
// #278 — indexRef 제거 (set만 되고 read 없는 dead code)
const points : RestaurantFeature [ ] = useMemo (
( ) = >
restaurants . map ( ( r ) = > ( {
@@ -86,7 +85,6 @@ function useSupercluster(restaurants: Restaurant[]) {
minPoints : 2 ,
} ) ;
sc . load ( points ) ;
indexRef . current = sc ;
return sc ;
} , [ points ] ) ;
@@ -129,12 +127,7 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh
const channelColors = useMemo ( ( ) = > getChannelColorMap ( restaurants ) , [ restaurants ] ) ;
const { getClusters , getExpansionZoom } = useSupercluster ( restaurants ) ;
// Build a lookup for restaurants by id
const restaurantMap = useMemo ( ( ) = > {
const m : Record < string , Restaurant > = { } ;
restaurants . forEach ( ( r ) = > { m [ r . id ] = r ; } ) ;
return m ;
} , [ restaurants ] ) ;
// #278 — restaurantMap 제거 (빌드만 되고 렌더에서 사용 안 됨, dead code)
const clusters = useMemo ( ( ) = > {
if ( ! bounds ) return [ ] ;
@@ -273,7 +266,7 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh
textDecoration : isClosed ? "line-through" : "none" ,
} }
>
< span className = "material-symbols-rounded" style = { { fontSize : 14 , marginRight : 3 , verticalAlign : "middle" , color : "#E8720C" } } > { getCuisineIcon ( r . cuisine_type ) } < / span >
< span className = "material-symbols-rounded" style = { { fontSize : 14 , width : 14 , height : 14 , overflow : "hidden" , display : "inline-flex" , alignItems : "center" , justifyContent : "center" , marginRight : 3 , verticalAlign : "middle" , color : "#E8720C" } } > { getCuisineIcon ( r . cuisine_type ) } < / span >
{ r . name }
< / div >
< div
@@ -298,7 +291,7 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh
>
< div style = { { backgroundColor : "#ffffff" , color : "#171717" , colorScheme : "light" } } className = "max-w-xs p-1" >
< div className = "flex items-center gap-2" >
< h3 className = "font-bold text-base" style = { { color : "#171717" } } > < span className = "material-symbols-rounded" style = { { fontSize : 18 , verticalAlign : "middle" , color : "#E8720C" , marginRight : 4 } } > { getCuisineIcon ( infoTarget . cuisine_type ) } < / span > { infoTarget . name } < / h3 >
< h3 className = "font-bold text-base" style = { { color : "#171717" } } > < span className = "material-symbols-rounded" style = { { fontSize : 18 , width : 18 , height : 18 , overflow : "hidden" , display : "inline-flex" , alignItems : "center" , justifyContent : "center" , verticalAlign : "middle" , color : "#E8720C" , marginRight : 4 } } > { getCuisineIcon ( infoTarget . cuisine_type ) } < / span > { infoTarget . name } < / h3 >
{ infoTarget . business_status === "CLOSED_PERMANENTLY" && (
< span className = "px-1.5 py-0.5 bg-red-100 text-red-700 rounded text-[10px] font-semibold" > 폐 업 < / span >
) }
@@ -357,6 +350,13 @@ export default function MapView({ restaurants, selected, onSelectRestaurant, onB
} , 150 ) ;
} , [ onBoundsChanged ] ) ;
// #278 — 언마운트 시 디바운스 타이머 정리 (메모리 누수 + unmounted setState 경고 방지)
useEffect ( ( ) = > {
return ( ) = > {
if ( boundsTimerRef . current ) clearTimeout ( boundsTimerRef . current ) ;
} ;
} , [ ] ) ;
return (
< APIProvider apiKey = { API_KEY } >
< Map
@@ -380,10 +380,12 @@ export default function MapView({ restaurants, selected, onSelectRestaurant, onB
{ onMyLocation && (
< button
onClick = { onMyLocation }
className = "absolute top-2 right-2 w-9 h-9 bg-surface rounded-lg shadow-md flex items-center justify-center text-gray-600 dark:text-gray-300 hover:text-brand-500 dark:hover:text-brand-400 transition-colors z-10 "
aria-label = "내 위치로 이동 "
// #278 — 44× 44px 터치 영역 확보 (이전 36px)
className = "absolute top-2 right-2 w-11 h-11 bg-surface rounded-lg shadow-md flex items-center justify-center text-gray-600 dark:text-gray-300 hover:text-brand-500 dark:hover:text-brand-400 transition-colors z-10 touch-manipulation"
title = "내 위치"
>
< Icon name = "my_location" size = { 20 } / >
< Icon name = "my_location" size = { 22 } / >
< / button >
) }
{ channelNames . length > 0 && (