feat(ui): 3-dot row menu + history compare/revert + DateTime fitting + tablet touch

Per user feedback screenshots:

SchemaDrivenForm DateTimeField:
- grid → flex flex-wrap: на узких cells (drawer 520px ÷ 2 cols = ~210px)
  time wraps под date вертикально. На wide — inline.
- Date min-w 8rem (128px), time w-24 (96px) — tablet touch-safe.

TableHeaderCell без visible label (sr-only для screen readers).
TableCell содержит DropdownMenu trigger 36×36px (touch target):
  - History (always)
  - Edit, Separator, Close (только canMutate, Close в danger color)
Заменяет 3 inline IconButton'a — экономит ширину + tablet friendly.

RecordHistoryDrawer accepts onCompare / onRevert callbacks. Каждая
non-current version row показывает:
  - Сравнить с текущим — opens ConflictDiffModal (reused diff UI)
  - Откатить к v{N} — submit updateMut с этой version данными как
    payload (+ confirm dialog). Backend создаёт новую active version,
    bitemporal history preserves старые.
JSON viewer переключился с <details> на state-driven (hide/show
без CSS rabbit hole).

history.hideData/compare/revert/revertConfirm (RU + EN).

- 3-dot trigger 36px = comfortable touch target (Apple HIG 44, ours
  acceptable для density)
- Drawer на <880px viewport = fullscreen sheet (already)
- DateTime wraps на cell <8rem+9rem+gap = 290px
- Sidebar collapse на <1024 = hamburger overlay
This commit is contained in:
Zimin A.N.
2026-05-12 00:01:04 +03:00
parent 11797f88ae
commit b312ba37ad
4 changed files with 169 additions and 40 deletions
@@ -802,15 +802,21 @@ type DateTimeFieldProps = {
const DateTimeField = ({ label, value, defaultTime, onChange, hint, error }: DateTimeFieldProps) => {
const dateValue = parseFormDate(value)
const timeValue = extractTime(value, defaultTime)
// flex-wrap вместо fixed grid: на узких ячейках (drawer 520px ÷ 2 cols =
// ~210px) time wraps под date. На wide (880px ÷ 2 = ~410px) — inline.
// min-w на date обеспечивает разумный размер до wrap'a. Время 6rem (96px)
// достаточно для HH:mm + tap target 44px touch-safe.
return (
<div>
<div className="grid grid-cols-[1fr_auto] gap-2 items-end">
<DatePicker
label={label}
value={dateValue}
onChange={(d) => onChange(combineDateTime(d, timeValue, value ?? ''))}
/>
<div className="w-28">
<div className="flex flex-wrap gap-2 items-end">
<div className="flex-1 min-w-[8rem]">
<DatePicker
label={label}
value={dateValue}
onChange={(d) => onChange(combineDateTime(d, timeValue, value ?? ''))}
/>
</div>
<div className="w-24 shrink-0">
<TextInput
label="HH:mm"
type="time"