Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[기능] 나의 코스 플랜 생성 페이지 UI 구현 #13

Merged
merged 5 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ export default function Footer() {
src={path.includes('/schedules') ? calendaPurple : calendar}
width={25}
height={25}
alt='일정'
alt='플랜'
/>
일정
플랜
</Link>
<Link href='/users/1' className='flex flex-col items-center'>
<Image
Expand Down
53 changes: 25 additions & 28 deletions src/app/components/KakaoMap.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
'use client'

import { useEffect, useRef } from 'react'

interface Place {
name: string
address: string
latlang: number[]
}
import { Place } from './SearchPlace'

interface KakaoMapProps {
places: Place[]
center?: number[]
id: number
}

Expand All @@ -19,7 +15,7 @@ declare global {
}
}

export default function KakaoMap({ places, id }: KakaoMapProps) {
export default function KakaoMap({ places, center, id }: KakaoMapProps) {
const mapContainerRef = useRef<HTMLDivElement>(null)

useEffect(() => {
Expand All @@ -46,31 +42,32 @@ export default function KakaoMap({ places, id }: KakaoMapProps) {
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,
center: center
? new window.kakao.maps.LatLng(center[0], center[1])
: new window.kakao.maps.LatLng(places[0].y, places[0].x),
level: center ? 8 : 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)
if (places.length > 0) {
places.forEach((place) => {
const markerPosition = new window.kakao.maps.LatLng(
place.y,
place.x
)
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>`,
// 마커 클릭 이벤트
const infoWindow = new window.kakao.maps.InfoWindow({
content: `<div class="text-sm p-2">${place.place_name}</div>`,
})
window.kakao.maps.event.addListener(marker, 'click', () => {
infoWindow.open(map, marker)
})
})
window.kakao.maps.event.addListener(marker, 'click', () => {
infoWindow.open(map, marker)
})
})
}
})
}

Expand Down
96 changes: 96 additions & 0 deletions src/app/components/SearchPlace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useEffect, useState } from 'react'
import { Input } from 'antd'

export interface Place {
id: string
place_name: string
category_name?: string
address_name: string
road_address_name?: string
place_url?: string
phone?: string
distance?: string
x: string
y: string
}

interface Meta {
total_count: number
pageable_count: number
is_end: boolean
}

interface SearchPlaceProps {
onOpenDrawer?: (open: boolean) => void
onSelectPlace?: (place: Place) => void
}

export default function SearchPlace({
onOpenDrawer,
onSelectPlace,
}: SearchPlaceProps) {
const [results, setResults] = useState<Place[]>([])
const [meta, setMeta] = useState<Meta>() // TODO: 페이지네이션 추가 필요
const [inputValue, setInputValue] = useState('')

const getResult = async (value: string) => {
if (!value) return

const result = await fetch(
`https://dapi.kakao.com/v2/local/search/keyword.json?query=${value}`,
{
headers: {
Authorization: `KakaoAK ${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}`,
},
}
)
const data = await result.json()
setResults(data.documents)
setMeta(data.meta)
}

const selectPlace = (place: Place) => {
if (onOpenDrawer && onSelectPlace) {
onOpenDrawer(false)
onSelectPlace(place)
setInputValue('')
}
}

useEffect(() => {
getResult(inputValue)
}, [inputValue])

return (
<div className='max-w-[375px] w-full m-auto'>
<div className='flex flex-col gap-[10px]'>
<span className='text-[15px] font-semibold'>장소명</span>
<Input.Search
placeholder='장소 이름을 입력해주세요.'
className='w-full'
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
</div>
<div className='w-full mt-[20px] mb-[20px] h-[2px] bg-gray-100' />
<div className='flex flex-col gap-[5px] justify-start items-center'>
{results.map((result) => {
return (
<div
key={result.id}
className='text-[12px] border-blue-100 rounded-[5px] border-[1px] flex flex-col w-full gap-[5px] px-[15px] py-[8px] cursor-pointer'
onClick={() => {
selectPlace(result)
}}
>
<span className='text-[13px] font-bold'>{result.place_name}</span>
<span className='text-[10px] text-gray-600'>
{result.address_name}
</span>
</div>
)
})}
</div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,30 @@ import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import dragIcon from '@images/drag_icon.png'
import Image from 'next/image'
import { Place } from '@/app/components/SearchPlace'

export default function SortableItem({ id, place, onEdit, onDelete }: any) {
interface SortableItemProps {
id: string
place: Place
onEdit?: (id: string) => void
onDelete: (id: string) => void
}

export default function SortableItem({
id,
place,
onEdit,
onDelete,
}: SortableItemProps) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id })

const handleEdit = (id: string) => {
if (onEdit) {
onEdit(id)
}
}

return (
<div
style={{
Expand All @@ -28,15 +47,17 @@ export default function SortableItem({ id, place, onEdit, onDelete }: any) {
className='w-[20px] h-[15px]'
alt='드래그'
/>
<span className='text-[13px]'>{place.name}</span>
<span className='text-[13px]'>{place.place_name}</span>
</div>
<div className='flex gap-[5px]'>
<Button
className='text-[10px] border-0 h-[20px] w-[20px]'
onClick={() => onEdit(place.id)}
>
수정
</Button>
{onEdit && (
<Button
className='text-[10px] border-0 h-[20px] w-[20px]'
onClick={() => handleEdit(place.id)}
>
수정
</Button>
)}
<Button
className='text-[10px] shadow-none border-0 h-[20px] w-[30px]'
onClick={() => onDelete(place.id)}
Expand Down
43 changes: 0 additions & 43 deletions src/app/courses/new/components/SearchPlace.tsx

This file was deleted.

43 changes: 28 additions & 15 deletions src/app/courses/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
'use client'

import { useState } from 'react'
import { useEffect, useState } from 'react'
import { Input, Drawer } from 'antd'
import { DndContext, closestCenter } from '@dnd-kit/core'
import {
SortableContext,
arrayMove,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import SortableItem from './components/SortableItem'
import SearchPlace from './components/SearchPlace'
import SortableItem from '@/app/components/SortableItem'
import SearchPlace, { Place } from '@/app/components/SearchPlace'
import { categories } from '@/types/Categories'
import getData from '@/app/schedules/getData'

export default function Page() {
const [clickedCategory, setClickedCategory] = useState<number[]>([])
const [places, setPlaces] = useState([
{ id: 1, name: '땀땀 강남점' },
{ id: 2, name: '마녀주방 강남점' },
{ id: 3, name: '미도인 강남' },
{ id: 4, name: '정돈 강남점' },
])
const [places, setPlaces] = useState<Place[]>([])
const [open, setOpen] = useState(false)

useEffect(() => {
const data = getData()
setPlaces(data.courses[0].places)
}, [])

const showDrawer = () => {
setOpen(true)
}
Expand All @@ -30,6 +31,10 @@ export default function Page() {
setOpen(false)
}

const onChangePlaces = (place: Place) => {
setPlaces((prevPlaces) => [...prevPlaces, place])
}

const handleCategoryClick = (id: number) => {
setClickedCategory((prev) =>
prev.includes(id)
Expand All @@ -50,11 +55,11 @@ export default function Page() {
}
}

const handleDelete = (id: number) => {
const handleDelete = (id: string) => {
setPlaces((prev) => prev.filter((place) => place.id !== id))
}

const handleEdit = (id: number) => {
const handleEdit = (id: string) => {
alert(`${id}번 장소를 수정합니다.`)
}

Expand Down Expand Up @@ -120,16 +125,24 @@ export default function Page() {
title='장소 검색'
height={600}
placement={'bottom'}
className='w-full rounded-[10px]'
className='w-[90%] max-w-[330px] rounded-t-[10px] mx-auto my-0'
onClose={onClose}
open={open}
mask={true}
maskClosable
>
<SearchPlace />
<SearchPlace
onOpenDrawer={setOpen}
onSelectPlace={onChangePlaces}
/>
</Drawer>
</div>
</div>
<button className='w-full rounded-[5px] mt-[40px] text-[12px] h-[40px] flex items-center justify-center bg-blue-100'>
<button
className={`w-full rounded-[5px] mt-[40px] text-[12px] h-[40px] flex items-center justify-center bg-blue-100 text-white ${
places.length === 0 ? 'cursor-default' : 'bg-blue-800 bg-opacity-50'
}`}
disabled={places.length === 0}
>
완료
</button>
</div>
Expand Down
Loading
Loading