feat(scada): add manual monitoring timeline
Generic Container CI/CD / test-build-publish (push) Successful in 1m12s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m12s

This commit is contained in:
2026-11-04 14:27:08 +08:00
parent 78481903d7
commit 0801e161ec
22 changed files with 1314 additions and 664 deletions
+10
View File
@@ -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) => {
+6 -3
View File
@@ -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);