Files
dc-observatio/services/pcp-tgu-ops-ui/src/components/timeFields.tsx
T
Дмитрий Соловьев 51fd8a446b pcp-tgu-ops-ui: надёжный 24ч ввод времени (своё текстовое поле вместо нативного)
Нативные input type=time/datetime-local рендерят время по локали браузера/ОС
(AM/PM) и игнорируют атрибут lang в Chrome — поэтому lang=ru-RU не помогал.
Заменено на собственные контролы (components/timeFields.tsx):
- Time24Field — текстовый ввод ЧЧ:ММ[:СС] с валидацией, детерминированный 24ч;
- DateTime24Field — нативный календарь для даты + Time24Field для времени.
Подключено в окне маршрута редактора и во всех datetime-local фильтрах
(Планы, заявки на карте, создание/фильтр заявок).
2026-06-03 09:02:27 +03:00

95 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState, type CSSProperties } from "react";
/**
* Ввод времени суток в строгом 24-часовом формате `ЧЧ:ММ[:СС]` (UTC).
*
* Нативные `<input type="time">`/`type="datetime-local"` рендерят время по локали
* браузера/ОС (часто AM/PM) и не подчиняются атрибуту `lang`, поэтому время вводим
* обычным текстовым полем с собственной валидацией. Локальный черновик позволяет
* печатать промежуточные значения; наружу отдаём только корректное `ЧЧ:ММ[:СС]`.
*/
type Time24FieldProps = {
/** Текущее значение в формате `ЧЧ:ММ` или `ЧЧ:ММ:СС`. */
value: string;
/** Показывать и принимать секунды. */
withSeconds?: boolean;
/** Вызывается при вводе корректного значения. */
onChange: (value: string) => void;
className?: string;
style?: CSSProperties;
"aria-label"?: string;
};
const TIME_RE_HM = /^([01]\d|2[0-3]):[0-5]\d$/;
const TIME_RE_HMS = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/;
export function Time24Field({ value, withSeconds = false, onChange, className, style, ...rest }: Time24FieldProps) {
const re = withSeconds ? TIME_RE_HMS : TIME_RE_HM;
const [draft, setDraft] = useState(value);
const [editing, setEditing] = useState(false);
// Пока поле не редактируется — синхронизируем отображение с внешним значением.
useEffect(() => {
if (!editing) setDraft(value);
}, [value, editing]);
return (
<input
{...rest}
type="text"
inputMode="numeric"
className={className}
style={style}
placeholder={withSeconds ? "чч:мм:сс" : "чч:мм"}
value={draft}
onFocus={() => setEditing(true)}
onChange={(event) => {
const next = event.target.value;
setDraft(next);
if (re.test(next)) onChange(next);
}}
onBlur={() => {
setEditing(false);
if (re.test(draft)) onChange(draft);
else setDraft(value); // невалидный ввод — откат к последнему корректному
}}
/>
);
}
/**
* Замена нативного `<input type="datetime-local">`: нативный календарь для даты
* (у даты нет проблемы AM/PM) + [Time24Field] для времени в 24-часовом формате.
* Контракт значения тот же — строка `YYYY-MM-DDTHH:mm` (UTC), как у datetime-local.
*/
type DateTime24FieldProps = {
value: string;
onChange: (value: string) => void;
className?: string;
};
export function DateTime24Field({ value, onChange, className }: DateTime24FieldProps) {
const sep = value.indexOf("T");
const datePart = sep >= 0 ? value.slice(0, sep) : value;
const timePart = sep >= 0 ? value.slice(sep + 1, sep + 6) : "";
const combine = (date: string, time: string) => `${date}T${time || "00:00"}`;
return (
<span className={`tgu-dt24${className ? ` ${className}` : ""}`} style={{ display: "flex", alignItems: "center", gap: "0.3rem" }}>
<input
type="date"
lang="ru-RU"
style={{ width: "8rem" }}
value={datePart}
onChange={(event) => onChange(combine(event.target.value, timePart))}
/>
<Time24Field
style={{ width: "4.6rem", textAlign: "center" }}
value={timePart}
onChange={(time) => onChange(combine(datePart, time))}
/>
</span>
);
}