feat(scada): add manual monitoring timeline
This commit is contained in:
@@ -33,6 +33,16 @@ describe("DateTimeField", () => {
|
||||
expect(onChange).toHaveBeenCalledWith("2024-02-29T09:30");
|
||||
});
|
||||
|
||||
it("limits minute choices to the configured step", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<DateTimeField label="演示开始时间" value="2024-02-28T09:25" onChange={vi.fn()} minuteStep={5} />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "演示开始时间" }));
|
||||
await user.click(screen.getByRole("combobox", { name: "分钟" }));
|
||||
expect(screen.getByRole("option", { name: "25" })).toBeVisible();
|
||||
expect(screen.queryByRole("option", { name: "26" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(["2025-02-29T12:00", "2024-04-31T12:00", "2024-02-29T12:00:30"])(
|
||||
"rejects invalid or unexpected source value %s",
|
||||
async (value) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface DateTimeFieldProps {
|
||||
onChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
contentClassName?: string;
|
||||
minuteStep?: number;
|
||||
}
|
||||
|
||||
interface DateTimeParts {
|
||||
@@ -122,7 +123,9 @@ function moveDay(parts: DateTimeParts, amount: number): DateTimeParts {
|
||||
return { ...parts, year, month: date.getMonth() + 1, day: date.getDate() };
|
||||
}
|
||||
|
||||
export function DateTimeField({ label, value, onChange, disabled = false, contentClassName }: DateTimeFieldProps) {
|
||||
export function DateTimeField({ label, value, onChange, disabled = false, contentClassName, minuteStep = 1 }: DateTimeFieldProps) {
|
||||
const safeMinuteStep = Number.isInteger(minuteStep) && minuteStep > 0 && 60 % minuteStep === 0 ? minuteStep : 1;
|
||||
const minuteOptions = MINUTE_OPTIONS.filter((_, minute) => minute % safeMinuteStep === 0);
|
||||
const id = useId();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [draft, setDraft] = useState<DateTimeParts>(
|
||||
@@ -133,7 +136,7 @@ export function DateTimeField({ label, value, onChange, disabled = false, conten
|
||||
const selectedDayRef = useRef<HTMLButtonElement>(null);
|
||||
const focusDayAfterNavigation = useRef(false);
|
||||
const validYear = /^\d{1,4}$/.test(yearText) && Number(yearText) >= 1;
|
||||
const canConfirm = !disabled && !sourceInvalid && validYear && isValidParts(draft);
|
||||
const canConfirm = !disabled && !sourceInvalid && validYear && isValidParts(draft) && draft.minute % safeMinuteStep === 0;
|
||||
|
||||
const resetDraft = (nextValue: string) => {
|
||||
const parsed = parseDateTimeValue(nextValue);
|
||||
@@ -364,7 +367,7 @@ export function DateTimeField({ label, value, onChange, disabled = false, conten
|
||||
contentClassName={contentClassName}
|
||||
label="分钟"
|
||||
value={pad(draft.minute)}
|
||||
options={MINUTE_OPTIONS}
|
||||
options={minuteOptions}
|
||||
onValueChange={(minute) => {
|
||||
setDraft({ ...draft, minute: Number(minute) });
|
||||
setSourceInvalid(false);
|
||||
|
||||
Reference in New Issue
Block a user