import { useState, useEffect, useRef } from "react"; import { useFetcher } from "react-router"; interface ScrapeLog { id: number; run_id: string; venue_id: string; venue_name: string; status: "running" | "ok" | "error"; events_saved: number; error: string | null; } interface StatusData { running: boolean; results: ScrapeLog[]; } interface ScrapeStartData { run_id: string; status: string; } export default function ScrapeButton({ venueId }: { venueId?: string }) { const triggerFetcher = useFetcher(); const statusFetcher = useFetcher(); const statusFetcherRef = useRef(statusFetcher); useEffect(() => { statusFetcherRef.current = statusFetcher; }); const [runId, setRunId] = useState(null); const [polling, setPolling] = useState(false); const [done, setDone] = useState(false); useEffect(() => { const data = triggerFetcher.data; if (data?.run_id) { setRunId(data.run_id); setPolling(true); setDone(false); } }, [triggerFetcher.data]); useEffect(() => { if (!polling || !runId) return; const id = setInterval(() => { statusFetcherRef.current.load(`/api/scrape-status?run_id=${runId}`); }, 2000); return () => clearInterval(id); }, [polling, runId]); useEffect(() => { const data = statusFetcher.data; if (data && !data.running) { setPolling(false); setDone(true); } }, [statusFetcher.data]); const results: ScrapeLog[] = statusFetcher.data?.results ?? []; const running = statusFetcher.data?.running ?? false; const isActive = triggerFetcher.state !== "idle" || polling; const okCount = results.filter((r) => r.status === "ok").length; const errCount = results.filter((r) => r.status === "error").length; const apiUrl = venueId ? `/api/scrape?venue_id=${venueId}` : "/api/scrape"; return (
{(isActive || done) && results.length > 0 && (

{running ? `スクレイプ中... ${okCount + errCount} / ${results.length} 完了` : `完了 — ✔ ${okCount}件成功 / ✖ ${errCount}件失敗`}

{results.map((r) => (
{r.status === "ok" ? "✔" : r.status === "error" ? "✖" : "⟳"} {r.venue_name} {r.status === "ok" && ( {r.events_saved}件 )} {r.status === "error" && ( エラー )}
))}
)}
); }