Files
site_aegisone/py_service/app/templates/pages/formulas.html
T

309 lines
15 KiB
HTML

{% extends "page.html" %}
{% block page_content %}
<style>
.tab-bar { display:flex; gap:0; margin-bottom:24px; border-bottom:2px solid var(--border); }
.tab-bar .tab { padding:10px 20px; cursor:pointer; border-bottom:2px solid transparent;
margin-bottom:-2px; color:var(--text-muted); font-weight:500; }
.tab-bar .tab.active { color:var(--accent); border-bottom-color:var(--accent); }
.tab-content { display:none; }
.tab-content.active { display:block; }
.formula-test-panel { margin-top: 16px; }
.formula-description {
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px;
margin-bottom: 12px;
}
.formula-code {
background: rgba(0,0,0,0.3);
padding: 12px;
border-radius: 8px;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
white-space: pre-wrap;
color: var(--accent);
}
.formula-inputs {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.formula-result {
margin-top: 12px;
padding: 12px 16px;
border-radius: var(--radius);
font-weight: 600;
font-size: 14px;
}
.formula-result-success {
background: rgba(34, 197, 94, 0.1);
border: 1px solid var(--success);
color: var(--success);
}
.formula-result-error {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--danger);
color: var(--danger);
}
.accordion { margin-bottom:8px; }
.accordion-header {
display:flex; justify-content:space-between; align-items:center;
padding:10px 14px; background:var(--bg-input); border:1px solid var(--border);
border-radius:var(--radius); cursor:pointer; user-select:none;
}
.accordion-header:hover { background:var(--bg-secondary); }
.accordion-header-left { display:flex; align-items:center; gap:8px; }
.accordion-arrow { font-size:10px; color:var(--text-muted); width:12px; }
.accordion-title { font-weight:600; font-size:13px; }
.accordion-badge {
font-size:11px; background:var(--accent); color:#fff;
border-radius:10px; padding:1px 8px;
}
.accordion-header-right { display:flex; align-items:center; gap:8px; }
.accordion-formula, .accordion-action {
cursor:pointer; font-size:16px; opacity:0.7;
}
.accordion-formula:hover, .accordion-action:hover { opacity:1; }
.accordion-body { display:none; padding:12px 0 4px; }
</style>
<div class="card">
<div class="card-header"><h2>Формулы</h2></div>
<div class="tab-bar">
<div class="tab active" onclick="switchTab('test')">Тест формул</div>
<div class="tab" onclick="switchTab('coeffs')">Коэффициенты</div>
</div>
<div class="tab-content active" id="tab-test">
<div class="form-group">
<label>Выберите формулу для тестирования</label>
<select id="formula-select" onchange="switchFormula(this.value)" style="max-width:400px">
{% for f in formulas %}
<option value="{{ f.key }}" {% if f.key == selected %}selected{% endif %}>{{ f.name }}</option>
{% endfor %}
</select>
</div>
{% for f in formulas %}
<div id="formula-{{ f.key }}" class="formula-test-panel" {% if f.key != selected %}style="display:none"{% endif %}>
<div class="formula-description">
<div style="margin-bottom:8px"><strong>Формула:</strong></div>
<div class="formula-code">{{ f.formula }}</div>
<div style="margin-top:8px"><strong>Результат:</strong> {{ f.result }}</div>
</div>
{% if f.sandbox %}
<form class="formula-sandbox-form" onsubmit="return testFormula(event, '{{ f.key }}')">
<h3 style="margin:16px 0 12px;font-size:14px;">Параметры расчёта</h3>
<div class="formula-inputs">
{% for inp in f.sandbox.inputs %}
<div class="form-group">
<label>{{ inp.label }}</label>
{% if inp.type == "checkbox" %}
<input type="checkbox" name="{{ inp.key }}" value="1" style="width:20px;height:20px;accent-color:var(--accent)">
{% elif inp.type == "select" %}
<select name="{{ inp.key }}">
{% for ok, ol in inp.options.items() %}
<option value="{{ ok }}" {% if ok == inp.default %}selected{% endif %}>{{ ol }}</option>
{% endfor %}
</select>
{% else %}
<input type="number" name="{{ inp.key }}" value="{{ inp.default|default(0) }}" step="any">
{% endif %}
</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary">Рассчитать</button>
<div class="formula-result" id="result-{{ f.key }}" style="display:none"></div>
</form>
{% else %}
<div class="empty-state" style="padding:24px 0">
<p>Для этой формулы пока нет тестовой песочницы.</p>
</div>
{% endif %}
<h3 style="margin:20px 0 10px;font-size:13px;color:var(--text-muted)">Связанные коэффициенты</h3>
<div class="table-wrap"><table><thead><tr><th>Ключ</th><th>Значение</th><th>Описание</th></tr></thead>
<tbody>{% for c in f.coeffs %}<tr>
<td><code>{{ c.key }}</code></td>
<td><strong>{{ c.value }}</strong></td>
<td><small>{{ c.description }}</small></td>
</tr>{% endfor %}</tbody></table>
</div>
</div>
{% endfor %}
</div>
<div class="tab-content" id="tab-coeffs">
{% if groups|length == 0 %}
<div class="card-body"><p class="empty">Нет коэффициентов</p></div>
{% else %}
{% for gkey, gdata in groups.items() %}
<div class="accordion">
<div class="accordion-header" onclick="toggleAccordion(this)">
<div class="accordion-header-left">
<span class="accordion-arrow"></span>
<span class="accordion-title">{{ gdata.info.name }}</span>
<span class="accordion-badge">{{ gdata.coeffs|length }}</span>
</div>
<div class="accordion-header-right">
<span class="accordion-formula" title="Формула" onclick="event.stopPropagation(); showFormulaPopover(this, '{{ gkey }}')" data-formula="{{ gdata.info.formula }}" data-result="{{ gdata.info.result }}">📐</span>
</div>
</div>
<div class="accordion-body">
<div class="table-wrap"><table><thead><tr><th>Ключ</th><th>Значение</th><th>Описание</th><th>Формула</th><th>Активен</th><th class="actions"></th></tr></thead>
<tbody>{% for c in gdata.coeffs %}<tr>
<td><code>{{ c.key }}</code></td>
<td><strong>{{ c.value }}</strong></td>
<td><small>{{ c.description }}</small></td>
<td><small>{{ c.formula_ref }}</small></td>
<td><form method="post" action="/service/coefficients/toggle"><input type="hidden" name="id" value="{{ c.id }}"><button class="btn btn-{{ 'success' if c.is_active else 'secondary' }} btn-sm">{{ '✓' if c.is_active else '✕' }}</button></form></td>
<td class="actions"><button class="btn btn-secondary btn-sm" onclick="editCoeff({{ c.id }},'{{ c.key|e }}','{{ c.value }}','{{ c.description|e }}','{{ c.formula_ref|e }}')"></button>
<form method="post" action="/service/coefficients/delete" style="display:inline" id="delete-formula-coeff-form-{{ c.id }}"><input type="hidden" name="id" value="{{ c.id }}"><button type="button" class="btn btn-danger btn-sm" onclick="openConfirmDeleteFormulaCoeff({{ c.id }},'{{ c.key|e }}')" title="Удалить"></button></form>
</td>
</tr>{% endfor %}</tbody></table>
</div>
</div>
</div>
{% endfor %}
<div style="margin-top:16px;">
<button class="btn btn-primary btn-sm" onclick="document.getElementById('modal-coeff').classList.add('open')">+ Добавить коэффициент</button>
</div>
{% endif %}
</div>
</div>
<!-- Coefficient edit modal -->
<div class="modal-overlay" id="modal-coeff"><div class="modal">
<button type="button" class="modal-close" onclick="this.closest('.modal-overlay').classList.remove('open')"></button>
<h2 id="modal-coeff-title">Новый коэффициент</h2>
<form method="post" id="modal-coeff-form" action="/service/coefficients/create">
<input type="hidden" name="id" id="coeff-id">
<div class="form-row">
<div class="form-group"><label>Ключ</label><input type="text" name="key" id="coeff-key" required></div>
<div class="form-group"><label>Значение</label><input type="text" name="value" id="coeff-value" required></div>
</div>
<div class="form-group"><label>Описание</label><input type="text" name="description" id="coeff-description"></div>
<div class="form-group"><label>Формула</label><input type="text" name="formula_ref" id="coeff-formula_ref"></div>
<div class="form-actions"><button type="submit" class="btn btn-primary">Сохранить</button>
<button type="button" class="btn btn-secondary" onclick="this.closest('.modal-overlay').classList.remove('open')">Отмена</button></div>
</form>
</div></div>
<!-- Formula info modal -->
<div class="modal-overlay" id="modal-formula-info"><div class="modal modal-sm">
<button type="button" class="modal-close" onclick="document.getElementById('modal-formula-info').classList.remove('open')"></button>
<h2 id="formula-info-title">Формула</h2>
<div id="formula-info-content" style="margin-top:12px"></div>
</div></div>
<div class="modal-overlay" id="confirm-delete-formula-coeff">
<div class="modal" style="max-width:420px;">
<h2 style="font-size:16px;margin-bottom:8px;">Удалить коэффициент?</h2>
<p style="color:var(--text-secondary);font-size:14px;margin-bottom:20px;">
<strong id="delete-formula-coeff-name"></strong> будет удалён навсегда. Это действие нельзя отменить.
</p>
<div class="form-actions" style="justify-content:flex-end;">
<button type="button" class="btn btn-danger" onclick="confirmDeleteFormulaCoeff()">Удалить</button>
<button type="button" class="btn" onclick="closeConfirmDeleteFormulaCoeff()">Отмена</button>
</div>
</div>
</div>
<script>
var deleteFormulaCoeffId = null;
function openConfirmDeleteFormulaCoeff(id, name){
deleteFormulaCoeffId = id;
document.getElementById('delete-formula-coeff-name').textContent = name || '#' + id;
document.getElementById('confirm-delete-formula-coeff').classList.add('open');
}
function closeConfirmDeleteFormulaCoeff(){
document.getElementById('confirm-delete-formula-coeff').classList.remove('open');
deleteFormulaCoeffId = null;
}
function confirmDeleteFormulaCoeff(){
if(!deleteFormulaCoeffId) return;
document.getElementById('delete-formula-coeff-form-' + deleteFormulaCoeffId).submit();
closeConfirmDeleteFormulaCoeff();
}
function switchTab(name){
document.querySelectorAll('.tab-content').forEach(function(t){ t.classList.remove('active'); });
document.querySelectorAll('.tab').forEach(function(t){ t.classList.remove('active'); });
document.getElementById('tab-' + name).classList.add('active');
document.querySelector('.tab[onclick*="' + name + '"]').classList.add('active');
}
function switchFormula(key) {
document.querySelectorAll('.formula-test-panel').forEach(function(el) {
el.style.display = 'none';
});
var panel = document.getElementById('formula-' + key);
if (panel) panel.style.display = 'block';
}
function testFormula(event, key) {
event.preventDefault();
var form = event.target;
var data = new URLSearchParams(new FormData(form));
var resultBox = document.getElementById('result-' + key);
resultBox.style.display = 'block';
resultBox.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
resultBox.className = 'formula-result';
fetch('/service/coefficients/test/' + key, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: data.toString()
}).then(function(r) { return r.json() }).then(function(resp) {
resultBox.textContent = 'Результат: ' + (resp.result || 'Ошибка');
resultBox.className = 'formula-result formula-result-success';
}).catch(function(err) {
resultBox.textContent = 'Ошибка: ' + err.message;
resultBox.className = 'formula-result formula-result-error';
});
return false;
}
function toggleAccordion(el) {
var arrow = el.querySelector('.accordion-arrow');
var body = el.nextElementSibling;
if (body.style.display === 'none' || body.style.display === '') {
body.style.display = 'block';
arrow.textContent = '▼';
} else {
body.style.display = 'none';
arrow.textContent = '▶';
}
}
function editCoeff(id,key,value,desc,ref){
document.getElementById('modal-coeff').classList.add('open');
document.getElementById('modal-coeff-title').textContent = 'Редактировать коэффициент';
document.getElementById('modal-coeff-form').action = '/service/coefficients/edit';
document.getElementById('coeff-id').value = id;
document.getElementById('coeff-key').value = key;
document.getElementById('coeff-value').value = value;
document.getElementById('coeff-description').value = desc;
document.getElementById('coeff-formula_ref').value = ref;
}
function showFormulaPopover(el, gkey) {
var formula = el.getAttribute('data-formula');
var result = el.getAttribute('data-result');
var title = el.closest('.accordion-header').querySelector('.accordion-title').textContent;
document.getElementById('formula-info-title').textContent = title;
document.getElementById('formula-info-content').innerHTML =
'<div style="margin-bottom:12px"><strong>Формула:</strong></div>' +
'<div style="background:var(--bg-input);padding:12px;border-radius:8px;font-family:monospace;font-size:13px;margin-bottom:12px;white-space:pre-wrap">' + formula + '</div>' +
'<div><strong>Результат:</strong> ' + result + '</div>';
document.getElementById('modal-formula-info').classList.add('open');
}
// Open first accordion by default
document.addEventListener('DOMContentLoaded', function() {
var first = document.querySelector('.accordion-body');
if (first) { first.style.display = 'block'; first.previousElementSibling.querySelector('.accordion-arrow').textContent = '▼'; }
});
</script>
{% endblock %}