diff --git a/backend/routes/schedules.js b/backend/routes/schedules.js index 3261eef..e72e41b 100644 --- a/backend/routes/schedules.js +++ b/backend/routes/schedules.js @@ -140,7 +140,7 @@ router.get("/:id", async (req, res) => { const [schedules] = await pool.query( ` - SELECT + SELECT s.*, c.name as category_name, c.color as category_color @@ -155,7 +155,16 @@ router.get("/:id", async (req, res) => { return res.status(404).json({ error: "일정을 찾을 수 없습니다." }); } - res.json(schedules[0]); + // 이미지 조회 + const [images] = await pool.query( + `SELECT image_url FROM schedule_images WHERE schedule_id = ? ORDER BY sort_order ASC`, + [id] + ); + + const schedule = schedules[0]; + schedule.images = images.map((img) => img.image_url); + + res.json(schedule); } catch (error) { console.error("일정 조회 오류:", error); res.status(500).json({ error: "일정 조회 중 오류가 발생했습니다." }); diff --git a/frontend/src/pages/pc/public/Schedule.jsx b/frontend/src/pages/pc/public/Schedule.jsx index 483177f..ec6e53a 100644 --- a/frontend/src/pages/pc/public/Schedule.jsx +++ b/frontend/src/pages/pc/public/Schedule.jsx @@ -366,8 +366,8 @@ function Schedule() { // 일정 클릭 핸들러 const handleScheduleClick = (schedule) => { - // 유튜브(id=2), X(id=3) 카테고리는 상세 페이지로 이동 - if (schedule.category_id === 2 || schedule.category_id === 3) { + // 유튜브(id=2), X(id=3), 콘서트(id=6) 카테고리는 상세 페이지로 이동 + if (schedule.category_id === 2 || schedule.category_id === 3 || schedule.category_id === 6) { navigate(`/schedule/${schedule.id}`); return; } diff --git a/frontend/src/pages/pc/public/ScheduleDetail.jsx b/frontend/src/pages/pc/public/ScheduleDetail.jsx index 5e0dfed..11396cb 100644 --- a/frontend/src/pages/pc/public/ScheduleDetail.jsx +++ b/frontend/src/pages/pc/public/ScheduleDetail.jsx @@ -1,9 +1,13 @@ import { useParams, Link } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; +import { useEffect, useRef, useState } from 'react'; import { motion } from 'framer-motion'; -import { Clock, Calendar, ExternalLink, ChevronRight, Link2 } from 'lucide-react'; +import { Clock, Calendar, ExternalLink, ChevronRight, Link2, MapPin, Navigation } from 'lucide-react'; import { getSchedule, getXProfile } from '../../../api/public/schedules'; +// 카카오맵 SDK 키 +const KAKAO_MAP_KEY = import.meta.env.VITE_KAKAO_MAP_KEY; + // 카테고리 ID 상수 const CATEGORY_ID = { YOUTUBE: 2, @@ -309,6 +313,195 @@ function XSection({ schedule }) { ); } +// 카카오맵 컴포넌트 +function KakaoMap({ lat, lng, name }) { + const mapRef = useRef(null); + const [mapLoaded, setMapLoaded] = useState(false); + + useEffect(() => { + // 카카오맵 SDK 동적 로드 + if (!window.kakao?.maps) { + const script = document.createElement('script'); + script.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${KAKAO_MAP_KEY}&autoload=false`; + script.onload = () => { + window.kakao.maps.load(() => setMapLoaded(true)); + }; + document.head.appendChild(script); + } else { + setMapLoaded(true); + } + }, []); + + useEffect(() => { + if (!mapLoaded || !mapRef.current) return; + + const position = new window.kakao.maps.LatLng(lat, lng); + const map = new window.kakao.maps.Map(mapRef.current, { + center: position, + level: 3, + }); + + // 마커 추가 + const marker = new window.kakao.maps.Marker({ + position, + map, + }); + + // 인포윈도우 추가 + if (name) { + const infowindow = new window.kakao.maps.InfoWindow({ + content: `
일시
+{formatDateRange()}
+ {schedule.time && ( +{formatTime(schedule.time)} 시작
+ )} +장소
+{schedule.location_name}
+ {schedule.location_address && ( +{schedule.location_address}
+ )} + {schedule.location_detail && ( +{schedule.location_detail}
+ )} ++ {decodeHtmlEntities(schedule.description)} +
+{schedule.location_name}
+ {schedule.location_address && ( +{schedule.location_address}
+ )} +