fix(timetravel): drop arbitrary +30d max — use real marks only
User feedback: «крайняя дата должна быть исходя из реальной в этом словаре». Раньше slider всегда extended до now + 30d даже без причины — если нет scheduled записей на 18.05, slider всё равно тянулся туда, пользователь блуждал по пустоте. Теперь max = max(marks) — last real change point (record validFrom включая upcoming scheduled, которые backend возвращает в /scheduled-summary). Если все marks в прошлом — max=now (always reachable). Если есть future scheduled на 18.05 — slider тянется ровно туда, не дальше. Touches: TimeTravelPicker.tsx (inline editor) + TimeTravelModal.tsx (modal).
This commit is contained in:
@@ -109,25 +109,22 @@ export function TimeTravelModal({
|
|||||||
isAtNow0 ? undefined : { at: new Date(picked).toISOString() },
|
isAtNow0 ? undefined : { at: new Date(picked).toISOString() },
|
||||||
)
|
)
|
||||||
|
|
||||||
// Range: от min(marks, createdAt) до max(now+30d, future marks)+padding.
|
// Range: derived purely from real data — marks (record validFroms,
|
||||||
|
// включая upcoming scheduled, передаются parent'ом), createdAt, now.
|
||||||
|
// No arbitrary future window: max берётся max(marks) — last real change
|
||||||
|
// point. User feedback: «крайняя дата должна быть исходя из реальной в
|
||||||
|
// этом словаре».
|
||||||
//
|
//
|
||||||
// Future support (fix paired с TimeTravelPicker !220): если у dict есть
|
// Если все marks в прошлом — max=now (user всё равно может перейти к now).
|
||||||
// scheduled records (validFrom > now) или user navigated к future date,
|
// Если есть scheduled validFrom > now — max тянется до furthest scheduled.
|
||||||
// slider должен дотягиваться. Раньше max = now + 1day pad — record с
|
|
||||||
// validFrom=18.05 был недостижим если сейчас 15.05.
|
|
||||||
//
|
|
||||||
// Default future window = 30 days, match'ит TimeTravelPicker.tsx logic.
|
|
||||||
const createdMs = Date.parse(detail.createdAt)
|
const createdMs = Date.parse(detail.createdAt)
|
||||||
const range = useMemo(() => {
|
const range = useMemo(() => {
|
||||||
const DAY_MS = 86_400_000
|
const DAY_MS = 86_400_000
|
||||||
const FUTURE_WINDOW_MS = 30 * DAY_MS
|
|
||||||
const minMark = marks.length > 0 ? Math.min(...marks, createdMs) : createdMs - DAY_MS
|
const minMark = marks.length > 0 ? Math.min(...marks, createdMs) : createdMs - DAY_MS
|
||||||
// Push max forward: at least now + 30 days, и further если есть future marks.
|
|
||||||
const maxMark = marks.length > 0 ? Math.max(...marks, now) : now
|
const maxMark = marks.length > 0 ? Math.max(...marks, now) : now
|
||||||
const effectiveMax = Math.max(maxMark, now + FUTURE_WINDOW_MS)
|
const span = maxMark - minMark
|
||||||
const span = effectiveMax - minMark
|
|
||||||
const pad = Math.max(span * 0.04, DAY_MS)
|
const pad = Math.max(span * 0.04, DAY_MS)
|
||||||
return { min: minMark - pad, max: effectiveMax + pad }
|
return { min: minMark - pad, max: maxMark + pad }
|
||||||
}, [marks, createdMs, now])
|
}, [marks, createdMs, now])
|
||||||
|
|
||||||
// Sync picked when value prop changes while modal open.
|
// Sync picked when value prop changes while modal open.
|
||||||
|
|||||||
@@ -39,20 +39,18 @@ export function TimeTravelPicker({
|
|||||||
return Number.isNaN(d.getTime()) ? undefined : d
|
return Number.isNaN(d.getTime()) ? undefined : d
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
// Range: from min(marks, value) − padding до max(now, future marks) + padding.
|
// Range: derived purely from real data — marks (record validFrom timestamps
|
||||||
|
// включая upcoming scheduled), createdAt, и picked value. Никаких arbitrary
|
||||||
|
// future windows: если в словаре нет записей на 18.05 — slider туда не
|
||||||
|
// тянется. User feedback: «крайняя дата должна быть исходя из реальной в
|
||||||
|
// этом словаре».
|
||||||
//
|
//
|
||||||
// Future support: если у dict'а есть records с validFrom > now (scheduled),
|
// Каноничный max берётся max(marks) — это последняя «интересная точка»
|
||||||
// или user navigated к future date (asOf=18.05 для preview new record),
|
// (либо last existing record validFrom, либо furthest scheduled validFrom).
|
||||||
// slider должен дотягиваться до этой даты. Раньше max = now + 1day pad —
|
// Если marks пусты — fallback на now + 1 day pad чтобы slider не вырождался.
|
||||||
// scheduled records на дату дальше 1 день были недоступны через slider
|
|
||||||
// (user не мог время-travel'ить чтобы их увидеть).
|
|
||||||
//
|
|
||||||
// Default future window = 30 days, чтобы планирование на месяц вперёд
|
|
||||||
// (типичный business horizon для NSI updates) было без ручного URL hack.
|
|
||||||
const range = useMemo(() => {
|
const range = useMemo(() => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const DAY_MS = 86_400_000
|
const DAY_MS = 86_400_000
|
||||||
const FUTURE_WINDOW_MS = 30 * DAY_MS
|
|
||||||
|
|
||||||
let minMark = marks.length > 0 ? Math.min(...marks) : now - 365 * DAY_MS
|
let minMark = marks.length > 0 ? Math.min(...marks) : now - 365 * DAY_MS
|
||||||
let maxMark = marks.length > 0 ? Math.max(...marks) : now
|
let maxMark = marks.length > 0 ? Math.max(...marks) : now
|
||||||
@@ -60,8 +58,9 @@ export function TimeTravelPicker({
|
|||||||
minMark = Math.min(minMark, valueDate.getTime())
|
minMark = Math.min(minMark, valueDate.getTime())
|
||||||
maxMark = Math.max(maxMark, valueDate.getTime())
|
maxMark = Math.max(maxMark, valueDate.getTime())
|
||||||
}
|
}
|
||||||
// Push max forward: at least now + 30 days, и further если есть будущие marks.
|
// Ensure max ≥ now чтобы «сейчас» всегда был достижим, even если все
|
||||||
const effectiveMax = Math.max(maxMark, now + FUTURE_WINDOW_MS)
|
// marks в прошлом (закрытый dict).
|
||||||
|
const effectiveMax = Math.max(maxMark, now)
|
||||||
const span = effectiveMax - minMark
|
const span = effectiveMax - minMark
|
||||||
const pad = Math.max(span * 0.05, DAY_MS) // ≥1 день
|
const pad = Math.max(span * 0.05, DAY_MS) // ≥1 день
|
||||||
return { min: minMark - pad, max: effectiveMax + pad }
|
return { min: minMark - pad, max: effectiveMax + pad }
|
||||||
|
|||||||
Reference in New Issue
Block a user