From 7619e40ef9477f08bf287b73e433a5636d7a4c12 Mon Sep 17 00:00:00 2001 From: Lee SeungJoon Date: Mon, 3 Aug 2026 20:39:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat=20:=20=EC=A7=80=EB=8F=84=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=20=EC=8B=A4=EC=A0=9C=20=ED=95=80=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MapPage.tsx에 useMapPins(viewport) 연결, onViewportChanged로 idle마다 갱신 - selectedMapPinId state 추가, 탭 시 갱신되도록 onSelectMapPin 연결 - 하드코딩돼 있던 mapPins={[]} / selectedMapPinId={null} 제거 - 개발 환경에서 실 API 응답이 비어있을 때 서울시청 근처 고정 좌표에 목데이터 4개를 띄워 배선/렌더링 확인 가능하게 함 (프로덕션 빌드 미포함) 그 외: MapPinMarker의 선택 시 scale(1.2)가 말풍선까지 같이 확대시키던 버그 수정 (아이콘 전용 wrapper로 분리) - feat/170 PR 리뷰에서 나왔던 수정사항이 머지 전에 누락돼 있어서 같이 반영 --- src/features/map/components/MapPinMarker.tsx | 52 ++++++++++---------- src/features/map/constants/devMockMapPins.ts | 43 ++++++++++++++++ src/pages/MapPage.tsx | 17 +++++-- 3 files changed, 82 insertions(+), 30 deletions(-) create mode 100644 src/features/map/constants/devMockMapPins.ts diff --git a/src/features/map/components/MapPinMarker.tsx b/src/features/map/components/MapPinMarker.tsx index 7448badd..d67d4ca6 100644 --- a/src/features/map/components/MapPinMarker.tsx +++ b/src/features/map/components/MapPinMarker.tsx @@ -19,14 +19,7 @@ export function MapPinMarker({ onPlay, }: MapPinMarkerProps) { return ( -
+
{isSelected && nickname && introduction ? (
) : null} - 핀 아이콘 -
- {coverUrl ? ( - - ) : null} + 핀 아이콘 + +
+ {coverUrl ? ( + + ) : null} +
); diff --git a/src/features/map/constants/devMockMapPins.ts b/src/features/map/constants/devMockMapPins.ts new file mode 100644 index 00000000..415db858 --- /dev/null +++ b/src/features/map/constants/devMockMapPins.ts @@ -0,0 +1,43 @@ +import type { MapPin } from '@/features/map/types'; + +/** + * 개발 환경에서 실제 API 응답이 비어있을 때(근처에 등록된 PIN이 없을 때) + * 배선/렌더링을 확인할 수 있도록 서울시청 근처(공공장소)에 고정으로 띄우는 목데이터. + * 화면 중심 기준으로 계산하지 않는다 — 지도를 움직여도 따라오지 않고 실제 + * 위치에 고정돼 있어야 하기 때문. 프로덕션 빌드(import.meta.env.DEV === false) + * 에서는 절대 사용하지 않는다. + */ +export const DEV_MOCK_MAP_PINS: MapPin[] = [ + { + id: 'dev-mock:1', + lat: 37.5663, + lng: 126.9779, + coverUrl: undefined, + nickname: '냥코', + introduction: '도가니 살리기 위잉잉잉~', + }, + { + id: 'dev-mock:2', + lat: 37.5658, + lng: 126.9784, + coverUrl: undefined, + nickname: '한강러버', + introduction: '야경 보면서 듣기 딱 좋아요', + }, + { + id: 'dev-mock:3', + lat: 37.5669, + lng: 126.9773, + coverUrl: undefined, + nickname: '산책러', + introduction: '퇴근길 산책 플레이리스트', + }, + { + id: 'dev-mock:4', + lat: 37.5661, + lng: 126.977, + coverUrl: undefined, + nickname: '댄스머신', + introduction: '기분 좋아지는 노래 추천', + }, +]; diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx index 0343422c..fd76ad9b 100644 --- a/src/pages/MapPage.tsx +++ b/src/pages/MapPage.tsx @@ -4,9 +4,11 @@ import { useNavigate } from 'react-router-dom'; import { SearchLauncher } from '@/components/ui/SearchInput'; import { Toast, ToastProvider, ToastViewport } from '@/components/ui/Toast'; import { Button } from '@/components/ui/button'; -import type { MapCoordinate, MapPlace } from '@/features/map/types'; +import type { MapCoordinate, MapPlace, MapViewport } from '@/features/map/types'; import { loadGoogleMapsScript } from '@/features/map/utils'; import { MapViewer, type MapViewerHandle } from '@/features/map/components/MapViewer'; +import { useMapPins } from '@/features/map/queries/useMapPins'; +import { DEV_MOCK_MAP_PINS } from '@/features/map/constants/devMockMapPins'; import { PinListSheet } from '@/features/pin/components/PinListSheet'; import type { PinSearchPlace, PlaceInfo } from '@/features/pin/types'; import BookmarkIcon from '@/assets/icons/bookmark.svg?react'; @@ -54,6 +56,13 @@ const MapPage: React.FC = ({ selectedMapPlace, onClearMapPlace }) ); const [currentLocation, setCurrentLocation] = useState(null); const [registrationToast, setRegistrationToast] = useState(null); + const [viewport, setViewport] = useState(null); + const [selectedMapPinId, setSelectedMapPinId] = useState(null); + const { data: mapPinsData } = useMapPins(viewport); + const mapPins = + import.meta.env.DEV && (mapPinsData?.pins.length ?? 0) === 0 + ? DEV_MOCK_MAP_PINS + : (mapPinsData?.pins ?? []); // develop 방식: selectedMapPlace prop으로 장소 결과 관리 const placeResults = useMemo( () => (selectedMapPlace ? [selectedMapPlace] : []), @@ -225,10 +234,12 @@ const MapPage: React.FC = ({ selectedMapPlace, onClearMapPlace }) zoom={zoom} placeResults={placeResults} selectedPlaceId={selectedPlaceId} - mapPins={[]} - selectedMapPinId={null} + mapPins={mapPins} + selectedMapPinId={selectedMapPinId} onZoomChanged={handleZoomChange} onCurrentLocationChanged={setCurrentLocation} + onViewportChanged={setViewport} + onSelectMapPin={setSelectedMapPinId} />
); From d26aeef170bc1a9ded4433a4d09a7861fce8bd8d Mon Sep 17 00:00:00 2001 From: Lee SeungJoon Date: Mon, 3 Aug 2026 20:49:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix=20:=20=EC=BD=94=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EB=B9=97=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mapPinsData가 undefined(로딩 중/줌 제한으로 비활성/API 에러)인 경우를 빈 응답으로 오인해 개발용 목데이터를 띄우던 문제 수정. 실제로 응답이 도착했는데 pins가 빈 배열인 경우에만 목데이터를 사용하도록 조건 변경 --- src/pages/MapPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx index fd76ad9b..ec749e3e 100644 --- a/src/pages/MapPage.tsx +++ b/src/pages/MapPage.tsx @@ -60,7 +60,7 @@ const MapPage: React.FC = ({ selectedMapPlace, onClearMapPlace }) const [selectedMapPinId, setSelectedMapPinId] = useState(null); const { data: mapPinsData } = useMapPins(viewport); const mapPins = - import.meta.env.DEV && (mapPinsData?.pins.length ?? 0) === 0 + import.meta.env.DEV && mapPinsData !== undefined && mapPinsData.pins.length === 0 ? DEV_MOCK_MAP_PINS : (mapPinsData?.pins ?? []); // develop 방식: selectedMapPlace prop으로 장소 결과 관리