From 0de8ebf11079dd0f385e7add602fcdd96dedd14c Mon Sep 17 00:00:00 2001 From: Doyun Lee <108219121+doyn511@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:52:40 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=EB=A7=B5=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20&=20=EC=A7=80=EB=8F=84=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EA=B5=AC=ED=98=84=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오맵 연결 * chore: asset 추가 * feat: 행정구역 별 위도, 경도 상수 추가 * feat: 카카오맵 초기세팅(사용자 위치에 따른 중심 설정, geolocation 설정) * feat: default 위도, 경도 지정 함수 * chore: 카카오맵 핀 asset 추가 * feat: 공공api(위치기반 관광정보 조회) 연결 * feat: 카카오맵 연결(화면에 맵 띄우기) * feat: 주변 여행지 찾아보기 기능 구현 * feat: 지도에 pin 생성하기 * feat: 타입 추가 * chore: 기본 이미지 추가 * chore: 바텀시트 배경 관련 props 추가 * feat: 검색 결과 바텀시트 연결 * chore: 주석 수정 * chore: 이미지 Url 반영 코드 수정 * chore: 어셋 수정 --- index.html | 15 +- src/Router.tsx | 5 + src/apis/index.ts | 1 - src/apis/public/locationBasedList1.ts | 36 ++ src/assets/icon/icon-refresh-mono.svg | 3 + src/assets/icon/icon_map_favorite.svg | 21 ++ src/assets/icon/icon_map_search_active.svg | 23 ++ src/assets/icon/icon_map_search_inactive.svg | 23 ++ src/assets/icon/index.ts | 63 ++-- src/assets/image/img_default.png | Bin 0 -> 7238 bytes src/assets/image/img_kakaomap_marker.png | Bin 0 -> 2869 bytes src/assets/image/index.ts | 2 + src/components/BottomSheet.tsx | 23 +- src/constants/REGION_LIST.ts | 6 +- src/constants/facilities.tsx | 4 +- src/types/globals.d.ts | 4 + src/types/kakao.d.ts | 61 ++++ src/types/locationBasedList1.ts | 39 +++ .../Map/components/BottomSheetContent.tsx | 95 +++++ .../Map/components/SearchBottomSheet.tsx | 39 +++ src/views/Map/constants/POSITION_LATLNG.ts | 329 ++++++++++++++++++ src/views/Map/pages/MapPage.tsx | 248 +++++++++++++ src/views/Map/utils/createKakaoMap.ts | 15 + src/views/Map/utils/createMapPin.ts | 70 ++++ src/views/Map/utils/getMapCenter.ts | 23 ++ src/views/Map/utils/setDefaultLoctaion.ts | 9 + 26 files changed, 1111 insertions(+), 46 deletions(-) create mode 100644 src/apis/public/locationBasedList1.ts create mode 100644 src/assets/icon/icon-refresh-mono.svg create mode 100644 src/assets/icon/icon_map_favorite.svg create mode 100644 src/assets/icon/icon_map_search_active.svg create mode 100644 src/assets/icon/icon_map_search_inactive.svg create mode 100644 src/assets/image/img_default.png create mode 100644 src/assets/image/img_kakaomap_marker.png create mode 100644 src/types/kakao.d.ts create mode 100644 src/types/locationBasedList1.ts create mode 100644 src/views/Map/components/BottomSheetContent.tsx create mode 100644 src/views/Map/components/SearchBottomSheet.tsx create mode 100644 src/views/Map/constants/POSITION_LATLNG.ts create mode 100644 src/views/Map/pages/MapPage.tsx create mode 100644 src/views/Map/utils/createKakaoMap.ts create mode 100644 src/views/Map/utils/createMapPin.ts create mode 100644 src/views/Map/utils/getMapCenter.ts create mode 100644 src/views/Map/utils/setDefaultLoctaion.ts diff --git a/index.html b/index.html index a13a95c..c360b32 100644 --- a/index.html +++ b/index.html @@ -3,16 +3,23 @@ - + UNITRIP +
- + crossorigin="anonymous"> + diff --git a/src/Router.tsx b/src/Router.tsx index 5bf95e0..56a8035 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -6,6 +6,7 @@ import ErrorReportPage from './views/ErrorReport/pages/ErrorReportPage'; import LoginCallBack from './views/Login/components/LoginCallBack'; import SignUpPage from './views/Login/pages/SignUpPage'; import MainPage from './views/Main/pages/MainPage'; +import MapPage from './views/Map/pages/MapPage'; import Mypage from './views/Mypage/pages/Mypage'; import SearchPage from './views/Search/pages/SearchPage'; import SearchResultPage from './views/Search/pages/SearchResultPage'; @@ -35,6 +36,10 @@ const router = createBrowserRouter([ element: , }, { path: '/error-report', element: }, + { + path: '/map', + element: , + }, // { // path: "*", // element: , diff --git a/src/apis/index.ts b/src/apis/index.ts index d55fc45..8857b96 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,5 +1,4 @@ import axios, { AxiosInstance } from 'axios'; - const client: AxiosInstance = axios.create({ baseURL: import.meta.env.BASE_URL, }); diff --git a/src/apis/public/locationBasedList1.ts b/src/apis/public/locationBasedList1.ts new file mode 100644 index 0000000..04ad6fc --- /dev/null +++ b/src/apis/public/locationBasedList1.ts @@ -0,0 +1,36 @@ +/** 위치기반 관광정보 조회 API */ +import { getLocationBasedList1Res } from '@/types/locationBasedList1'; +import { Response } from '@/types/public'; + +import { publicDataClient } from '..'; + +interface locationBasedList1Params { + pageNo: number; + numOfRows: number; + MobileOS: 'IOS' | 'AND' | 'WIN' | 'ETC'; + mapX: string | undefined; + mapY: string | undefined; + radius: string; + contentTypeId: number; +} + +export const getLocationBasedList1 = async ( + paramsInfo: locationBasedList1Params, +) => { + let params = `MobileApp=UNITRIP&_type=json&arrange=O&serviceKey=${import.meta.env.VITE_PUBLIC_DATA_SERVICE_KEY}`; + + for (const [key, value] of Object.entries(paramsInfo)) { + params += `&${key}=${value}`; + } + + const { + data: { + response: { + body: { items }, + }, + }, + } = await publicDataClient.get>( + `/locationBasedList1?${params}`, + ); + return items; +}; diff --git a/src/assets/icon/icon-refresh-mono.svg b/src/assets/icon/icon-refresh-mono.svg new file mode 100644 index 0000000..2b04ae0 --- /dev/null +++ b/src/assets/icon/icon-refresh-mono.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/icon_map_favorite.svg b/src/assets/icon/icon_map_favorite.svg new file mode 100644 index 0000000..0eb1a6b --- /dev/null +++ b/src/assets/icon/icon_map_favorite.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icon/icon_map_search_active.svg b/src/assets/icon/icon_map_search_active.svg new file mode 100644 index 0000000..c9fce56 --- /dev/null +++ b/src/assets/icon/icon_map_search_active.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icon/icon_map_search_inactive.svg b/src/assets/icon/icon_map_search_inactive.svg new file mode 100644 index 0000000..80b1526 --- /dev/null +++ b/src/assets/icon/icon_map_search_inactive.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icon/index.ts b/src/assets/icon/index.ts index ae5d419..dab9f60 100644 --- a/src/assets/icon/index.ts +++ b/src/assets/icon/index.ts @@ -50,7 +50,13 @@ export { default as PhysicalTypeIcon } from './icon_physical_type.svg?react'; export { default as BigInfoIcon } from './icon_big_info.svg?react'; export { default as HeartFillMonoIcon } from './icon-heart-fill-mono.svg?react'; -// Universal icon +//Map +export { default as RefreshMonoIcon } from './icon-refresh-mono.svg?react'; +export { default as MapFavoirteIcon } from './icon_map_favorite.svg?react'; +export { default as MapSearchActiveIcon } from './icon_map_search_active.svg?react'; +export { default as MapSearchInactiveIcon } from './icon_map_search_inactive.svg?react'; + +// Universal Detail export { default as AudioGuideActiveIcon } from './icon_audioguide_active.svg?react'; export { default as AudioGuideInActiveIcon } from './icon_audioguide_inactive.svg?react'; export { default as AuditoriumActiveIcon } from './icon_auditorium_active.svg?react'; @@ -81,8 +87,6 @@ export { default as PromotionActiveIcon } from './icon_promotion_active.svg?reac export { default as PromotionInActiveIcon } from './icon_promotion_inactive.svg?react'; export { default as PublicTransportActiveIcon } from './icon_publictransport_active.svg?react'; export { default as PublicTransportInActiveIcon } from './icon_publictransport_inactive.svg?react'; -export { default as PublicTransportDefaultIcon } from './publictransport_default.svg?react'; -export { default as PublicTransportSelectedIcon } from './publictransport_selected.svg?react'; export { default as RestroomActiveIcon } from './icon_restroom_active.svg?react'; export { default as RestroomInActiveIcon } from './icon_restroom_inactive.svg?react'; export { default as RouteActiveIcon } from './icon_route_active.svg?react'; @@ -93,39 +97,20 @@ export { default as StrollerActiveIcon } from './icon_stroller_active.svg?react' export { default as StrollerInActiveIcon } from './icon_stroller_inactive.svg?react'; export { default as TicketOfficeActiveIcon } from './icon_ticketoffice_active.svg?react'; export { default as TicketOfficeInActiveIcon } from './icon_ticketoffice_inactive.svg?react'; -export { default as TicketOfficeDefaultIcon } from './ticketoffice_default.svg?react'; -export { default as TicketOfficeSelectedIcon } from './ticketoffice_selected.svg?react'; export { default as VideoGuideActiveIcon } from './icon_videoguide_active.svg?react'; export { default as VideoGuideInActiveIcon } from './icon_videoguide_inactive.svg?react'; export { default as WheelChairAcitveIcon } from './icon_wheelchair_active.svg?react'; export { default as WheelChairInAcitveIcon } from './icon_wheelchair_inactive.svg?react'; -export { default as EmptyPhotoIcon } from './icon_empty_photo.svg?react'; export { default as ArrowBackIconIosDownIcon } from './icon-arrow-back-ios-down.svg?react'; export { default as ArrowBackIconIosUpIcon } from './icon-arrow-back-ios-up.svg?react'; +export { default as EmptyPhotoIcon } from './icon_empty_photo.svg?react'; -export { default as ParkingDefaultIcon } from './parking_default.svg?react'; -export { default as ParkingSelectedIcon } from './parking_selected.svg?react'; -export { default as PromotionDefaultIcon } from './promotion_default.svg?react'; -export { default as PromotionSelectedIcon } from './promotion_selected.svg?react'; -export { default as RestroomDefaultIcon } from './promotion_default.svg?react'; -export { default as RestroomSelectedIcon } from './promotion_selected.svg?react'; -export { default as RouteDefaultIcon } from './route_default.svg?react'; -export { default as RouteSelectedIcon } from './route_selected.svg?react'; -export { default as WheelChairDefaultIcon } from './wheelchair_default.svg?react'; -export { default as WheelChairSelectedIcon } from './wheelchair_selected.svg?react'; -export { default as LactationRoomDefaultIcon } from './lactationroom_default.svg?react'; -export { default as LactationRoomSelectedIcon } from './lactationroom_selected.svg?react'; -export { default as SignGuideDefaultIcon } from './signguide_default.svg?react'; -export { default as SignGuideSelectedIcon } from './signguide_selected.svg?react'; -export { default as StrollerDefaultIcon } from './stroller_default.svg?react'; -export { default as StrollerSelectedIcon } from './stroller_selected.svg?react'; -export { default as VideoGuideDefaultIcon } from './videoguide_default.svg?react'; -export { default as VideoGuideSelectedIcon } from './videoguide_selected.svg?react'; +//Universal Filter export { default as AudioGuideDefaultIcon } from './audioguide_default.svg?react'; -export { default as AudioGuideInSelectedIcon } from './audioguide_selected.svg?react'; +export { default as AudioGuideSelectedIcon } from './audioguide_selected.svg?react'; export { default as AuditoriumDefaultIcon } from './auditorium_default.svg?react'; -export { default as AuditoriumSelectedIcon } from './audioguide_selected.svg?react'; +export { default as AuditoriumSelectedIcon } from './auditorium_selected.svg?react'; export { default as BabySpareChairDefaultIcon } from './babysparechair_default.svg?react'; export { default as BabySpareChairSelectedIcon } from './babysparechair_selected.svg?react'; export { default as BigPrintDefaultIcon } from './bigprint_default.svg?react'; @@ -140,7 +125,29 @@ export { default as ExitDefaultIcon } from './exit_default.svg?react'; export { default as ExitSelectedIcon } from './exit_selected.svg?react'; export { default as GuideHumanDefaultIcon } from './guidehuman_default.svg?react'; export { default as GuideHumanSelectedIcon } from './guidehuman_selected.svg?react'; -export { default as GuideSystemDefaultIcon } from './guidehuman_default.svg?react'; -export { default as GuideSystemSelectedIcon } from './guidehuman_selected.svg?react'; +export { default as GuideSystemDefaultIcon } from './guidesystem_default.svg?react'; +export { default as GuideSystemSelectedIcon } from './guidesystem_selected.svg?react'; export { default as HelpDogDefaultIcon } from './helpdog_default.svg?react'; export { default as HelpDogSelectedIcon } from './helpdog_selected.svg?react'; +export { default as LactationRoomDefaultIcon } from './lactationroom_default.svg?react'; +export { default as LactationRoomSelectedIcon } from './lactationroom_selected.svg?react'; +export { default as ParkingDefaultIcon } from './parking_default.svg?react'; +export { default as ParkingSelectedIcon } from './parking_selected.svg?react'; +export { default as PromotionDefaultIcon } from './promotion_default.svg?react'; +export { default as PromotionSelectedIcon } from './promotion_selected.svg?react'; +export { default as PublicTransportDefaultIcon } from './publictransport_default.svg?react'; +export { default as PublicTransportSelectedIcon } from './publictransport_selected.svg?react'; +export { default as RestroomDefaultIcon } from './restroom_default.svg?react'; +export { default as RestroomSelectedIcon } from './restroom_selected.svg?react'; +export { default as RouteDefaultIcon } from './route_default.svg?react'; +export { default as RouteSelectedIcon } from './route_selected.svg?react'; +export { default as SignGuideDefaultIcon } from './signguide_default.svg?react'; +export { default as SignGuideSelectedIcon } from './signguide_selected.svg?react'; +export { default as StrollerDefaultIcon } from './stroller_default.svg?react'; +export { default as StrollerSelectedIcon } from './stroller_selected.svg?react'; +export { default as TicketOfficeDefaultIcon } from './ticketoffice_default.svg?react'; +export { default as TicketOfficeSelectedIcon } from './ticketoffice_selected.svg?react'; +export { default as VideoGuideDefaultIcon } from './videoguide_default.svg?react'; +export { default as VideoGuideSelectedIcon } from './videoguide_selected.svg?react'; +export { default as WheelChairDefaultIcon } from './wheelchair_default.svg?react'; +export { default as WheelChairSelectedIcon } from './wheelchair_selected.svg?react'; diff --git a/src/assets/image/img_default.png b/src/assets/image/img_default.png new file mode 100644 index 0000000000000000000000000000000000000000..ee843bdf4d80f6794c93bd23d4aa7bf841c84e13 GIT binary patch literal 7238 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 { const { @@ -28,6 +30,7 @@ const BottomSheet = (props: BottomSheetProps) => { buttonText, noButton, bottomSheetCss, + sheetBackgroundCss, children, } = props; @@ -42,7 +45,12 @@ const BottomSheet = (props: BottomSheetProps) => { }; const portalContent = ( -
+
css` position: absolute; bottom: 0; + overflow: auto; width: 100vw; height: ${height}; - border-radius: 1.2rem 1.2rem 0rem 0rem; + border-radius: 1.2rem 1.2rem 0 0; background-color: white; - - overflow: auto; `; const buttonCotainerCss = css` position: fixed; - left: 0; bottom: 1.2rem; + left: 0; width: 100vw; padding: 0 2rem; @@ -109,11 +116,11 @@ const buttonCotainerCss = css` const buttonCss = css` width: 100%; padding: 1.4rem 0; - border-radius: 1.2rem; - color: ${COLORS.brand1}; background-color: ${COLORS.brand2}; + color: ${COLORS.brand1}; + ${FONTS.Body3}; `; diff --git a/src/constants/REGION_LIST.ts b/src/constants/REGION_LIST.ts index 973cfcc..6616857 100644 --- a/src/constants/REGION_LIST.ts +++ b/src/constants/REGION_LIST.ts @@ -105,7 +105,7 @@ export const REGION_LIST = [ '종촌동', '고운동', '보람동', - ' 대평동', + '대평동', '소담동', '반곡동', ], @@ -244,7 +244,7 @@ export const REGION_LIST = [ '영암군', '무안군', '함평군', - ' 영광군', + '영광군', '장성군', '완도군', '진도군', @@ -273,7 +273,7 @@ export const REGION_LIST = [ '성주군', '칠곡군', '예천군', - ' 봉화군', + '봉화군', '울진군', '울릉군', ], diff --git a/src/constants/facilities.tsx b/src/constants/facilities.tsx index cc8c854..b42eab7 100644 --- a/src/constants/facilities.tsx +++ b/src/constants/facilities.tsx @@ -2,7 +2,7 @@ import { AudioGuideActiveIcon, AudioGuideDefaultIcon, AudioGuideInActiveIcon, - AudioGuideInSelectedIcon, + AudioGuideSelectedIcon, AuditoriumActiveIcon, AuditoriumDefaultIcon, AuditoriumInActiveIcon, @@ -197,7 +197,7 @@ export const VISUAL_FACILITIES: Facility[] = [ active: , inactive: , default: , - selected: , + selected: , }, { name: '큰 활자', diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 0351bab..c0fbfb4 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -8,6 +8,10 @@ declare global { authorize(options: { redirectUri: string }): void; }; }; + + /* eslint-disable @typescript-eslint/no-explicit-any */ + kakao: any; + /* eslint-enable @typescript-eslint/no-explicit-any */ } } diff --git a/src/types/kakao.d.ts b/src/types/kakao.d.ts new file mode 100644 index 0000000..bcd0aaf --- /dev/null +++ b/src/types/kakao.d.ts @@ -0,0 +1,61 @@ +declare namespace kakao.maps { + class Map { + constructor(container: HTMLElement, options: MapOptions); + getCenter(): LatLng; + setCenter(latlng: LatLng | Coords): void; + getLevel(): number; + setLevel(level: number); + } + + interface MapOptions { + center: LatLng; + level: number; + } + + class LatLng { + constructor(lat: number, lng: number); + + La: number; + Ma: number; + } + + class LatLng { + La: number; + Ma: number; + } + + class Size { + constructor(XSize: number, YSize: number); + } + + class Point { + constructor(X: number, Y: number); + } + + class MarkerImage { + constructor(string, Size, Point); + } + + class Marker { + constructor({ map, position, image }) { + this.map = map; + this.position = position; // LatLng 타입 + this.image = image; // MarkerImage 타입 + } + + map: kakao.maps.Map | undefined; + setMap: React.Dispatch>; + } + + namespace event { + interface Listener { + (event: Event): void; + } + + function addListener( + target: Marker, + eventName: string, + listener: Listener, + ): void; + } +} diff --git a/src/types/locationBasedList1.ts b/src/types/locationBasedList1.ts new file mode 100644 index 0000000..4db19ed --- /dev/null +++ b/src/types/locationBasedList1.ts @@ -0,0 +1,39 @@ +export interface locationBasedList1Res { + tel: 'string'; + title: 'string'; + firstimage: 'string'; + createdtime: 'string'; + dist: 'string'; + cat2: 'string'; + contentid: 'string'; + contenttypeid: 'string'; + addr1: 'string'; + addr2: 'string'; + areacode: 'string'; + booktour: 'string'; + cat1: 'string'; + mlevel: 'string'; + modifiedtime: 'string'; + sigungucode: 'string'; + cpyrhtDivCd: 'string'; + firstimage2: 'string'; + mapx: 'string'; + mapy: 'string'; + cat3: 'string'; +} + +export interface getLocationBasedList1Res { + header: { + resultMsg: 'string'; + resultCode: 'string'; + }; + body: { + pageNo: number; + totalCount: number; + items: + | { + item: locationBasedList1Res[]; + } + | ''; + }; +} diff --git a/src/views/Map/components/BottomSheetContent.tsx b/src/views/Map/components/BottomSheetContent.tsx new file mode 100644 index 0000000..41a7dda --- /dev/null +++ b/src/views/Map/components/BottomSheetContent.tsx @@ -0,0 +1,95 @@ +/** 바텀시트 내부 맵핑 할 내용들 */ + +import { css } from '@emotion/react'; + +import { DefaultImage } from '@/assets/image'; +import { COLORS, FONTS } from '@/styles/constants'; + +interface contentProps { + title: string; + address: string; + image: string; + contentId: string; +} + +const BottomSheetContent = (props: contentProps) => { + const { title, address, image, contentId } = props; + const isImageNone = image === ''; + + return ( +
console.log(contentId)}> +
+
+

{title}

+ 관광지 +
+

{address}

+
+ {`${title} +
+ ); +}; + +export default BottomSheetContent; + +const contentContainer = css` + display: flex; + gap: 1rem; + justify-content: space-between; + + width: 100%; + padding: 4rem 1.7rem 10.5rem; +`; + +const textSection = css` + display: flex; + gap: 0.2rem; + flex-direction: column; + max-width: 22.9rem; +`; + +const titleSectionCss = css` + display: flex; + gap: 0.5rem; + align-items: center; + + width: 100%; +`; + +const titleText = (type: string) => css` + ${type === 'title' + ? css` + overflow: hidden; + + max-width: 80%; + + text-overflow: ellipsis; + + color: ${COLORS.brand1}; + white-space: nowrap; + ${FONTS.H4}; + ` + : css` + width: 20%; + + color: ${COLORS.gray4}; + white-space: nowrap; + ${FONTS.Body5}; + `} +`; + +const addressText = css` + color: ${COLORS.gray6}; + ${FONTS.Body4}; + font-weight: 400; +`; + +const imageCss = css` + width: 8.3rem; + height: 8.3rem; + border-radius: 1rem; +`; diff --git a/src/views/Map/components/SearchBottomSheet.tsx b/src/views/Map/components/SearchBottomSheet.tsx new file mode 100644 index 0000000..968f0db --- /dev/null +++ b/src/views/Map/components/SearchBottomSheet.tsx @@ -0,0 +1,39 @@ +import { css } from '@emotion/react'; + +import BottomSheet from '@/components/BottomSheet'; + +import BottomSheetContent from './BottomSheetContent'; + +interface searchBottomSheetProps { + title: string; + address: string; + image: string; + contentId: string; + closeBottomSheet: () => void; +} + +/** 주변 여행지 검색 시 보여지는 바텀시트 */ +const SearchBottomSheet = (props: searchBottomSheetProps) => { + const { title, address, image, contentId, closeBottomSheet } = props; + + return ( +
+ + + +
+ ); +}; + +export default SearchBottomSheet; diff --git a/src/views/Map/constants/POSITION_LATLNG.ts b/src/views/Map/constants/POSITION_LATLNG.ts new file mode 100644 index 0000000..7812e9d --- /dev/null +++ b/src/views/Map/constants/POSITION_LATLNG.ts @@ -0,0 +1,329 @@ +// const [selectedRegion, setSelectedRegion] = useState({ city: '', town: '' }); +export const POSITION_LATLNG = [ + { + city: '서울특별시', + town: [ + { key: '종로구', lat: 37.5973, lng: 126.9777 }, + { key: '중구', lat: 37.560938, lng: 126.996027 }, + { key: '용산구', lat: 37.530981, lng: 126.980214 }, + { key: '성동구', lat: 37.558083, lng: 127.039217 }, + { key: '광진구', lat: 37.5498, lng: 127.0823 }, + { key: '동대문구', lat: 37.5793, lng: 127.0382 }, + { key: '중랑구', lat: 37.606, lng: 127.0895 }, + { key: '성북구', lat: 37.6109, lng: 127.0227 }, + { key: '강북구', lat: 37.6392, lng: 127.0252 }, + { key: '도봉구', lat: 37.6681, lng: 127.0283 }, + { key: '노원구', lat: 37.6551, lng: 127.0777 }, + { key: '은평구', lat: 37.6043, lng: 126.933 }, + { key: '서대문구', lat: 37.5795, lng: 126.9655 }, + { key: '마포구', lat: 37.5523, lng: 126.9143 }, + { key: '양천구', lat: 37.5188, lng: 126.8647 }, + { key: '강서구', lat: 37.5502, lng: 126.8491 }, + { key: '구로구', lat: 37.4955, lng: 126.8845 }, + { key: '금천구', lat: 37.4573, lng: 126.9018 }, + { key: '영등포구', lat: 37.5254, lng: 126.8957 }, + { key: '동작구', lat: 37.5035, lng: 126.9485 }, + { key: '관악구', lat: 37.4783, lng: 126.9514 }, + { key: '서초구', lat: 37.4839, lng: 127.0335 }, + { key: '강남구', lat: 37.4969, lng: 127.0276 }, + { key: '송파구', lat: 37.5144, lng: 127.1059 }, + { key: '강동구', lat: 37.5506, lng: 127.1399 }, + ], + }, + { + city: '부산광역시', + town: [ + { key: '중구', lat: 35.1035, lng: 129.0403 }, + { key: '서구', lat: 35.096, lng: 129.014 }, + { key: '동구', lat: 35.1104, lng: 129.0407 }, + { key: '영도구', lat: 35.0729, lng: 129.1008 }, + { key: '부산진구', lat: 35.1595, lng: 129.059 }, + { key: '남구', lat: 35.129, lng: 129.0755 }, + { key: '북구', lat: 35.1846, lng: 129.0182 }, + { key: '강서구', lat: 35.164, lng: 128.9475 }, + { key: '해운대구', lat: 35.1587, lng: 129.1605 }, + { key: '사하구', lat: 35.1145, lng: 128.9791 }, + { key: '금정구', lat: 35.2283, lng: 129.0871 }, + { key: '연제구', lat: 35.1809, lng: 129.1052 }, + { key: '수영구', lat: 35.1568, lng: 129.1164 }, + { key: '사상구', lat: 35.1485, lng: 128.9882 }, + { key: '기장군', lat: 35.2296, lng: 129.2272 }, + ], + }, + { + city: '대구광역시', + town: [ + { key: '중구', lat: 35.8656, lng: 128.5911 }, + { key: '동구', lat: 35.8833, lng: 128.6104 }, + { key: '서구', lat: 35.869, lng: 128.5889 }, + { key: '남구', lat: 35.8483, lng: 128.6011 }, + { key: '북구', lat: 35.9002, lng: 128.609 }, + { key: '수성구', lat: 35.8549, lng: 128.6171 }, + { key: '달서구', lat: 35.8352, lng: 128.5805 }, + { key: '달성군', lat: 35.706, lng: 128.5138 }, + { key: '군위군', lat: 36.0525, lng: 128.6178 }, + ], + }, + { + city: '인천광역시', + town: [ + { key: '중구', lat: 37.4802, lng: 126.6157 }, + { key: '동구', lat: 37.4765, lng: 126.6202 }, + { key: '미추홀구', lat: 37.452, lng: 126.6544 }, + { key: '연수구', lat: 37.4449, lng: 126.6515 }, + { key: '남동구', lat: 37.4531, lng: 126.7383 }, + { key: '부평구', lat: 37.4931, lng: 126.7245 }, + { key: '계양구', lat: 37.5217, lng: 126.726 }, + { key: '서구', lat: 37.4903, lng: 126.6519 }, + { key: '강화군', lat: 37.7034, lng: 126.4845 }, + { key: '옹진군', lat: 37.4761, lng: 126.5224 }, + ], + }, + { + city: '광주광역시', + town: [ + { key: '동구', lat: 35.1585, lng: 126.9222 }, + { key: '서구', lat: 35.1437, lng: 126.8835 }, + { key: '남구', lat: 35.1342, lng: 126.8915 }, + { key: '북구', lat: 35.1661, lng: 126.9184 }, + { key: '광산구', lat: 35.1709, lng: 126.7987 }, + ], + }, + { + city: '대전광역시', + town: [ + { key: '중구', lat: 36.3228, lng: 127.4295 }, + { key: '서구', lat: 36.3396, lng: 127.382 }, + { key: '동구', lat: 36.328, lng: 127.455 }, + { key: '유성구', lat: 36.3664, lng: 127.345 }, + { key: '대덕구', lat: 36.325, lng: 127.448 }, + ], + }, + { + city: '세종특별자치시', + town: [ + { key: '조치원읍', lat: 36.4844, lng: 127.2867 }, + { key: '연기면', lat: 36.4759, lng: 127.254 }, + { key: '연동면', lat: 36.4976, lng: 127.2653 }, + { key: '부강면', lat: 36.4873, lng: 127.3048 }, + { key: '금남면', lat: 36.5152, lng: 127.3383 }, + { key: '장군면', lat: 36.4879, lng: 127.3749 }, + { key: '연서면', lat: 36.5097, lng: 127.3669 }, + { key: '전의면', lat: 36.4835, lng: 127.3181 }, + { key: '전동면', lat: 36.4982, lng: 127.3823 }, + { key: '소정면', lat: 36.4873, lng: 127.3528 }, + { key: '한솔동', lat: 36.487, lng: 127.282 }, + { key: '새롬동', lat: 36.4875, lng: 127.2825 }, + { key: '나성동', lat: 36.4878, lng: 127.286 }, + { key: '다정동', lat: 36.485, lng: 127.2725 }, + { key: '도담동', lat: 36.4931, lng: 127.2869 }, + { key: '어진동', lat: 36.4825, lng: 127.295 }, + { key: '해밀동', lat: 36.4864, lng: 127.2878 }, + { key: '아름동', lat: 36.4857, lng: 127.2834 }, + { key: '종촌동', lat: 36.4902, lng: 127.2837 }, + { key: '고운동', lat: 36.4668, lng: 127.2908 }, + { key: '보람동', lat: 36.489, lng: 127.28 }, + { key: '대평동', lat: 36.4851, lng: 127.292 }, + { key: '소담동', lat: 36.4843, lng: 127.2905 }, + { key: '반곡동', lat: 36.4905, lng: 127.284 }, + ], + }, + { + city: '경기도', + town: [ + { key: '수원특례시', lat: 37.2636, lng: 127.0286 }, + { key: '성남시', lat: 37.4372, lng: 127.1288 }, + { key: '의정부시', lat: 37.7382, lng: 127.0505 }, + { key: '안양시', lat: 37.3943, lng: 126.9257 }, + { key: '부천시', lat: 37.4812, lng: 126.782 }, + { key: '광명시', lat: 37.4786, lng: 126.8832 }, + { key: '동두천시', lat: 37.9022, lng: 127.0614 }, + { key: '평택시', lat: 37.0812, lng: 127.0861 }, + { key: '안산시', lat: 37.3213, lng: 126.8294 }, + { key: '고양특례시', lat: 37.6575, lng: 126.834 }, + { key: '과천시', lat: 37.4801, lng: 126.9931 }, + { key: '구리시', lat: 37.6005, lng: 127.1267 }, + { key: '남양주시', lat: 37.6356, lng: 127.2187 }, + { key: '오산시', lat: 37.1472, lng: 127.0674 }, + { key: '시흥시', lat: 37.398, lng: 126.7918 }, + { key: '군포시', lat: 37.3594, lng: 126.9492 }, + { key: '의왕시', lat: 37.3388, lng: 126.998 }, + { key: '하남시', lat: 37.5505, lng: 127.2011 }, + { key: '용인특례시', lat: 37.2419, lng: 127.177 }, + { key: '파주시', lat: 37.7592, lng: 126.7701 }, + { key: '이천시', lat: 37.2841, lng: 127.488 }, + { key: '안성시', lat: 37.0145, lng: 127.2748 }, + { key: '김포시', lat: 37.628, lng: 126.7064 }, + { key: '화성시', lat: 37.2056, lng: 126.8357 }, + { key: '광주시', lat: 37.4023, lng: 127.2531 }, + { key: '양주시', lat: 37.7575, lng: 127.0362 }, + { key: '포천시', lat: 37.8958, lng: 127.1985 }, + { key: '여주시', lat: 37.2966, lng: 127.6316 }, + { key: '연천군', lat: 37.8685, lng: 127.0583 }, + { key: '가평군', lat: 37.8341, lng: 127.491 }, + { key: '양평군', lat: 37.4892, lng: 127.445 }, + ], + }, + { + city: '강원특별자치도', + town: [ + { key: '춘천시', lat: 37.8834, lng: 127.729 }, + { key: '원주시', lat: 37.3494, lng: 127.3674 }, + { key: '강릉시', lat: 37.7515, lng: 128.876 }, + { key: '동해시', lat: 37.5162, lng: 129.1169 }, + { key: '태백시', lat: 37.1601, lng: 128.9869 }, + { key: '속초시', lat: 38.2065, lng: 128.5911 }, + { key: '삼척시', lat: 38.4421, lng: 128.4591 }, + { key: '홍천군', lat: 37.6167, lng: 127.8912 }, + { key: '횡성군', lat: 37.3396, lng: 127.8492 }, + { key: '영월군', lat: 37.1892, lng: 127.1209 }, + { key: '평창군', lat: 37.4701, lng: 128.3937 }, + { key: '정선군', lat: 37.3334, lng: 128.653 }, + { key: '철원군', lat: 38.211, lng: 127.3832 }, + { key: '화천군', lat: 38.1004, lng: 127.6575 }, + { key: '양구군', lat: 38.1126, lng: 127.972 }, + { key: '인제군', lat: 38.1342, lng: 128.2322 }, + { key: '고성군', lat: 38.3602, lng: 128.5799 }, + { key: '양양군', lat: 38.062, lng: 128.65 }, + ], + }, + { + city: '충청북도', + town: [ + { key: '청주시', lat: 36.6356, lng: 127.4895 }, + { key: '충주시', lat: 37.0045, lng: 127.9258 }, + { key: '제천시', lat: 37.1412, lng: 128.195 }, + { key: '보은군', lat: 36.3715, lng: 127.724 }, + { key: '옥천군', lat: 36.2875, lng: 127.6463 }, + { key: '영동군', lat: 36.1435, lng: 127.494 }, + { key: '증평군', lat: 36.767, lng: 127.4894 }, + { key: '진천군', lat: 37.0706, lng: 127.3604 }, + { key: '괴산군', lat: 36.6627, lng: 127.7526 }, + { key: '음성군', lat: 36.9083, lng: 127.6468 }, + { key: '단양군', lat: 36.9714, lng: 128.3748 }, + ], + }, + { + city: '충청남도', + town: [ + { key: '천안시', lat: 36.8045, lng: 127.1229 }, + { key: '공주시', lat: 36.4602, lng: 127.1105 }, + { key: '보령시', lat: 36.3662, lng: 126.589 }, + { key: '아산시', lat: 36.7992, lng: 127.002 }, + { key: '서산시', lat: 36.7741, lng: 126.4531 }, + { key: '논산시', lat: 36.2044, lng: 127.7319 }, + { key: '계룡시', lat: 36.3034, lng: 127.2878 }, + { key: '당진시', lat: 36.7711, lng: 126.5979 }, + { key: '금산군', lat: 36.1442, lng: 127.7175 }, + { key: '부여군', lat: 36.2959, lng: 126.9319 }, + { key: '서천군', lat: 36.0654, lng: 126.8055 }, + { key: '청양군', lat: 36.4676, lng: 126.7991 }, + { key: '홍성군', lat: 36.6104, lng: 126.6739 }, + { key: '예산군', lat: 36.6788, lng: 126.8286 }, + { key: '태안군', lat: 36.754, lng: 126.2918 }, + ], + }, + { + city: '전북특별자치도', + town: [ + { key: '전주시', lat: 35.8269, lng: 127.148 }, + { key: '군산시', lat: 35.9659, lng: 126.7351 }, + { key: '익산시', lat: 35.948, lng: 126.9706 }, + { key: '정읍시', lat: 35.5861, lng: 126.85 }, + { key: '남원시', lat: 35.36, lng: 127.375 }, + { key: '김제시', lat: 35.816, lng: 126.8832 }, + { key: '완주군', lat: 35.8969, lng: 127.166 }, + { key: '진안군', lat: 35.6855, lng: 127.3624 }, + { key: '무주군', lat: 35.6751, lng: 127.6931 }, + { key: '장수군', lat: 35.6754, lng: 127.7101 }, + { key: '임실군', lat: 35.5653, lng: 127.2637 }, + { key: '순창군', lat: 35.4256, lng: 127.1516 }, + { key: '고창군', lat: 35.4176, lng: 126.6887 }, + { key: '부안군', lat: 35.6833, lng: 126.7561 }, + ], + }, + { + city: '전라남도', + town: [ + { key: '목포시', lat: 34.8109, lng: 126.395 }, + { key: '여수시', lat: 34.7604, lng: 127.6627 }, + { key: '순천시', lat: 34.9485, lng: 127.4878 }, + { key: '나주시', lat: 35.0146, lng: 126.6905 }, + { key: '광양시', lat: 34.9502, lng: 127.7009 }, + { key: '담양군', lat: 35.3368, lng: 126.9888 }, + { key: '곡성군', lat: 35.3461, lng: 127.3142 }, + { key: '구례군', lat: 35.1614, lng: 127.7249 }, + { key: '고흥군', lat: 34.5963, lng: 127.1421 }, + { key: '보성군', lat: 34.7747, lng: 127.0803 }, + { key: '화순군', lat: 35.0162, lng: 127.2763 }, + { key: '장흥군', lat: 34.694, lng: 126.9427 }, + { key: '강진군', lat: 34.5902, lng: 126.7282 }, + { key: '해남군', lat: 34.5637, lng: 126.5853 }, + { key: '영암군', lat: 34.6856, lng: 126.6822 }, + { key: '무안군', lat: 34.984, lng: 126.622 }, + { key: '함평군', lat: 35.0323, lng: 126.573 }, + { key: '영광군', lat: 35.184, lng: 126.4614 }, + { key: '장성군', lat: 35.3529, lng: 126.8837 }, + { key: '완도군', lat: 34.3231, lng: 126.7465 }, + { key: '진도군', lat: 34.4421, lng: 126.2563 }, + { key: '신안군', lat: 34.7578, lng: 125.3913 }, + ], + }, + { + city: '경상북도', + town: [ + { key: '포항시', lat: 36.0194, lng: 129.3433 }, + { key: '경주시', lat: 35.8406, lng: 129.2113 }, + { key: '김천시', lat: 36.1393, lng: 128.1127 }, + { key: '안동시', lat: 36.5665, lng: 128.7204 }, + { key: '구미시', lat: 36.1132, lng: 128.348 }, + { key: '영주시', lat: 36.8253, lng: 128.6129 }, + { key: '영천시', lat: 35.9786, lng: 128.9344 }, + { key: '상주시', lat: 36.4382, lng: 128.1557 }, + { key: '문경시', lat: 36.5918, lng: 128.1844 }, + { key: '경산시', lat: 35.8355, lng: 128.7335 }, + { key: '의성군', lat: 36.1687, lng: 128.8873 }, + { key: '청송군', lat: 36.4453, lng: 129.0484 }, + { key: '영양군', lat: 36.6721, lng: 129.1344 }, + { key: '영덕군', lat: 36.4207, lng: 129.3671 }, + { key: '청도군', lat: 35.6897, lng: 128.7042 }, + { key: '고령군', lat: 35.6008, lng: 128.2719 }, + { key: '성주군', lat: 35.7793, lng: 128.2354 }, + { key: '칠곡군', lat: 35.995, lng: 128.4748 }, + { key: '예천군', lat: 36.6347, lng: 128.4584 }, + { key: '봉화군', lat: 36.9167, lng: 128.7676 }, + { key: '울진군', lat: 36.9981, lng: 129.4047 }, + { key: '울릉군', lat: 37.4825, lng: 130.8981 }, + ], + }, + { + city: '경상남도', + town: [ + { key: '창원시', lat: 35.2285, lng: 128.674 }, + { key: '진주시', lat: 35.1795, lng: 128.1148 }, + { key: '통영시', lat: 34.8402, lng: 128.4335 }, + { key: '사천시', lat: 34.9611, lng: 128.592 }, + { key: '김해시', lat: 35.228, lng: 128.8827 }, + { key: '밀양시', lat: 35.4872, lng: 128.7481 }, + { key: '거제시', lat: 34.9009, lng: 128.6207 }, + { key: '양산시', lat: 35.3295, lng: 129.0006 }, + { key: '의령군', lat: 35.3883, lng: 128.4071 }, + { key: '함안군', lat: 35.2188, lng: 128.452 }, + { key: '창녕군', lat: 35.2859, lng: 128.5475 }, + { key: '고성군', lat: 34.9347, lng: 128.5143 }, + { key: '남해군', lat: 34.8355, lng: 128.8511 }, + { key: '하동군', lat: 35.1049, lng: 127.759 }, + { key: '산청군', lat: 35.323, lng: 127.9116 }, + { key: '함양군', lat: 35.3991, lng: 127.5893 }, + { key: '거창군', lat: 35.6948, lng: 127.8896 }, + { key: '합천군', lat: 35.5481, lng: 128.0912 }, + ], + }, + { + city: '제주특별자치도', + town: [ + { key: '제주시', lat: 33.4996, lng: 126.5312 }, + { key: '서귀포시', lat: 33.2548, lng: 126.5604 }, + ], + }, +]; diff --git a/src/views/Map/pages/MapPage.tsx b/src/views/Map/pages/MapPage.tsx new file mode 100644 index 0000000..90bda3d --- /dev/null +++ b/src/views/Map/pages/MapPage.tsx @@ -0,0 +1,248 @@ +import { css } from '@emotion/react'; +import { useEffect, useRef, useState } from 'react'; + +import { + MapFavoirteIcon, + MapSearchActiveIcon, + MapSearchInactiveIcon, + RefreshMonoIcon, +} from '@/assets/icon'; +import MenuBar from '@/components/MenuBar'; +import { COLORS, FONTS } from '@/styles/constants'; +import { locationBasedList1Res } from '@/types/locationBasedList1'; + +import SearchBottomSheet from '../components/SearchBottomSheet'; +import { createKakaoMap } from '../utils/createKakaoMap'; +import { createMapPin } from '../utils/createMapPin'; +import { getMapCenter } from '../utils/getMapCenter'; +import { setDefaultLocation } from '../utils/setDefaultLoctaion'; + +export interface locType { + lat: number | undefined; + lng: number | undefined; +} + +export interface bottomSheetType { + title: string; + address: string; + image: string; + contentId: string; +} + +export type mapType = kakao.maps.Map | undefined; + +const MapPage = () => { + const [map, setMap] = useState(); // 카카오맵 관리 + const [markers, setMarkers] = useState([]); // 주변여행지 검색 시 마커 리스트 + const [region, setRegion] = useState({ city: '', town: '' }); // 사용자 지정 지역 + const [defaultLoc, setDefaultLoc] = useState(); // 사용자 지정 지역 좌표 + const [getLocActive, setGetLocActive] = useState(false); // 위치 허용에 따른 아이콘 변화 + + // 바텀시트 내용 + const [bottomSheetContent, setBottomSheetContent] = useState( + { + title: '', + address: '', + image: '', + contentId: '', + }, + ); + + const [isPinClicked, setIsPinClicked] = useState(false); // 핀 클릭 바텀시트 + + const apiRes = useRef(); + + /** 기본 사용자의 위치에 따른 위도, 경도 값 업데이트 */ + useEffect(() => { + setRegion({ city: '서울특별시', town: '광진구' }); + const currentTown = setDefaultLocation(region.city, region.town); + setDefaultLoc({ + lat: currentTown?.lat, + lng: currentTown?.lng, + }); + }, [region.city, region.town]); + + /** '/map' 진입시, 사용자가 회원가입 할 때 등록했던 지역을 기준으로 지도 띄우기 */ + useEffect(() => { + const kakaoMap = createKakaoMap(defaultLoc, 5); + setMap(kakaoMap); + }, [defaultLoc]); + + const openBottomSheet = () => { + setIsPinClicked(true); + }; + + const closeBottomSheet = () => { + setIsPinClicked(false); + }; + + /** 사용자의 현재 위치 (위도, 경도) 받아오기 */ + const getCurrentLoc = () => { + if ('geolocation' in navigator) { + navigator.geolocation.getCurrentPosition( + (position) => { + const latlng = new kakao.maps.LatLng( + position.coords.latitude, + position.coords.longitude, + ); + if (map) { + clearMarker(); + map.setCenter(latlng); + map.setLevel(4); + } + setGetLocActive(true); + }, + (err) => { + alert('동의 거부 - 권한 재설정 필요'); + console.log(err); + }, + ); + } else { + //브라우저가 geolocation 지원 X + alert('해당 브라우저에서 현재 위치를 가져올 수 없습니다.'); + } + }; + + /** 지도에서 마커 제거, 마커 state 초기화 */ + const clearMarker = () => { + markers.forEach((marker) => marker.setMap(null)); + setMarkers([]); + }; + + //주변 여행지 찾기 클릭 시 지도 중심좌표 값 받아오기 & 공공api 검색 / 지도에 핀 박기 + const onClickSearch = async () => { + const response = await getMapCenter(map); + + if (map && response && response.item) { + clearMarker(); + + apiRes.current = response.item; + + const { curMarkers } = createMapPin( + apiRes.current, + map, + setBottomSheetContent, + openBottomSheet, + ); + + if (curMarkers) { + curMarkers.forEach((marker) => { + marker.setMap(map); + }); + + setMarkers(curMarkers); + map.setLevel(6); + } + } + }; + + return ( +
+
+
+ +
+ {!isPinClicked ? ( +
+ + +
+ ) : null} +
+
+ {isPinClicked && ( + + )} + +
+
+ ); +}; + +export default MapPage; + +const MapContainer = css` + position: relative; + + width: 100%; + height: 100dvh; +`; + +const buttonSection = css` + position: absolute; + z-index: 2; + + width: 100%; +`; + +const topButtonSection = css` + display: flex; + justify-content: flex-end; + + width: 100%; + padding: 0.5rem 1.3rem 0.5rem 0; +`; + +const bottomButtonSection = css` + display: flex; + justify-content: center; + align-items: center; + position: relative; + position: fixed; + bottom: 10.3rem; + + width: 100%; + padding-right: 1.3rem; +`; + +const searchButton = css` + display: flex; + gap: 0.4rem; + justify-content: center; + align-items: center; + position: absolute; + bottom: 1.4rem; + + width: 17.7rem; + height: 3.9rem; + padding: 0.9rem 1.6rem; + border-radius: 1rem; + + background-color: ${COLORS.brand1}; + + color: ${COLORS.white}; + + ${FONTS.Body3}; +`; + +const rightButton = css` + position: absolute; + right: 1.3rem; + bottom: 1.1rem; +`; + +const bottomSection = css` + position: relative; +`; + +const menuBarCss = css` + position: absolute; + z-index: 1000; +`; diff --git a/src/views/Map/utils/createKakaoMap.ts b/src/views/Map/utils/createKakaoMap.ts new file mode 100644 index 0000000..14b5032 --- /dev/null +++ b/src/views/Map/utils/createKakaoMap.ts @@ -0,0 +1,15 @@ +import { locType } from '../pages/MapPage'; + +const { kakao } = window; + +export const createKakaoMap = (loc: locType | undefined, level: number) => { + if (loc?.lat && loc?.lng) { + const container = document.getElementById('map'); + const options = { + center: new kakao.maps.LatLng(loc.lat, loc.lng), + level: level, + }; + const kakaoMap = new kakao.maps.Map(container, options); + return kakaoMap; + } +}; diff --git a/src/views/Map/utils/createMapPin.ts b/src/views/Map/utils/createMapPin.ts new file mode 100644 index 0000000..e589a27 --- /dev/null +++ b/src/views/Map/utils/createMapPin.ts @@ -0,0 +1,70 @@ +import { KakaoMarkerImage } from '@/assets/image'; +import { locationBasedList1Res } from '@/types/locationBasedList1'; + +import { bottomSheetType, mapType } from '../pages/MapPage'; + +interface responseType { + title: string; + latlng: kakao.maps.LatLng; + address: string; + image: string; + contentId: string; +} + +export const createMapPin = ( + apiRes: locationBasedList1Res[] | undefined, + kakaoMap: mapType | undefined, + setBottomSheet: React.Dispatch>, + openBottomSheet: () => void, +) => { + if (!apiRes || apiRes.length === 0) { + console.log('검색 결과가 없습니다.'); + return { markers: [], kakaoMap: null }; + } + + const response: responseType[] = []; + const imageSrc = KakaoMarkerImage; + const imageSize = new kakao.maps.Size(25, 40); + const imageOption = { offset: new kakao.maps.Point(27, 69) }; + + const markerImage = new kakao.maps.MarkerImage( + imageSrc, + imageSize, + imageOption, + ); + + apiRes?.forEach((item) => { + response.push({ + title: item.title, + latlng: new kakao.maps.LatLng(Number(item.mapy), Number(item.mapx)), + address: item.addr1, + image: item.firstimage, + contentId: item.contentid, + }); + }); + + const markers: kakao.maps.Marker[] = []; + + response.forEach((item) => { + const marker = new kakao.maps.Marker({ + map: kakaoMap, + position: item.latlng, + image: markerImage, + }); + + /** 마커마다 클릭 이벤트 생성 */ + kakao.maps.event.addListener(marker, 'click', function () { + setBottomSheet({ + title: item.title, + address: item.address, + image: item.image, + contentId: item.contentId, + }); + openBottomSheet(); + }); + + markers.push(marker); + }); + + return { curMarkers: markers }; +}; diff --git a/src/views/Map/utils/getMapCenter.ts b/src/views/Map/utils/getMapCenter.ts new file mode 100644 index 0000000..24b26a8 --- /dev/null +++ b/src/views/Map/utils/getMapCenter.ts @@ -0,0 +1,23 @@ +import { getLocationBasedList1 } from '@/apis/public/locationBasedList1'; + +import { mapType } from '../pages/MapPage'; + +export const getMapCenter = async (map: mapType | undefined) => { + if (map) { + try { + const response = await getLocationBasedList1({ + pageNo: 1, + numOfRows: 10, + MobileOS: 'ETC', + mapX: map.getCenter().La.toString(), + mapY: map.getCenter().Ma.toString(), + radius: '3000', + contentTypeId: 12, + }); + + return response; + } finally { + console.log('finish'); + } + } +}; diff --git a/src/views/Map/utils/setDefaultLoctaion.ts b/src/views/Map/utils/setDefaultLoctaion.ts new file mode 100644 index 0000000..2b6ed38 --- /dev/null +++ b/src/views/Map/utils/setDefaultLoctaion.ts @@ -0,0 +1,9 @@ +import { POSITION_LATLNG } from '../constants/POSITION_LATLNG'; + +/** 저장된 유저 정보로 default 위도, 경도 검색 */ +export const setDefaultLocation = (city: string, town: string) => { + const currentCity = POSITION_LATLNG.find((item) => item.city === city); + const currentTown = currentCity?.town.find((item) => item.key === town); + + return currentTown; +};