feat: 3-mode inspection with tabbed UI + batch upload
- Add batch inspection backend (multipart upload, SSE streaming, MongoDB) - Add tabbed UI (single page / site crawling / batch upload) on home and history pages - Add batch inspection progress, result pages with 2-panel layout - Rename "사이트 전체" to "사이트 크롤링" across codebase - Add python-multipart dependency for file upload - Consolidate nginx SSE location for all inspection types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
123
frontend/src/hooks/useBatchInspectionSSE.ts
Normal file
123
frontend/src/hooks/useBatchInspectionSSE.ts
Normal file
@ -0,0 +1,123 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useBatchInspectionStore } from "@/stores/useBatchInspectionStore";
|
||||
import { api } from "@/lib/api";
|
||||
import type {
|
||||
SSEBatchPageStart,
|
||||
SSEBatchPageComplete,
|
||||
SSEBatchAggregateUpdate,
|
||||
SSEBatchComplete,
|
||||
} from "@/types/batch-inspection";
|
||||
|
||||
/**
|
||||
* SSE를 통해 배치 검사 진행 상태를 수신하는 커스텀 훅.
|
||||
* EventSource로 검사 진행 상태를 실시간 수신하고
|
||||
* Zustand 스토어를 업데이트한다.
|
||||
* 크롤링 단계 없이 page_start, page_complete, aggregate_update, complete, error만 리스닝.
|
||||
*/
|
||||
export function useBatchInspectionSSE(batchInspectionId: string | null) {
|
||||
const {
|
||||
updatePageStatus,
|
||||
setPageComplete,
|
||||
updateAggregateScores,
|
||||
setCompleted,
|
||||
setError,
|
||||
} = useBatchInspectionStore();
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const eventSourceRef = useRef<EventSource | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!batchInspectionId) return;
|
||||
|
||||
const streamUrl = api.getBatchStreamUrl(batchInspectionId);
|
||||
const eventSource = new EventSource(streamUrl);
|
||||
eventSourceRef.current = eventSource;
|
||||
|
||||
/** 개별 페이지 검사 시작 이벤트 */
|
||||
eventSource.addEventListener("page_start", (e: MessageEvent) => {
|
||||
try {
|
||||
const data: SSEBatchPageStart = JSON.parse(e.data);
|
||||
updatePageStatus(data.page_url, "inspecting");
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
});
|
||||
|
||||
/** 개별 페이지 검사 완료 이벤트 */
|
||||
eventSource.addEventListener("page_complete", (e: MessageEvent) => {
|
||||
try {
|
||||
const data: SSEBatchPageComplete = JSON.parse(e.data);
|
||||
setPageComplete(data);
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
});
|
||||
|
||||
/** 집계 점수 업데이트 이벤트 */
|
||||
eventSource.addEventListener("aggregate_update", (e: MessageEvent) => {
|
||||
try {
|
||||
const data: SSEBatchAggregateUpdate = JSON.parse(e.data);
|
||||
updateAggregateScores(data);
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
});
|
||||
|
||||
/** 배치 검사 완료 이벤트 */
|
||||
eventSource.addEventListener("complete", (e: MessageEvent) => {
|
||||
try {
|
||||
const data: SSEBatchComplete = JSON.parse(e.data);
|
||||
setCompleted(data.aggregate_scores);
|
||||
eventSource.close();
|
||||
// stale 캐시 제거 → 결과 페이지에서 fresh 데이터 로드
|
||||
queryClient.removeQueries({
|
||||
queryKey: ["batchInspection", batchInspectionId],
|
||||
});
|
||||
router.push(`/batch-inspections/${batchInspectionId}`);
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
});
|
||||
|
||||
/** 에러 이벤트 */
|
||||
eventSource.addEventListener("error", (e: Event) => {
|
||||
if (e instanceof MessageEvent) {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
setError(data.message || "배치 검사 중 오류가 발생했습니다");
|
||||
} catch {
|
||||
setError("배치 검사 중 오류가 발생했습니다");
|
||||
}
|
||||
}
|
||||
// 네트워크 에러인 경우
|
||||
if (eventSource.readyState === EventSource.CLOSED) {
|
||||
setError("서버와의 연결이 끊어졌습니다");
|
||||
}
|
||||
});
|
||||
|
||||
// SSE 연결 타임아웃 (10분 - 배치 검사는 시간이 더 소요될 수 있음)
|
||||
const timeout = setTimeout(() => {
|
||||
eventSource.close();
|
||||
setError("배치 검사 시간이 초과되었습니다 (10분)");
|
||||
}, 600000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
eventSource.close();
|
||||
eventSourceRef.current = null;
|
||||
};
|
||||
}, [
|
||||
batchInspectionId,
|
||||
updatePageStatus,
|
||||
setPageComplete,
|
||||
updateAggregateScores,
|
||||
setCompleted,
|
||||
setError,
|
||||
router,
|
||||
queryClient,
|
||||
]);
|
||||
}
|
||||
@ -15,7 +15,7 @@ import type {
|
||||
} from "@/types/site-inspection";
|
||||
|
||||
/**
|
||||
* SSE를 통해 사이트 전체 검사 진행 상태를 수신하는 커스텀 훅.
|
||||
* SSE를 통해 사이트 크롤링 검사 진행 상태를 수신하는 커스텀 훅.
|
||||
* EventSource로 크롤링 + 검사 진행 상태를 실시간 수신하고
|
||||
* Zustand 스토어를 업데이트한다.
|
||||
*/
|
||||
@ -122,7 +122,7 @@ export function useSiteInspectionSSE(siteInspectionId: string | null) {
|
||||
}
|
||||
});
|
||||
|
||||
// SSE 연결 타임아웃 (10분 - 사이트 전체 검사는 시간이 더 소요됨)
|
||||
// SSE 연결 타임아웃 (10분 - 사이트 크롤링 검사는 시간이 더 소요됨)
|
||||
const timeout = setTimeout(() => {
|
||||
eventSource.close();
|
||||
setError("사이트 검사 시간이 초과되었습니다 (10분)");
|
||||
|
||||
Reference in New Issue
Block a user