import { useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import { PageHeader } from "../components/layout/PageHeader"; import { Badge, Button, Card, EmptyState } from "../components/ui"; import { useData, useRepos } from "../data/DataContext"; import type { Job } from "../domain/types"; import { useI18n } from "../i18n/I18nContext"; import { formatLocalDateTime } from "../lib/time"; /** 首屏顯示筆數;太多時用「載入更多」而不是分頁 */ const INITIAL = 15; const STEP = 15; export function JobsPage() { const repos = useRepos(); const { refresh, tick } = useData(); const { t } = useI18n(); const [jobs, setJobs] = useState([]); const [visible, setVisible] = useState(INITIAL); const [busy, setBusy] = useState(false); useEffect(() => { void repos.jobs.list().then(setJobs); }, [repos.jobs, tick]); useEffect(() => { const onStore = () => refresh(); window.addEventListener("harbor:store", onStore); return () => window.removeEventListener("harbor:store", onStore); }, [refresh]); const sorted = useMemo( () => jobs.slice().sort((a, b) => b.updated_at - a.updated_at), [jobs], ); // 列表變短時收回可見量,避免空白 useEffect(() => { setVisible((v) => Math.min(Math.max(INITIAL, v), Math.max(INITIAL, sorted.length))); }, [sorted.length]); const shown = sorted.slice(0, visible); const hasMore = visible < sorted.length; async function startDemo() { setBusy(true); try { await repos.jobs.startDemo(); setVisible(INITIAL); refresh(); } finally { setBusy(false); } } return ( <> {sorted.length === 0 ? ( ) : (

{t("jobs.total", { n: sorted.length })} {hasMore ? t("jobs.showing", { n: shown.length }) : ""}

{shown.map((job) => (
{job.template_type}

{job.progress_summary}

{job.progress_percent}% · {formatLocalDateTime(job.updated_at)}

{job.status}
))} {hasMore ? (
) : null}
)} ); }