Files
web-page/src/pages/music/components/PipelineCard.jsx

91 lines
3.6 KiB
JavaScript

import { useState } from 'react';
import { cancelPipeline, publishPipeline } from '../../../api';
import PipelineDetailModal from './PipelineDetailModal';
const STEP_LABELS = ['커버','영상','썸네','메타','검토','발행'];
function stepIndex(state) {
if (!state) return -1;
if (state.startsWith('cover')) return 0;
if (state.startsWith('video')) return 1;
if (state.startsWith('thumb')) return 2;
if (state.startsWith('meta')) return 3;
if (state.startsWith('ai_review') || state === 'publish_pending') return 4;
if (state.startsWith('publish')) return 5;
if (state === 'published') return 6;
return -1;
}
export default function PipelineCard({ pipeline, onChanged }) {
const [showDetail, setShowDetail] = useState(false);
const i = stepIndex(pipeline.state);
const title = pipeline.compile_title || pipeline.track_title || `Pipeline #${pipeline.id}`;
const handleCardClick = (e) => {
if (e.target.closest('button') || e.target.closest('a')) return;
setShowDetail(true);
};
return (
<>
<div className="pipeline-card" onClick={handleCardClick}>
<div className="pipeline-card__head">
<h4>{title}</h4>
{pipeline.visual_style && (
<span className="pipeline-style-badge">{pipeline.visual_style}</span>
)}
{!['published','cancelled','failed'].includes(pipeline.state) && (
<button onClick={async () => { await cancelPipeline(pipeline.id); onChanged(); }}>
취소
</button>
)}
</div>
<div className="pipeline-previews">
{pipeline.cover_url && (
<img src={pipeline.cover_url} alt="" className="pipeline-preview-mini" />
)}
{pipeline.thumbnail_url && (
<img src={pipeline.thumbnail_url} alt="" className="pipeline-preview-mini" />
)}
{pipeline.video_url && <span className="pipeline-video-icon"></span>}
</div>
<div className="pipeline-progress">
{STEP_LABELS.map((lbl, idx) => (
<div key={lbl}
className={`pipeline-dot ${idx <= i ? 'is-done' : ''} ${idx === i ? 'is-current' : ''}`}>
<span>{lbl}</span>
</div>
))}
</div>
<div className="pipeline-state">현재: {pipeline.state}</div>
{pipeline.review && (
<div className="pipeline-review">
AI 검토: <strong>{pipeline.review.verdict}</strong>
({pipeline.review.weighted_total}/100)
</div>
)}
{pipeline.state === 'publish_pending' && (
<button className="button primary"
onClick={async () => { await publishPipeline(pipeline.id); onChanged(); }}>
YouTube 업로드
</button>
)}
{pipeline.youtube_video_id && (
<a href={`https://youtu.be/${pipeline.youtube_video_id}`}
target="_blank" rel="noreferrer">
유튜브에서 보기
</a>
)}
</div>
{showDetail && <PipelineDetailModal pipeline={pipeline} onClose={() => setShowDetail(false)} />}
</>
);
}