import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { listPublicationPoints } from "../api/service"; import type { PublicationPointRecord } from "../api/schemas"; import { CursorPagerControls } from "../components/CursorPagerControls"; import { DataTable, type Column } from "../components/DataTable"; import { PageHeader } from "../components/PageHeader"; import { Panel } from "../components/Panel"; import { StatusPill } from "../components/StatusPill"; import { useCursorPager } from "../lib/cursor"; import { formatDurationMs, formatInt, truncateMiddle } from "../lib/format"; import { withRunParam } from "../lib/run"; import { useRun } from "../lib/useRun"; export default function PublicationPointsPage() { const { runId } = useRun(); const navigate = useNavigate(); const pager = useCursorPager(runId); const [needle, setNeedle] = useState(""); const ppsQuery = useQuery({ queryKey: ["pps", runId, pager.cursor], queryFn: () => listPublicationPoints(runId, { limit: 50, cursor: pager.cursor }), placeholderData: (prev) => prev, }); const rows = useMemo(() => { const items = ppsQuery.data?.items ?? []; const q = needle.trim().toLowerCase(); if (!q) return items; return items.filter((pp) => [pp.manifestRsyncUri, pp.publicationPointRsyncUri, pp.rrdpNotificationUri, pp.rsyncBaseUri, pp.ppId] .filter(Boolean) .some((value) => value!.toLowerCase().includes(q)), ); }, [ppsQuery.data, needle]); const columns: Column[] = [ { key: "manifest", header: "Publication point", render: (pp) => (
{pp.manifestRsyncUri ? truncateMiddle(pp.manifestRsyncUri, 68) : pp.ppId}
{pp.ppId}
), }, { key: "source", header: "Sync source", render: (pp) => {pp.repoSyncSource ?? pp.source ?? "—"}, }, { key: "state", header: "Terminal state", render: (pp) => , }, { key: "objects", header: "Objects", numeric: true, render: (pp) => formatInt(pp.objects) }, { key: "rejected", header: "Rejected", numeric: true, render: (pp) => pp.rejectedObjects ? ( {formatInt(pp.rejectedObjects)} ) : ( "0" ), }, { key: "warnings", header: "Warnings", numeric: true, render: (pp) => formatInt(pp.warnings) }, { key: "duration", header: "Sync time", numeric: true, render: (pp) => formatDurationMs(pp.repoSyncDurationMs), }, ]; return (
setNeedle(e.target.value)} placeholder="Filter current page (URI/id)…" aria-label="Filter current page" style={{ minWidth: 220, padding: "5px 8px", border: "1px solid var(--border)", borderRadius: "var(--radius-m)" }} /> } flush > pp.ppId} loading={ppsQuery.isPending} error={ppsQuery.isError ? ppsQuery.error : undefined} onRetry={() => ppsQuery.refetch()} emptyTitle="No publication points indexed" caption="Publication points" onRowClick={(pp) => navigate(withRunParam(`/publication-points/${encodeURIComponent(pp.ppId)}`, runId)) } /> pager.goNext(ppsQuery.data?.nextCursor ?? null)} itemCount={rows.length} />
); }