Programming
1. dashboard
{% extends "portfolio/base.html" %}
{% block loanbook_content %}
<div class="lb-kpis-strip">
<div class="lb-wrap">
<div class="lb-kpis">
{% for k in kpis %}
<div class="lb-kpi">
<div class="k">{{ k.label }}</div>
<div class="v">{{ k.value }}</div>
<div class="d {{ k.cls }}">{{ k.delta|default:" " }}</div>
</div>
{% endfor %}
</div>
</div>
</div>
<nav class="lb-tabs"><div class="lb-wrap">
{% for s in sections %}
<button type="button" class="lb-tab {% if s.slug == active %}is-active{% endif %}"
data-section="{{ s.slug }}">{{ s.title }}</button>
{% endfor %}
</div></nav>
{% if quality %}
<div class="lb-wrap">
<div class="lb-alerts">
<b>{{ t.methodology }}</b>
<ul>
{% for q in quality %}
<li><span class="status-{{ q.status }}">{{ q.status }}</span>
{{ q.check_name }} — {{ q.detail }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{# First section is server-rendered; the rest arrive from section_partial. #}
<div id="lb-section-host">
{% include "portfolio/_section.html" %}
</div>
<section class="lb-wrap lb-table-block">
<h3>{{ t.filial }} — {{ period_label }}</h3>
<table class="lb-table">
<thead><tr>
<th>{{ t.filial }}</th><th>{{ t.exposure }}</th><th>{{ t.npl_ratio }}</th>
<th>Δ 12м, п.п.</th><th>{{ t.watch_ratio }}</th>
<th>{{ t.coverage_ratio }}</th><th>{{ t.n_loans }}</th>
</tr></thead>
<tbody>
{% for b in branches %}
<tr>
<td>{{ b.name }}</td><td>{{ b.exposure }}</td><td>{{ b.npl }}</td>
<td class="{{ b.delta_cls }}">{{ b.delta }}</td>
<td>{{ b.watch }}</td><td>{{ b.coverage }}</td><td>{{ b.n_loans }}</td>
</tr>
{% empty %}
<tr><td colspan="7" class="muted">Нет данных по филиалам.</td></tr>
{% endfor %}
</tbody>
</table>
<p class="lb-dl">
<a href="{% url 'portfolio:mart_csv' 'portfolio_monthly' %}">CSV</a> ·
<a href="{% url 'portfolio:mart_api' 'portfolio_monthly' %}?dim_name=filial">JSON</a>
</p>
</section>
{% endblock %}
{% block loanbook_scripts %}
<script>
// Tabs fetch their section on first click and cache the markup afterwards.
// Plotly needs the returned <script> tags executed, which innerHTML will not do,
// so they are re-created explicitly.
(function () {
const host = document.getElementById('lb-section-host');
if (!host) return;
const cache = {};
const qs = new URLSearchParams(window.location.search);
const base = "{% url 'portfolio:dashboard' %}";
function inject(html) {
host.innerHTML = html;
host.querySelectorAll('script').forEach(old => {
const s = document.createElement('script');
if (old.src) s.src = old.src; else s.textContent = old.textContent;
old.replaceWith(s);
});
window.loanbookRelayout && window.loanbookRelayout();
}
document.querySelectorAll('.lb-tab').forEach(btn => {
btn.addEventListener('click', async () => {
const slug = btn.dataset.section;
document.querySelectorAll('.lb-tab')
.forEach(b => b.classList.toggle('is-active', b === btn));
if (cache[slug]) return inject(cache[slug]);
host.innerHTML = '<div class="lb-wrap lb-loading">Загрузка…</div>';
try {
const r = await fetch(`${base}section/${slug}/?${qs.toString()}`,
{headers: {'X-Requested-With': 'fetch'}});
if (!r.ok) throw new Error(r.status);
cache[slug] = await r.text();
inject(cache[slug]);
} catch (e) {
host.innerHTML = `Не удалось загрузить раздел (${e.message}). Обновите страницу.</div>`;
}
});
});
})();
</script>
{% endblock %}
PO
powerty
Author
· Staff
Aug. 26, 2026
Aug. 26, 2026
5
Views
0
Likes
2m
Read