From 711e9c80907aff6099e0b2400b420d672a0c708e Mon Sep 17 00:00:00 2001 From: yeilkk Date: Fri, 27 Sep 2024 02:10:44 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20UI=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {src => public}/images/course/jeju2.jpg | Bin {src => public}/images/course/jeju3.jpg | Bin {src => public}/images/course/jeju4.jpg | Bin src/App.jsx | 2 +- src/components/AroundMap.jsx | 8 +-- src/components/Blues.jsx | 8 +-- src/components/BluesPlan.jsx | 76 +++++++++++++++++++++--- src/components/ItemCard.jsx | 4 +- src/components/ItemDetailCard.jsx | 26 +++++--- src/pages/AlongCourses.jsx | 4 +- src/pages/ItemList.jsx | 4 +- src/styles/CourseItem.css | 9 +-- src/styles/Footer.css | 2 +- src/styles/ItemCard.css | 1 + src/styles/ItemCardList.css | 2 - src/styles/SquareCard.css | 11 ---- 16 files changed, 104 insertions(+), 53 deletions(-) rename {src => public}/images/course/jeju2.jpg (100%) rename {src => public}/images/course/jeju3.jpg (100%) rename {src => public}/images/course/jeju4.jpg (100%) diff --git a/src/images/course/jeju2.jpg b/public/images/course/jeju2.jpg similarity index 100% rename from src/images/course/jeju2.jpg rename to public/images/course/jeju2.jpg diff --git a/src/images/course/jeju3.jpg b/public/images/course/jeju3.jpg similarity index 100% rename from src/images/course/jeju3.jpg rename to public/images/course/jeju3.jpg diff --git a/src/images/course/jeju4.jpg b/public/images/course/jeju4.jpg similarity index 100% rename from src/images/course/jeju4.jpg rename to public/images/course/jeju4.jpg diff --git a/src/App.jsx b/src/App.jsx index 10af1ec..29efb89 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,7 +19,7 @@ function App() { return new Promise((resolve, reject) => { const kakaoAppKey = import.meta.env.VITE_KAKAO_APP_KEY; const script = document.createElement('script'); - script.src = `https://dapi.kakao.com/v2/maps/sdk.js?appkey=${kakaoAppKey}&autoload=false`; // autoload=false 옵션 사용 + script.src = `https://dapi.kakao.com/v2/maps/sdk.js?appkey=${kakaoAppKey}&libraries=services&autoload=false`; // autoload=false 옵션 사용 script.onload = () => { window.kakao.maps.load(() => { // Kakao API가 완전히 로드된 후에 실행 setIsKakaoLoaded(true); diff --git a/src/components/AroundMap.jsx b/src/components/AroundMap.jsx index 319db38..4185137 100644 --- a/src/components/AroundMap.jsx +++ b/src/components/AroundMap.jsx @@ -14,10 +14,10 @@ function AroundMap({keyword, searchTrigger}) { const currCategoryRef = useRef(currCategory); const categories = [ - { id: 'AT4', name: '관광', url: '../src/images/icon/category_6.svg' }, - { id: 'AD5', name: '숙박', url: '../src/images/icon/category_7.svg' }, - { id: 'FD6', name: '음식', url: '../src/images/icon/category_8.svg' }, - { id: 'CE7', name: '카페', url: '../src/images/icon/category_9.svg' } + { id: 'AT4', name: '관광', url: '/images/icon/category_6.svg' }, + { id: 'AD5', name: '숙박', url: '/images/icon/category_7.svg' }, + { id: 'FD6', name: '음식', url: '/images/icon/category_8.svg' }, + { id: 'CE7', name: '카페', url: '/images/icon/category_9.svg' } ]; // 카테고리 리스트 useEffect(() => { diff --git a/src/components/Blues.jsx b/src/components/Blues.jsx index ae97f6c..18af596 100644 --- a/src/components/Blues.jsx +++ b/src/components/Blues.jsx @@ -4,10 +4,10 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; const popularBlues = [ - { name: '함덕해수욕장', url: '/src/images/course/jeju.jpg' }, - { name: '협재해수욕장', url:'/src/images/course/jeju2.jpg'}, - { name: '용머리해안', url: '/src/images/course/jeju3.jpg' }, - { name: '중문색달해수욕장', url: '/src/images/course/jeju4.jpg' } + { name: '함덕해수욕장', url: '/images/course/jeju.jpg' }, + { name: '협재해수욕장', url:'/images/course/jeju2.jpg'}, + { name: '용머리해안', url: '/images/course/jeju3.jpg' }, + { name: '중문색달해수욕장', url: '/images/course/jeju4.jpg' } ]; const Blues = ({ jejuBlues, seogwipoBlues }) => { diff --git a/src/components/BluesPlan.jsx b/src/components/BluesPlan.jsx index 121cd70..ce6a97a 100644 --- a/src/components/BluesPlan.jsx +++ b/src/components/BluesPlan.jsx @@ -1,22 +1,80 @@ -import React from 'react'; -import "../styles/BluesPlan.css" -import { useState } from 'react'; -import { useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; +import "../styles/BluesPlan.css"; const blue = { id: 11, name: "이호테우해수욕장", xMap: "126.4531570913", - yMap: 33.4974183784 + yMap: "33.4974183784" }; -const BluesPlan = ({ id }) => { - const [blue, setBlue] = useState(null); // 현재 위치 상태 관리 - useEffect +const BluesPlan = () => { + const [selectedBlue, setSelectedBlue] = useState(null); + const mapRef = useRef(null); + + useEffect(() => { + // selectedBlue를 한 번만 설정합니다. + setSelectedBlue(blue); + }, []); + + useEffect(() => { + // selectedBlue가 설정되지 않은 상태라면 지도 렌더링을 하지 않습니다. + if (!selectedBlue) return; + + const { kakao } = window; + + // 지도를 표시할 div를 가져옵니다. + const mapContainer = document.getElementById('map'); + const mapOption = { + center: new kakao.maps.LatLng(33.386666, 126.55667), // 제주도 중심 좌표 + level: 10 // 지도 확대 레벨 + }; + + // 지도 객체를 생성합니다. + mapRef.current = new kakao.maps.Map(mapContainer, mapOption); + + // 줌 컨트롤 추가 + const zoomControl = new window.kakao.maps.ZoomControl(); + mapRef.current.addControl(zoomControl, window.kakao.maps.ControlPosition.RIGHT); // 줌 컨트롤러 추가 + + const markerPosition = new kakao.maps.LatLng(selectedBlue.yMap, selectedBlue.xMap); // 마커의 위치 좌표 설정 + + // 마커 이미지 설정 + const markerImageSrc = "https://t1.daumcdn.net/mapjsapi/images/2x/marker.png"; // 마커 이미지 URL + const imageSize = new kakao.maps.Size(20, 28); // 마커 크기 설정 + const markerImage = new kakao.maps.MarkerImage(markerImageSrc, imageSize); // 마커 이미지 객체 생성 + + const marker = new kakao.maps.Marker({ + map: mapRef.current, + position: markerPosition, // 마커를 표시할 위치 + image: markerImage // 마커 이미지 설정 + }); + + // 커스텀 오버레이 내용 정의 + const content = + `
+ ${selectedBlue.name} +
` + ; + + // 커스텀 오버레이 생성 + const overlay = new kakao.maps.CustomOverlay({ + content: content, + position: markerPosition, + yAnchor: 1.5, // 오버레이의 위치 조정을 위한 값 + xAnchor: 0.5 + }); + + // 마커에 클릭 이벤트를 추가하여 커스텀 오버레이를 표시 + kakao.maps.event.addListener(marker, 'click', () => { + // 클릭한 마커의 오버레이를 열기 + overlay.setMap(mapRef.current); + }); + }, [selectedBlue]); // selectedBlue가 설정된 후에만 실행 return (
-
+
); }; diff --git a/src/components/ItemCard.jsx b/src/components/ItemCard.jsx index 02c4c93..0cf7498 100644 --- a/src/components/ItemCard.jsx +++ b/src/components/ItemCard.jsx @@ -12,11 +12,11 @@ function ItemCard({ item }) {
{item.title}
- +
- +
{item.address}
diff --git a/src/components/ItemDetailCard.jsx b/src/components/ItemDetailCard.jsx index a1c98b6..4df7609 100644 --- a/src/components/ItemDetailCard.jsx +++ b/src/components/ItemDetailCard.jsx @@ -10,8 +10,18 @@ function ItemDetailCard({item}) { navigate(`/`); }; - const handleBeforeClick = () => { - navigate(-1); + const handleBeforeClick = () => { + const referrer = document.referrer; // 이전 페이지의 URL + + // 이전 페이지가 우리 사이트 내부 URL인지 확인 + if (referrer && referrer.includes(window.location.origin)) { + // history 스택에 두 개 이상의 항목이 있을 때만 뒤로 이동 (-1) + if (window.history.length > 1) { + navigate(-1); + } + } else { + navigate('/'); // 외부에서 접속했거나, 이전 페이지가 없으면 홈으로 이동 + } }; useEffect(() => { @@ -51,8 +61,8 @@ function ItemDetailCard({item}) {
- - + +
{item.name} @@ -61,23 +71,23 @@ function ItemDetailCard({item}) {
{item.name}
- +
- +
{item.address}
- +
{item.holiday}
- +
{item.weather}
diff --git a/src/pages/AlongCourses.jsx b/src/pages/AlongCourses.jsx index 9f97f87..928a2ea 100644 --- a/src/pages/AlongCourses.jsx +++ b/src/pages/AlongCourses.jsx @@ -6,7 +6,7 @@ import PageHeader from '../components/PageHeader'; const courseData = [ { - profileImg: '/src/images/user/user1.jpg', + profileImg: '/images/user/user1.jpg', userName: '치즈버거', userComment: '우와아아아아아아', courseTitle: '제주의 해안가를 따라', @@ -26,7 +26,7 @@ const courseData = [ tags: ['일출', '일출', '일출'] }, { - profileImg: '/src/images/user/user2.jpg', + profileImg: '/images/user/user2.jpg', userName: '호랑나비', userComment: '한 마리 가야아', courseTitle: '제주도 가성비 코스', diff --git a/src/pages/ItemList.jsx b/src/pages/ItemList.jsx index 38ac9ca..4b46e1d 100644 --- a/src/pages/ItemList.jsx +++ b/src/pages/ItemList.jsx @@ -16,7 +16,7 @@ const foods = [ "#고등어구이", "#제주맛집" ], - image1: '../src/images/food/명진전복.png', + image1: '/images/food/명진전복.png', image2: 'https://blog.kakaocdn.net/dn/b3fewH/btqWVSsjaEO/JcMi0zIGZg75AFXSYvDqGk/img.jpg' }, { @@ -29,7 +29,7 @@ const foods = [ "#제주고기국수맛집", "#수요미식회맛집" ], - image1: '../src/images/food/올래국수.jpeg', + image1: '/images/food/올래국수.jpeg', image2: 'https://api.cdn.visitjeju.net/photomng/imgpath/201910/23/cfb35a88-5beb-4434-96b6-c878a6073d49.jpg'}, ]; diff --git a/src/styles/CourseItem.css b/src/styles/CourseItem.css index 56e0174..364c19d 100644 --- a/src/styles/CourseItem.css +++ b/src/styles/CourseItem.css @@ -1,8 +1,7 @@ .course-item { width: 90%; border-bottom: 1px solid #ddd; - margin-bottom: 5%; - font-family: 'NanumSquareRound'; + margin-top: 5%; } .profile { @@ -84,13 +83,9 @@ margin-top: 2em; } -h3 { - font-size: 20px; - margin: 10px 0 5px 0; -} - .description { font-size: 14px; color: #666; margin-bottom: 2em; + line-height: 1.5em; } diff --git a/src/styles/Footer.css b/src/styles/Footer.css index 4919d57..61e56c2 100644 --- a/src/styles/Footer.css +++ b/src/styles/Footer.css @@ -1,5 +1,5 @@ .footer { - padding: 0.5em 0; + padding: 1.5em 0; color: #292929; /* width: 80%; */ margin: 0 auto; diff --git a/src/styles/ItemCard.css b/src/styles/ItemCard.css index 2b88700..710eed6 100644 --- a/src/styles/ItemCard.css +++ b/src/styles/ItemCard.css @@ -1,6 +1,7 @@ .item-card { width: 90%; border-bottom: solid 1px #DBDBDB; + margin-top: 5%; } .item-images-container { diff --git a/src/styles/ItemCardList.css b/src/styles/ItemCardList.css index dea746f..a92c288 100644 --- a/src/styles/ItemCardList.css +++ b/src/styles/ItemCardList.css @@ -1,5 +1,4 @@ .item-card-list-container { - margin: 0 auto; } @@ -7,5 +6,4 @@ display: flex; flex-wrap: wrap; justify-content: center; - gap: 1.2em; } diff --git a/src/styles/SquareCard.css b/src/styles/SquareCard.css index 9d7e31d..ca3d445 100644 --- a/src/styles/SquareCard.css +++ b/src/styles/SquareCard.css @@ -5,17 +5,6 @@ cursor: pointer; } -/* .square-card-img { - border-radius: 5px; - background-image: linear-gradient( - rgba(0, 0, 0, 0), - rgba(0, 0, 0, 0.5) - ), url(${item.image}); - background-size: 'cover'; - background-repeat: 'no-repeat'; - height: '12.5em', -} */ - .square-card-info { position: absolute; bottom: 0; From e3ea199aff9c59ff17919eb26361f31908444a31 Mon Sep 17 00:00:00 2001 From: yeilkk Date: Fri, 27 Sep 2024 14:44:41 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EB=B0=94=EB=8B=B9=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EC=A7=80=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- src/App.jsx | 2 + src/components/AroundMap.jsx | 62 +++++++- src/components/AroundMap3.jsx | 289 ++++++++++++++++++++++++++++++++++ src/components/BluesPlan.jsx | 82 ---------- src/components/Header.jsx | 4 +- src/components/PageHeader.jsx | 4 +- src/pages/AlongBluesPlan.jsx | 17 +- src/pages/Courses.jsx | 22 +++ src/styles/AroundMap.css | 28 +++- src/styles/AroundMap3.css | 158 +++++++++++++++++++ src/styles/BluesPlan.css | 5 - 12 files changed, 575 insertions(+), 100 deletions(-) create mode 100644 src/components/AroundMap3.jsx delete mode 100644 src/components/BluesPlan.jsx create mode 100644 src/pages/Courses.jsx create mode 100644 src/styles/AroundMap3.css delete mode 100644 src/styles/BluesPlan.css diff --git a/index.html b/index.html index 4cc4306..015993e 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + 바당따라 diff --git a/src/App.jsx b/src/App.jsx index 29efb89..ee002c3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,7 @@ import ItemDetail from './pages/ItemDetail.jsx'; import AlongCourses from './pages/AlongCourses.jsx'; import AlongBlues from './pages/AlongBlues.jsx'; import AlongBluesPlan from './pages/AlongBluesPlan.jsx'; +import Courses from './pages/Courses.jsx'; function App() { @@ -51,6 +52,7 @@ function App() { } /> } /> } /> + } />
diff --git a/src/components/AroundMap.jsx b/src/components/AroundMap.jsx index 4185137..e859eb0 100644 --- a/src/components/AroundMap.jsx +++ b/src/components/AroundMap.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import "../styles/AroundMap.css"; // 스타일은 여기에 추가합니다. -function AroundMap({keyword, searchTrigger}) { +function AroundMap({keyword, searchTrigger, selectedBlue}) { const [position, setPosition] = useState(null); // 현재 위치 상태 관리 const [currCategory, setCurrCategory] = useState(''); // 현재 선택된 카테고리 @@ -12,6 +12,9 @@ function AroundMap({keyword, searchTrigger}) { const placeOverlayRef = useRef(null); // placeOverlay를 Ref로 관리 const contentNodeRef = useRef(null); // 오버레이 콘텐츠 노드를 Ref로 관리 const currCategoryRef = useRef(currCategory); + + const overNodeRef = useRef(null); + const infoOverlayRef = useRef(null); const categories = [ { id: 'AT4', name: '관광', url: '/images/icon/category_6.svg' }, @@ -69,7 +72,10 @@ function AroundMap({keyword, searchTrigger}) { // 현재 위치 가져오기 useEffect(() => { - if (navigator.geolocation) { + if(selectedBlue){ + setPosition({lat: selectedBlue.yMap, lng: selectedBlue.xMap}) + + } else if (navigator.geolocation) { navigator.geolocation.getCurrentPosition((pos) => { const { latitude, longitude } = pos.coords; setPosition({ lat: latitude, lng: longitude }); @@ -110,6 +116,18 @@ function AroundMap({keyword, searchTrigger}) { }); placeOverlayRef.current = customOverlay; + // 커스텀 오버레이 생성 및 설정 + const infolayNode = document.createElement('div'); + infolayNode.className = 'over-info-wrap'; + overNodeRef.current = infolayNode; // Ref로 오버레이 콘텐츠 노드를 관리 + + // placeOverlay를 Ref로 관리 + const infoOverlay = new window.kakao.maps.CustomOverlay({ + content: infolayNode, + zIndex: 1, + }); + infoOverlayRef.current = infoOverlay; + // 줌 컨트롤 추가 const zoomControl = new window.kakao.maps.ZoomControl(); kakaoMap.addControl(zoomControl, window.kakao.maps.ControlPosition.RIGHT); // 줌 컨트롤러 추가 @@ -168,6 +186,15 @@ function AroundMap({keyword, searchTrigger}) { // 마커 클릭 이벤트 등록 window.kakao.maps.event.addListener(marker, 'click', () => { displayPlaceInfo(place); // 클릭 시 오버레이를 표시 + closeOverInfo(); + }); + + window.kakao.maps.event.addListener(marker, 'mouseover', function() { + displayOverInfo(place); + }); + + window.kakao.maps.event.addListener(marker, 'mouseout', function() { + closeOverInfo(); }); return marker; @@ -234,7 +261,9 @@ function AroundMap({keyword, searchTrigger}) { content += `${place.address_name}`; } - content += `${place.phone}
`; + content += `${place.phone}
+ 추가 +
`; // 오버레이 내용 업데이트 및 위치 설정 contentNodeRef.current.innerHTML = content; // Ref로 관리되는 contentNode에 콘텐츠 설정 @@ -259,6 +288,33 @@ function AroundMap({keyword, searchTrigger}) { } }; + // 마우스 올리면 오버레이 표시 + const displayOverInfo = (place) => { + // 오버레이가 정의되지 않은 상태에서는 작동하지 않도록 예외 처리 + if (!overNodeRef.current) { + return; + } + + // 기존 오버레이가 남아 있을 경우 닫기 + if (infoOverlayRef.current) { + infoOverlayRef.current.setMap(null); // 기존 오버레이를 숨기기 + } + + // 오버레이 콘텐츠 설정 + let content = `
${place.place_name}
`; + + // 오버레이 내용 업데이트 및 위치 설정 + overNodeRef.current.innerHTML = content; // Ref로 관리되는 contentNode에 콘텐츠 설정 + infoOverlayRef.current.setContent(overNodeRef.current); // 오버레이 콘텐츠 업데이트 + infoOverlayRef.current.setPosition(new window.kakao.maps.LatLng(place.y, place.x)); + infoOverlayRef.current.setMap(mapRef.current); // 오버레이를 지도에 표시 + }; + + const closeOverInfo = () => { + if(!infoOverlayRef) return; + infoOverlayRef.current.setMap(null); // 오버레이 닫기 + } + return (
diff --git a/src/components/AroundMap3.jsx b/src/components/AroundMap3.jsx new file mode 100644 index 0000000..3046493 --- /dev/null +++ b/src/components/AroundMap3.jsx @@ -0,0 +1,289 @@ +import React, { useEffect, useState, useRef } from 'react'; +import "../styles/AroundMap.css"; // 스타일은 여기에 추가합니다. + +function AroundMap({keyword, searchTrigger, selectedBlue}) { + const [position, setPosition] = useState(null); // 현재 위치 상태 관리 + const [currCategory, setCurrCategory] = useState(''); // 현재 선택된 카테고리 + + // Ref로 map, placesService, markers, placeOverlay, contentNode를 관리 + const mapRef = useRef(null); + const placesServiceRef = useRef(null); + const markersRef = useRef([]); // 마커 배열을 Ref로 관리 + const placeOverlayRef = useRef(null); // placeOverlay를 Ref로 관리 + const contentNodeRef = useRef(null); // 오버레이 콘텐츠 노드를 Ref로 관리 + const currCategoryRef = useRef(currCategory); + + const categories = [ + { id: 'AT4', name: '관광', url: '/images/icon/category_6.svg' }, + { id: 'AD5', name: '숙박', url: '/images/icon/category_7.svg' }, + { id: 'FD6', name: '음식', url: '/images/icon/category_8.svg' }, + { id: 'CE7', name: '카페', url: '/images/icon/category_9.svg' } + ]; // 카테고리 리스트 + + useEffect(() => { + const ps = placesServiceRef.current; + const map = mapRef.current; + + if(ps && map){ + clearUnusedMarkers(); + if (currCategory !== '') { + setCurrCategory(''); // 카테고리를 초기화 + } + searchKeyword(keyword, ps, map); + } + }, [searchTrigger]) + + const searchKeyword = (keyword, ps) => { + if (placeOverlayRef.current) { + placeOverlayRef.current.setMap(null); + } + + ps.keywordSearch( + keyword, + (data, status, pagination) => { + if (status === window.kakao.maps.services.Status.OK) { + console.log(data); + displayPlaces(data); + + // displayPagination(pagination); + } else if (status === window.kakao.maps.services.Status.ZERO_RESULT) { + alert('검색 결과가 존재하지 않습니다.'); + return; + } else if (status === window.kakao.maps.services.Status.ERROR) { + alert('검색 결과 중 오류가 발생했습니다.'); + return; + } + }, + { + useMapCenter: true, + useMapBounds: true, + sort: window.kakao.maps.services.SortBy.ACCURACY + } + ); + }; + + // currCategory가 변경될 때마다 ref 업데이트 + useEffect(() => { + currCategoryRef.current = currCategory; + }, [currCategory]); + + // 현재 위치 가져오기 + useEffect(() => { + if(selectedBlue){ + setPosition({lat: selectedBlue.yMap, lng: selectedBlue.xMap}) + + } else if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition((pos) => { + const { latitude, longitude } = pos.coords; + setPosition({ lat: latitude, lng: longitude }); + }); + } else { + alert("현재 위치를 가져올 수 없습니다."); + } + }, []); + + // 지도 초기화 및 idle 이벤트 등록 (map은 한 번만 초기화) + useEffect(() => { + if (position && !mapRef.current) { + console.log("Initializing map..."); + + const container = document.getElementById('map'); + const options = { + center: new window.kakao.maps.LatLng(position.lat, position.lng), + level: 4, // 지도 확대 레벨 + }; + + // Kakao Map 초기화 + const kakaoMap = new window.kakao.maps.Map(container, options); + mapRef.current = kakaoMap; + + // Kakao Places 서비스 초기화 + const ps = new window.kakao.maps.services.Places(kakaoMap); + placesServiceRef.current = ps; + + // 커스텀 오버레이 생성 및 설정 + const overlayNode = document.createElement('div'); + overlayNode.className = 'placeinfo_wrap'; + contentNodeRef.current = overlayNode; // Ref로 오버레이 콘텐츠 노드를 관리 + + // placeOverlay를 Ref로 관리 + const customOverlay = new window.kakao.maps.CustomOverlay({ + content: overlayNode, + zIndex: 1, + }); + placeOverlayRef.current = customOverlay; + + // 줌 컨트롤 추가 + const zoomControl = new window.kakao.maps.ZoomControl(); + kakaoMap.addControl(zoomControl, window.kakao.maps.ControlPosition.RIGHT); // 줌 컨트롤러 추가 + + // idle 이벤트 등록 (지도의 중심 좌표가 변경될 때) + window.kakao.maps.event.addListener(kakaoMap, 'idle', () => { + const center = kakaoMap.getCenter(); // 지도의 중심 좌표 + console.log("Map idle, updating center position:", center); + setPosition({ + lat: center.getLat(), + lng: center.getLng(), + }); + + // 지도 이동 후에도 마커를 최신 상태로 업데이트 + if (currCategoryRef.current) { + console.log("Map moved, updating places for current category..."); + searchPlaces(center); // 최신 지도 중심에 대한 장소 검색 + } + }); + } + }, [position]); // map은 한 번만 생성되므로 의존성 배열에서 map을 제거 + + // 카테고리가 변경될 때마다 장소를 다시 검색 + useEffect(() => { + + if (mapRef.current && currCategory) { + console.log("Category changed, updating places..."); + clearUnusedMarkers(); // 기존 마커 중 중복되지 않는 마커를 유지 + searchPlaces(mapRef.current.getCenter()); // 최신 지도 중심 좌표를 사용해 검색 + } + }, [currCategory]); + + // 기존 마커 중 필요 없는 마커를 제거하는 함수 + const clearUnusedMarkers = () => { + markersRef.current.forEach((marker) => { + marker.setMap(null); // 각 마커를 지도에서 제거 + }); + markersRef.current = []; // 마커 배열 초기화 + }; + + // 장소를 지도에 마커로 표시하는 함수 (중복 마커 제거) + const displayPlaces = (places) => { + const existingMarkerPositions = new Set( + markersRef.current.map((marker) => `${marker.getPosition().getLat()},${marker.getPosition().getLng()}`) + ); + + const newMarkers = places + .filter((place) => { + const positionKey = `${place.y},${place.x}`; + return !existingMarkerPositions.has(positionKey); // 중복되지 않는 새로운 마커만 추가 + }) + .map((place) => { + const markerPosition = new window.kakao.maps.LatLng(place.y, place.x); + const marker = addMarker(markerPosition); + + // 마커 클릭 이벤트 등록 + window.kakao.maps.event.addListener(marker, 'click', () => { + displayPlaceInfo(place); // 클릭 시 오버레이를 표시 + }); + + return marker; + }); + + // 새로 생성된 마커들을 markersRef에 추가 + markersRef.current = [...markersRef.current, ...newMarkers]; + }; + + // 마커를 생성하고 지도에 표시하는 함수 + const addMarker = (position) => { + const marker = new window.kakao.maps.Marker({ + position, + }); + marker.setMap(mapRef.current); // 지도에 마커 표시 + return marker; + }; + + // 카테고리 검색 함수 + const searchPlaces = (center) => { + console.log(center); + if (!currCategoryRef.current || !mapRef.current || !placesServiceRef.current) return; + + // 커스텀 오버레이 숨기기 + if (placeOverlayRef.current) { + placeOverlayRef.current.setMap(null); + } + + // 카테고리로 장소 검색 + placesServiceRef.current.categorySearch( + currCategoryRef.current, + (data, status) => { + if (status === window.kakao.maps.services.Status.OK) { + displayPlaces(data); // 중복 마커를 필터링한 후 마커를 추가 + } + }, + { location: center, radius: 5000 } + ); + }; + + // 장소 상세 정보를 커스텀 오버레이로 표시하는 함수 + const displayPlaceInfo = (place) => { + // 오버레이가 정의되지 않은 상태에서는 작동하지 않도록 예외 처리 + if (!contentNodeRef.current) { + console.error("contentNode is not initialized."); + return; + } + + // 기존 오버레이가 남아 있을 경우 닫기 + if (placeOverlayRef.current) { + placeOverlayRef.current.setMap(null); // 기존 오버레이를 숨기기 + } + + // 오버레이 콘텐츠 설정 + let content = `
+ + ${place.place_name} + X`; + + if (place.road_address_name) { + content += `${place.road_address_name} + (지번 : ${place.address_name})`; + } else { + content += `${place.address_name}`; + } + + content += `${place.phone}
+ 추가 +
`; + + // 오버레이 내용 업데이트 및 위치 설정 + contentNodeRef.current.innerHTML = content; // Ref로 관리되는 contentNode에 콘텐츠 설정 + placeOverlayRef.current.setContent(contentNodeRef.current); // 오버레이 콘텐츠 업데이트 + placeOverlayRef.current.setPosition(new window.kakao.maps.LatLng(place.y, place.x)); + placeOverlayRef.current.setMap(mapRef.current); // 오버레이를 지도에 표시 + + // 오버레이 닫기 이벤트 등록 + const closeBtn = contentNodeRef.current.querySelector('.close'); + closeBtn.addEventListener('click', () => { + placeOverlayRef.current.setMap(null); // 오버레이 닫기 + }); + }; + + // 카테고리 클릭 시 호출되는 함수 + const onClickCategory = (id) => { + if (currCategory === id) { + clearUnusedMarkers(); // 동일 카테고리 클릭 시 기존 마커 제거 + setCurrCategory(''); // 카테고리 해제 + } else { + setCurrCategory(id); // 새로운 카테고리 선택 + } + }; + + return ( +
+
+
+
    + {categories.map((category) => ( +
  • onClickCategory(category.id)} + > + + {category.name} +
  • + ))} +
+
+
+ ); +} + +export default AroundMap; diff --git a/src/components/BluesPlan.jsx b/src/components/BluesPlan.jsx deleted file mode 100644 index ce6a97a..0000000 --- a/src/components/BluesPlan.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import "../styles/BluesPlan.css"; - -const blue = { - id: 11, - name: "이호테우해수욕장", - xMap: "126.4531570913", - yMap: "33.4974183784" -}; - -const BluesPlan = () => { - const [selectedBlue, setSelectedBlue] = useState(null); - const mapRef = useRef(null); - - useEffect(() => { - // selectedBlue를 한 번만 설정합니다. - setSelectedBlue(blue); - }, []); - - useEffect(() => { - // selectedBlue가 설정되지 않은 상태라면 지도 렌더링을 하지 않습니다. - if (!selectedBlue) return; - - const { kakao } = window; - - // 지도를 표시할 div를 가져옵니다. - const mapContainer = document.getElementById('map'); - const mapOption = { - center: new kakao.maps.LatLng(33.386666, 126.55667), // 제주도 중심 좌표 - level: 10 // 지도 확대 레벨 - }; - - // 지도 객체를 생성합니다. - mapRef.current = new kakao.maps.Map(mapContainer, mapOption); - - // 줌 컨트롤 추가 - const zoomControl = new window.kakao.maps.ZoomControl(); - mapRef.current.addControl(zoomControl, window.kakao.maps.ControlPosition.RIGHT); // 줌 컨트롤러 추가 - - const markerPosition = new kakao.maps.LatLng(selectedBlue.yMap, selectedBlue.xMap); // 마커의 위치 좌표 설정 - - // 마커 이미지 설정 - const markerImageSrc = "https://t1.daumcdn.net/mapjsapi/images/2x/marker.png"; // 마커 이미지 URL - const imageSize = new kakao.maps.Size(20, 28); // 마커 크기 설정 - const markerImage = new kakao.maps.MarkerImage(markerImageSrc, imageSize); // 마커 이미지 객체 생성 - - const marker = new kakao.maps.Marker({ - map: mapRef.current, - position: markerPosition, // 마커를 표시할 위치 - image: markerImage // 마커 이미지 설정 - }); - - // 커스텀 오버레이 내용 정의 - const content = - `
- ${selectedBlue.name} -
` - ; - - // 커스텀 오버레이 생성 - const overlay = new kakao.maps.CustomOverlay({ - content: content, - position: markerPosition, - yAnchor: 1.5, // 오버레이의 위치 조정을 위한 값 - xAnchor: 0.5 - }); - - // 마커에 클릭 이벤트를 추가하여 커스텀 오버레이를 표시 - kakao.maps.event.addListener(marker, 'click', () => { - // 클릭한 마커의 오버레이를 열기 - overlay.setMap(mapRef.current); - }); - }, [selectedBlue]); // selectedBlue가 설정된 후에만 실행 - - return ( -
-
-
- ); -}; - -export default BluesPlan; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 73debb6..d08349f 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -13,13 +13,11 @@ function Header() { navigate('/my') } - const url = 'https://noticemju.s3.ap-northeast-2.amazonaws.com/blue/logo.svg'; - return (
- logo + logo
바당따라
diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx index 9aef03d..0ccc095 100644 --- a/src/components/PageHeader.jsx +++ b/src/components/PageHeader.jsx @@ -2,8 +2,6 @@ import React from 'react'; import { useNavigate } from 'react-router-dom'; import "../styles/PageHeader.css" -const url = 'https://noticemju.s3.ap-northeast-2.amazonaws.com/blue/left_arrow.svg'; - function PageHeader({title}) { const navigate = useNavigate(); @@ -23,7 +21,7 @@ function PageHeader({title}) { return (
- before + before
{title}
); diff --git a/src/pages/AlongBluesPlan.jsx b/src/pages/AlongBluesPlan.jsx index 5dd80fe..29419c2 100644 --- a/src/pages/AlongBluesPlan.jsx +++ b/src/pages/AlongBluesPlan.jsx @@ -1,15 +1,28 @@ +import Search from "../components/Search"; import Footer from "../components/Footer"; import PageHeader from "../components/PageHeader"; +import { useState } from "react"; import { useParams } from "react-router-dom"; -import BluesPlan from "../components/BluesPlan"; +import "../styles/Page.css" +import AroundMap from "../components/AroundMap"; const AlongBluesPlan = () => { const { id } = useParams(); + const [keyword, setKeyword] = useState(''); // 검색어 상태 관리 + const [searchTrigger, setSearchTrigger] = useState(0); + + const blue = { + id: 11, + name: "이호테우해수욕장", + xMap: "126.4531570913", + yMap: "33.4974183784" + }; return (
- + +
); diff --git a/src/pages/Courses.jsx b/src/pages/Courses.jsx new file mode 100644 index 0000000..af01c81 --- /dev/null +++ b/src/pages/Courses.jsx @@ -0,0 +1,22 @@ +import Footer from '../components/Footer'; +import AroundMap from '../components/AroundMap'; +import Search from '../components/Search'; +import { useState } from 'react'; +import "../styles/Page.css"; +import PageHeader from '../components/PageHeader'; + +function Courses() { + const [keyword, setKeyword] = useState(''); // 검색어 상태 관리 + const [searchTrigger, setSearchTrigger] = useState(0); + + return ( +
+ + {/* 검색어를 입력받음 */} + {/* 검색어에 따라 지도 업데이트 */} +
+ ); +} + +export default Courses; \ No newline at end of file diff --git a/src/styles/AroundMap.css b/src/styles/AroundMap.css index 5a661d3..3944be6 100644 --- a/src/styles/AroundMap.css +++ b/src/styles/AroundMap.css @@ -1,6 +1,6 @@ .map-container { position: relative; - width: 80%; + /* width: 80%; */ /* height: 100%; */ margin: 0 auto; } @@ -156,3 +156,29 @@ height: 12px; background: url('https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/vertex_white.png'); } + +/* .marker-over { + display: flex; + justify-content: center; + align-items: center; + z-index: 3; + text-align: center; +} */ + +.over-info-wrap { + display: flex; + align-items: center; + background-color: white; + border: 1px solid #292929; + border-radius: 0.5em; + margin-top: 1.2em; +} + +.over-info-text { + display: flex; + justify-content: center; + color: #292929; + padding: 0.5em; + white-space: nowrap; + font-size: 0.8em; +} \ No newline at end of file diff --git a/src/styles/AroundMap3.css b/src/styles/AroundMap3.css new file mode 100644 index 0000000..b778626 --- /dev/null +++ b/src/styles/AroundMap3.css @@ -0,0 +1,158 @@ +.map-container { + position: relative; + /* width: 80%; */ + /* height: 100%; */ + margin: 0 auto; + } + +.around-map { + position: relative; + width: 100%; + height: 35em; + /* overflow: hidden; */ +} + +.map-category-container { + display: flex; + width: 97%; + height: auto; + position: absolute; + top: 0; + left: 1%; + /* border-radius: 5px; + border: 1px solid #909090; */ + /* box-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); */ + /* background: white; */ + /* overflow: hidden; */ + z-index: 1; + /* padding: 0 1em; */ + font-family: 'NanumSquareRound'; + justify-content: space-between; +} +.map-category-list { + background: white; + border-radius: 5px; + border: 1px solid #909090; + padding: 0; + font-size: 0.7em; +} + +.map-category-item { + display: inline-block; + list-style: none; + /* width: 50px; */ + width: 3.5em; + text-align: center; + cursor: pointer; + border-right: 1px solid #acacac; + padding: 0.5em 0.2em; +} + +.map-category-item.on { + background-color: #eee; +} + +.map-category-item:hover { + background-color: #EDEDED; + border-left: 1px solid #acacac; + margin-left: -1px; +} + +.map-category-item:last-child { + border-right: 0; +} + +.map-category_bg { + display: block; + margin: 0 auto 3px; + width: 27px; + height: 28px; + /* background: url('https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/places_category.png') no-repeat; */ +} + +.map-search-again { + margin-top: 30%; + background-color: white; + border: 1px solid #5C5C5C; + cursor: pointer; + width: 0.9em; + padding: 0.4em 0.5em; + border-radius: 5px; +} + +.placeinfo_wrap { + position: absolute; + bottom: 2em; + left: -9em; + width: 18em; +} + +.placeinfo { + position: relative; + width: 70%; + border-radius: 6px; + border: 1px solid #ccc; + border-bottom: 2px solid #ddd; + padding-bottom: 10px; + background: #fff; + font-family: 'NanumSquareRound'; + z-index: 2; +} + +.placeinfo a, +.placeinfo a:hover, +.placeinfo a:active { + color: #fff; + text-decoration: none; +} + +.placeinfo a, +.placeinfo span { + display: block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.placeinfo .title { + font-weight: bold; + font-size: 0.8em; + border-radius: 6px 6px 0 0; + margin: -1px -1px 0 -1px; + padding: 10px; + color: #fff; + background: #1F75FE; + /* background: #d95050 url('https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/arrow_white.png') no-repeat right 14px center; */ +} + +.placeinfo .close { + position: absolute; + top: 0.5em; + right: 0.5em; + color: white; + font-size: 1.2em; +} +.overlay-address { + font-size: 0.8em; +} + +.placeinfo .tel { + color: #0f7833; + font-size: 0.8em; +} + +.placeinfo .jibun { + color: #999; + font-size: 11px; + margin-top: 0; +} + +.after { + content: ''; + position: relative; + margin-left: -12px; + left: 50%; + width: 22px; + height: 12px; + background: url('https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/vertex_white.png'); +} diff --git a/src/styles/BluesPlan.css b/src/styles/BluesPlan.css deleted file mode 100644 index 3d31be4..0000000 --- a/src/styles/BluesPlan.css +++ /dev/null @@ -1,5 +0,0 @@ -.blues-plan-map { - width: 100%; - height: 40vh; - margin: 0 auto; -} \ No newline at end of file