"use client"; import { useActionState, useState } from "react"; import { setChannelCadencesAction, type SaveResult } from "../../../scheduler/actions"; import { DurationField } from "../../../components/DurationField"; import { FULL_SWEEP_PRESETS, SYNC_INTERVAL_PRESETS, } from "../../../scheduler/intervalPresets"; import { formatCadence } from "./cadence"; // Per-row cadence cell for the scheduler table. Collapsed to two read-only // chips by default — this page's job is to show every channel's cadence at // once, and two full editors per row would bury that — expanding to the pair of // editors on click. // // Holds its own state (seeded once from props) so the page's 5s status poll // can't clobber an in-progress edit: the poll replaces the whole channels array // and re-renders every row, but rows are keyed by slug so this is re-rendered, // never remounted, and React discards the recomputed initial value. There must // be no prop -> state effect here. scheduler.spec.ts guards exactly this. export function ChannelCadenceEditor({ slug, configuredInterval, configuredFullSweep, defaultIntervalMinutes, defaultFullSweepMinutes, }: { slug: string; configuredInterval: number | undefined; configuredFullSweep: number | undefined; defaultIntervalMinutes: number; defaultFullSweepMinutes: number; }) { const [open, setOpen] = useState(false); const [state, formAction, pending] = useActionState< SaveResult | undefined, FormData >(setChannelCadencesAction.bind(null, [slug]), undefined); if (!open) { return ( ); } return (
{/* Distinct label prefix on purpose: a second control matching /Auto-sync interval/ would make the existing row-scoped getByLabel queries strict-mode-ambiguous. */} {/* One Save for both cadences — a second per-row submit would make getByRole("button", { name: "Save" }) ambiguous inside the row. */}
{state?.ok === true && ( Saved )} {state?.ok === false && ( {state.error} )}
); } function Chip({ label, value }: { label: string; value: string }) { return ( {label} {value} ); }