diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index c11878c..22fbf1c 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -13,6 +13,7 @@ import PCAlbumDetail from './pages/pc/public/AlbumDetail';
import PCAlbumGallery from './pages/pc/public/AlbumGallery';
import PCTrackDetail from './pages/pc/public/TrackDetail';
import PCSchedule from './pages/pc/public/Schedule';
+import PCScheduleDetail from './pages/pc/public/ScheduleDetail';
import PCNotFound from './pages/pc/public/NotFound';
// 모바일 페이지
@@ -84,6 +85,7 @@ function App() {
} />
} />
} />
+ } />
} />
diff --git a/frontend/src/pages/pc/public/Schedule.jsx b/frontend/src/pages/pc/public/Schedule.jsx
index e5a2c54..a664ac3 100644
--- a/frontend/src/pages/pc/public/Schedule.jsx
+++ b/frontend/src/pages/pc/public/Schedule.jsx
@@ -354,6 +354,12 @@ function Schedule() {
// 일정 클릭 핸들러
const handleScheduleClick = (schedule) => {
+ // 유튜브 카테고리(id=2)는 상세 페이지로 이동
+ if (schedule.category_id === 2) {
+ navigate(`/schedule/${schedule.id}`);
+ return;
+ }
+
// 설명이 없고 URL만 있으면 바로 링크 열기
if (!schedule.description && schedule.source_url) {
window.open(schedule.source_url, '_blank');
diff --git a/frontend/src/pages/pc/public/ScheduleDetail.jsx b/frontend/src/pages/pc/public/ScheduleDetail.jsx
new file mode 100644
index 0000000..b998b5a
--- /dev/null
+++ b/frontend/src/pages/pc/public/ScheduleDetail.jsx
@@ -0,0 +1,316 @@
+import { useParams, Link } from 'react-router-dom';
+import { useQuery } from '@tanstack/react-query';
+import { motion } from 'framer-motion';
+import { Clock, Calendar, ExternalLink, ChevronRight, Link2 } from 'lucide-react';
+import { getSchedule } from '../../../api/public/schedules';
+
+// 카테고리 ID 상수
+const CATEGORY_ID = {
+ YOUTUBE: 2,
+ X: 3,
+ ALBUM: 4,
+ FANSIGN: 5,
+ CONCERT: 6,
+ TICKET: 7,
+};
+
+// HTML 엔티티 디코딩 함수
+const decodeHtmlEntities = (text) => {
+ if (!text) return '';
+ const textarea = document.createElement('textarea');
+ textarea.innerHTML = text;
+ return textarea.value;
+};
+
+// 유튜브 비디오 ID 추출
+const extractYoutubeVideoId = (url) => {
+ if (!url) return null;
+ const shortMatch = url.match(/youtu\.be\/([a-zA-Z0-9_-]{11})/);
+ if (shortMatch) return shortMatch[1];
+ const watchMatch = url.match(/youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/);
+ if (watchMatch) return watchMatch[1];
+ const shortsMatch = url.match(/youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/);
+ if (shortsMatch) return shortsMatch[1];
+ return null;
+};
+
+// 날짜 포맷팅
+const formatFullDate = (dateStr) => {
+ if (!dateStr) return '';
+ const date = new Date(dateStr);
+ const dayNames = ['일', '월', '화', '수', '목', '금', '토'];
+ return `${date.getFullYear()}. ${date.getMonth() + 1}. ${date.getDate()}. (${dayNames[date.getDay()]})`;
+};
+
+// 시간 포맷팅
+const formatTime = (timeStr) => {
+ if (!timeStr) return null;
+ return timeStr.slice(0, 5);
+};
+
+// 영상 정보 컴포넌트 (공통)
+function VideoInfo({ schedule, isShorts }) {
+ return (
+
+ {/* 제목 */}
+
+ {decodeHtmlEntities(schedule.title)}
+
+
+ {/* 메타 정보 */}
+
+ {/* 날짜 */}
+
+
+ {formatFullDate(schedule.date)}
+
+
+ {/* 시간 */}
+ {schedule.time && (
+ <>
+
+
+
+ {formatTime(schedule.time)}
+
+ >
+ )}
+
+ {/* 채널명 */}
+ {schedule.source_name && (
+ <>
+
+
+
+ {schedule.source_name}
+
+ >
+ )}
+
+
+ {/* 유튜브에서 보기 버튼 */}
+
+
+ );
+}
+
+// 유튜브 섹션 컴포넌트
+function YoutubeSection({ schedule }) {
+ const videoId = extractYoutubeVideoId(schedule.source_url);
+ const isShorts = schedule.source_url?.includes('/shorts/');
+
+ if (!videoId) return null;
+
+ // 숏츠: 가로 레이아웃 (영상 + 정보)
+ if (isShorts) {
+ return (
+
+ {/* 영상 임베드 */}
+
+
+
+
+
+
+ {/* 영상 정보 카드 */}
+
+
+
+
+ );
+ }
+
+ // 일반 영상: 세로 레이아웃 (영상 위, 정보 아래)
+ return (
+
+ {/* 영상 임베드 */}
+
+
+
+
+
+
+ {/* 영상 정보 카드 */}
+
+
+
+
+ );
+}
+
+// 기본 섹션 컴포넌트 (다른 카테고리용)
+function DefaultSection({ schedule }) {
+ return (
+
+ {/* 제목 */}
+
+ {decodeHtmlEntities(schedule.title)}
+
+
+ {/* 메타 정보 */}
+
+
+
+ {formatFullDate(schedule.date)}
+
+ {schedule.time && (
+
+
+ {formatTime(schedule.time)}
+
+ )}
+
+
+ {/* 설명 */}
+ {schedule.description && (
+
+
+ {decodeHtmlEntities(schedule.description)}
+
+
+ )}
+
+ {/* 원본 링크 */}
+ {schedule.source_url && (
+
+
+ 원본 보기
+
+ )}
+
+ );
+}
+
+function ScheduleDetail() {
+ const { id } = useParams();
+
+ const { data: schedule, isLoading, error } = useQuery({
+ queryKey: ['schedule', id],
+ queryFn: () => getSchedule(id),
+ });
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (error || !schedule) {
+ return (
+
+
+
😢
+
일정을 찾을 수 없습니다
+
+ 일정 목록으로
+
+
+
+ );
+ }
+
+ // 카테고리별 섹션 렌더링
+ const renderCategorySection = () => {
+ switch (schedule.category_id) {
+ case CATEGORY_ID.YOUTUBE:
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const isYoutube = schedule.category_id === CATEGORY_ID.YOUTUBE;
+
+ return (
+
+
+ {/* 브레드크럼 네비게이션 */}
+
+
+ 일정
+
+
+
+ {schedule.category_name}
+
+
+
+ {decodeHtmlEntities(schedule.title)}
+
+
+
+ {/* 메인 컨텐츠 */}
+
+ {renderCategorySection()}
+
+
+
+ );
+}
+
+export default ScheduleDetail;