From 80703f9caa2a38e76cef80788b8e029a9815f321 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 4 May 2026 03:59:53 +0300 Subject: [PATCH] =?UTF-8?q?fix(admin-ui):=20timezone=20bug=20=D0=B2=20vali?= =?UTF-8?q?dTo/validFrom=20+=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=81=D0=B1=D1=80=D0=BE=D1=81=D0=B0=20DatePicker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: ставил 8 мая, показывало 9. Раньше formatIsoDateTime отдавал 'YYYY-MM-DDTHH:MM:00Z' (UTC). На load new Date(s) парсил UTC и показывал в МСК (UTC+3) → +3 часа → день вперёд. Сейчас: - localTzOffset(d) вычисляет offset из браузера, например '+03:00' - formatIsoDateTime(d, time) собирает Date в local TZ и отдаёт ISO с явным offset: '2026-05-08T23:59:00+03:00' - extractTime(s, fallback) парсит через new Date + getHours/getMinutes, показывая time в local TZ юзера Bug 2: кнопка сброса (X) в DatePicker не очищала validFrom/validTo. combineDateTime(null) возвращал current value вместо ''. Фикс — на null возвращаем пустую строку, value уходит в form state как ''. --- .../src/components/form/SchemaDrivenForm.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx index 700db52..9d460d3 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx @@ -62,21 +62,35 @@ const formatIsoDate = (d: Date): string => { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` } -const formatIsoDateTime = (d: Date, time: string = '00:00'): string => - `${formatIsoDate(d)}T${time}:00Z` +const localTzOffset = (d: Date): string => { + const pad = (n: number) => String(n).padStart(2, '0') + const total = -d.getTimezoneOffset() + const sign = total >= 0 ? '+' : '-' + const abs = Math.abs(total) + return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}` +} + +const formatIsoDateTime = (d: Date, time: string = '00:00'): string => { + const pad = (n: number) => String(n).padStart(2, '0') + const [hh, mm] = time.split(':').map(Number) + const local = new Date(d.getFullYear(), d.getMonth(), d.getDate(), hh || 0, mm || 0, 0) + return `${formatIsoDate(local)}T${pad(local.getHours())}:${pad(local.getMinutes())}:00${localTzOffset(local)}` +} const extractTime = (s: string | undefined, fallback: string): string => { if (!s) return fallback - const m = /T(\d{2}):(\d{2})/.exec(s) - return m ? `${m[1]}:${m[2]}` : fallback + const d = new Date(s) + if (Number.isNaN(d.getTime())) return fallback + const pad = (n: number) => String(n).padStart(2, '0') + return `${pad(d.getHours())}:${pad(d.getMinutes())}` } const combineDateTime = ( d: Date | null, time: string, - current: string, + _current: string, ): string => { - if (!d) return current + if (!d) return '' const safeTime = /^\d{2}:\d{2}$/.test(time) ? time : '00:00' return formatIsoDateTime(d, safeTime) }