- 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>
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
"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>
|
|
);
|
|
}
|