docs(spec): music YouTube 탭 프론트엔드 설계 스펙
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
# Music YouTube Tab Frontend — Design Spec
|
||||
|
||||
**Date:** 2026-05-01
|
||||
**Repo:** `web-page` (React + Vite SPA at `/Users/jaeohpark/development/web-page/`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Goal
|
||||
|
||||
MusicStudio 페이지에 **🎯 YouTube 탭**을 추가한다. 기존 4개 탭(Create / Lyrics / Library / Remix) 옆에 하나 더 붙이며, 탭 내부에 3개의 서브탭을 둔다.
|
||||
|
||||
- **🎬 영상 제작** — 트랙 선택 → 포맷·국가 설정 → 렌더링 → 내보내기
|
||||
- **💰 수익 추적** — 수동 수익 기록 입력 + 장르별 RPM 차트 + 기록 테이블
|
||||
- **📊 시장 트렌드** — agent-office가 매일 수집한 YouTube/Trends/Billboard 데이터 표시 + AI 프롬프트 추천
|
||||
|
||||
---
|
||||
|
||||
## 2. 영향 파일
|
||||
|
||||
### 수정
|
||||
| 파일 | 변경 내용 |
|
||||
|------|-----------|
|
||||
| `src/pages/music/MusicStudio.jsx` | tab 상태에 `'youtube'` 추가, 탭 버튼 추가, YoutubeTab 렌더링 |
|
||||
| `src/api.js` | 비디오 프로젝트 / 수익 / 시장 트렌드 API 함수 추가 |
|
||||
|
||||
### 신규 생성
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `src/pages/music/components/YoutubeTab.jsx` | YouTube 탭 루트 컴포넌트 (서브탭 상태 관리) |
|
||||
| `src/pages/music/components/VideoProjectsTab.jsx` | 🎬 영상 제작 서브탭 |
|
||||
| `src/pages/music/components/RevenueTab.jsx` | 💰 수익 추적 서브탭 |
|
||||
| `src/pages/music/components/TrendsTab.jsx` | 📊 시장 트렌드 서브탭 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 컴포넌트 계층
|
||||
|
||||
```
|
||||
MusicStudio
|
||||
└── [tab === 'youtube']
|
||||
└── YoutubeTab
|
||||
├── subtab 상태: 'video' | 'revenue' | 'trends'
|
||||
├── [subtab === 'video'] → VideoProjectsTab
|
||||
├── [subtab === 'revenue'] → RevenueTab
|
||||
└── [subtab === 'trends'] → TrendsTab
|
||||
```
|
||||
|
||||
**YoutubeTab props:**
|
||||
- `library: Array` — 라이브러리 트랙 목록 (MusicStudio에서 내려줌, 트랙 선택 드롭다운용)
|
||||
- `initialTrackId?: string` — Library 탭의 "영상 만들기" 버튼 클릭 시 pre-select용
|
||||
|
||||
---
|
||||
|
||||
## 4. 서브탭 상세
|
||||
|
||||
### 4-1. VideoProjectsTab (`subtab === 'video'`)
|
||||
|
||||
**① 새 영상 만들기 패널**
|
||||
- 트랙 선택 드롭다운 (`library` prop에서 목록, `title` 표시)
|
||||
- 형식 선택: `비주얼라이저` | `슬라이드쇼` (toggle)
|
||||
- 타겟 국가 칩: BR / US / ID / MX / KR (복수 선택 가능)
|
||||
- "프로젝트 생성" 버튼 → `POST /api/music/video-project`
|
||||
|
||||
**② 영상 프로젝트 목록**
|
||||
- `GET /api/music/video-projects` 폴링 (렌더링 중인 프로젝트 있을 때 5초 간격)
|
||||
- 상태별 표시:
|
||||
- `pending` — "대기" 배지 + "▶ 렌더" 버튼 → `POST /api/music/video-project/:id/render`
|
||||
- `rendering` — "처리중" 배지 + 진행 바 (시작 시각 기준 경과 시간 표시)
|
||||
- `done` — "✓ 완료" 배지 + "↓ 내보내기" 버튼
|
||||
- `failed` — "실패" 배지 (빨간색)
|
||||
|
||||
**③ 내보내기 패키지 (done 상태 프로젝트 선택 시)**
|
||||
- `GET /api/music/video-project/:id/export` → `{mp4_url, thumbnail_url, metadata}`
|
||||
- mp4 다운로드 링크, thumbnail 다운로드 링크, metadata.json 미리보기 (title / tags / target)
|
||||
|
||||
**상태 관리:**
|
||||
```js
|
||||
const [projects, setProjects] = useState([])
|
||||
const [selectedTrackId, setSelectedTrackId] = useState(initialTrackId ?? '')
|
||||
const [format, setFormat] = useState('visualizer')
|
||||
const [countries, setCountries] = useState(['BR'])
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [exportData, setExportData] = useState(null) // 선택된 done 프로젝트의 export
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4-2. RevenueTab (`subtab === 'revenue'`)
|
||||
|
||||
**대시보드 카드 (3개)**
|
||||
- `GET /api/music/revenue/dashboard` → `{total_revenue_usd, total_views, avg_rpm}`
|
||||
- 총 수익 / 총 조회수 / 가중평균 RPM
|
||||
|
||||
**장르별 RPM 바 차트**
|
||||
- `GET /api/music/revenue` → 레코드 목록에서 장르별로 RPM 집계
|
||||
- 바 차트 (CSS 기반, 라이브러리 없음) — genre / rpm / color 매핑
|
||||
|
||||
**수익 기록 추가 폼**
|
||||
- 필드: `yt_video_id`, `record_month` (YYYY-MM), `revenue_usd`, `views`, `country`
|
||||
- "저장" → `POST /api/music/revenue`
|
||||
- 성공 시 목록 + 대시보드 리프레시
|
||||
|
||||
**수익 기록 테이블**
|
||||
- `GET /api/music/revenue` — 영상 제목 / 월 / 수익 / 조회수 / RPM
|
||||
- 행 클릭 → 수정 폼 인라인 펼침
|
||||
- 삭제 버튼 → `DELETE /api/music/revenue/:id`
|
||||
|
||||
**장르 추론:** `yt_video_id`는 자유 입력이고 장르 컬럼이 DB에 없으므로, `genre` 필드를 수익 기록 폼에 optional 셀렉트로 추가한다. DB 스키마에 이미 없으면 프론트에서만 관리하지 않고, API 명세 확인 후 처리.
|
||||
|
||||
> **참고:** `revenue_records` 테이블에 `genre` 컬럼이 없다. 차트는 `yt_video_id`별 집계만 가능. 장르별 RPM 차트는 "영상별 RPM 비교"로 레이블을 바꿔서 구현한다.
|
||||
|
||||
**상태 관리:**
|
||||
```js
|
||||
const [dashboard, setDashboard] = useState(null)
|
||||
const [records, setRecords] = useState([])
|
||||
const [form, setForm] = useState({ yt_video_id:'', record_month:'', revenue_usd:'', views:'', country:'BR' })
|
||||
const [editingId, setEditingId] = useState(null)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4-3. TrendsTab (`subtab === 'trends'`)
|
||||
|
||||
**수집 상태 바**
|
||||
- `GET /api/music/market/report/latest` → `{report_date, created_at, top_genres, recommended_styles}`
|
||||
- 마지막 수집 일시 + 트렌드 수 표시
|
||||
- "↻ 수동 수집" 버튼 → `POST /api/agent-office/youtube/research` (body: `{}`)
|
||||
|
||||
**오늘의 인기 장르 Top 5**
|
||||
- `top_genres` 배열에서 상위 5개 렌더링
|
||||
- 각 항목: 장르명 / 대상 국가 플래그 / 점수 바
|
||||
|
||||
**AI 추천 Suno 프롬프트**
|
||||
- `GET /api/music/market/suggest` → `[{genre, suno_prompt, target_countries, reason}]`
|
||||
- 카드 형태, 프롬프트 클릭 시 클립보드 복사
|
||||
|
||||
**트렌드 리포트 이력**
|
||||
- `GET /api/music/market/report` → 날짜 목록
|
||||
- 날짜 클릭 → 해당 날짜 리포트 상세 표시 (top_genres + recommended_styles)
|
||||
|
||||
**상태 관리:**
|
||||
```js
|
||||
const [latestReport, setLatestReport] = useState(null)
|
||||
const [reports, setReports] = useState([])
|
||||
const [suggestions, setSuggestions] = useState([])
|
||||
const [selectedReport, setSelectedReport] = useState(null)
|
||||
const [researching, setResearching] = useState(false)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 추가 목록 (`src/api.js`)
|
||||
|
||||
```js
|
||||
// 기존 api.js 헬퍼: apiGet / apiPost / apiPut / apiDelete (plain fetch 래퍼)
|
||||
|
||||
// Video Projects
|
||||
export const createVideoProject = (data) => apiPost('/api/music/video-project', data)
|
||||
export const getVideoProjects = () => apiGet('/api/music/video-projects')
|
||||
export const renderVideoProject = (id) => apiPost(`/api/music/video-project/${id}/render`)
|
||||
export const exportVideoProject = (id) => apiGet(`/api/music/video-project/${id}/export`)
|
||||
export const deleteVideoProject = (id) => apiDelete(`/api/music/video-project/${id}`)
|
||||
|
||||
// Revenue
|
||||
export const getRevenueDashboard = () => apiGet('/api/music/revenue/dashboard')
|
||||
export const getRevenueRecords = () => apiGet('/api/music/revenue')
|
||||
export const addRevenueRecord = (data) => apiPost('/api/music/revenue', data)
|
||||
export const updateRevenueRecord = (id, data) => apiPut(`/api/music/revenue/${id}`, data)
|
||||
export const deleteRevenueRecord = (id) => apiDelete(`/api/music/revenue/${id}`)
|
||||
|
||||
// Market Trends
|
||||
export const getLatestTrendReport = () => apiGet('/api/music/market/report/latest')
|
||||
export const getTrendReports = () => apiGet('/api/music/market/report')
|
||||
export const getMarketSuggestions = () => apiGet('/api/music/market/suggest')
|
||||
export const triggerYoutubeResearch = () => apiPost('/api/agent-office/youtube/research', {})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Library 탭 연동
|
||||
|
||||
`MusicStudio.jsx`의 `LibraryCard` 컴포넌트에 **"🎬 영상 만들기"** 버튼 추가:
|
||||
|
||||
```jsx
|
||||
<button onClick={() => {
|
||||
setTab('youtube')
|
||||
setInitialTrackId(track.id)
|
||||
}}>🎬 영상 만들기</button>
|
||||
```
|
||||
|
||||
`initialTrackId` 상태를 MusicStudio 루트에 두고 YoutubeTab에 prop으로 내려준다. VideoProjectsTab이 마운트되면 해당 트랙을 드롭다운에 pre-select.
|
||||
|
||||
---
|
||||
|
||||
## 7. 스타일 가이드
|
||||
|
||||
기존 MusicStudio.css의 다크 테마 변수 재사용:
|
||||
- 배경: `#111827` / `#0d1117` / `#1f2937`
|
||||
- 강조색: `#22c55e` (초록, 완료·생성), `#f59e0b` (노랑, 처리중), `#3b82f6` (파랑, 수익), `#a855f7` (보라, 트렌드)
|
||||
- 새 CSS 클래스는 `MusicStudio.css`에 추가 (별도 파일 없음)
|
||||
|
||||
---
|
||||
|
||||
## 8. 범위 외 (Out of scope)
|
||||
|
||||
- YouTube Analytics OAuth 자동 동기화 (나중에 확장)
|
||||
- 영상 업로드 자동화 (YouTube Data API write scope)
|
||||
- 차트 라이브러리 도입 (CSS 바로 구현)
|
||||
Reference in New Issue
Block a user