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:
95
frontend/src/components/inspection/RecentSiteInspections.tsx
Normal file
95
frontend/src/components/inspection/RecentSiteInspections.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ScoreBadge } from "@/components/common/ScoreBadge";
|
||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||
import { EmptyState } from "@/components/common/EmptyState";
|
||||
import { useRecentSiteInspections } from "@/lib/queries";
|
||||
import { formatDate, getScoreTailwindColor } from "@/lib/constants";
|
||||
import { Globe } from "lucide-react";
|
||||
import type { Grade } from "@/types/inspection";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/** 최근 사이트 크롤링 이력 (메인 페이지용) */
|
||||
export function RecentSiteInspections() {
|
||||
const { data, isLoading, isError } = useRecentSiteInspections();
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner message="최근 사이트 크롤링 이력을 불러오는 중..." />;
|
||||
}
|
||||
|
||||
if (isError || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.items.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
message="사이트 크롤링 이력이 없습니다"
|
||||
description="사이트 URL을 입력하여 첫 번째 크롤링을 시작해보세요"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-2xl mx-auto mt-8">
|
||||
<h2 className="text-lg font-semibold mb-4">최근 사이트 크롤링 이력</h2>
|
||||
<div className="space-y-3">
|
||||
{data.items.map((item) => (
|
||||
<Link
|
||||
key={item.site_inspection_id}
|
||||
href={`/site-inspections/${item.site_inspection_id}`}
|
||||
>
|
||||
<Card className="hover:shadow-md transition-shadow cursor-pointer">
|
||||
<CardContent className="flex items-center justify-between py-4 px-5">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-sm font-medium truncate">
|
||||
{item.domain || item.root_url}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDate(item.created_at)}
|
||||
</p>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{item.pages_total}페이지
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 ml-4">
|
||||
{item.overall_score !== null && (
|
||||
<>
|
||||
<span
|
||||
className={cn(
|
||||
"text-lg font-bold",
|
||||
getScoreTailwindColor(item.overall_score)
|
||||
)}
|
||||
>
|
||||
{item.overall_score}점
|
||||
</span>
|
||||
{item.grade && <ScoreBadge grade={item.grade as Grade} />}
|
||||
</>
|
||||
)}
|
||||
{item.overall_score === null && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{item.status === "crawling"
|
||||
? "크롤링 중"
|
||||
: item.status === "inspecting"
|
||||
? "검사 중"
|
||||
: item.status === "error"
|
||||
? "오류"
|
||||
: "대기 중"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user