0da9c13192
Иерархия рамочных/дочерних договоров и платежей, интернет-биллинг, месячные IT-отчёты (импорт, DOCX/PDF, шаблоны выводов). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
330 lines
12 KiB
JavaScript
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();
|
|
}
|
|
});
|
|
})();
|