Files
ACORE/static/assets/js/reports-template-editor.js
T
lokses 0da9c13192 it_manager+reports: иерархия договоров, биллинг и месячные отчёты
Иерархия рамочных/дочерних договоров и платежей, интернет-биллинг,
месячные IT-отчёты (импорт, DOCX/PDF, шаблоны выводов).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 18:03:44 +03:00

330 lines
12 KiB
JavaScript

/**
* Конструктор шаблонов отчётов (секции + подписи) и Ace для advanced HTML.
*/
(function () {
var ACE_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.2/ace.js';
var editors = {};
var previewTimer = null;
var aceReady = false;
function csrfToken() {
var el = document.querySelector('#report-tpl-form input[name=csrfmiddlewaretoken]');
if (el && el.value) return el.value;
var m = document.cookie.match(/csrftoken=([^;]+)/);
return m ? decodeURIComponent(m[1]) : '';
}
function currentMode() {
var root = document.getElementById('report-tpl-editor-root');
return (root && root.getAttribute('data-mode')) || 'constructor';
}
function setMode(mode) {
var root = document.getElementById('report-tpl-editor-root');
if (!root) return;
root.setAttribute('data-mode', mode);
var engine = document.getElementById('tpl-engine');
if (engine) engine.value = mode === 'advanced' ? 'django_html' : 'blocks_v1';
var c = document.getElementById('tpl-panel-constructor');
var a = document.getElementById('tpl-panel-advanced');
if (c) c.hidden = mode !== 'constructor';
if (a) a.hidden = mode !== 'advanced';
document.querySelectorAll('.tpl-mode-btn').forEach(function (btn) {
var on = btn.getAttribute('data-mode') === mode;
btn.style.fontWeight = on ? '600' : '';
btn.style.borderBottom = on ? '2px solid var(--color-primary)' : '';
btn.style.borderRadius = on ? '0' : '';
});
if (mode === 'advanced') {
ensureAce(function () {
mountEditor('tpl-body-html', 'ace/mode/html');
mountEditor('tpl-body-md', 'ace/mode/markdown');
// sync blocks JSON into a visible ace if host exists
var blocksTa = document.getElementById('tpl-blocks');
var adv = document.getElementById('tpl-blocks-adv');
if (blocksTa && adv) adv.value = blocksTa.value;
mountEditor('tpl-blocks-adv', 'ace/mode/json');
// mirror: when blocks-adv changes, copy to tpl-blocks
if (editors['tpl-blocks-adv']) {
editors['tpl-blocks-adv'].session.off('change', editors['tpl-blocks-adv']._blocksMirror);
editors['tpl-blocks-adv']._blocksMirror = function () {
var ta = document.getElementById('tpl-blocks');
if (ta) ta.value = editors['tpl-blocks-adv'].getValue();
schedulePreview();
};
editors['tpl-blocks-adv'].session.on('change', editors['tpl-blocks-adv']._blocksMirror);
}
});
} else {
syncConstructorToBlocks();
}
schedulePreview();
}
function loadAce(cb) {
if (window.ace) {
cb();
return;
}
if (window._aceLoading) {
window._aceLoading.push(cb);
return;
}
window._aceLoading = [cb];
var s = document.createElement('script');
s.src = ACE_CDN;
s.onload = function () {
var q = window._aceLoading || [];
window._aceLoading = null;
q.forEach(function (fn) { fn(); });
};
s.onerror = function () {
window._aceLoading = null;
console.error('Не удалось загрузить Ace editor');
};
document.head.appendChild(s);
}
function ensureAce(cb) {
loadAce(function () {
aceReady = true;
cb();
});
}
function syncAceToTextareas() {
Object.keys(editors).forEach(function (id) {
var ta = document.getElementById(id);
if (ta && editors[id]) ta.value = editors[id].getValue();
});
var adv = document.getElementById('tpl-blocks-adv');
var blocks = document.getElementById('tpl-blocks');
if (adv && blocks && currentMode() === 'advanced') {
blocks.value = adv.value;
}
}
function mountEditor(textareaId, mode) {
var root = document.getElementById('report-tpl-editor-root');
var readOnly = root && root.getAttribute('data-readonly') === '1';
var ta = document.getElementById(textareaId);
if (!ta || !window.ace) return;
var hostId = textareaId === 'tpl-blocks-adv' ? 'tpl-blocks-ace' : (textareaId + '-ace');
var host = document.getElementById(hostId);
if (!host) return;
if (editors[textareaId]) {
try { editors[textareaId].destroy(); } catch (e) { /* ignore */ }
delete editors[textareaId];
}
host.innerHTML = '';
host.style.display = 'block';
ta.style.position = 'absolute';
ta.style.left = '-9999px';
ta.style.height = '1px';
ta.style.width = '1px';
ta.setAttribute('aria-hidden', 'true');
var ed = ace.edit(host);
ed.setTheme('ace/theme/textmate');
ed.session.setMode(mode);
ed.setValue(ta.value || '', -1);
ed.setReadOnly(!!readOnly);
ed.setOptions({
fontSize: '13px',
showPrintMargin: false,
wrap: true,
useSoftTabs: true,
tabSize: 2,
});
ed.session.on('change', function () {
ta.value = ed.getValue();
if (textareaId === 'tpl-body-html' || textareaId === 'tpl-blocks-adv') {
schedulePreview();
}
});
editors[textareaId] = ed;
}
function readBlocksJson() {
var ta = document.getElementById('tpl-blocks');
try {
return JSON.parse((ta && ta.value) || '{}');
} catch (e) {
return { labels: {}, sections: [] };
}
}
function writeBlocksJson(obj) {
var ta = document.getElementById('tpl-blocks');
if (ta) ta.value = JSON.stringify(obj, null, 2);
if (editors['tpl-blocks-adv']) {
editors['tpl-blocks-adv'].setValue(ta.value, -1);
}
}
function syncConstructorToBlocks() {
var data = readBlocksJson();
if (!data.labels || typeof data.labels !== 'object') data.labels = {};
document.querySelectorAll('.tpl-label-input').forEach(function (inp) {
var key = inp.getAttribute('data-label-key');
if (key) data.labels[key] = inp.value;
});
var sections = [];
document.querySelectorAll('#tpl-sections-list .tpl-section-row').forEach(function (row) {
var on = row.querySelector('.tpl-section-on');
if (on && on.checked) sections.push(row.getAttribute('data-section-id'));
});
data.sections = sections;
writeBlocksJson(data);
return data;
}
function moveSection(row, dir) {
var list = document.getElementById('tpl-sections-list');
if (!list || !row) return;
if (dir < 0 && row.previousElementSibling) {
list.insertBefore(row, row.previousElementSibling);
} else if (dir > 0 && row.nextElementSibling) {
list.insertBefore(row.nextElementSibling, row);
}
syncConstructorToBlocks();
schedulePreview();
}
function schedulePreview() {
if (previewTimer) clearTimeout(previewTimer);
previewTimer = setTimeout(runPreview, 500);
}
function runPreview() {
var root = document.getElementById('report-tpl-editor-root');
var frame = document.getElementById('report-tpl-preview-frame');
if (!root || !frame) return;
var pk = root.getAttribute('data-tpl-id');
if (!pk) return;
if (currentMode() === 'constructor') {
syncConstructorToBlocks();
} else {
syncAceToTextareas();
}
var htmlTa = document.getElementById('tpl-body-html');
var blocksTa = document.getElementById('tpl-blocks');
var engineEl = document.getElementById('tpl-engine');
var monthSel = document.getElementById('tpl-preview-month');
var status = document.getElementById('report-tpl-preview-status');
var fd = new FormData();
fd.append('engine', engineEl ? engineEl.value : 'blocks_v1');
fd.append('body_html', htmlTa ? htmlTa.value : '');
fd.append('blocks', blocksTa ? blocksTa.value : '{}');
if (monthSel) fd.append('month', monthSel.value);
if (status) status.textContent = 'Обновление…';
fetch('/reports/templates/' + pk + '/preview-draft/', {
method: 'POST',
headers: { 'X-CSRFToken': csrfToken() },
body: fd,
credentials: 'same-origin',
}).then(function (res) {
return res.text().then(function (text) {
return { ok: res.ok, text: text };
});
}).then(function (r) {
frame.srcdoc = r.text;
if (status) status.textContent = r.ok ? 'Предпросмотр' : 'Ошибка в шаблоне';
}).catch(function () {
if (status) status.textContent = 'Сеть / ошибка запроса';
});
}
function bindConstructor() {
var list = document.getElementById('tpl-sections-list');
if (list && !list._bound) {
list._bound = true;
list.addEventListener('change', function (e) {
if (e.target && e.target.classList.contains('tpl-section-on')) {
var row = e.target.closest('.tpl-section-row');
if (row) row.style.opacity = e.target.checked ? '' : '0.55';
syncConstructorToBlocks();
schedulePreview();
}
});
list.addEventListener('click', function (e) {
var up = e.target.closest('.tpl-sec-up');
var down = e.target.closest('.tpl-sec-down');
if (up) {
e.preventDefault();
moveSection(up.closest('.tpl-section-row'), -1);
} else if (down) {
e.preventDefault();
moveSection(down.closest('.tpl-section-row'), 1);
}
});
}
document.querySelectorAll('.tpl-label-input').forEach(function (inp) {
if (inp._bound) return;
inp._bound = true;
inp.addEventListener('input', function () {
syncConstructorToBlocks();
schedulePreview();
});
});
}
function init() {
var root = document.getElementById('report-tpl-editor-root');
if (!root) return;
editors = {};
document.querySelectorAll('.tpl-mode-btn').forEach(function (btn) {
if (btn._bound) return;
btn._bound = true;
btn.addEventListener('click', function () {
setMode(btn.getAttribute('data-mode') || 'constructor');
});
});
bindConstructor();
var form = document.getElementById('report-tpl-form');
if (form && !form._tplBound) {
form._tplBound = true;
form.addEventListener('submit', function () {
if (currentMode() === 'constructor') {
syncConstructorToBlocks();
} else {
syncAceToTextareas();
}
});
}
var monthSel = document.getElementById('tpl-preview-month');
if (monthSel && !monthSel._tplBound) {
monthSel._tplBound = true;
monthSel.addEventListener('change', schedulePreview);
}
var btn = document.getElementById('report-tpl-preview-btn');
if (btn && !btn._tplBound) {
btn._tplBound = true;
btn.addEventListener('click', function (e) {
e.preventDefault();
runPreview();
});
}
setMode(currentMode());
}
document.addEventListener('DOMContentLoaded', init);
document.addEventListener('htmx:afterSwap', function (ev) {
var t = ev.detail && ev.detail.target;
if (t && (t.id === 'main-content' || (t.querySelector && t.querySelector('#report-tpl-editor-root')))) {
init();
}
});
})();