feat(map): NaverMapView 채널별 마커 색상

- GoogleMapView와 동일 팔레트 (8 색)
- 식당 첫 채널 기준 색상, activeChannel 있으면 우선
- getChannelColorMap 재사용 패턴 동일

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-06-16 17:09:02 +09:00
parent 8de8696424
commit 247547c516
2 changed files with 37 additions and 5 deletions

View File

@@ -6,6 +6,10 @@
## 2026-06-16
### 🎨 NaverMapView 채널별 마커 색상 (v0.1.60)
- GoogleMapView와 동일 팔레트 (amber/blue/green/pink/purple/red/teal/yellow)
- 식당의 첫 채널 기준 색상, activeChannel 있으면 그 채널 우선
### ⚡ NaverMapView SDK 네이티브 마커 + InfoWindow (v0.1.59)
- 마커를 React `absolute div` overlay → `naver.maps.Marker` 네이티브로 교체
- 줌/팬 시 SDK가 GPU 최적화, 매 frame React 리렌더링 없음 → 랙 해소

View File

@@ -44,6 +44,30 @@ type NaverInfoWindow = {
const NAVER_CLIENT_ID = process.env.NEXT_PUBLIC_NAVER_MAP_CLIENT_ID || "";
// Channel color palette — GoogleMapView와 동일
const CHANNEL_COLORS = [
{ border: "#f59e0b" }, // amber (default)
{ border: "#3b82f6" }, // blue
{ border: "#22c55e" }, // green
{ border: "#ec4899" }, // pink
{ border: "#a855f7" }, // purple
{ border: "#ef4444" }, // red
{ border: "#14b8a6" }, // teal
{ border: "#eab308" }, // yellow
];
function getChannelColorMap(restaurants: Restaurant[]) {
const channels = new Set<string>();
restaurants.forEach((r) => r.channels?.forEach((ch) => channels.add(ch)));
const map: Record<string, typeof CHANNEL_COLORS[0]> = {};
let i = 0;
for (const ch of channels) {
map[ch] = CHANNEL_COLORS[i % CHANNEL_COLORS.length];
i++;
}
return map;
}
function useNaverMaps(): { ready: boolean; error: string | null } {
const [ready, setReady] = useState(typeof window !== "undefined" && !!window.naver?.maps);
const [error, setError] = useState<string | null>(null);
@@ -101,9 +125,9 @@ function getClusterSize(count: number): number {
return 54;
}
// SVG data URL — 단일 마커(주황 핀)
function markerIconHtml(): string {
return `<div style="width:28px;height:28px;border-radius:9999px;background:#f59e0b;border:2px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,.25);"></div>`;
// 단일 마커 — 채널 색상별
function markerIconHtml(color: string): string {
return `<div style="width:28px;height:28px;border-radius:9999px;background:${color};border:2px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,.25);"></div>`;
}
// SVG data URL — 클러스터(숫자)
function clusterIconHtml(count: number, size: number): string {
@@ -117,7 +141,9 @@ export default function NaverMapView({
onBoundsChanged,
flyTo,
onMyLocation,
activeChannel,
}: MapViewProps) {
const channelColors = useMemo(() => getChannelColorMap(restaurants), [restaurants]);
const { ready, error } = useNaverMaps();
const divRef = useRef<HTMLDivElement | null>(null);
const mapRef = useRef<NaverMapInstance | null>(null);
@@ -230,12 +256,14 @@ export default function NaverMapView({
markersRef.current.push(marker);
} else {
const r = (feature.properties as RestaurantProps).restaurant;
const chKey = activeChannel && r.channels?.includes(activeChannel) ? activeChannel : r.channels?.[0];
const chColor = chKey ? channelColors[chKey] : CHANNEL_COLORS[0];
const marker = new naver.Marker({
position: new naver.LatLng(lat, lng),
map: m,
title: r.name,
icon: {
content: markerIconHtml(),
content: markerIconHtml(chColor?.border ?? "#f59e0b"),
anchor: new naver.Point(14, 14),
},
});
@@ -252,7 +280,7 @@ export default function NaverMapView({
markersRef.current.push(marker);
}
}
}, [clusters, getExpansionZoom, onSelectRestaurant]);
}, [clusters, getExpansionZoom, onSelectRestaurant, channelColors, activeChannel]);
// 컴포넌트 unmount 시 마커 정리
useEffect(() => {