From b16aa963cd47a4fbd4f325f65dadd8d874b21012 Mon Sep 17 00:00:00 2001 From: caadiq Date: Tue, 3 Feb 2026 14:57:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C=20=EC=98=88?= =?UTF-8?q?=EC=A0=95=20=EC=9D=BC=EC=A0=95=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PC YoutubeSection에 예정 일정 placeholder UI 추가 - 예정 일정일 경우 "예정" 배지 표시 - 영상 준비 중 placeholder 컴포넌트 추가 - 예정 일정에도 channelName 반환하도록 API 수정 Co-Authored-By: Claude Opus 4.5 --- backend/src/services/schedule.js | 20 ++-- .../schedule/sections/YoutubeSection.jsx | 106 ++++++++++++++---- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/backend/src/services/schedule.js b/backend/src/services/schedule.js index 43dabb3..5dfbd61 100644 --- a/backend/src/services/schedule.js +++ b/backend/src/services/schedule.js @@ -242,13 +242,19 @@ export async function getScheduleDetail(db, id, getXProfile = null) { }; // 카테고리별 추가 필드 - if (s.category_id === CATEGORY_IDS.YOUTUBE && s.youtube_video_id) { - result.videoId = s.youtube_video_id; - result.videoType = s.youtube_video_type; - result.channelName = s.youtube_channel; - result.videoUrl = s.youtube_video_type === 'shorts' - ? `https://www.youtube.com/shorts/${s.youtube_video_id}` - : `https://www.youtube.com/watch?v=${s.youtube_video_id}`; + if (s.category_id === CATEGORY_IDS.YOUTUBE) { + // 채널 이름은 항상 반환 (예정 일정 포함) + if (s.youtube_channel) { + result.channelName = s.youtube_channel; + } + // video_id가 있는 경우에만 영상 관련 필드 추가 + if (s.youtube_video_id) { + result.videoId = s.youtube_video_id; + result.videoType = s.youtube_video_type; + result.videoUrl = s.youtube_video_type === 'shorts' + ? `https://www.youtube.com/shorts/${s.youtube_video_id}` + : `https://www.youtube.com/watch?v=${s.youtube_video_id}`; + } } else if (s.category_id === CATEGORY_IDS.X && s.x_post_id) { const username = config.x.defaultUsername; result.postId = s.x_post_id; diff --git a/frontend/src/pages/pc/public/schedule/sections/YoutubeSection.jsx b/frontend/src/pages/pc/public/schedule/sections/YoutubeSection.jsx index 2cd8846..4247f5d 100644 --- a/frontend/src/pages/pc/public/schedule/sections/YoutubeSection.jsx +++ b/frontend/src/pages/pc/public/schedule/sections/YoutubeSection.jsx @@ -1,23 +1,32 @@ import { motion } from 'framer-motion'; -import { Calendar, Link2 } from 'lucide-react'; +import { Calendar, Link2, Clock } from 'lucide-react'; import { decodeHtmlEntities, formatXDateTimeWithTime } from './utils'; /** * 영상 정보 컴포넌트 (공통) */ -function VideoInfo({ schedule, isShorts }) { +function VideoInfo({ schedule, isShorts, isScheduled = false }) { const members = schedule.members || []; const isFullGroup = members.length === 5; + // 채널명: channelName 또는 source.name에서 가져옴 + const channelName = schedule.channelName || schedule.source?.name; return (
{/* 제목 */} -

- {decodeHtmlEntities(schedule.title)} -

+
+

+ {decodeHtmlEntities(schedule.title)} +

+ {isScheduled && ( + + 예정 + + )} +
{/* 메타 정보 */} -
+
{/* 날짜/시간 */}
@@ -25,12 +34,12 @@ function VideoInfo({ schedule, isShorts }) {
{/* 채널명 */} - {schedule.channelName && ( + {channelName && ( <>
- {schedule.channelName} + {channelName}
)} @@ -51,19 +60,55 @@ function VideoInfo({ schedule, isShorts }) {
)} - {/* 유튜브에서 보기 버튼 */} - + ); +} + +/** + * 예정 일정 Placeholder 컴포넌트 + */ +function ScheduledPlaceholder() { + return ( +
+ {/* 배경 패턴 */} +
+
+
+ + {/* 유튜브 아이콘 */} +
+
+ - YouTube에서 보기 - +
+
+ + {/* 텍스트 */} +
+
+ + 영상 준비 중 +
+

곧 업로드될 예정입니다

); @@ -75,8 +120,29 @@ function VideoInfo({ schedule, isShorts }) { function YoutubeSection({ schedule }) { const videoId = schedule.videoId; const isShorts = schedule.videoType === 'shorts'; + const isScheduled = !videoId; // videoId가 없으면 예정 일정 - if (!videoId) return null; + // 예정 일정: 세로 레이아웃 (Placeholder + 정보) + if (isScheduled) { + return ( +
+ {/* 예정 Placeholder */} + + + + + {/* 영상 정보 카드 */} + + + +
+ ); + } // 숏츠: 가로 레이아웃 (영상 + 정보) if (isShorts) {