Files
model-platform/frontend/app/features/schedules/ScheduleList.tsx
T
2026-08-28 12:00:14 +08:00

88 lines
3.8 KiB
TypeScript

import { type Schedule } from "../../services/api";
import { Search } from "lucide-react";
export function ScheduleList({
schedules,
selectedScheduleId,
keyword,
loading,
onSelect,
onContextMenu,
onKeywordChange,
}: {
schedules: Schedule[];
selectedScheduleId: string | null;
keyword: string;
loading: boolean;
onSelect: (scheduleId: string) => void;
onContextMenu: (event: React.MouseEvent, schedule: Schedule | null) => void;
onKeywordChange: (keyword: string) => void;
}) {
const filtered = keyword.trim()
? schedules.filter((item) => item.schedule_name.toLowerCase().includes(keyword.trim().toLowerCase()))
: schedules;
return (
<section className="flex min-h-0 flex-col overflow-hidden border-b border-[#e3e9ef]">
<div
className="flex min-h-[48px] items-center justify-between px-3"
onContextMenu={(event) => onContextMenu(event, null)}
>
<div className="flex items-center gap-[7px]">
<strong className="text-[14px] text-[#223247]">调度方案</strong>
<span className="inline-grid h-[20px] min-w-[20px] place-items-center rounded-[10px] bg-[#eef3f8] px-[5px] text-[10px] text-[#718195]">
{schedules.length}
</span>
</div>
</div>
<label className="mx-[10px] mb-[7px] flex min-h-[35px] items-center gap-[7px] rounded-[6px] border border-line bg-white px-[9px] text-[#95a3b2] focus-within:border-[#8db9e8] focus-within:ring-3 focus-within:ring-brand/10">
<Search size={15} />
<input
className="min-w-0 flex-1 border-0 bg-transparent text-[12px] text-[#33475d] outline-none placeholder:text-[#a6b2be]"
value={keyword}
placeholder="搜索调度名称"
onChange={(event) => onKeywordChange(event.target.value)}
/>
</label>
<div
className="min-h-0 overflow-auto px-2 pb-[9px]"
onContextMenu={(event) => onContextMenu(event, null)}
>
{loading ? (
<p className="my-[18px_10px] text-center text-[11px] leading-[1.6] text-[#95a2b0]">
正在加载调度方案…
</p>
) : filtered.length ? (
filtered.map((item) => (
<button
className={`relative mb-1.5 flex min-h-[62px] w-full cursor-pointer items-center gap-[9px] rounded-[6px] border border-[#e1e8ef] bg-white px-[10px] py-2 text-left text-[#526276] hover:border-[#b9cfe7] hover:bg-[#f8fbff] data-[state=active]:border-[#89b8ec] data-[state=active]:bg-brand-soft data-[state=active]:text-[#1c64b1] data-[state=active]:shadow-[inset_3px_0_#247fdc]`}
data-state={selectedScheduleId === item.schedule_id ? "active" : "idle"}
key={item.schedule_id}
type="button"
onClick={() => onSelect(item.schedule_id)}
onContextMenu={(event) => onContextMenu(event, item)}
>
<span
data-state={item.enabled ? "enabled" : "disabled"}
className="h-2 w-2 flex-none rounded-full bg-[#aeb9c5] ring-3 ring-ink-subtle/15 data-[state=enabled]:bg-success data-[state=enabled]:ring-3 data-[state=enabled]:ring-success/10"
/>
<span className="flex min-w-0 flex-col gap-1">
<strong className="max-w-[205px] truncate text-[12px] text-[#283b50]">
{item.schedule_name}
</strong>
<small className="text-[10px] text-[#8d9aab]">
{item.node_count} 个节点 · {item.enabled ? "已启用" : "未启用"}
</small>
</span>
</button>
))
) : (
<p className="my-[18px_10px] text-center text-[11px] leading-[1.6] text-[#95a2b0]">
暂无调度方案
</p>
)}
</div>
</section>
);
}