diff --git a/frontend/src/pages/pc/public/ScheduleDetail.jsx b/frontend/src/pages/pc/public/ScheduleDetail.jsx
index 3c722c9..298085d 100644
--- a/frontend/src/pages/pc/public/ScheduleDetail.jsx
+++ b/frontend/src/pages/pc/public/ScheduleDetail.jsx
@@ -1,8 +1,8 @@
import { useParams, Link } from 'react-router-dom';
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import { useEffect, useRef, useState } from 'react';
-import { motion } from 'framer-motion';
-import { Clock, Calendar, ExternalLink, ChevronRight, Link2, MapPin, Navigation } from 'lucide-react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Clock, Calendar, ExternalLink, ChevronRight, ChevronDown, ChevronLeft, Check, Link2, MapPin, Navigation } from 'lucide-react';
import { getSchedule, getXProfile } from '../../../api/public/schedules';
// 카카오맵 SDK 키
@@ -398,12 +398,12 @@ function KakaoMap({ lat, lng, name }) {
href={`https://map.kakao.com/link/to/${encodeURIComponent(name)},${lat},${lng}`}
target="_blank"
rel="noopener noreferrer"
- className="block w-full h-80 rounded-2xl bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center hover:from-gray-200 hover:to-gray-300 transition-all group"
+ className="group flex h-full w-full items-center justify-center bg-white/80 text-center backdrop-blur transition-all hover:bg-white"
>
-
-
-
{name}
-
클릭하여 길찾기
+
);
@@ -412,27 +412,117 @@ function KakaoMap({ lat, lng, name }) {
return (
);
}
// 콘서트 섹션 컴포넌트
function ConcertSection({ schedule }) {
- const hasLocation = schedule.location_lat && schedule.location_lng;
- const hasPoster = schedule.images?.length > 0;
+ // 현재 선택된 회차 ID (내부 state로 관리 - URL 변경 없음)
+ const [selectedDateId, setSelectedDateId] = useState(schedule.id);
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const dropdownRef = useRef(null);
+ const dropdownListRef = useRef(null);
+
+ // 표시할 데이터 state (변경된 부분만 업데이트)
+ const [displayData, setDisplayData] = useState({
+ posterUrl: schedule.images?.[0] || null,
+ title: schedule.title,
+ date: schedule.date,
+ time: schedule.time,
+ locationName: schedule.location_name,
+ locationAddress: schedule.location_address,
+ locationLat: schedule.location_lat,
+ locationLng: schedule.location_lng,
+ description: schedule.description,
+ sourceUrl: schedule.source_url,
+ });
+
+ // 선택된 회차 데이터 조회
+ const { data: selectedSchedule } = useQuery({
+ queryKey: ['schedule', selectedDateId],
+ queryFn: () => getSchedule(selectedDateId),
+ placeholderData: keepPreviousData,
+ enabled: selectedDateId !== schedule.id,
+ });
+
+ // 데이터 비교 후 변경된 부분만 업데이트
+ useEffect(() => {
+ const newData = selectedDateId === schedule.id ? schedule : selectedSchedule;
+ if (!newData) return;
+
+ setDisplayData(prev => {
+ const updates = {};
+ const newPosterUrl = newData.images?.[0] || null;
+
+ if (prev.posterUrl !== newPosterUrl) updates.posterUrl = newPosterUrl;
+ if (prev.title !== newData.title) updates.title = newData.title;
+ if (prev.date !== newData.date) updates.date = newData.date;
+ if (prev.time !== newData.time) updates.time = newData.time;
+ if (prev.locationName !== newData.location_name) updates.locationName = newData.location_name;
+ if (prev.locationAddress !== newData.location_address) updates.locationAddress = newData.location_address;
+ if (prev.locationLat !== newData.location_lat) updates.locationLat = newData.location_lat;
+ if (prev.locationLng !== newData.location_lng) updates.locationLng = newData.location_lng;
+ if (prev.description !== newData.description) updates.description = newData.description;
+ if (prev.sourceUrl !== newData.source_url) updates.sourceUrl = newData.source_url;
+
+ if (Object.keys(updates).length > 0) {
+ return { ...prev, ...updates };
+ }
+ return prev;
+ });
+ }, [selectedDateId, schedule, selectedSchedule]);
+
+ // 드롭다운 외부 클릭 감지
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ setIsDropdownOpen(false);
+ }
+ };
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ // 드롭다운 열릴 때 선택된 항목으로 자동 스크롤
+ useEffect(() => {
+ if (isDropdownOpen && dropdownListRef.current) {
+ const selectedElement = dropdownListRef.current.querySelector('[data-selected="true"]');
+ if (selectedElement) {
+ // 약간의 지연 후 스크롤 (애니메이션 후)
+ setTimeout(() => {
+ selectedElement.scrollIntoView({ block: 'center', behavior: 'smooth' });
+ }, 50);
+ }
+ }
+ }, [isDropdownOpen]);
+
const relatedDates = schedule.related_dates || [];
const hasMultipleDates = relatedDates.length > 1;
+ const hasLocation = displayData.locationLat && displayData.locationLng;
+ const hasPoster = !!displayData.posterUrl;
+ const hasDescription = !!displayData.description;
- // 개별 날짜 포맷팅
- const formatSingleDate = (dateStr, timeStr) => {
+ // 현재 선택된 회차 인덱스
+ const selectedIndex = relatedDates.findIndex(d => d.id === selectedDateId);
+ const selectedDisplayIndex = selectedIndex >= 0 ? selectedIndex + 1 : 1;
+
+ // 회차 선택 핸들러
+ const handleSelectDate = (id) => {
+ setSelectedDateId(id);
+ setIsDropdownOpen(false);
+ };
+
+ // 짧은 날짜 포맷팅 (회차 목록용)
+ const formatShortDate = (dateStr, timeStr) => {
const date = new Date(dateStr);
const dayNames = ['일', '월', '화', '수', '목', '금', '토'];
const month = date.getMonth() + 1;
const day = date.getDate();
const weekday = dayNames[date.getDay()];
- let result = `${month}월 ${day}일 (${weekday})`;
+ let result = `${month}/${day} (${weekday})`;
if (timeStr) {
result += ` ${timeStr.slice(0, 5)}`;
}
@@ -440,182 +530,267 @@ function ConcertSection({ schedule }) {
};
return (
-
- {/* 상단: 포스터 + 정보 카드 (가로 배치) */}
-
- {/* 포스터 이미지 */}
- {hasPoster && (
-
-
+
+ {/* ========== 히어로 섹션 ========== */}
+
+ {/* 배경 레이어 - 포스터 확대 + 블러 */}
+
+ {hasPoster ? (
+ <>

-
-
-
- )}
+
+ >
+ ) : (
+
+ )}
+
- {/* 정보 카드 */}
-
+
+ {/* 포스터 */}
+ {hasPoster && (
+
+
+
+ )}
+
+ {/* 제목 및 회차 선택 */}
+
+ {/* 제목 */}
+
+ {decodeHtmlEntities(displayData.title)}
+
+
+ {/* 회차 선택 드롭다운 */}
+ {hasMultipleDates && (
+
+
+
+
+ {isDropdownOpen && (
+
+ {/* 드롭다운 헤더 */}
+
+ 공연 일정 선택
+
+
+
+ {relatedDates.map((item, index) => {
+ const isSelected = item.id === selectedDateId;
+ return (
+
+ );
+ })}
+
+
+ )}
+
+
+ )}
+
+
+
+
+
+ {/* ========== 장소 정보 카드 ========== */}
+ {displayData.locationName && (
+
-
- {/* 헤더 */}
-
-
- {decodeHtmlEntities(schedule.title)}
-
-
-
- {/* 정보 목록 */}
-
- {/* 공연 일정 목록 */}
-
-
-
+ {/* 헤더 */}
+
+
+
+ {/* 장소 아이콘 */}
+
+
-
- {hasMultipleDates ? (
-
- {relatedDates.map((item, index) => {
- const isCurrentDate = item.id === schedule.id;
- return (
-
-
- {index + 1}회차
-
- {formatSingleDate(item.date, item.time)}
-
- );
- })}
-
- ) : (
-
-
- {formatSingleDate(schedule.date, schedule.time)}
-
-
+ {/* 텍스트 */}
+
+
{displayData.locationName}
+ {displayData.locationAddress && (
+
{displayData.locationAddress}
)}
-
- {/* 장소 */}
- {schedule.location_name && (
-
-
-
-
-
-
{schedule.location_name}
- {schedule.location_address && (
-
{schedule.location_address}
- )}
-
-
- )}
-
- {/* 설명 */}
- {schedule.description && (
-
-
- {decodeHtmlEntities(schedule.description)}
-
-
- )}
+
+ {/* 길찾기 버튼 - 카카오맵(국내) / 구글맵(해외) */}
+
+
+ 길찾기
+
+
- {/* 버튼 영역 */}
- {schedule.source_url && (
-
+ {/* 지도 - 높이 2배 */}
+
+ {hasLocation ? (
+
+ ) : (
+
)}
-
-
-
- {/* 하단: 지도 */}
- {schedule.location_name && hasLocation && (
-
-
-
-
-
-
-
-
위치 안내
-
{schedule.location_name}
-
-
-
-
- 길찾기
-
-
-
-
-
-
+
)}
- {/* 해외 공연 등 좌표 없는 경우 */}
- {schedule.location_name && !hasLocation && (
+ {/* ========== 공연 정보 카드 ========== */}
+ {hasDescription && (
+
+
+
+ {decodeHtmlEntities(displayData.description)}
+
+
+ )}
+
+ {/* ========== 외부 링크 버튼 ========== */}
+ {displayData.sourceUrl && (
-
-
-
- {schedule.location_name}
- {schedule.location_address && (
- {schedule.location_address}
- )}
- {schedule.location_detail && (
- {schedule.location_detail}
- )}
+
+
+ 티켓 예매 및 상세 정보
+
)}
@@ -794,7 +969,7 @@ function ScheduleDetail() {
return (