88 lines
3.8 KiB
TypeScript
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>
|
|
);
|
|
}
|