A start in the past fires the latest missed time once, right away, so the form says that instead of 'already passed'. A one-shot past its time reads 'past' / 'time passed' (the controller's clock, not a record of firing) and keeps its pause button. The edit form compares against what it opened with, so a schedule changing under it is not reverted on save.
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
// <ScheduleCard> — one scheduled prompt as a card: its name as the title,
|
|
// "agents: foo, bar" beneath, then the message, the cadence and when it
|
|
// fires next. Edit, pause/resume and remove sit at the foot; edit swaps the
|
|
// card for `ScheduleForm` (the page owns that swap).
|
|
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
|
import { Button } from "../../ui/button/Button.js";
|
|
import { Card } from "../../ui/card/Card.js";
|
|
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
|
|
import {
|
|
fmtInterval,
|
|
fmtLocal,
|
|
scheduleState,
|
|
type Schedule,
|
|
type ScheduleState,
|
|
} from "./ScheduleTypes.js";
|
|
import "./ScheduleCard.css";
|
|
|
|
const STATE_TONE: Record<ScheduleState, BadgeTone> = {
|
|
active: "positive",
|
|
paused: "warning",
|
|
past: "neutral",
|
|
};
|
|
|
|
export function ScheduleCard({
|
|
schedule,
|
|
pending,
|
|
onEdit,
|
|
onTogglePaused,
|
|
onRemove,
|
|
}: {
|
|
schedule: Schedule;
|
|
/** A pause/resume or remove for this schedule is in flight. */
|
|
pending: boolean;
|
|
onEdit: () => void;
|
|
onTogglePaused: () => void;
|
|
onRemove: () => void;
|
|
}) {
|
|
const state = scheduleState(schedule);
|
|
return (
|
|
<Card class="ui-schedule-card">
|
|
<div class="ui-schedule-card-head">
|
|
<h3 class="ui-schedule-card-name">{schedule.name}</h3>
|
|
<Badge tone={STATE_TONE[state]} value={state} />
|
|
<Badge
|
|
value={
|
|
schedule.interval_seconds === null
|
|
? "once"
|
|
: `every ${fmtInterval(schedule.interval_seconds)}`
|
|
}
|
|
/>
|
|
</div>
|
|
<div class="ui-schedule-card-agents">
|
|
agents: {schedule.agents.join(", ")}
|
|
</div>
|
|
<p class="ui-schedule-card-body">{schedule.body}</p>
|
|
<div class="ui-schedule-card-next">
|
|
{schedule.next_fire_unix !== null ? (
|
|
<>
|
|
next fire:{" "}
|
|
<span title={fmtLocal(schedule.next_fire_unix)}>
|
|
<RelativeTime epochMs={schedule.next_fire_unix * 1000} upcoming />
|
|
</span>
|
|
</>
|
|
) : state === "paused" ? (
|
|
"paused, will not fire"
|
|
) : (
|
|
<>
|
|
time passed{" "}
|
|
<span title={fmtLocal(schedule.anchor_unix)}>
|
|
<RelativeTime epochMs={schedule.anchor_unix * 1000} />
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div class="ui-schedule-card-actions">
|
|
<Button onClick={onEdit} disabled={pending}>
|
|
edit
|
|
</Button>
|
|
<Button onClick={onTogglePaused} disabled={pending}>
|
|
{schedule.paused ? "resume" : "pause"}
|
|
</Button>
|
|
<Button onClick={onRemove} disabled={pending}>
|
|
remove
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|