/* eslint-disable */
/* timing-print.jsx — печатный A4-документ + библиотека + сводка. */
(function () {
const R = window.React;
const { useState } = R;
const D = window.TimingData;
/* ============ Библиотека блоков (drag-источник) ============ */
function Library({ dragInfo, onAdd, onCollapse }) {
const groups = D.TYPE_ORDER.map(ty => ({
ty, label: D.TYPES[ty].label, color: D.TYPES[ty].color,
blocks: D.LIBRARY.filter(b => b.y === ty),
}));
return (
Библиотека блоков
{onCollapse && (
)}
Перетащите в тайминг или нажмите, чтобы добавить в конец
{groups.map(g => (
{g.label}
{g.blocks.map((b, i) => (
{ dragInfo.current = { type: 'lib', block: b }; }}
onDragEnd={() => { dragInfo.current = null; }}
onClick={() => onAdd(b)}
style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '8px 10px', background: 'var(--color-surface)',
border: '1px solid var(--color-line-subtle)', borderRadius: 'var(--radius-card)',
cursor: 'grab', userSelect: 'none',
}}>
{b.t}
{b.d}м
))}
))}
);
}
/* ============ Сводка / инспектор ============ */
function Summary({ doc, phase, computed, span, onMeta, onPhaseStart, days }) {
const m = doc.meta;
const rows = computed.filter(c => c.kind === 'row');
const hasDays = days && days.length > 1;
const byType = {};
rows.forEach(r => { byType[r.type] = (byType[r.type] || 0) + r.dur; });
const totalDur = rows.reduce((s, r) => s + r.dur, 0) || 1;
const overlaps = rows.filter(r => r._overlap > 0);
const phaseStart = phase === 'prep' ? doc.prepStart : doc.eventStart;
const field = (label, key) => (
onMeta(key, e.target.value)} style={{
border: '1px solid var(--color-line-subtle)', background: 'var(--color-canvas)', borderRadius: 'var(--radius-input)',
padding: '6px 8px', fontSize: 12.5, color: 'var(--color-ink-900)', outline: 'none',
}} />
);
return (
{/* Totals */}
{hasDays ? (
Подготовка и монтаж
{days.length}
{days.length < 5 ? 'дня' : 'дней'}
{D.dur(days.reduce((s, d) => s + d.dur, 0))} работы · {rows.length} блоков
) : (
{phase === 'prep' ? 'Подготовка и монтаж' : 'Мероприятие'}
{D.fmt(span.start)}
{D.fmt(span.end)}
{D.dur(span.total)} · {rows.length} блоков
)}
{/* Phase start */}
Старт {phase === 'prep' ? 'подготовки' : 'мероприятия'}
якорь для авторасчёта
onPhaseStart(D.parse(e.target.value))} style={{
width: 64, border: '1px solid var(--color-line-subtle)', background: 'var(--color-canvas)', borderRadius: 'var(--radius-input)',
padding: '6px 8px', fontFamily: 'var(--font-mono)', fontSize: 15, fontWeight: 600, textAlign: 'center',
color: 'var(--color-ink-900)', fontVariantNumeric: 'tabular-nums', outline: 'none',
}} />
{hasDays && (
Дни подготовки
{days.map((d, i) => (
{i + 1}
{d.label}
{D.fmt(d.start)}–{D.fmt(d.end)}
{D.dur(d.dur)}
))}
)}
{/* Type breakdown */}
Распределение времени
{D.TYPE_ORDER.filter(ty => byType[ty]).map(ty => (
{D.TYPES[ty].label}
{D.dur(byType[ty])}
))}
{!rows.length &&
Нет блоков
}
{/* Warnings */}
{overlaps.length > 0 && (
Накладки по времени
{overlaps.map(o => (
«{o.title || 'Блок'}» начинается на {D.dur(o._overlap)} раньше окончания предыдущего
))}
)}
{/* Event meta */}
Шапка документа
{field('Мероприятие', 'event')}
{field('Дата', 'date')}
{field('Площадка', 'venue')}
{field('Масштаб', 'guests')}
{field('Код', 'code')}
{field('Менеджер (PM)', 'pm')}
);
}
/* ============ Сводка для сетки мероприятия ============ */
function GridSummary({ doc, grid, span, onMeta }) {
const m = doc.meta;
const field = (label, key) => (
onMeta(key, e.target.value)} style={{
border: '1px solid var(--color-line-subtle)', background: 'var(--color-canvas)', borderRadius: 'var(--radius-input)',
padding: '6px 8px', fontSize: 12.5, color: 'var(--color-ink-900)', outline: 'none',
}} />
);
return (
Мероприятие
{D.fmt(span.start)}
{D.fmt(span.end)}
{D.dur(span.total)} · {grid.slots.length} слотов · {grid.cards.length} карточек
Колонки (треки)
{grid.tracks.map(t => {
const n = grid.cards.filter(c => c.trackId === t.id).length;
return (
{t.name}
{n}
);
})}
{!grid.tracks.length &&
Нет колонок
}
Как пользоваться
Карточки тащите между ячейками. Блоки из библиотеки можно бросать прямо в ячейку. «+» в ячейке — новая карточка.
Шапка документа
{field('Мероприятие', 'event')}
{field('Дата', 'date')}
{field('Площадка', 'venue')}
{field('Масштаб', 'guests')}
{field('Код', 'code')}
{field('Менеджер (PM)', 'pm')}
);
}
/* ============ Печатный документ A4 ============ */
function PrintTiming({ title, range, items, phaseStart }) {
const computed = D.computeTimes(items, phaseStart);
let dayNum = 0;
return (
{title}
{range}
{computed.map(it => {
if (it.kind === 'day') {
dayNum++;
return (
День {dayNum}
{it.date && {it.date}}
старт {D.fmt(it.start)}
);
}
if (it.kind === 'section') {
return
{it.title}
;
}
const t = D.TYPES[it.type];
return (
{D.fmt(it._start)}–{D.fmt(it._end)}
{D.dur(it.dur)}
{t.label}
{it.title}
{it.who && {it.who}}
{it.zone && {it.zone}}
{it.note && {it.note}}
);
})}
);
}
/* ============ Печатная сетка мероприятия ============ */
function PrintGrid({ title, range, grid }) {
const slots = grid.slots.slice().sort((a, b) => a.time - b.time);
const cols = '46px repeat(' + grid.tracks.length + ', 1fr)';
return (
{title}
{range}
Время
{grid.tracks.map(t => (
{t.name}
))}
{slots.map((slot, i) => {
const next = slots[i + 1];
const delta = next ? next.time - slot.time : 0;
return (
{D.fmt(slot.time)}
{delta > 0 && +{D.dur(delta)}}
{grid.tracks.map(t => {
const cards = grid.cards.filter(c => c.trackId === t.id && c.slotId === slot.id);
return (
{cards.map(c => (
{c.title}
{(c.dur || c.who) &&
{[c.dur ? D.dur(c.dur) : null, c.who || null].filter(Boolean).join(' · ')}
}
))}
);
})}
);
})}
);
}
function PrintSheet({ tag, m, today, pageStyle, first, children }) {
return (
{tag}
{m.code || '—'} · напечатано {today}
{m.event || 'Мероприятие'}
{[m.date, m.venue, m.guests].filter(Boolean).join(' · ')}
{m.pm && PM: {m.pm}}
{children}
);
}
function PrintDoc({ doc, pageStyle }) {
const m = doc.meta;
const pDays = D.dayBlocks(doc.prep, doc.prepStart);
const hasDays = doc.prep.some(it => it.kind === 'day');
const prepSpan = D.phaseSpan(D.computeTimes(doc.prep, doc.prepStart), doc.prepStart);
const evSpan = D.gridSpan(doc.eventGrid);
const today = new Date().toLocaleDateString('ru-RU', { day: '2-digit', month: 'short', year: 'numeric' });
const prepRange = hasDays
? pDays.length + ' дн. · ' + D.dur(pDays.reduce((s, d) => s + d.dur, 0)) + ' работы'
: D.fmt(prepSpan.start) + '–' + D.fmt(prepSpan.end) + ' · ' + D.dur(prepSpan.total);
return (
Типы работ:
{D.TYPE_ORDER.map(ty => (
{D.TYPES[ty].label}
))}
);
}
window.TimingPrint = { Library, Summary, GridSummary, PrintDoc };
})();