fix: schedule card placement, preview state, and scheduled-workflow docs

This commit is contained in:
2026-08-04 17:08:11 +01:00
parent b9802e6b04
commit d9184312aa
4 changed files with 110 additions and 12 deletions
@@ -54,7 +54,7 @@ export function EditWorkflowModal({ open, workflow, onSaved, onClose }: { open:
};
return (
<Modal open={open} onClose={onClose} title="Edit workflow">
<Modal open={open} onClose={onClose} title="Edit workflow" wide>
<div className="space-y-4">
{error && <div className="rounded border border-danger/30 bg-danger/10 px-3 py-2 text-sm text-danger">{error}</div>}
<div>
@@ -79,7 +79,12 @@ export function EditWorkflowModal({ open, workflow, onSaved, onClose }: { open:
{servers && servers.length === 0 && <p className="text-xs text-text-secondary">No servers registered.</p>}
</div>
</div>
<div className="flex items-center justify-between pt-2">
{/* The schedule saves through its own endpoint, so it sits above
the footer rather than under it — the footer's Save covers the
name and targets only, and the two are labelled accordingly. */}
<ScheduleCard workflow={workflow} />
<div className="flex items-center justify-between border-t border-border-soft pt-4">
<Button variant="danger" onClick={del} loading={busy}>
Delete workflow
</Button>
@@ -88,11 +93,10 @@ export function EditWorkflowModal({ open, workflow, onSaved, onClose }: { open:
Cancel
</Button>
<Button variant="primary" onClick={save} loading={busy} disabled={!name.trim()}>
Save
Save workflow
</Button>
</div>
</div>
<ScheduleCard workflow={workflow} />
</div>
</Modal>
);
+18 -8
View File
@@ -28,7 +28,10 @@ export function ScheduleCard({ workflow }: { workflow: Workflow }) {
const [tz, setTz] = useState(workflow.schedule?.tz ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC");
const [error, setError] = useState<string | null>(null);
const { data: preview } = useQuery({
// isError, not !preview: an in-flight query and a rejected expression both
// leave data undefined, so keying the invalid message off the data alone
// flashes "not valid" at every keystroke on a perfectly good cron string.
const { data: preview, isError: previewFailed } = useQuery({
queryKey: ["schedule-preview", workflow.workflow_id, cron, tz],
queryFn: () => api.previewSchedule(workflow.workflow_id, cron, tz),
retry: false,
@@ -44,13 +47,16 @@ export function ScheduleCard({ workflow }: { workflow: Workflow }) {
});
return (
<div className="rounded-lg border border-border bg-surface">
<div className="flex items-baseline justify-between gap-3 border-b border-border-soft px-5 py-3.5">
<h2 className="text-[15px] font-semibold text-text-primary">Schedule</h2>
// No panel chrome: this renders inside a Modal that already supplies the
// border, the background and a title bar, and nesting a second card in
// one produced a box inside a box.
<div className="border-t border-border-soft pt-4">
<div className="flex items-baseline justify-between gap-3">
<h3 className="text-xs font-semibold uppercase tracking-wide text-text-secondary">Schedule</h3>
<span className="font-mono text-[10px] uppercase tracking-[0.16em] text-text-tertiary">{enabled ? "Active" : "Off"}</span>
</div>
<div className="flex flex-col gap-4 p-5">
<div className="flex flex-col gap-4 pt-4">
<label className="flex items-start gap-3">
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} className="mt-0.5 h-4 w-4 accent-accent" />
<span>
@@ -103,14 +109,16 @@ export function ScheduleCard({ workflow }: { workflow: Workflow }) {
<div className="rounded-lg bg-well px-4 py-3">
<p className="font-mono text-[10px] uppercase tracking-[0.16em] text-text-tertiary">Next three runs</p>
{preview ? (
{previewFailed ? (
<p className="mt-1.5 font-mono text-[11.5px] text-danger">That expression is not valid.</p>
) : preview ? (
<ul className="mt-1.5 flex flex-col gap-0.5 font-mono text-[11.5px] text-text-secondary">
{preview.occurrences.map((o) => (
<li key={o}>{new Date(o).toLocaleString()}</li>
))}
</ul>
) : (
<p className="mt-1.5 font-mono text-[11.5px] text-danger">That expression is not valid.</p>
<p className="mt-1.5 font-mono text-[11.5px] text-text-tertiary">Working it out</p>
)}
</div>
@@ -128,7 +136,9 @@ export function ScheduleCard({ workflow }: { workflow: Workflow }) {
{error && <p className="text-sm text-danger">{error}</p>}
<div>
<Button variant="primary" loading={isPending} onClick={() => save()}>
{/* Gated on the preview: the server has already rejected this
expression once, and submitting it only earns the same 400. */}
<Button variant="primary" loading={isPending} disabled={previewFailed} onClick={() => save()}>
Save schedule
</Button>
</div>