Files
worldquant-alpha-system/frontend/src/components/JobPanel.tsx
T
yuxuanhui 3cd280d068 feat: add initial frontend setup with styles, types, and testing framework
- Created a global CSS file for styling the frontend with responsive design.
- Introduced TypeScript types for various entities including Research, Alpha, and Account.
- Implemented Playwright tests for account management and data synchronization workflows.
- Configured TypeScript with strict settings and included necessary libraries.
- Set up Vite as the build tool with React plugin and API proxy configuration.
- Added a Python script to initialize environment variables securely.
2026-09-07 14:54:20 +08:00

154 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import {
Button,
Empty,
Progress,
SideSheet,
Tag,
Toast,
} from "@douyinfe/semi-ui-19";
import { api, formatTime, jobLabels, jobStateLabels, post } from "../api";
import type { Job } from "../types";
export function JobPanel({
visible,
jobs,
timezone,
onClose,
onChange,
onAccount,
}: {
visible: boolean;
jobs: Job[];
timezone?: string;
onClose: () => void;
onChange: () => void;
onAccount: () => void;
}) {
const [errors, setErrors] = useState<
Record<string, { alpha_id: string; error: string }[]>
>({});
const [busy, setBusy] = useState("");
async function action(job: Job, name: string) {
setBusy(job.id);
try {
await post(`/sync-jobs/${job.id}/${name}`);
onChange();
} catch (e) {
Toast.error((e as Error).message);
} finally {
setBusy("");
}
}
return (
<SideSheet
closeOnEsc
className="job-panel"
title="同步任务"
visible={visible}
width={520}
onCancel={onClose}
>
<p className="muted">同步进度保存在服务端,关闭此面板不会中断任务。</p>
{!jobs.length && <Empty description="暂时没有同步任务" />}
{jobs.map((job) => (
<article className="job-card" key={job.id}>
<div className="job-header">
<strong>{jobLabels[job.kind] ?? job.kind}</strong>
<Tag
color={
job.status === "completed"
? "green"
: ["failed", "completed_with_errors"].includes(job.status)
? "red"
: "blue"
}
>
{jobStateLabels[job.status] ?? job.status}
</Tag>
</div>
<p className="muted">{formatTime(job.created_at, timezone)}</p>
<div className="job-numbers">
<span>
已处理 <b>{job.processed}</b>
</span>
<span>
失败 <b>{job.failed}</b>
</span>
<span>
总数 <b>{job.total ?? "计算中"}</b>
</span>
</div>
{job.total !== null && job.total > 0 && (
<Progress
percent={Math.min(
100,
Math.round(((job.processed + job.failed) / job.total) * 100),
)}
showInfo={false}
/>
)}
{job.error && <p className="error-text">{job.error}</p>}
{job.next_retry_at && (
<p className="muted">
下次重试:{formatTime(job.next_retry_at, timezone)}
</p>
)}
<div className="inline-actions">
{[
"queued",
"running",
"waiting_auth",
"waiting_connection",
].includes(job.status) && (
<Button
size="small"
loading={busy === job.id}
onClick={() => void action(job, "cancel")}
>
取消
</Button>
)}
{["failed", "cancelled", "completed_with_errors"].includes(
job.status,
) && (
<Button
size="small"
loading={busy === job.id}
onClick={() => void action(job, "retry")}
>
重试未完成部分
</Button>
)}
{["waiting_auth", "waiting_connection"].includes(job.status) && (
<Button size="small" onClick={onAccount}>
处理账户连接
</Button>
)}
{job.failed > 0 && (
<Button
size="small"
theme="borderless"
onClick={() =>
api<{ alpha_id: string; error: string }[]>(
`/sync-jobs/${job.id}/errors`,
)
.then((rows) => setErrors({ ...errors, [job.id]: rows }))
.catch((e) => Toast.error(e.message))
}
>
查看失败记录
</Button>
)}
</div>
{errors[job.id]?.map((e) => (
<p className="error-text" key={e.alpha_id}>
{e.alpha_id}:{e.error}
</p>
))}
</article>
))}
</SideSheet>
);
}