/* eslint-disable */ /* timing-data.jsx — типы активностей, библиотека блоков, шаблоны, хелперы. */ (function () { /* ---------- Время ---------- */ function pad(n) { return n < 10 ? '0' + n : '' + n; } function fmt(min) { min = ((min % 1440) + 1440) % 1440; return pad(Math.floor(min / 60)) + ':' + pad(min % 60); } function parse(str) { if (str == null) return null; const m = String(str).match(/(\d{1,2})\D*(\d{0,2})/); if (!m) return null; let h = Math.min(47, parseInt(m[1] || '0', 10)); let mi = Math.min(59, parseInt(m[2] || '0', 10)); return h * 60 + mi; } function dur(min) { if (min <= 0) return '0м'; const h = Math.floor(min / 60), m = min % 60; return (h ? h + 'ч' : '') + (m ? ' ' + m + 'м' : '') ; } let _id = 0; function uid() { return 'it' + (++_id) + '_' + Math.random().toString(36).slice(2, 6); } /* ---------- Типы активностей (цвет = метка, не единственный сигнал) ---------- */ const TYPES = { logi: { label: 'Логистика', color: 'var(--color-ink-400)' }, setup: { label: 'Монтаж', color: 'var(--color-info-500)' }, tech: { label: 'Техника', color: 'var(--color-accent-500)' }, reh: { label: 'Репетиция', color: 'var(--color-warning-500)' }, program: { label: 'Программа', color: 'var(--color-success-500)' }, catering: { label: 'Кейтеринг', color: 'var(--color-danger-500)' }, }; const TYPE_ORDER = ['logi', 'setup', 'tech', 'reh', 'program', 'catering']; /* ---------- Библиотека блоков (drag-источник) ---------- */ const LIBRARY = [ { y: 'logi', t: 'Разгрузка оборудования', d: 45, z: 'Парковка / вход' }, { y: 'logi', t: 'Завоз декора и мебели', d: 30, z: 'Служебный вход' }, { y: 'logi', t: 'Финальная проверка площадки', d: 30, z: 'Везде' }, { y: 'setup', t: 'Сборка сцены и подиума', d: 90, z: 'Главный зал' }, { y: 'setup', t: 'Брендинг и навигация', d: 60, z: 'Холл' }, { y: 'setup', t: 'Флористика и фотозона', d: 45, z: 'Холл' }, { y: 'tech', t: 'Монтаж звука и света', d: 60, z: 'Сцена' }, { y: 'tech', t: 'Видеостена и контент', d: 45, z: 'Сцена' }, { y: 'tech', t: 'Саундчек и настройка', d: 30, z: 'Сцена' }, { y: 'reh', t: 'Прогон с ведущим', d: 45, z: 'Сцена' }, { y: 'reh', t: 'Репетиция спикеров', d: 30, z: 'Сцена' }, { y: 'program', t: 'Регистрация гостей', d: 45, z: 'Холл' }, { y: 'program', t: 'Приветственное слово', d: 10, z: 'Сцена' }, { y: 'program', t: 'Выступление спикера', d: 15, z: 'Сцена' }, { y: 'program', t: 'Нетворкинг', d: 60, z: 'Лаунж' }, { y: 'catering', t: 'Расстановка фуршета', d: 30, z: 'Лаунж' }, { y: 'catering', t: 'Welcome-коктейль', d: 15, z: 'Лаунж' }, { y: 'catering', t: 'Фуршет', d: 90, z: 'Лаунж' }, ]; /* ---------- Шаблоны ---------- */ function S(t) { return { k: 's', t }; } function R(y, t, d, w, z, opt) { return Object.assign({ k: 'r', y, t, d, w: w || '', z: z || '' }, opt || {}); } function Dn(date, start) { return { k: 'd', date, start: parse(start) }; } const TEMPLATES = { opening: { name: 'Открытие / презентация', hint: 'Открытие шоурума, запуск продукта, перерезание ленты', meta: { event: 'Открытие шоурума IkonTyres', date: '12 июня 2026', venue: 'Ташкент, ул. Амира Темура 15', guests: '200 гостей', code: '26UZ029', pm: 'Эрбол Рахимов' }, prepStart: 480, eventStart: 960, prep: [ S('Завоз и логистика'), R('logi', 'Разгрузка оборудования и декора', 60, 'Бригада монтажа', 'Парковка / вход'), R('setup', 'Сборка сцены и подиума', 90, 'Подрядчик «СтройСцена»', 'Главный зал'), S('Техника'), R('tech', 'Монтаж звука и света', 60, 'Тех-бригада', 'Сцена'), R('tech', 'Видеостена и брендированный контент', 45, 'Видео-инженер', 'Сцена'), R('tech', 'Саундчек и настройка микрофонов', 30, 'Звукорежиссёр', 'Сцена'), S('Оформление и репетиция'), R('setup', 'Брендинг, флористика, фотозона', 75, 'Декор-команда', 'Холл'), R('reh', 'Прогон с ведущим и спикерами', 45, 'Режиссёр', 'Сцена'), R('catering', 'Расстановка фуршета и бара', 30, 'Кейтеринг «Вкус»', 'Лаунж'), R('logi', 'Финальная проверка и клининг', 45, 'Менеджер площадки', 'Везде'), ], event: [ S('Сбор гостей'), R('program', 'Регистрация и welcome-зона', 45, 'Хостес · 3 чел', 'Холл', { p: 960 }), R('catering', 'Приветственный коктейль', 15, 'Кейтеринг', 'Лаунж'), S('Официальная часть'), R('program', 'Открытие, слово ведущего', 5, 'Ведущий', 'Сцена', { p: 1020 }), R('program', 'Речь генерального директора', 10, 'IkonTyres', 'Сцена'), R('program', 'Презентация новой линейки шин', 15, 'Бренд-менеджер', 'Сцена'), R('program', 'Перерезание ленты, запуск', 10, 'VIP-гости', 'Вход в шоурум'), R('tech', 'Лазер-шоу и видеомэппинг', 20, 'Тех-бригада', 'Фасад'), S('Неформальная часть'), R('catering', 'Фуршет, живая музыка, нетворкинг', 90, 'Кейтеринг · бэнд', 'Лаунж'), R('program', 'Розыгрыш призов', 20, 'Ведущий', 'Сцена'), R('program', 'Финальное слово, общее фото', 10, 'Ведущий', 'Сцена'), S('Завершение'), R('logi', 'Проводы гостей, демонтаж', 60, 'Бригада монтажа', 'Везде'), ], }, conference: { name: 'Конференция / форум', hint: 'Деловая программа, сессии, кофе-брейки, панель', meta: { event: 'Бизнес-форум «Рост 2026»', date: '20 июня 2026', venue: 'Hyatt Regency, конференц-зал', guests: '350 делегатов', code: '26UZ034', pm: 'Мунира Касым' }, prepStart: 420, eventStart: 540, prep: [ S('Подготовка зала'), R('logi', 'Завоз оборудования и раздаточных', 45, 'Логистика', 'Служебный вход'), R('setup', 'Рассадка, бейджи, стойки регистрации', 60, 'Координатор', 'Фойе'), R('setup', 'Брендинг сцены и баннеры', 45, 'Декор-команда', 'Зал'), S('Техника и прогон'), R('tech', 'Монтаж звука, света, проекции', 60, 'Тех-бригада', 'Зал'), R('tech', 'Тест презентаций спикеров', 30, 'Видео-инженер', 'Сцена'), R('reh', 'Репетиция модератора и тайминга', 30, 'Режиссёр', 'Сцена'), ], event: [ S('Регистрация'), R('program', 'Регистрация, кофе, нетворкинг', 60, 'Хостес · 4 чел', 'Фойе', { p: 540 }), S('Пленарная часть'), R('program', 'Открытие, приветствие модератора', 10, 'Модератор', 'Сцена'), R('program', 'Кейноут: тренды рынка', 30, 'Спикер 1', 'Сцена'), R('program', 'Доклад партнёра', 25, 'Спикер 2', 'Сцена'), R('catering', 'Кофе-брейк', 20, 'Кейтеринг', 'Фойе'), S('Панель и сессии'), R('program', 'Панельная дискуссия', 45, 'Модератор · 4 эксперта', 'Сцена'), R('program', 'Сессия вопросов и ответов', 20, 'Модератор', 'Сцена'), R('catering', 'Обед-фуршет', 60, 'Кейтеринг', 'Ресторан'), S('Завершение'), R('program', 'Итоги, награждение, фото', 20, 'Модератор', 'Сцена'), ], }, corporate: { name: 'Корпоратив / вечеринка', hint: 'Праздник компании, шоу-программа, банкет, диджей', meta: { event: 'Новогодний корпоратив CCI', date: '26 декабря 2026', venue: 'Banquet Hall «Жемчуг»', guests: '180 гостей', code: '26UZ041', pm: 'Мадина Юсуп' }, prepStart: 600, eventStart: 1140, prep: [ S('Монтаж и декор'), R('logi', 'Завоз декора, мебели, реквизита', 45, 'Логистика', 'Служебный вход'), R('setup', 'Сборка сцены и танцпола', 75, 'Монтажники', 'Банкетный зал'), R('setup', 'Декор столов, оформление зала', 90, 'Декор-команда', 'Зал'), S('Техника и саундчек'), R('tech', 'Звук, свет, дым-машины', 60, 'Тех-бригада', 'Сцена'), R('tech', 'Саундчек диджея и кавер-бэнда', 45, 'Звукорежиссёр', 'Сцена'), R('reh', 'Прогон шоу-номеров с артистами', 45, 'Режиссёр', 'Сцена'), R('catering', 'Сервировка банкета', 60, 'Кейтеринг', 'Зал'), ], event: [ S('Сбор'), R('program', 'Сбор гостей, welcome-зона', 30, 'Хостес', 'Холл', { p: 1140 }), S('Банкет и шоу'), R('program', 'Слово директора, тост', 15, 'Ведущий', 'Сцена'), R('catering', 'Подача горячего, банкет', 45, 'Кейтеринг', 'Зал'), R('program', 'Шоу-номер: артисты', 20, 'Артисты', 'Сцена'), R('program', 'Конкурсы и игры с гостями', 30, 'Ведущий', 'Зал'), R('program', 'Награждение сотрудников года', 25, 'Директор', 'Сцена'), S('Дискотека'), R('program', 'Выступление кавер-бэнда', 45, 'Кавер-бэнд', 'Сцена'), R('program', 'Диджей-сет, танцы', 90, 'DJ', 'Танцпол'), R('program', 'Финал, фейерверк, фото', 15, 'Ведущий', 'Терраса'), ], }, concert: { name: 'Концерт / шоу', hint: 'Сцена, саундчеки, артисты, зрительный зал', meta: { event: 'Концерт ко Дню города', date: '1 сентября 2026', venue: 'Amphitheatre, открытая площадка', guests: '5 000 зрителей', code: '26UZ048', pm: 'Санжар Усман' }, prepStart: 360, eventStart: 1140, prep: [ Dn('31 авг, накануне', '08:00'), S('Сцена и инфраструктура'), R('logi', 'Завоз и разгрузка сценического комплекса', 90, 'Логистика', 'Площадка'), R('setup', 'Сборка сцены, рамп, барьеров', 180, 'Сцен-подрядчик', 'Площадка'), R('setup', 'Установка экранов и ферм', 90, 'Монтажники', 'Сцена'), Dn('1 сен, день концерта', '08:00'), S('Свет, звук, видео'), R('tech', 'Монтаж линейного массива и мониторов', 90, 'Звук-бригада', 'Сцена'), R('tech', 'Монтаж света и спецэффектов', 90, 'Свет-бригада', 'Сцена'), R('tech', 'Лайн-чек, саундчек хедлайнера', 60, 'FOH-инженер', 'Сцена'), S('Репетиция'), R('reh', 'Прогон выходов и переходов', 60, 'Сцен-менеджер', 'Сцена'), R('logi', 'Проверка периметра и безопасности', 45, 'Служба охраны', 'Периметр'), ], event: [ S('Запуск'), R('program', 'Открытие ворот, вход зрителей', 60, 'Стюарды', 'Входы', { p: 1140 }), R('program', 'Разогрев: молодые артисты', 40, 'Артисты', 'Сцена'), S('Основная программа'), R('program', 'Выход хедлайнера', 75, 'Хедлайнер', 'Сцена'), R('tech', 'Пиро- и лазер-шоу финала', 10, 'Тех-бригада', 'Сцена'), R('program', 'Бис', 15, 'Хедлайнер', 'Сцена'), S('Завершение'), R('logi', 'Вывод зрителей, демонтаж', 90, 'Стюарды · бригада', 'Площадка'), ], }, expo: { name: 'Выставочный стенд / экспо', hint: 'Монтаж стенда, работа на выставке, демонтаж', meta: { event: 'Стенд Cloud на ICT Expo', date: '8 октября 2026', venue: 'УзЭкспоцентр, павильон B', guests: 'Стенд 6×8 м', code: '26UZ031', pm: 'Эрбол Рахимов' }, prepStart: 360, eventStart: 600, prep: [ S('Монтаж стенда'), R('logi', 'Завоз конструкций и оборудования', 60, 'Логистика', 'Грузовой въезд'), R('setup', 'Сборка каркаса и стен стенда', 120, 'Монтажники', 'Павильон B'), R('setup', 'Брендинг, графика, мебель', 75, 'Декор-команда', 'Стенд'), S('Техника и контент'), R('tech', 'Подключение экранов и демо-зоны', 60, 'Тех-специалист', 'Стенд'), R('tech', 'Тест презентаций и интерактива', 30, 'Контент-менеджер', 'Стенд'), R('reh', 'Брифинг стендистов', 30, 'Менеджер стенда', 'Стенд'), ], event: [ S('Рабочий день выставки'), R('program', 'Открытие, приём первых посетителей', 30, 'Стендисты', 'Стенд', { p: 600 }), R('program', 'Демо продукта, лидогенерация', 180, 'Стендисты', 'Демо-зона'), R('catering', 'Кофе-пауза для гостей стенда', 30, 'Кейтеринг', 'Лаунж стенда'), R('program', 'Презентация на мини-сцене', 20, 'Спикер', 'Мини-сцена'), R('program', 'Работа с посетителями, нетворкинг', 150, 'Стендисты', 'Стенд'), S('Завершение дня'), R('logi', 'Подведение итогов, фиксация лидов', 30, 'Менеджер стенда', 'Стенд'), R('logi', 'Консервация стенда на ночь', 20, 'Дежурный', 'Стенд'), ], }, }; const TEMPLATE_ORDER = ['opening', 'conference', 'corporate', 'concert', 'expo']; /* ---------- Палитра треков (колонок) сетки мероприятия ---------- */ const TRACK_PALETTE = [ { key: 'accent', color: 'var(--color-accent-500)' }, { key: 'info', color: 'var(--color-info-500)' }, { key: 'success', color: 'var(--color-success-500)' }, { key: 'warning', color: 'var(--color-warning-500)' }, { key: 'danger', color: 'var(--color-danger-500)' }, { key: 'ink', color: 'var(--color-ink-400)' }, ]; function trackColor(key) { const p = TRACK_PALETTE.find(x => x.key === key); return p ? p.color : 'var(--color-ink-400)'; } function nextTrackKey(used) { const free = TRACK_PALETTE.map(p => p.key).find(k => !used.includes(k)); return free || TRACK_PALETTE[used.length % TRACK_PALETTE.length].key; } /* Ручной cue-sheet для демо-шаблона «Открытие» (start 16:00). card: [trackIdx, slotTime, title, dur|null, who|note] */ const GRIDS = { opening: { tracks: [['Ведущий', 'success'], ['Звук', 'accent'], ['Экран', 'info'], ['Свет', 'warning'], ['Кейтеринг', 'danger'], ['Зал / гости', 'ink']], slots: ['16:00', '16:45', '17:00', '17:05', '17:15', '17:30', '17:40', '18:00', '19:30', '19:50', '20:00'], cards: [ [5, '16:00', 'Регистрация, welcome-зона', 45, 'Хостес · 3'], [4, '16:00', 'Welcome-зона готова', null, ''], [4, '16:45', 'Приветственный коктейль', 15, ''], [1, '16:45', 'Фоновый плейлист', null, ''], [0, '17:00', 'Открытие, слово ведущего', 5, 'Ведущий'], [1, '17:00', 'Микрофон, стоп фон', null, 'Звукореж.'], [2, '17:00', 'Заставка мероприятия', null, ''], [0, '17:05', 'Речь ген. директора', 10, 'IkonTyres'], [2, '17:05', 'Слайды спикера', null, ''], [0, '17:15', 'Презентация линейки шин', 15, 'Бренд-менеджер'], [2, '17:15', 'Видеоролик продукта', null, ''], [0, '17:30', 'Перерезание ленты, запуск', 10, 'VIP-гости'], [5, '17:30', 'VIP к входу в шоурум', null, ''], [3, '17:40', 'Лазер-шоу', 20, 'Тех-бригада'], [2, '17:40', 'Видеомэппинг на фасаде', null, ''], [1, '17:40', 'Музыкальное сопровождение', null, ''], [4, '18:00', 'Фуршет', 90, 'Кейтеринг'], [1, '18:00', 'Живая музыка / бэнд', 90, ''], [5, '18:00', 'Нетворкинг', null, ''], [0, '19:30', 'Розыгрыш призов', 20, 'Ведущий'], [0, '19:50', 'Финальное слово, фото', 10, 'Ведущий'], [5, '20:00', 'Проводы гостей', null, ''], ], }, }; function expandGrid(g) { const tracks = g.tracks.map(([name, color]) => ({ id: uid(), name, color })); const slots = g.slots.map(t => ({ id: uid(), time: parse(t) })); const byTime = {}; slots.forEach(s => { byTime[s.time] = s.id; }); const cards = g.cards.map(c => ({ id: uid(), trackId: tracks[c[0]].id, slotId: byTime[parse(c[1])], title: c[2], dur: c[3] != null ? c[3] : null, who: c[4] || '', note: '', })); return { tracks, slots, cards }; } /* Авто-конвертация списка мероприятия в сетку (колонки по типам активности). */ const TYPE_TRACK = { program: ['Программа / сцена', 'success'], tech: ['Техника', 'accent'], catering: ['Кейтеринг', 'danger'], logi: ['Логистика / зал', 'ink'], reh: ['Репетиция', 'warning'], setup: ['Монтаж', 'info'], }; function deriveGrid(eventItems, eventStart) { const comp = computeTimes(eventItems, eventStart).filter(c => c.kind === 'row'); const present = TYPE_ORDER.filter(ty => comp.some(c => c.type === ty)); const tracks = present.map(ty => ({ id: uid(), name: TYPE_TRACK[ty][0], color: TYPE_TRACK[ty][1], _ty: ty })); const trackByTy = {}; tracks.forEach(t => { trackByTy[t._ty] = t.id; }); const times = Array.from(new Set(comp.map(c => c._start))).sort((a, b) => a - b); const slots = times.map(t => ({ id: uid(), time: t })); const slotByTime = {}; slots.forEach(s => { slotByTime[s.time] = s.id; }); const cards = comp.map(c => ({ id: uid(), trackId: trackByTy[c.type], slotId: slotByTime[c._start], title: c.title, dur: c.dur, who: c.who, note: c.note })); tracks.forEach(t => delete t._ty); return { tracks, slots, cards }; } function blankGrid(start) { const tracks = [['Ведущий', 'success'], ['Звук', 'accent'], ['Экран', 'info'], ['Свет', 'warning'], ['Кейтеринг', 'danger'], ['Зал / гости', 'ink']] .map(([n, c]) => ({ id: uid(), name: n, color: c })); const slots = [start, start + 30, start + 60].map(t => ({ id: uid(), time: t })); return { tracks, slots, cards: [] }; } function gridSpan(grid) { if (!grid.slots.length) return { start: 0, end: 0, total: 0 }; const sorted = grid.slots.slice().sort((a, b) => a.time - b.time); const start = sorted[0].time; let end = sorted[sorted.length - 1].time; grid.cards.forEach(c => { const s = grid.slots.find(x => x.id === c.slotId); if (s && c.dur) end = Math.max(end, s.time + c.dur); }); return { start, end, total: end - start }; } function expand(list) { return list.map(it => { if (it.k === 's') return { id: uid(), kind: 'section', title: it.t }; if (it.k === 'd') return { id: uid(), kind: 'day', date: it.date || '', start: it.start != null ? it.start : 540, collapsed: false }; return { id: uid(), kind: 'row', type: it.y, title: it.t, dur: it.d, who: it.w || '', zone: it.z || '', note: it.n || '', pin: (it.p != null ? it.p : null) }; }); } function loadTemplate(key) { const tpl = TEMPLATES[key]; return { key, meta: Object.assign({}, tpl.meta), prepStart: tpl.prepStart, eventStart: tpl.eventStart, prep: expand(tpl.prep), event: expand(tpl.event), eventGrid: GRIDS[key] ? expandGrid(GRIDS[key]) : deriveGrid(expand(tpl.event), tpl.eventStart), }; } /* ---------- Каскад времени (с учётом разделителей дней) ---------- */ function computeTimes(items, phaseStart) { let cursor = phaseStart; let dayIdx = 0; return items.map(it => { if (it.kind === 'day') { cursor = (it.start != null ? it.start : cursor); dayIdx++; return Object.assign({}, it, { _start: cursor, _dayIdx: dayIdx }); } if (it.kind === 'section') return Object.assign({}, it, { _start: cursor, _dayIdx: dayIdx }); let start = cursor, gap = 0, overlap = 0; if (it.pin != null) { if (it.pin >= cursor) { gap = it.pin - cursor; start = it.pin; } else { overlap = cursor - it.pin; start = it.pin; } } const end = start + it.dur; cursor = end; return Object.assign({}, it, { _start: start, _end: end, _gap: gap, _overlap: overlap, _dayIdx: dayIdx }); }); } function phaseSpan(computed, phaseStart) { const rows = computed.filter(c => c.kind === 'row'); if (!rows.length) return { start: phaseStart, end: phaseStart, total: 0 }; const start = Math.min(phaseStart, rows[0]._start); const end = rows[rows.length - 1]._end; return { start, end, total: end - start }; } /* Разбивка подготовки на дни (неявный «День 1» + явные разделители). */ function dayBlocks(items, phaseStart) { const comp = computeTimes(items, phaseStart); const days = []; let cur = { date: null, start: phaseStart, rows: [] }; let started = false; comp.forEach(it => { if (it.kind === 'day') { if (started || cur.rows.length) days.push(cur); cur = { date: it.date, start: (it.start != null ? it.start : phaseStart), rows: [] }; started = true; } else if (it.kind === 'row') { cur.rows.push(it); } }); days.push(cur); return days.map((d, i) => { const rows = d.rows; const start = rows.length ? Math.min(d.start, rows[0]._start) : d.start; const end = rows.length ? rows[rows.length - 1]._end : d.start; const durs = rows.reduce((s, r) => s + (r.dur || 0), 0); return { date: d.date, label: d.date || ('День ' + (i + 1)), start, end, dur: durs, count: rows.length }; }); } window.TimingData = { fmt, parse, dur, uid, TYPES, TYPE_ORDER, LIBRARY, TEMPLATES, TEMPLATE_ORDER, loadTemplate, expand, computeTimes, phaseSpan, dayBlocks, TRACK_PALETTE, trackColor, nextTrackKey, blankGrid, gridSpan, }; })();