From bf7e5f33943cc489d9d77bc72d436358cfacb2dc Mon Sep 17 00:00:00 2001 From: yoouung Date: Sat, 21 Dec 2024 15:30:10 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=98=20=EC=BD=94=EC=8A=A4?= =?UTF-8?q?=20=ED=94=8C=EB=9E=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/KakaoMap.tsx | 92 +++++++++++ src/app/schedules/[id]/update/page.tsx | 3 + src/app/schedules/components/CourseCard.tsx | 75 +++++++++ src/app/schedules/getData.ts | 173 ++++++++++++++++++++ src/app/schedules/new/page.tsx | 3 + src/app/schedules/page.tsx | 34 +++- 6 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 src/app/components/KakaoMap.tsx create mode 100644 src/app/schedules/[id]/update/page.tsx create mode 100644 src/app/schedules/components/CourseCard.tsx create mode 100644 src/app/schedules/getData.ts create mode 100644 src/app/schedules/new/page.tsx diff --git a/src/app/components/KakaoMap.tsx b/src/app/components/KakaoMap.tsx new file mode 100644 index 0000000..8386edb --- /dev/null +++ b/src/app/components/KakaoMap.tsx @@ -0,0 +1,92 @@ +'use client' + +import { useEffect, useRef } from 'react' + +interface Place { + name: string + address: string + latlang: number[] +} + +interface KakaoMapProps { + places: Place[] + id: number +} + +declare global { + interface Window { + kakao: any + } +} + +export default function KakaoMap({ places, id }: KakaoMapProps) { + const mapContainerRef = useRef(null) + + useEffect(() => { + const scriptId = `kakao-map-script-${id}` + + if (!document.getElementById(scriptId)) { + const script = document.createElement('script') + script.id = scriptId + script.src = `https://dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_API_KEY}&autoload=false&libraries=services` + script.async = true + script.onload = initializeMap + document.head.appendChild(script) + } else { + if (window.kakao && window.kakao.maps) { + initializeMap() + } + } + + function initializeMap() { + if (!mapContainerRef.current) return + + window.kakao.maps.load(() => { + if (!mapContainerRef.current || !window.kakao || !window.kakao.maps) + return + + const map = new window.kakao.maps.Map(mapContainerRef.current, { + center: new window.kakao.maps.LatLng( + places[0]?.latlang[0] || 37.5665, + places[0]?.latlang[1] || 126.978 + ), + level: 6, + }) + + places.forEach((place) => { + const markerPosition = new window.kakao.maps.LatLng( + place.latlang[0], + place.latlang[1] + ) + const marker = new window.kakao.maps.Marker({ + position: markerPosition, + }) + marker.setMap(map) + + // 마커 클릭 이벤트 + const infoWindow = new window.kakao.maps.InfoWindow({ + content: `
${place.name}
`, + }) + window.kakao.maps.event.addListener(marker, 'click', () => { + infoWindow.open(map, marker) + }) + }) + }) + } + + return () => { + const script = document.getElementById(scriptId) + if (script) { + document.head.removeChild(script) + } + } + }, [places]) + + return ( +
+ ) +} diff --git a/src/app/schedules/[id]/update/page.tsx b/src/app/schedules/[id]/update/page.tsx new file mode 100644 index 0000000..c174313 --- /dev/null +++ b/src/app/schedules/[id]/update/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null +} diff --git a/src/app/schedules/components/CourseCard.tsx b/src/app/schedules/components/CourseCard.tsx new file mode 100644 index 0000000..fcb108f --- /dev/null +++ b/src/app/schedules/components/CourseCard.tsx @@ -0,0 +1,75 @@ +'use client' + +import { useRouter } from 'next/navigation' +import { Course } from '../getData' +import KakaoMap from '@/app/components/KakaoMap' +import { CopyOutlined } from '@ant-design/icons' +import { message } from 'antd' + +export default function CourseCard({ courseData }: { courseData: Course }) { + const { location, places, planned_for, id } = courseData + const router = useRouter() + + const [messageApi, contextHolder] = message.useMessage() + const toast = (address: string) => { + // 클립보드에 복사 + navigator.clipboard.writeText(address).then(() => { + // 토스트 메시지 + messageApi.open({ + type: 'success', + content: '주소가 클립보드에 복사되었습니다.', + duration: 1, + }) + }) + } + + return ( +
+
+ {planned_for} +
+ {location} + { + router.push('/courses/new') + }} + > + 코스로 공유하기 + +
+
+ + + +
+
+ | 장소 정보 + +
+ {places.map((place, index) => ( +
+
+ {place.name} + {place.address} +
+ toast(place.address)} + /> +
+ ))} +
+ + {contextHolder} +
+ ) +} diff --git a/src/app/schedules/getData.ts b/src/app/schedules/getData.ts new file mode 100644 index 0000000..e8c934e --- /dev/null +++ b/src/app/schedules/getData.ts @@ -0,0 +1,173 @@ +interface Place { + name: string + address: string + latlang: number[] +} + +export interface Course { + id: number + location: string + places: Place[] + created_at: string + updated_at: string + planned_for: string +} + +interface Data { + user_name: string + courses: Course[] +} + +export default function getData() { + const data: Data = { + user_name: '홍인데유', + courses: [ + { + id: 1, + location: '강남구', + places: [ + { + name: '알베르', + address: '서울 강남구 강남대로102길 34', + latlang: [37.50304, 127.028098], + }, + { + name: '땀땀 본점', + address: '서울 강남구 강남대로98길 12-5', + latlang: [37.500398, 127.027992], + }, + { + name: '마녀주방', + address: '서울 강남구 강남대로94길 9', + latlang: [37.499486, 127.028079], + }, + { + name: 'CGV 강남', + address: '서울 강남구 강남대로 438', + latlang: [37.501678, 127.026387], + }, + ], + created_at: '2024.12.16', + updated_at: '', + planned_for: '2024.12.16', + }, + { + id: 2, + location: '강남구', + places: [ + { + name: '알베르', + address: '서울 강남구 강남대로102길 34', + latlang: [37.50304, 127.028098], + }, + { + name: '땀땀 본점', + address: '서울 강남구 강남대로98길 12-5', + latlang: [37.500398, 127.027992], + }, + { + name: '마녀주방', + address: '서울 강남구 강남대로94길 9', + latlang: [37.499486, 127.028079], + }, + { + name: 'CGV 강남', + address: '서울 강남구 강남대로 438', + latlang: [37.501678, 127.026387], + }, + ], + created_at: '2024.12.16', + updated_at: '', + planned_for: '2024.12.16', + }, + { + id: 3, + location: '강남구', + places: [ + { + name: '알베르', + address: '서울 강남구 강남대로102길 34', + latlang: [37.50304, 127.028098], + }, + { + name: '땀땀 본점', + address: '서울 강남구 강남대로98길 12-5', + latlang: [37.500398, 127.027992], + }, + { + name: '마녀주방', + address: '서울 강남구 강남대로94길 9', + latlang: [37.499486, 127.028079], + }, + { + name: 'CGV 강남', + address: '서울 강남구 강남대로 438', + latlang: [37.501678, 127.026387], + }, + ], + created_at: '2024.12.16', + updated_at: '', + planned_for: '2024.12.16', + }, + { + id: 4, + location: '강남구', + places: [ + { + name: '알베르', + address: '서울 강남구 강남대로102길 34', + latlang: [37.50304, 127.028098], + }, + { + name: '땀땀 본점', + address: '서울 강남구 강남대로98길 12-5', + latlang: [37.500398, 127.027992], + }, + { + name: '마녀주방', + address: '서울 강남구 강남대로94길 9', + latlang: [37.499486, 127.028079], + }, + { + name: 'CGV 강남', + address: '서울 강남구 강남대로 438', + latlang: [37.501678, 127.026387], + }, + ], + created_at: '2024.12.16', + updated_at: '', + planned_for: '2024.12.16', + }, + { + id: 5, + location: '강남구', + places: [ + { + name: '알베르', + address: '서울 강남구 강남대로102길 34', + latlang: [37.50304, 127.028098], + }, + { + name: '땀땀 본점', + address: '서울 강남구 강남대로98길 12-5', + latlang: [37.500398, 127.027992], + }, + { + name: '마녀주방', + address: '서울 강남구 강남대로94길 9', + latlang: [37.499486, 127.028079], + }, + { + name: 'CGV 강남', + address: '서울 강남구 강남대로 438', + latlang: [37.501678, 127.026387], + }, + ], + created_at: '2024.12.16', + updated_at: '', + planned_for: '2024.12.16', + }, + ], + } + return data +} diff --git a/src/app/schedules/new/page.tsx b/src/app/schedules/new/page.tsx new file mode 100644 index 0000000..c174313 --- /dev/null +++ b/src/app/schedules/new/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null +} diff --git a/src/app/schedules/page.tsx b/src/app/schedules/page.tsx index c174313..ad31425 100644 --- a/src/app/schedules/page.tsx +++ b/src/app/schedules/page.tsx @@ -1,3 +1,35 @@ +'use client' + +import { useRouter } from 'next/navigation' +import getData from './getData' +import CourseCard from './components/CourseCard' +import { PlusOutlined } from '@ant-design/icons' + export default function Page() { - return null + const router = useRouter() + const courseData = getData() + const userName = courseData.user_name + const planList = courseData.courses + + return ( +
+ +

{userName}

+

님의 코스 플랜

+
+ + {planList.map((course, index) => ( + + ))} + +
+ +
+
+ ) }