diff --git a/client/src/App.js b/client/src/App.js index c708ca9..576a68e 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -8,6 +8,8 @@ import RegisterPage from './pages/signuppage'; import AnalysisPage from './pages/analysispage'; import AnalysisDetailPage from './pages/AnalysisDetailPage'; import VideoAnalysisPage from './pages/VideoAnalysisPage'; +import VideoTimelinePage from './pages/VideoTimelinePage'; +import HeatmapPage from './pages/HeatmapPage'; axios.defaults.withCredentials = true; @@ -42,6 +44,7 @@ function App() { } /> + } /> } /> @@ -49,10 +52,15 @@ function App() { } /> } /> + + } /> + + } /> } /> } /> } /> + } /> ); diff --git a/client/src/pages/AnalysisDetailPage.js b/client/src/pages/AnalysisDetailPage.js index bdc6c08..36f0235 100644 --- a/client/src/pages/AnalysisDetailPage.js +++ b/client/src/pages/AnalysisDetailPage.js @@ -1,22 +1,62 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -const apiUrl = process.env.REACT_APP_API_URL; +import axios from 'axios'; + +const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:8000"; const AnalysisDetailPage = ({ sessionUser }) => { - const { state: data } = useLocation(); + const { state } = useLocation(); const navigate = useNavigate(); - if (!data) return
데이터를 찾을 수 없습니다.
; + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); - /* 수치 추출 로직(백엔드 DB에서 사용하는 컬럼명과 API 응답 객체 이름을 모두 매핑하기) */ - const prob = data.prob ?? data.score ?? data.analysis?.prob ?? -1; - - // 백엔드 DB 컬럼명인 face_conf, face_ratio, face_brightness 최우선으로 체크 + useEffect(() => { + const fetchDetail = async () => { + const imageId = state?.image_id; + const videoId = state?.video_id; + + if (!imageId && !videoId) { setLoading(false); return; } + + try { + if (imageId) { + const res = await axios.get(`/image/history/${imageId}`); + setData(res.data.context); + } else { + const res = await axios.get(`/video/history/${videoId}`); + setData(res.data.context); + } + } catch (e) { + console.error("상세 조회 실패", e); + } finally { + setLoading(false); + } + }; + fetchDetail(); + }, [state]); + + if (loading) { + return ( +
+

불러오는 중...

+
+ ); + } + + if (!data) { + return ( +
+

분석 데이터를 찾을 수 없거나 비정상적인 접근입니다.

+ +
+ ); + } + + const prob = data.prob ?? data.score ?? data.analysis?.prob ?? data.result_prob ?? -1; const face_conf = data.face_conf ?? data.face_confidence ?? data.conf ?? data.analysis?.face_conf ?? 0; const face_ratio = data.face_ratio ?? data.ratio ?? data.analysis?.face_ratio ?? 0; const face_brightness = data.face_brightness ?? data.brightness ?? data.analysis?.face_brightness ?? 0; - // 라벨 결정 로직 let label = 'UNKNOWN'; if (data.label && data.label !== 'UNKNOWN') { label = data.label.toUpperCase(); @@ -24,80 +64,122 @@ const AnalysisDetailPage = ({ sessionUser }) => { label = prob > 0.5 ? 'FAKE' : 'REAL'; } - // 확률이 -1인 경우 (데이터 로드 실패) N/A 표시를 위해 isInvalid 설정 const isInvalid = prob === -1; const displayProb = isInvalid ? 'N/A' : (Number(prob) * 100).toFixed(1) + '%'; const brightnessColor = face_brightness < 20 ? '#FF4B4B' : '#39FF14'; const ratioColor = face_ratio >= 3 ? '#39FF14' : '#FF4B4B'; - const mediaLoc = data.video_loc || data.image_loc || ''; - const isVideo = !!data.video_loc || (data.score !== undefined && !data.image_loc); + const mediaLoc = data.video_loc || data.media_loc || data.image_loc || ''; + const mediaSrc = mediaLoc.startsWith('blob') ? mediaLoc : `${apiUrl}${mediaLoc}`; + const isVideo = !!data.video_loc || (data.score !== undefined && !data.image_loc) || window.location.pathname.includes('video'); + + // [수정] WARNING 여부 판단 + const isWarning = data.status?.toUpperCase() === 'WARNING'; return ( -
+
+ + {/* 상단 네비게이션 헤더 바 */}
-
+
- - {data.version_type?.toUpperCase() || 'V1'} - - - {data.domain_type || '서양인'} - + {data.version_type?.toUpperCase() || 'V1'} + {data.domain_type || '서양인'} + {data.model_type?.toUpperCase() || 'FAST'}
{sessionUser && ( -
+
분석 담당: {sessionUser.name}
)}
+ {/* 2분할 메인 그리드 레이아웃 */}
-
- {/* 비디오 결과이면서 video_loc가 있을 경우 비디오 태그, 그 외엔 이미지 태그 */} - {data.score !== undefined && data.image_loc ? ( -