diff --git a/frontend/src/pages/mobile/public/ScheduleDetail.jsx b/frontend/src/pages/mobile/public/ScheduleDetail.jsx
index 368960f..00df894 100644
--- a/frontend/src/pages/mobile/public/ScheduleDetail.jsx
+++ b/frontend/src/pages/mobile/public/ScheduleDetail.jsx
@@ -2,90 +2,12 @@ import { useParams, Link } from 'react-router-dom';
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import { useEffect, useState, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
-import { Calendar, Clock, ChevronLeft, Check, Link2, MapPin, Navigation, ExternalLink, X, ChevronRight } from 'lucide-react';
+import { Calendar, Clock, ChevronLeft, Link2, X, ChevronRight } from 'lucide-react';
import Linkify from 'react-linkify';
import { getSchedule } from '../../../api/public/schedules';
import { formatXDateTime } from '../../../utils/date';
import '../../../mobile.css';
-// 카카오맵 SDK 키
-const KAKAO_MAP_KEY = import.meta.env.VITE_KAKAO_JS_KEY;
-
-// 카카오맵 컴포넌트
-function KakaoMap({ lat, lng, name }) {
- const mapRef = useRef(null);
- const [mapLoaded, setMapLoaded] = useState(false);
- const [mapError, setMapError] = useState(false);
-
- useEffect(() => {
- if (!KAKAO_MAP_KEY) {
- setMapError(true);
- return;
- }
-
- 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));
- };
- script.onerror = () => setMapError(true);
- document.head.appendChild(script);
- } else {
- setMapLoaded(true);
- }
- }, []);
-
- useEffect(() => {
- if (!mapLoaded || !mapRef.current || mapError) return;
-
- try {
- 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: `
${name}
`,
- });
- infowindow.open(map, marker);
- }
- } catch (e) {
- setMapError(true);
- }
- }, [mapLoaded, lat, lng, name, mapError]);
-
- if (mapError) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
-}
-
// 전체화면 시 자동 가로 회전 훅 (숏츠가 아닐 때만)
function useFullscreenOrientation(isShorts) {
useEffect(() => {
@@ -130,10 +52,6 @@ function useFullscreenOrientation(isShorts) {
const CATEGORY_ID = {
YOUTUBE: 2,
X: 3,
- ALBUM: 4,
- FANSIGN: 5,
- CONCERT: 6,
- TICKET: 7,
};
// HTML 엔티티 디코딩 함수
@@ -158,13 +76,6 @@ const formatTime = (timeStr) => {
return timeStr.slice(0, 5);
};
-// X URL에서 username 추출
-const extractXUsername = (url) => {
- if (!url) return null;
- const match = url.match(/(?:twitter\.com|x\.com)\/([^/]+)/);
- return match ? match[1] : null;
-};
-
// 유튜브 섹션 컴포넌트
function YoutubeSection({ schedule }) {
const videoId = schedule.videoId;
@@ -511,334 +422,6 @@ function XSection({ schedule }) {
);
}
-// 콘서트 섹션 컴포넌트
-function ConcertSection({ schedule }) {
- // 현재 선택된 회차 ID (내부 state로 관리 - URL 변경 없음)
- const [selectedDateId, setSelectedDateId] = useState(schedule.id);
- // 다이얼로그 열림 상태
- const [isDialogOpen, setIsDialogOpen] = useState(false);
- // 다이얼로그 목록 ref (자동 스크롤용)
- const listRef = useRef(null);
- const selectedItemRef = 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(() => {
- if (isDialogOpen && selectedItemRef.current) {
- setTimeout(() => {
- selectedItemRef.current?.scrollIntoView({ block: 'center', behavior: 'instant' });
- }, 50);
- }
- }, [isDialogOpen]);
-
- const relatedDates = schedule.related_dates || [];
- const hasMultipleDates = relatedDates.length > 1;
- const hasLocation = displayData.locationLat && displayData.locationLng;
-
- // 현재 선택된 회차 인덱스
- const selectedIndex = relatedDates.findIndex(d => d.id === selectedDateId);
-
- // 회차 선택 핸들러
- const handleSelectDate = (id) => {
- setSelectedDateId(id);
- setIsDialogOpen(false);
- };
-
- // 개별 날짜 포맷팅
- const formatSingleDate = (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})`;
- if (timeStr) {
- result += ` ${timeStr.slice(0, 5)}`;
- }
- return result;
- };
-
- return (
- <>
-
- {/* 히어로 헤더 */}
-
- {/* 배경 블러 이미지 */}
- {displayData.posterUrl ? (
-
-

-
- ) : (
-
- )}
- {/* 오버레이 그라디언트 */}
-
-
- {/* 콘텐츠 */}
-
-
- {/* 포스터 */}
- {displayData.posterUrl && (
-
-

-
- )}
- {/* 제목 */}
-
- {decodeHtmlEntities(displayData.title)}
-
-
-
-
-
- {/* 카드 섹션 */}
-
- {/* 공연 일정 카드 */}
-
-
-
- 공연 일정
-
- {/* 현재 회차 표시 */}
-
-
- {hasMultipleDates && {selectedIndex + 1}회차 ·}
- {formatSingleDate(displayData.date, displayData.time)}
-
-
- {/* 다른 회차 선택 버튼 */}
- {hasMultipleDates && (
-
- )}
-
-
- {/* 장소 카드 */}
- {displayData.locationName && (
-
-
-
- 장소
-
- {displayData.locationName}
- {displayData.locationAddress && (
- {displayData.locationAddress}
- )}
-
- {/* 지도 - 좌표가 있으면 카카오맵, 없으면 구글맵 */}
- {hasLocation ? (
-
-
-
- ) : (
-
-
-
- )}
-
- )}
-
- {/* 설명 */}
- {displayData.description && (
-
-
- {decodeHtmlEntities(displayData.description)}
-
-
- )}
-
- {/* 버튼 영역 */}
-
- {displayData.locationName && (
-
-
- 길찾기
-
- )}
- {displayData.sourceUrl && (
-
-
- 상세 정보
-
- )}
-
-
-
-
- {/* 회차 선택 다이얼로그 */}
-
- {isDialogOpen && (
-
- {/* 백드롭 */}
-
setIsDialogOpen(false)}
- />
- {/* 다이얼로그 */}
-
- {/* 헤더 */}
-
-
회차 선택
-
- {/* 회차 목록 */}
-
- {relatedDates.map((item, index) => {
- const isSelected = item.id === selectedDateId;
- return (
-
- );
- })}
-
- {/* 닫기 버튼 */}
-
-
-
-
-
- )}
-
- >
- );
-}
-
// 기본 섹션 컴포넌트 (다른 카테고리용 - 임시)
function DefaultSection({ schedule }) {
return (
@@ -991,8 +574,6 @@ function MobileScheduleDetail() {
return ;
case CATEGORY_ID.X:
return ;
- case CATEGORY_ID.CONCERT:
- return ;
default:
return ;
}
diff --git a/frontend/src/pages/pc/public/ScheduleDetail.jsx b/frontend/src/pages/pc/public/ScheduleDetail.jsx
index d87348b..42ab0db 100644
--- a/frontend/src/pages/pc/public/ScheduleDetail.jsx
+++ b/frontend/src/pages/pc/public/ScheduleDetail.jsx
@@ -8,7 +8,6 @@ import { getSchedule } from '../../../api/public/schedules';
import {
YoutubeSection,
XSection,
- ConcertSection,
DefaultSection,
CATEGORY_ID,
decodeHtmlEntities,
@@ -125,8 +124,6 @@ function ScheduleDetail() {
return ;
case CATEGORY_ID.X:
return ;
- case CATEGORY_ID.CONCERT:
- return ;
default:
return ;
}
@@ -134,12 +131,11 @@ function ScheduleDetail() {
const isYoutube = categoryId === CATEGORY_ID.YOUTUBE;
const isX = categoryId === CATEGORY_ID.X;
- const isConcert = categoryId === CATEGORY_ID.CONCERT;
- const hasCustomLayout = isYoutube || isX || isConcert;
+ const hasCustomLayout = isYoutube || isX;
return (
-
+
{/* 브레드크럼 네비게이션 */}
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 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})`;
- if (timeStr) {
- result += ` ${timeStr.slice(0, 5)}`;
- }
- return result;
- };
-
- return (
-
- {/* ========== 히어로 섹션 ========== */}
-
- {/* 배경 레이어 - 포스터 확대 + 블러 */}
-
- {hasPoster ? (
- <>
-

-
- >
- ) : (
-
- )}
-
-
- {/* 메인 콘텐츠 */}
-
-
- {/* 포스터 */}
- {hasPoster && (
-
-
-
- )}
-
- {/* 제목 및 회차 선택 */}
-
- {/* 제목 */}
-
- {decodeHtmlEntities(displayData.title)}
-
-
- {/* 회차 선택 드롭다운 */}
- {hasMultipleDates && (
-
-
-
-
- {isDropdownOpen && (
-
- {/* 드롭다운 헤더 */}
-
- 공연 일정 선택
-
-
-
- {relatedDates.map((item, index) => {
- const isSelected = item.id === selectedDateId;
- return (
-
- );
- })}
-
-
- )}
-
-
- )}
-
-
-
-
-
- {/* ========== 장소 정보 카드 ========== */}
- {displayData.locationName && (
-
- {/* 헤더 */}
-
-
-
- {/* 장소 아이콘 */}
-
-
-
- {/* 텍스트 */}
-
-
{displayData.locationName}
- {displayData.locationAddress && (
-
{displayData.locationAddress}
- )}
-
-
-
- {/* 길찾기 버튼 - 카카오맵(국내) / 구글맵(해외) */}
-
-
- 길찾기
-
-
-
-
- {/* 지도 - 높이 2배 */}
-
- {hasLocation ? (
-
- ) : (
-
- )}
-
-
- )}
-
- {/* ========== 공연 정보 카드 ========== */}
- {hasDescription && (
-
-
-
- {decodeHtmlEntities(displayData.description)}
-
-
- )}
-
- {/* ========== 외부 링크 버튼 ========== */}
- {displayData.sourceUrl && (
-
-
-
- 티켓 예매 및 상세 정보
-
-
- )}
-
- );
-}
-
-export default ConcertSection;
diff --git a/frontend/src/pages/pc/public/schedule-sections/KakaoMap.jsx b/frontend/src/pages/pc/public/schedule-sections/KakaoMap.jsx
deleted file mode 100644
index 5d196bb..0000000
--- a/frontend/src/pages/pc/public/schedule-sections/KakaoMap.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-import { Navigation } from 'lucide-react';
-
-// 카카오맵 SDK 키
-const KAKAO_MAP_KEY = import.meta.env.VITE_KAKAO_JS_KEY;
-
-// 카카오맵 컴포넌트
-function KakaoMap({ lat, lng, name }) {
- const mapRef = useRef(null);
- const [mapLoaded, setMapLoaded] = useState(false);
- const [mapError, setMapError] = useState(false);
-
- useEffect(() => {
- // API 키가 없으면 에러
- if (!KAKAO_MAP_KEY) {
- setMapError(true);
- return;
- }
-
- // 카카오맵 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));
- };
- script.onerror = () => setMapError(true);
- document.head.appendChild(script);
- } else {
- setMapLoaded(true);
- }
- }, []);
-
- useEffect(() => {
- if (!mapLoaded || !mapRef.current || mapError) return;
-
- try {
- 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: `${name}
`,
- });
- infowindow.open(map, marker);
- }
- } catch (e) {
- setMapError(true);
- }
- }, [mapLoaded, lat, lng, name, mapError]);
-
- // 에러 시 정적 이미지 또는 링크로 대체
- if (mapError) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
-}
-
-export default KakaoMap;
diff --git a/frontend/src/pages/pc/public/schedule-sections/index.js b/frontend/src/pages/pc/public/schedule-sections/index.js
index 42ea1f5..af76dd4 100644
--- a/frontend/src/pages/pc/public/schedule-sections/index.js
+++ b/frontend/src/pages/pc/public/schedule-sections/index.js
@@ -1,6 +1,4 @@
export { default as YoutubeSection } from "./YoutubeSection";
export { default as XSection } from "./XSection";
-export { default as ConcertSection } from "./ConcertSection";
export { default as DefaultSection } from "./DefaultSection";
-export { default as KakaoMap } from "./KakaoMap";
export * from "./utils";
diff --git a/frontend/src/pages/pc/public/schedule-sections/utils.js b/frontend/src/pages/pc/public/schedule-sections/utils.js
index 75c1142..5a0d942 100644
--- a/frontend/src/pages/pc/public/schedule-sections/utils.js
+++ b/frontend/src/pages/pc/public/schedule-sections/utils.js
@@ -49,8 +49,4 @@ export const formatXDateTime = (dateStr, timeStr) => {
export const CATEGORY_ID = {
YOUTUBE: 2,
X: 3,
- ALBUM: 4,
- FANSIGN: 5,
- CONCERT: 6,
- TICKET: 7,
};