diff --git a/app/Http/Controllers/Video/TutorialVideoController.php b/app/Http/Controllers/Video/TutorialVideoController.php index 8dfd6564..e87bb1b4 100644 --- a/app/Http/Controllers/Video/TutorialVideoController.php +++ b/app/Http/Controllers/Video/TutorialVideoController.php @@ -230,6 +230,29 @@ public function history(): JsonResponse ]); } + /** + * 이력 상세 (스크립트/분석 데이터) + */ + public function detail(int $id): JsonResponse + { + $tutorial = TutorialVideo::where('user_id', auth()->id()) + ->findOrFail($id); + + return response()->json([ + 'success' => true, + 'data' => [ + 'id' => $tutorial->id, + 'title' => $tutorial->title, + 'status' => $tutorial->status, + 'progress' => $tutorial->progress, + 'analysis_data' => $tutorial->analysis_data, + 'slides_data' => $tutorial->slides_data, + 'cost_usd' => $tutorial->cost_usd, + 'created_at' => $tutorial->created_at?->toIso8601String(), + ], + ]); + } + /** * 이력 삭제 */ diff --git a/resources/views/video/tutorial/index.blade.php b/resources/views/video/tutorial/index.blade.php index 652369e5..811d3d7d 100644 --- a/resources/views/video/tutorial/index.blade.php +++ b/resources/views/video/tutorial/index.blade.php @@ -652,6 +652,76 @@ className="px-5 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red ); }; +// ============================================================ +// History Detail Panel (행 클릭 시 표시) +// ============================================================ +const HistoryDetail = ({ detail, onClose }) => { + if (!detail) return null; + + const analysis = detail.analysis_data || []; + const totalSteps = analysis.reduce((sum, s) => sum + (s.steps || []).length, 0); + const totalDuration = analysis.reduce((sum, s) => + sum + (s.steps || []).reduce((d, step) => d + (step.duration || 6), 0), 6 + ); + + return ( + + +
+
+
+

{detail.title || '제목 없음'}

+ + {totalSteps}단계 / 예상 {totalDuration}초 + +
+ +
+ + {analysis.length === 0 ? ( +

분석 데이터가 없습니다.

+ ) : ( +
+ {analysis.map((screen, i) => { + const steps = screen.steps || []; + return ( +
+
+ + {screen.screen_number || i + 1} + + {screen.title || `화면 ${i + 1}`} + {steps.length}단계 +
+
+ {steps.map((step, j) => ( +
+ + {step.step_number || j + 1} + +
+
+ + {step.focused_element?.label || `단계 ${j + 1}`} + + {step.duration || 6}초 +
+

{step.narration || '-'}

+
+
+ ))} +
+
+ ); + })} +
+ )} +
+ + + ); +}; + // ============================================================ // History Table // ============================================================ @@ -659,17 +729,26 @@ className="px-5 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red const [items, setItems] = useState([]); const [selected, setSelected] = useState([]); const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [expandedId, setExpandedId] = useState(null); + const [detailData, setDetailData] = useState({}); + const [detailLoading, setDetailLoading] = useState(null); - const fetchHistory = async () => { + const fetchHistory = async (showSpinner) => { + if (showSpinner) setRefreshing(true); try { const data = await api('/video/tutorial/history'); setItems(data.data || []); } catch (err) { console.error(err); + } finally { + if (showSpinner) setRefreshing(false); } }; - useEffect(() => { fetchHistory(); }, [refreshKey]); + useEffect(() => { fetchHistory(false); }, [refreshKey]); + + const handleRefresh = () => { fetchHistory(true); }; const toggleSelect = (id) => { setSelected(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); @@ -688,7 +767,7 @@ className="px-5 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red body: JSON.stringify({ ids: selected }), }); setSelected([]); - fetchHistory(); + fetchHistory(false); } catch (err) { alert(err.message); } finally { @@ -696,6 +775,27 @@ className="px-5 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red } }; + const toggleDetail = async (id) => { + if (expandedId === id) { + setExpandedId(null); + return; + } + + setExpandedId(id); + + if (!detailData[id]) { + setDetailLoading(id); + try { + const data = await api(`/video/tutorial/detail/${id}`); + setDetailData(prev => ({ ...prev, [id]: data.data })); + } catch (err) { + console.error('상세 조회 실패:', err); + } finally { + setDetailLoading(null); + } + } + }; + const statusLabel = (status) => { const map = { pending: { label: '대기', cls: 'bg-gray-100 text-gray-600' }, @@ -716,7 +816,19 @@ className="px-5 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red return (
-

생성 이력

+
+

생성 이력

+ +
{selected.length > 0 && (