/* eslint-disable */ /* timing-builder.jsx — интерактивный конструктор тайминга. */ (function () { const R = window.React; const { useState, useRef, useEffect, useMemo } = R; const D = window.TimingData; const STORE_KEY = 'ezc.timing.doc.v1'; /* ============ Inline-редактируемый текст ============ */ function EditText({ value, onChange, placeholder, mono, weight, size, color, align, width, flex, onEnter, autoFocus }) { return ( onChange(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && onEnter) { e.preventDefault(); onEnter(); } }} style={{ width: width || '100%', flex: flex, border: '1px solid transparent', background: 'transparent', borderRadius: 5, padding: '3px 6px', margin: '-3px -6px', fontFamily: mono ? 'var(--font-mono)' : 'inherit', fontWeight: weight || 400, fontSize: size || 13, color: color || 'var(--color-ink-900)', textAlign: align || 'left', fontVariantNumeric: mono ? 'tabular-nums' : 'normal', outline: 'none', }} onFocus={e => { e.target.style.background = 'var(--color-surface)'; e.target.style.borderColor = 'var(--color-line-strong)'; e.target.style.boxShadow = 'var(--ring)'; }} onBlur={e => { e.target.style.background = 'transparent'; e.target.style.borderColor = 'transparent'; e.target.style.boxShadow = 'none'; }} /> ); } /* ============ Селектор типа (цветная метка) ============ */ function TypeSelect({ value, onChange }) { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { if (!open) return; const h = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, [open]); const t = D.TYPES[value]; return (
{open && (
{D.TYPE_ORDER.map(k => ( ))}
)}
); } /* ============ Строка тайминга ============ */ function TimingRow({ item, idx, onPatch, onDelete, onDup, onGrab, grabbed, dragOver, onAddAfter, autoFocusId }) { const t = D.TYPES[item.type]; const [hover, setHover] = useState(false); return (
onGrab(null)} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: 'relative', display: 'grid', gridTemplateColumns: '20px 78px 4px 1fr 64px', gap: 10, alignItems: 'stretch', padding: '10px 12px 10px 6px', background: grabbed ? 'var(--color-muted-50)' : 'var(--color-surface)', borderBottom: '1px solid var(--color-line-subtle)', opacity: grabbed ? 0.5 : 1, }}> {/* drag handle */} {/* time rail */}
onPatch({ pin: D.parse(e.target.value) })} style={{ width: 50, border: '1px solid transparent', background: 'transparent', borderRadius: 4, padding: '1px 3px', fontFamily: 'var(--font-mono)', fontSize: 14, fontWeight: 600, color: 'var(--color-ink-900)', fontVariantNumeric: 'tabular-nums', outline: 'none', }} onFocus={e => { e.target.style.background = 'var(--color-surface)'; e.target.style.borderColor = 'var(--color-line-strong)'; }} onBlur={e => { e.target.style.background = 'transparent'; e.target.style.borderColor = 'transparent'; }} /> {item.pin != null && ( )}
onPatch({ dur: Math.max(0, parseInt(e.target.value || '0', 10)) })} style={{ width: 26, border: '1px solid transparent', background: 'transparent', borderRadius: 4, padding: '0 2px', fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--color-ink-500)', fontVariantNumeric: 'tabular-nums', outline: 'none', textAlign: 'right', }} onFocus={e => { e.target.style.background = 'var(--color-surface)'; e.target.style.borderColor = 'var(--color-line-strong)'; }} onBlur={e => { e.target.style.background = 'transparent'; e.target.style.borderColor = 'transparent'; }} /> мин
{/* color bar */}
{/* main */}
onPatch({ type: v })} /> onPatch({ title: v })} placeholder="Действие…" weight={500} size={13.5} flex={1} onEnter={onAddAfter ? () => onAddAfter(item.id) : undefined} autoFocus={autoFocusId === item.id} />
{item.who ? : } onPatch({ who: v })} placeholder="Ответственный" size={12} color="var(--color-ink-700)" width={150} />
onPatch({ zone: v })} placeholder="Зона" size={12} color="var(--color-ink-500)" width={120} />
onPatch({ note: v })} placeholder="Заметка" size={12} color="var(--color-ink-500)" flex={1} />
{/* actions */}
{dragOver === 'before' && } {dragOver === 'after' && }
); } const actBtn = { width: 26, height: 26, borderRadius: 5, border: 0, background: 'transparent', color: 'var(--color-ink-400)', display: 'flex', alignItems: 'center', justifyContent: 'center', }; function DropLine({ pos }) { return
; } /* ============ Заголовок раздела ============ */ function SectionRow({ item, onPatch, onDelete, onGrab, grabbed, dragOver, collapsed, onToggle, subtotal }) { const [hover, setHover] = useState(false); return (
onGrab(null)} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: 'relative', display: 'grid', gridTemplateColumns: '20px 1fr auto 64px', gap: 10, alignItems: 'center', padding: '12px 12px 8px 6px', background: 'var(--color-canvas)', borderBottom: '1px solid var(--color-line-subtle)', borderTop: '1px solid var(--color-line-subtle)', opacity: grabbed ? 0.5 : 1, }}>
onPatch({ title: e.target.value })} placeholder="Раздел…" style={{ flex: 1, minWidth: 0, border: '1px solid transparent', background: 'transparent', borderRadius: 5, padding: '2px 6px', margin: '-2px -2px', fontSize: 11.5, fontWeight: 600, letterSpacing: '.07em', textTransform: 'uppercase', color: 'var(--color-ink-500)', outline: 'none', }} onFocus={e => { e.target.style.background = 'var(--color-surface)'; e.target.style.borderColor = 'var(--color-line-strong)'; }} onBlur={e => { e.target.style.background = 'transparent'; e.target.style.borderColor = 'transparent'; }} />
{subtotal && subtotal.count > 0 ? {D.dur(subtotal.dur)} · {subtotal.count} : }
{dragOver === 'before' && } {dragOver === 'after' && }
); } /* ============ Разделитель дня ============ */ function DayRow({ item, onPatch, onDelete, onGrab, grabbed, dragOver, collapsed, onToggle, subtotal, dayNum }) { const [hover, setHover] = useState(false); const focusOn = e => { e.target.style.background = 'var(--color-surface)'; e.target.style.borderColor = 'var(--color-line-strong)'; }; const focusOff = e => { e.target.style.background = 'transparent'; e.target.style.borderColor = 'transparent'; }; return (
onGrab(null)} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: 'relative', display: 'grid', gridTemplateColumns: '20px auto 1fr auto auto 32px', gap: 10, alignItems: 'center', padding: '13px 12px 13px 6px', background: 'var(--color-muted-50)', borderTop: '1px solid var(--color-line-strong)', borderBottom: '1px solid var(--color-line-strong)', opacity: grabbed ? 0.5 : 1, }}>
День {dayNum}
onPatch({ date: e.target.value })} placeholder="Дата — напр. 10 июня, пн" style={{ minWidth: 0, border: '1px solid transparent', background: 'transparent', borderRadius: 5, padding: '3px 6px', margin: '-3px -6px', fontSize: 13, fontWeight: 600, color: 'var(--color-ink-900)', outline: 'none' }} onFocus={focusOn} onBlur={focusOff} />
старт onPatch({ start: D.parse(e.target.value) })} style={{ width: 52, border: '1px solid var(--color-line-subtle)', background: 'var(--color-surface)', borderRadius: 'var(--radius-input)', padding: '3px 6px', fontFamily: 'var(--font-mono)', fontSize: 13, fontWeight: 600, textAlign: 'center', color: 'var(--color-ink-900)', fontVariantNumeric: 'tabular-nums', outline: 'none' }} />
{subtotal && subtotal.count > 0 ? {D.dur(subtotal.dur)} · {subtotal.count} : }
{dragOver === 'before' && } {dragOver === 'after' && }
); } /* ============ Пауза между блоками ============ */ function GapBar({ minutes }) { return (
пауза {D.dur(minutes)}
); } window.TimingBuilderParts = { EditText, TypeSelect, TimingRow, SectionRow, DayRow, GapBar, DropLine, actBtn }; })();