diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb3ce6..faa31a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ ## 2026-06-16 +### ๐Ÿท๏ธ NaverMapView ๋งˆ์ปค์— ์‹๋‹น๋ช… ๋ฐ•์Šค (v0.1.61) +- ๋‹จ์ˆœ ๋™๊ทธ๋ผ๋ฏธ โ†’ GoogleMapView์™€ ๋™์ผ ํ•€ ๋””์ž์ธ(๋ฐ•์Šค+ํ™”์‚ดํ‘œ+์‹๋‹น๋ช…+cuisine ์•„์ด์ฝ˜) +- ์ฑ„๋„๋ณ„ ๋ฐฐ๊ฒฝ/ํ…Œ๋‘๋ฆฌ/ํ™”์‚ดํ‘œ ์ƒ‰์ƒ, ํ์—…(business_status CLOSED_*) ํ‘œ์‹œ ํšŒ์ƒ‰ + ์ทจ์†Œ์„  +- selected ์‹๋‹น ๊ฐ•์กฐ (1.15ร— scale + ํŒŒ๋ž€ ๋ฐ•์Šค), zIndex 1000 +- InfoWindow ์ œ๊ฑฐ (์‹๋‹น๋ช… ์ž์ฒด๊ฐ€ ๋ฐ•์Šค๋กœ ๋ณด์ด๋ฏ€๋กœ ๋ถˆํ•„์š”) + ### ๐ŸŽจ NaverMapView ์ฑ„๋„๋ณ„ ๋งˆ์ปค ์ƒ‰์ƒ (v0.1.60) - GoogleMapView์™€ ๋™์ผ ํŒ”๋ ˆํŠธ (amber/blue/green/pink/purple/red/teal/yellow) - ์‹๋‹น์˜ ์ฒซ ์ฑ„๋„ ๊ธฐ์ค€ ์ƒ‰์ƒ, activeChannel ์žˆ์œผ๋ฉด ๊ทธ ์ฑ„๋„ ์šฐ์„  diff --git a/frontend/src/components/NaverMapView.tsx b/frontend/src/components/NaverMapView.tsx index c4d5aaa..96b3558 100644 --- a/frontend/src/components/NaverMapView.tsx +++ b/frontend/src/components/NaverMapView.tsx @@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Supercluster from "supercluster"; import type { Restaurant } from "@/lib/api"; import Icon from "@/components/Icon"; +import { getCuisineIcon } from "@/lib/cuisine-icons"; import type { MapBounds, MapViewProps } from "@/components/MapView.types"; declare global { @@ -46,14 +47,14 @@ 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 + { bg: "#fff7ed", text: "#78350f", border: "#f59e0b", arrow: "#f59e0b" }, // amber (default) + { bg: "#eff6ff", text: "#1e3a5f", border: "#3b82f6", arrow: "#3b82f6" }, // blue + { bg: "#f0fdf4", text: "#14532d", border: "#22c55e", arrow: "#22c55e" }, // green + { bg: "#fdf2f8", text: "#831843", border: "#ec4899", arrow: "#ec4899" }, // pink + { bg: "#faf5ff", text: "#581c87", border: "#a855f7", arrow: "#a855f7" }, // purple + { bg: "#fff1f2", text: "#7f1d1d", border: "#ef4444", arrow: "#ef4444" }, // red + { bg: "#f0fdfa", text: "#134e4a", border: "#14b8a6", arrow: "#14b8a6" }, // teal + { bg: "#fefce8", text: "#713f12", border: "#eab308", arrow: "#eab308" }, // yellow ]; function getChannelColorMap(restaurants: Restaurant[]) { @@ -125,9 +126,29 @@ function getClusterSize(count: number): number { return 54; } -// ๋‹จ์ผ ๋งˆ์ปค โ€” ์ฑ„๋„ ์ƒ‰์ƒ๋ณ„ -function markerIconHtml(color: string): string { - return `
`; +// ๋‹จ์ผ ๋งˆ์ปค โ€” ์‹๋‹น๋ช… ๋ฐ•์Šค + ํ™”์‚ดํ‘œ ํ•€ (GoogleMapView์™€ ๋™์ผ ๋””์ž์ธ) +function markerIconHtml( + name: string, + cuisineIcon: string, + c: typeof CHANNEL_COLORS[0], + opts: { isSelected: boolean; isClosed: boolean } +): string { + const { isSelected, isClosed } = opts; + const bg = isSelected ? "#2563eb" : isClosed ? "#f3f4f6" : c.bg; + const text = isSelected ? "#fff" : isClosed ? "#9ca3af" : c.text; + const border = isSelected ? "2px solid #1d4ed8" : `1.5px solid ${c.border}`; + const shadow = isSelected ? "0 2px 8px rgba(37,99,235,0.4)" : `0 1px 4px ${c.border}40`; + const arrowColor = isSelected ? "#1d4ed8" : c.arrow; + const opacity = isClosed ? 0.5 : 1; + const deco = isClosed ? "line-through" : "none"; + return ` +
+
+ ${escapeHtml(cuisineIcon)}${escapeHtml(name)} +
+
+
+ `; } // SVG data URL โ€” ํด๋Ÿฌ์Šคํ„ฐ(์ˆซ์ž) function clusterIconHtml(count: number, size: number): string { @@ -258,29 +279,28 @@ export default function NaverMapView({ 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 isSelected = selected?.id === r.id; + const isClosed = r.business_status === "CLOSED_PERMANENTLY" || r.business_status === "CLOSED_TEMPORARILY"; + const cuisineIcon = getCuisineIcon(r.cuisine_type); const marker = new naver.Marker({ position: new naver.LatLng(lat, lng), map: m, title: r.name, + zIndex: isSelected ? 1000 : 1, icon: { - content: markerIconHtml(chColor?.border ?? "#f59e0b"), - anchor: new naver.Point(14, 14), + content: markerIconHtml(r.name, cuisineIcon, chColor ?? CHANNEL_COLORS[0], { isSelected, isClosed }), + // ๋ฐ•์Šค ํญ ๊ฐ€๋ณ€ โ€” ํ™”์‚ดํ‘œ ๋(ํ•˜๋‹จ ์ค‘์•™)์ด ์ขŒํ‘œ์— ์œ„์น˜ํ•˜๋„๋ก ์ถ”์ • anchor + // approxWidth = textLen * 7 + 30 (icon+padding), height = box 24 + arrow 6 = 30 + anchor: new naver.Point(Math.min(r.name.length * 4 + 18, 64), 30), }, }); naver.Event.addListener(marker, "click", () => { - const iw = infoWindowRef.current; - if (iw && m) { - iw.setContent( - `
${escapeHtml(r.name)}
` - ); - iw.open(m, marker); - } onSelectRestaurant?.(r); }); markersRef.current.push(marker); } } - }, [clusters, getExpansionZoom, onSelectRestaurant, channelColors, activeChannel]); + }, [clusters, getExpansionZoom, onSelectRestaurant, channelColors, activeChannel, selected]); // ์ปดํฌ๋„ŒํŠธ unmount ์‹œ ๋งˆ์ปค ์ •๋ฆฌ useEffect(() => {