90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Banner, Pagination, Spin } from "@douyinfe/semi-ui-19";
|
|
import { api, displayValue, formatTime } from "../api";
|
|
import { WorkspaceTable } from "../components/WorkspaceTable";
|
|
import type { Components } from "./types";
|
|
|
|
/** Component evidence is always read from its own immutable observation. */
|
|
export function ComponentsPanel({
|
|
url,
|
|
timezone,
|
|
}: {
|
|
url: string;
|
|
timezone?: string;
|
|
}) {
|
|
const [page, setPage] = useState(1);
|
|
const [data, setData] = useState<Components>();
|
|
const [error, setError] = useState("");
|
|
useEffect(() => {
|
|
setPage(1);
|
|
setData(undefined);
|
|
}, [url]);
|
|
useEffect(() => {
|
|
const c = new AbortController();
|
|
setError("");
|
|
void api<Components>(
|
|
`${url}${url.includes("?") ? "&" : "?"}limit=25&offset=${(page - 1) * 25}`,
|
|
{ signal: c.signal },
|
|
)
|
|
.then((value) => {
|
|
if (!c.signal.aborted) setData(value);
|
|
})
|
|
.catch((e: Error) => {
|
|
if (!c.signal.aborted) setError(e.message);
|
|
});
|
|
return () => c.abort();
|
|
}, [url, page]);
|
|
if (error) return <Banner type="danger" description={error} />;
|
|
if (!data) return <Spin />;
|
|
return (
|
|
<section className="super-components">
|
|
<p>
|
|
{data.source === "preview" ? "Selection 预览组件" : "回测实际组件"} ·{" "}
|
|
{data.complete
|
|
? `已核实 ${data.reported_total ?? data.total} 个`
|
|
: "完整性未核实"}{" "}
|
|
· 观察时间 {formatTime(data.observed_at, timezone)}
|
|
</p>
|
|
{data.complete && data.total === 0 && (
|
|
<Banner
|
|
type="warning"
|
|
description="组件池为空,回测可能无法产生结果。请调整 Selection。"
|
|
/>
|
|
)}
|
|
{(data.warnings ?? []).map((w, i) => (
|
|
<Banner key={i} type="warning" description={displayValue(w)} />
|
|
))}
|
|
<p className="muted">组件指纹:{data.component_hash ?? "未核实"}</p>
|
|
<WorkspaceTable
|
|
rowKey="id"
|
|
dataSource={data.items}
|
|
scroll={{ x: 1100, y: 320 }}
|
|
columns={[
|
|
{ title: "组件 ID", dataIndex: "id", width: 190 },
|
|
{
|
|
title: "平台选择值",
|
|
width: 180,
|
|
render: (_, r) =>
|
|
displayValue(r?.value ?? r?.selectionValue ?? r?.selection),
|
|
},
|
|
{
|
|
title: "可用指标与平台返回字段",
|
|
width: 730,
|
|
render: (_, r) => JSON.stringify(r),
|
|
},
|
|
]}
|
|
empty="未提供组件"
|
|
/>
|
|
<footer className="workspace-table-footer super-toolbar">
|
|
<span>已保存 {data.total} 条</span>
|
|
<Pagination
|
|
total={data.total}
|
|
currentPage={page}
|
|
pageSize={25}
|
|
onPageChange={setPage}
|
|
/>
|
|
</footer>
|
|
</section>
|
|
);
|
|
}
|