assistant/console/components/Schools.tsx

93 lines
3.6 KiB
TypeScript
Raw Permalink 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 { Copy, Plus, RefreshCw } from 'lucide-react';
import { api, errorText } from '../api';
import type { School } from '../types';
export function Schools({ schools, onChanged, onMessage }: {
schools: School[];
onChanged: () => void;
onMessage: (text: string) => void;
}) {
const [schoolId, setSchoolId] = useState('');
const [name, setName] = useState('');
const [issued, setIssued] = useState<{ school_id: string; token: string } | null>(null);
const [busy, setBusy] = useState(false);
async function register(id: string, label: string): Promise<void> {
if (!id.trim()) return;
setBusy(true);
try {
const result = await api.post<{ school_id: string; token: string }>('/api/schools', {
school_id: id.trim(), name: label.trim(),
});
setIssued(result);
setSchoolId('');
setName('');
onChanged();
} catch (error) {
onMessage(errorText(error));
} finally {
setBusy(false);
}
}
return (
<section className="panel">
<h2>学校与上报凭证</h2>
<p className="muted">
每所学校的桌面端用自己的凭证上报草稿、拉取已发布的经验。凭证只在生成时显示一次,丢了就重新生成。
</p>
<form className="school-form" onSubmit={event => { event.preventDefault(); void register(schoolId, name); }}>
<input value={schoolId} placeholder="学校标识,如 demo_school" onChange={event => setSchoolId(event.target.value)} />
<input value={name} placeholder="学校名称(可选)" onChange={event => setName(event.target.value)} />
<button type="submit" className="primary-button small" disabled={busy || !schoolId.trim()}>
<Plus size={14} />生成凭证
</button>
</form>
{issued && (
<div className="token-box">
<div className="token-head">
<strong>{issued.school_id} 的上报凭证</strong>
<button type="button" className="text-action" onClick={() => {
void navigator.clipboard.writeText(issued.token).then(() => onMessage('已复制到剪贴板'));
}}>
<Copy size={14} />复制
</button>
</div>
<code>{issued.token}</code>
<p className="muted">
写进那台机器的 .env:<code>HUB_URL=http://本机地址:8801</code> 和 <code>HUB_TOKEN=上面这串</code>,重启桌面端生效。
这串只显示这一次。
</p>
</div>
)}
<table className="school-table">
<thead>
<tr><th>学校</th><th>已报条目</th><th>最近上报</th><th>最近拉取</th><th /></tr>
</thead>
<tbody>
{schools.length === 0 && <tr><td colSpan={5} className="muted">还没有学校注册</td></tr>}
{schools.map(school => (
<tr key={school.id}>
<td><strong>{school.name || school.id}</strong><br /><span className="muted">{school.id}</span></td>
<td>{school.reported_entries}</td>
<td className="muted">{school.last_report_at ?? '—'}</td>
<td className="muted">{school.last_pull_at ?? '—'}</td>
<td>
<button type="button" className="text-action" disabled={busy}
onClick={() => { void register(school.id, school.name); }}
title="生成一串新凭证,旧的立即失效">
<RefreshCw size={14} />重置凭证
</button>
</td>
</tr>
))}
</tbody>
</table>
</section>
);
}