fix: schedule card placement, preview state, and scheduled-workflow docs
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user