/* 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 };
})();