Skip to content

Commit

Permalink
feat: 나의 코스 플랜 페이지 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
yoouung committed Dec 21, 2024
1 parent 90face5 commit bf7e5f3
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 1 deletion.
92 changes: 92 additions & 0 deletions src/app/components/KakaoMap.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(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: `<div class="text-sm p-2">${place.name}</div>`,
})
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 (
<div
id={`map-${id}`}
className='w-full mt-[10px] h-[180px] z-0'
ref={mapContainerRef}
/>
)
}
3 changes: 3 additions & 0 deletions src/app/schedules/[id]/update/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return null
}
75 changes: 75 additions & 0 deletions src/app/schedules/components/CourseCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='w-full bg-white rounded-[8px] py-[16px] mt-[10px] items-center border-b'>
<div className='flex flex-col justify-start ml-[8px]'>
<span className='text-[12px] text-[#999999]'>{planned_for}</span>
<div className='flex flex-row justify-between items-center w-full'>
<span className='font-bold text-[16px]'>{location}</span>
<span
className='underline text-[12px] text-gray mr-[8px] cursor-pointer'
onClick={() => {
router.push('/courses/new')
}}
>
코스로 공유하기
</span>
</div>
</div>

<KakaoMap places={places} id={id} />

<section className='flex flex-col mt-[20px] text-[15px]'>
<div className='flex flex-row justify-between items-center'>
<span>| 장소 정보</span>
<button
className='w-[50px] h-[30px] text-[12px] border rounded-[10px] cursor-pointer'
onClick={() => router.push('/schedules/1/update')}
>
수정
</button>
</div>
{places.map((place, index) => (
<div
key={index}
className='flex flex-row mt-[5px] gap-[5px] bg-gray-100 px-[15px] py-[8px] rounded-[10px] justify-between items-center'
>
<div className='flex flex-col items-start'>
<span className='text-[13px] font-bold'>{place.name}</span>
<span className='text-[10px] text-gray-600'>{place.address}</span>
</div>
<CopyOutlined
className='w-[12px] h-[12px] cursor-pointer'
onClick={() => toast(place.address)}
/>
</div>
))}
</section>

{contextHolder}
</div>
)
}
173 changes: 173 additions & 0 deletions src/app/schedules/getData.ts
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions src/app/schedules/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return null
}
34 changes: 33 additions & 1 deletion src/app/schedules/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='w-full pt-[20px] pb-[32px] px-[16px] flex flex-col'>
<span className='border-b text-[18px] inline-flex items-center'>
<p className='font-bold'>{userName}</p>
<p>님의 코스 플랜</p>
</span>

{planList.map((course, index) => (
<CourseCard key={index} courseData={course} />
))}

<div className='fixed bottom-[60px] right-[20px] flex items-center justify-center cursor-pointer z-[50]'>
<button
className='w-[50px] h-[50px] bg-white rounded-full flex items-center justify-center shadow-lg border border-blue-800 border-opacity-20'
onClick={() => router.push('/schedules/new')}
>
<PlusOutlined className='text-[20px] text-blue-800' />
</button>
</div>
</div>
)
}

0 comments on commit bf7e5f3

Please sign in to comment.