diff --git a/src/pages/music/components/VideoProjectsTab.jsx b/src/pages/music/components/VideoProjectsTab.jsx index 51b82d7..2a87a95 100644 --- a/src/pages/music/components/VideoProjectsTab.jsx +++ b/src/pages/music/components/VideoProjectsTab.jsx @@ -1,3 +1,269 @@ +// src/pages/music/components/VideoProjectsTab.jsx +import { useState, useEffect, useRef } from 'react'; +import { + createVideoProject, getVideoProjects, + renderVideoProject, exportVideoProject, deleteVideoProject, +} from '../../../api'; + +const COUNTRY_OPTIONS = ['BR', 'US', 'ID', 'MX', 'KR']; +const COUNTRY_FLAGS = { BR: '๐Ÿ‡ง๐Ÿ‡ท', US: '๐Ÿ‡บ๐Ÿ‡ธ', ID: '๐Ÿ‡ฎ๐Ÿ‡ฉ', MX: '๐Ÿ‡ฒ๐Ÿ‡ฝ', KR: '๐Ÿ‡ฐ๐Ÿ‡ท' }; + export default function VideoProjectsTab({ library, initialTrackId, onClearInitialTrack }) { - return null; + 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); + const [exportingId, setExportingId] = useState(null); + const pollRef = useRef(null); + + // initialTrackId prop ๋ฐ˜์˜ + useEffect(() => { + if (initialTrackId) { + setSelectedTrackId(String(initialTrackId)); + onClearInitialTrack?.(); + } + }, [initialTrackId]); + + const loadProjects = async () => { + try { + const data = await getVideoProjects(); + setProjects(Array.isArray(data) ? data : data.projects ?? []); + } catch (e) { + console.error('getVideoProjects:', e); + } + }; + + useEffect(() => { loadProjects(); }, []); + + // ๋ Œ๋”๋ง ์ค‘์ธ ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ์œผ๋ฉด 5์ดˆ๋งˆ๋‹ค ํด๋ง + useEffect(() => { + const hasRendering = projects.some(p => p.status === 'rendering'); + if (hasRendering && !pollRef.current) { + pollRef.current = setInterval(loadProjects, 5000); + } else if (!hasRendering && pollRef.current) { + clearInterval(pollRef.current); + pollRef.current = null; + } + return () => { + if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } + }; + }, [projects]); + + const toggleCountry = (c) => { + setCountries(prev => + prev.includes(c) ? prev.filter(x => x !== c) : [...prev, c] + ); + }; + + const handleCreate = async () => { + if (!selectedTrackId || countries.length === 0) return; + setCreating(true); + try { + await createVideoProject({ + track_id: Number(selectedTrackId), + format, + target_countries: countries, + }); + await loadProjects(); + } catch (e) { + console.error('createVideoProject:', e); + } finally { + setCreating(false); + } + }; + + const handleRender = async (id) => { + try { + await renderVideoProject(id); + await loadProjects(); + } catch (e) { + console.error('renderVideoProject:', e); + } + }; + + const handleExport = async (id) => { + setExportingId(id); + try { + const data = await exportVideoProject(id); + setExportData({ id, ...data }); + } catch (e) { + console.error('exportVideoProject:', e); + } finally { + setExportingId(null); + } + }; + + const handleDelete = async (id) => { + if (!window.confirm('์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์‚ญ์ œํ• ๊นŒ์š”?')) return; + try { + await deleteVideoProject(id); + setProjects(prev => prev.filter(p => p.id !== id)); + if (exportData?.id === id) setExportData(null); + } catch (e) { + console.error('deleteVideoProject:', e); + } + }; + + return ( +
+ {/* โ‘  ์ƒˆ ์˜์ƒ ๋งŒ๋“ค๊ธฐ */} +
+

โ‘  ์ƒˆ ์˜์ƒ ๋งŒ๋“ค๊ธฐ

+
+ +
+ {['visualizer', 'slideshow'].map(f => ( + + ))} +
+
+
ํƒ€๊ฒŸ ๊ตญ๊ฐ€ (๋ณต์ˆ˜ ์„ ํƒ)
+
+ {COUNTRY_OPTIONS.map(c => ( + + ))} +
+ +
+ + {/* โ‘ก ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก */} +
+

โ‘ก ์˜์ƒ ํ”„๋กœ์ ํŠธ

+ {projects.length === 0 ? ( +

ํŠธ๋ž™์„ ์„ ํƒํ•ด ์˜์ƒ์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”

+ ) : ( +
+ {projects.map(p => ( + + ))} +
+ )} +
+ + {/* โ‘ข ๋‚ด๋ณด๋‚ด๊ธฐ ํŒจํ‚ค์ง€ */} + {exportData && ( +
+

โ‘ข ๋‚ด๋ณด๋‚ด๊ธฐ ํŒจํ‚ค์ง€

+
+ {exportData.mp4_url && ( + + ๐Ÿ“น output.mp4 ๋‹ค์šด๋กœ๋“œ + + )} + {exportData.thumbnail_url && ( + + ๐Ÿ–ผ๏ธ thumbnail.jpg + + )} +
+ {exportData.metadata && ( +
+
metadata.json ๋ฏธ๋ฆฌ๋ณด๊ธฐ
+
+                                {JSON.stringify(exportData.metadata, null, 2)}
+                            
+
+ )} +
+ )} +
+ ); +} + +function ProjectCard({ project, onRender, onExport, onDelete, isExporting }) { + const STATUS_MAP = { + pending: { text: '๋Œ€๊ธฐ', cls: 'yt-status--pending' }, + rendering: { text: 'โš™ ์ฒ˜๋ฆฌ์ค‘', cls: 'yt-status--rendering' }, + done: { text: 'โœ“ ์™„๋ฃŒ', cls: 'yt-status--done' }, + failed: { text: '์‹คํŒจ', cls: 'yt-status--failed' }, + }; + const s = STATUS_MAP[project.status] ?? { text: project.status, cls: '' }; + + return ( +
+
+ {project.status === 'rendering' ? 'โš™๏ธ' : project.status === 'done' ? '๐ŸŽฌ' : '๐ŸŽต'} +
+
+
+ {project.title ?? `ํ”„๋กœ์ ํŠธ #${project.id}`} +
+
+ {project.format} ยท {(project.target_countries ?? []).join(' ')} +
+ {project.status === 'rendering' && ( +
+
+
+ )} +
+ {s.text} + {project.status === 'pending' && ( + + )} + {project.status === 'done' && ( + + )} + +
+ ); }