import { useLoaderData, Link, Form } from "react-router"; import type { Route } from "./+types/venues"; import { getVenues, getLastScrapePerVenue, type ScrapeLog } from "~/lib/db.server"; import { getScraperIds } from "~/lib/venue-meta.server"; export async function loader(_: Route.LoaderArgs) { const venues = getVenues(); const scraperIds = getScraperIds(); const scrapeStatus = getLastScrapePerVenue(); return { venues, scraperIds, scrapeStatus }; } export default function Venues() { const { venues, scraperIds: scraperIdList, scrapeStatus } = useLoaderData(); const scraperIds = new Set(scraperIdList); const statusMap = new Map(scrapeStatus.map((s) => [s.venue_id, s])); return (
🎸 東京ライブハウス

会場一覧

現在 {scraperIdList.length} 会場のスクレイパーが登録されています。

{venues.length === 0 ? (

まだ会場データがありません。「全会場を更新」してください。

) : (
{venues.map((v) => { const log = statusMap.get(v.id); return (
{/* 会場名 + エリア */}
{v.name} {v.area &&

{v.area}

}
{/* イベント件数 */} {v.event_count ?? 0} {/* 最終スクレイプ状態 */} {log ? ( ) : ( 未実行 )} {/* 個別更新ボタン */} {scraperIds.has(v.id) && (
)}
); })}
)}
); } function ScrapeStatus({ log }: { log: ScrapeLog }) { if (log.status === "running") { return ⟳ 実行中...; } if (log.status === "error") { return ( ✖ エラー ); } const time = log.finished_at?.slice(0, 16).replace("T", " ") ?? ""; return ( ✔ {time} ); }