From 40e1f957c7a1990a7be2002f37750ecbc0c37744 Mon Sep 17 00:00:00 2001 From: caadiq Date: Fri, 16 Jan 2026 10:55:40 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20ScheduleDetail=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=EB=B3=84=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - schedule-sections 폴더 생성 - YoutubeSection, XSection, ConcertSection, DefaultSection 분리 - KakaoMap, utils.js 공통 모듈 분리 - 메인 ScheduleDetail.jsx 1010줄 → 175줄로 간소화 --- .../src/pages/pc/public/ScheduleDetail.jsx | 859 +----------------- .../schedule-sections/ConcertSection.jsx | 389 ++++++++ .../schedule-sections/DefaultSection.jsx | 52 ++ .../pc/public/schedule-sections/KakaoMap.jsx | 88 ++ .../pc/public/schedule-sections/XSection.jsx | 114 +++ .../schedule-sections/YoutubeSection.jsx | 173 ++++ .../pc/public/schedule-sections/index.js | 6 + .../pc/public/schedule-sections/utils.js | 56 ++ 8 files changed, 892 insertions(+), 845 deletions(-) create mode 100644 frontend/src/pages/pc/public/schedule-sections/ConcertSection.jsx create mode 100644 frontend/src/pages/pc/public/schedule-sections/DefaultSection.jsx create mode 100644 frontend/src/pages/pc/public/schedule-sections/KakaoMap.jsx create mode 100644 frontend/src/pages/pc/public/schedule-sections/XSection.jsx create mode 100644 frontend/src/pages/pc/public/schedule-sections/YoutubeSection.jsx create mode 100644 frontend/src/pages/pc/public/schedule-sections/index.js create mode 100644 frontend/src/pages/pc/public/schedule-sections/utils.js diff --git a/frontend/src/pages/pc/public/ScheduleDetail.jsx b/frontend/src/pages/pc/public/ScheduleDetail.jsx index 298085d..b530f1e 100644 --- a/frontend/src/pages/pc/public/ScheduleDetail.jsx +++ b/frontend/src/pages/pc/public/ScheduleDetail.jsx @@ -1,854 +1,23 @@ import { useParams, Link } from 'react-router-dom'; import { useQuery, keepPreviousData } from '@tanstack/react-query'; -import { useEffect, useRef, useState } from '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 키 -const KAKAO_MAP_KEY = import.meta.env.VITE_KAKAO_JS_KEY; - -// 카테고리 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); -}; - -// X용 날짜/시간 포맷팅 (오후 2:30 · 2026년 1월 15일) -const formatXDateTime = (dateStr, timeStr) => { - if (!dateStr) return ''; - const date = new Date(dateStr); - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - - let result = `${year}년 ${month}월 ${day}일`; - - if (timeStr) { - const [hours, minutes] = timeStr.split(':').map(Number); - const period = hours < 12 ? '오전' : '오후'; - const hour12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours; - result = `${period} ${hour12}:${String(minutes).padStart(2, '0')} · ${result}`; - } - - return result; -}; - -// 영상 정보 컴포넌트 (공통) -function VideoInfo({ schedule, isShorts }) { - const members = schedule.members || []; - const isFullGroup = members.length === 5; - - return ( -
- {/* 제목 */} -

- {decodeHtmlEntities(schedule.title)} -

- - {/* 메타 정보 */} -
- {/* 날짜 */} -
- - {formatFullDate(schedule.date)} -
- - {/* 시간 */} - {schedule.time && ( - <> -
-
- - {formatTime(schedule.time)} -
- - )} - - {/* 채널명 */} - {schedule.source_name && ( - <> -
-
- - {schedule.source_name} -
- - )} -
- - {/* 멤버 목록 */} - {members.length > 0 && ( -
- {isFullGroup ? ( - - 프로미스나인 - - ) : ( - members.map((member) => ( - - {member.name} - - )) - )} -
- )} - - {/* 유튜브에서 보기 버튼 */} -
- - - - - YouTube에서 보기 - -
-
- ); -} - -// 유튜브 섹션 컴포넌트 -function YoutubeSection({ schedule }) { - const videoId = extractYoutubeVideoId(schedule.source_url); - const isShorts = schedule.source_url?.includes('/shorts/'); - - if (!videoId) return null; - - // 숏츠: 가로 레이아웃 (영상 + 정보) - if (isShorts) { - return ( -
- {/* 영상 임베드 */} - -
-