Опубликовано через кнопку
This commit is contained in:
parent
174556a14e
commit
14290a426f
534
index.html
534
index.html
@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<!-- Дизайн: Стандартный дизайн. Сохраняй все пять CSS; подробности в design.md. -->
|
<!-- no-design-system -->
|
||||||
|
<!-- Дизайн: Стандартный дизайн. Финансовый рабочий экран КФУ. Палитра задана пользователем. -->
|
||||||
<html lang="ru" data-theme="light">
|
<html lang="ru" data-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@ -13,243 +14,428 @@
|
|||||||
<script src="design-system/icons/kt-ai-icons.js"></script>
|
<script src="design-system/icons/kt-ai-icons.js"></script>
|
||||||
<script src="vendor/xlsx.full.min.js"></script>
|
<script src="vendor/xlsx.full.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
/* Финансовые токены — пользовательские, поверх ДС */
|
||||||
|
:root {
|
||||||
|
--kfu-bg: #F4F6F8;
|
||||||
|
--kfu-surface: #FFFFFF;
|
||||||
|
--kfu-ink: #1E2430;
|
||||||
|
--kfu-ink-2: #5A6472;
|
||||||
|
--kfu-ink-3: #8A93A3;
|
||||||
|
--kfu-accent: #0D9AAA;
|
||||||
|
--kfu-accent-hover: #0A7E8C;
|
||||||
|
--kfu-accent-soft: #E4F4F5;
|
||||||
|
--kfu-border: #E1E5EA;
|
||||||
|
--kfu-border-2: #CBD2DB;
|
||||||
|
--kfu-risk: #C03D32;
|
||||||
|
--kfu-risk-bg: #FBEAE8;
|
||||||
|
--kfu-warn: #A5701A;
|
||||||
|
--kfu-warn-bg: #FDF4E3;
|
||||||
|
--kfu-ok: #247A4B;
|
||||||
|
--kfu-ok-bg: #E7F4EE;
|
||||||
|
--kfu-neutral: #5A6472;
|
||||||
|
--kfu-neutral-bg: #F0F2F5;
|
||||||
|
--kfu-radius: 10px;
|
||||||
|
--kfu-radius-sm: 6px;
|
||||||
|
--kfu-shadow: 0 1px 3px rgba(30,36,48,.06), 0 1px 2px rgba(30,36,48,.04);
|
||||||
|
--kfu-shadow-md: 0 4px 16px rgba(30,36,48,.08);
|
||||||
|
--kfu-max: 1180px;
|
||||||
|
}
|
||||||
|
|
||||||
@layer product {
|
@layer product {
|
||||||
[hidden] { display: none !important; }
|
[hidden] { display: none !important; }
|
||||||
a.kt-ai-btn { text-decoration: none; }
|
a.kt-ai-btn { text-decoration: none; }
|
||||||
|
body { background: var(--kfu-bg); }
|
||||||
|
.kt-ai-sidebar { display: none !important; }
|
||||||
|
|
||||||
.kfu-shell { display: grid; grid-template-columns: var(--kt-ai-sidebar-width) minmax(0, 1fr); min-height: 100dvh; }
|
/* ---------- верхняя полоса ---------- */
|
||||||
.kfu-main { min-width: 0; background: var(--kt-ai-bg); display: flex; flex-direction: column; }
|
.kfu-topbar { position: sticky; top: 0; z-index: var(--kt-ai-z-sticky);
|
||||||
|
background: var(--kfu-surface); border-bottom: 1px solid var(--kfu-border);
|
||||||
|
height: 56px; display: flex; align-items: center; padding: 0 24px; gap: 16px; }
|
||||||
|
.kfu-brand { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
||||||
|
.kfu-brand-mark { width: 32px; height: 32px; border-radius: 8px; background: var(--kfu-ink);
|
||||||
|
color: #fff; display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 14px; font-weight: 700; letter-spacing: .3px; }
|
||||||
|
.kfu-brand-text { line-height: 1.2; }
|
||||||
|
.kfu-brand-text b { display: block; font-size: 14px; font-weight: 600; color: var(--kfu-ink); letter-spacing: -.01em; }
|
||||||
|
.kfu-brand-text span { display: block; font-size: 11px; color: var(--kfu-ink-3); }
|
||||||
|
.kfu-topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
|
||||||
|
.kfu-file-badge { font-size: 12px; color: var(--kfu-ink-2); display: flex; align-items: center; gap: 8px;
|
||||||
|
background: var(--kfu-neutral-bg); padding: 5px 12px; border-radius: var(--kfu-radius-sm);
|
||||||
|
max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.kfu-file-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--kfu-accent); flex-shrink: 0; }
|
||||||
|
.kfu-btn { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px;
|
||||||
|
border-radius: var(--kfu-radius-sm); font-size: 13px; font-weight: 500; cursor: pointer;
|
||||||
|
border: 1px solid var(--kfu-border-2); background: var(--kfu-surface); color: var(--kfu-ink);
|
||||||
|
transition: background .12s, border-color .12s, box-shadow .12s; white-space: nowrap; }
|
||||||
|
.kfu-btn:hover { background: var(--kfu-bg); border-color: var(--kfu-accent); }
|
||||||
|
.kfu-btn:focus-visible { outline: 2px solid var(--kfu-accent); outline-offset: 1px; }
|
||||||
|
.kfu-btn.primary { background: var(--kfu-accent); color: #fff; border-color: var(--kfu-accent); }
|
||||||
|
.kfu-btn.primary:hover { background: var(--kfu-accent-hover); }
|
||||||
|
.kfu-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
|
||||||
|
|
||||||
.kfu-content { box-sizing: border-box; width: 100%; min-width: 0; padding: var(--kt-ai-space-8) var(--kt-ai-space-8) var(--kt-ai-space-11); max-width: 1440px; margin: 0 auto; }
|
/* ---------- контент ---------- */
|
||||||
|
.kfu-wrap { max-width: var(--kfu-max); margin: 0 auto; padding: 24px 24px 64px; }
|
||||||
|
|
||||||
.kfu-title-row { display: flex; align-items: baseline; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-5); flex-wrap: wrap; }
|
/* ---------- заголовок ---------- */
|
||||||
.kfu-title-row h1 { font-size: var(--kt-ai-text-xl); font-weight: 400; margin: 0; letter-spacing: -.02em; }
|
.kfu-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
|
||||||
.kfu-title-row .kt-ai-meta { white-space: nowrap; }
|
margin-bottom: 20px; flex-wrap: wrap; }
|
||||||
|
.kfu-header-left { min-width: 0; }
|
||||||
|
.kfu-header h1 { font-size: 32px; font-weight: 600; color: var(--kfu-ink); letter-spacing: -.025em;
|
||||||
|
line-height: 1.15; margin: 0 0 6px; }
|
||||||
|
.kfu-header-sub { font-size: 15px; color: var(--kfu-ink-2); line-height: 1.4; margin: 0; }
|
||||||
|
.kfu-header-sub b { color: var(--kfu-ink); font-weight: 600; }
|
||||||
|
.kfu-recon-chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
|
||||||
|
border-radius: var(--kfu-radius-sm); background: var(--kfu-ok-bg); color: var(--kfu-ok);
|
||||||
|
font-size: 12px; font-weight: 500; white-space: nowrap; flex-shrink: 0; }
|
||||||
|
.kfu-recon-chip svg { width: 14px; height: 14px; }
|
||||||
|
|
||||||
.kfu-actions { display: flex; gap: var(--kt-ai-space-3); flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); }
|
/* ---------- сводка (горизонтальная полоса) ---------- */
|
||||||
.dropzone { display: inline-flex; align-items: center; gap: var(--kt-ai-space-4); padding: 10px 16px;
|
.kfu-brief { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
|
||||||
border: 1.5px dashed var(--kt-ai-border-strong); border-radius: var(--vibe-card-radius);
|
background: var(--kfu-border); border: 1px solid var(--kfu-border); border-radius: var(--kfu-radius);
|
||||||
background: var(--kt-ai-bg-elevated); color: var(--kt-ai-fg-muted); cursor: pointer; transition: border-color .15s, background .15s; }
|
overflow: hidden; margin-bottom: 20px; box-shadow: var(--kfu-shadow); }
|
||||||
.dropzone:hover { border-color: var(--kt-ai-primary); background: var(--kt-ai-bg-hover); color: var(--kt-ai-fg); }
|
.kfu-brief-item { background: var(--kfu-surface); padding: 16px 20px; display: flex; align-items: center; gap: 14px; }
|
||||||
.dropzone input[type=file] { display: none; }
|
.kfu-brief-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||||
|
.kfu-brief-dot.risk { background: var(--kfu-risk); }
|
||||||
|
.kfu-brief-dot.warn { background: var(--kfu-warn); }
|
||||||
|
.kfu-brief-dot.ok { background: var(--kfu-ok); }
|
||||||
|
.kfu-brief-dot.neutral { background: var(--kfu-neutral); }
|
||||||
|
.kfu-brief-num { font-size: 28px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums;
|
||||||
|
line-height: 1; color: var(--kfu-ink); }
|
||||||
|
.kfu-brief-num-b { font-size: 13px; font-weight: 500; color: var(--kfu-ink-3); margin-left: 6px; line-height: 1; }
|
||||||
|
.kfu-brief-lbl { font-size: 12px; color: var(--kfu-ink-3); margin-top: 2px; }
|
||||||
|
|
||||||
.kfu-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: var(--kt-ai-space-4); margin: 0 0 var(--kt-ai-space-6); }
|
/* ---------- поиск + фильтры ---------- */
|
||||||
.kfu-kpis .kt-ai-kpi { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--vibe-card-radius); padding: 12px 14px; }
|
.kfu-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
|
||||||
.kfu-kpis .kt-ai-kpi .label { font-size: 10px; }
|
.kfu-search { position: relative; flex: 1; min-width: 200px; max-width: 320px; }
|
||||||
.kfu-kpis .kt-ai-kpi .value { font-size: var(--kt-ai-text-xl); }
|
.kfu-search svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
|
||||||
|
width: 15px; height: 15px; color: var(--kfu-ink-3); pointer-events: none; }
|
||||||
|
.kfu-search input { width: 100%; height: 34px; padding: 0 12px 0 32px; border-radius: var(--kfu-radius-sm);
|
||||||
|
border: 1px solid var(--kfu-border-2); background: var(--kfu-surface); font-size: 13px; color: var(--kfu-ink);
|
||||||
|
transition: border-color .12s, box-shadow .12s; }
|
||||||
|
.kfu-search input:focus { outline: none; border-color: var(--kfu-accent); box-shadow: 0 0 0 3px var(--kfu-accent-soft); }
|
||||||
|
.kfu-search input::placeholder { color: var(--kfu-ink-3); }
|
||||||
|
.kfu-chip-filter { display: inline-flex; align-items: center; height: 32px; padding: 0 14px;
|
||||||
|
border-radius: var(--kfu-radius-sm); border: 1px solid var(--kfu-border-2); background: var(--kfu-surface);
|
||||||
|
font-size: 13px; font-weight: 500; color: var(--kfu-ink-2); cursor: pointer;
|
||||||
|
transition: all .12s; white-space: nowrap; }
|
||||||
|
.kfu-chip-filter:hover { border-color: var(--kfu-accent); color: var(--kfu-ink); }
|
||||||
|
.kfu-chip-filter.active { background: var(--kfu-accent); color: #fff; border-color: var(--kfu-accent); }
|
||||||
|
.kfu-table-count { font-size: 13px; color: var(--kfu-ink-3); margin-left: auto; white-space: nowrap; }
|
||||||
|
|
||||||
.kfu-toolbar { display: flex; gap: var(--kt-ai-space-3); flex-wrap: wrap; align-items: center; margin-bottom: var(--kt-ai-space-4); }
|
/* ---------- таблица ---------- */
|
||||||
.kfu-toolbar .kt-ai-search { min-width: 240px; }
|
.kfu-table-card { background: var(--kfu-surface); border: 1px solid var(--kfu-border);
|
||||||
.kfu-toolbar .kt-ai-select { min-width: 150px; width: auto; }
|
border-radius: var(--kfu-radius); box-shadow: var(--kfu-shadow); overflow-x: auto; }
|
||||||
.kfu-toolbar .kt-ai-checkbox { display: inline-flex; align-items: center; gap: 6px; font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
|
|
||||||
|
|
||||||
.kfu-table-wrap { box-sizing: border-box; max-width: 100%; background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--vibe-card-radius);
|
|
||||||
overflow-x: auto; box-shadow: var(--kt-ai-card-shadow); }
|
|
||||||
.kfu-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
.kfu-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||||
.kfu-table colgroup col:nth-child(1) { width: 34%; }
|
.kfu-table colgroup col:nth-child(1) { width: 30%; }
|
||||||
.kfu-table colgroup col:nth-child(2) { width: 16%; }
|
.kfu-table colgroup col:nth-child(2) { width: 12%; }
|
||||||
.kfu-table colgroup col:nth-child(3) { width: 17%; }
|
.kfu-table colgroup col:nth-child(3) { width: 16%; }
|
||||||
.kfu-table colgroup col:nth-child(4) { width: 17%; }
|
.kfu-table colgroup col:nth-child(4) { width: 16%; }
|
||||||
.kfu-table colgroup col:nth-child(5) { width: 16%; }
|
.kfu-table colgroup col:nth-child(5) { width: 16%; }
|
||||||
.kfu-table th { text-align: right; padding: 10px 10px; font-size: var(--kt-ai-text-xs); font-weight: 600; color: var(--kt-ai-fg-muted);
|
.kfu-table colgroup col:nth-child(6) { width: 10%; }
|
||||||
text-transform: uppercase; letter-spacing: .4px; border-bottom: 1px solid var(--kt-ai-divider); white-space: nowrap; cursor: pointer; user-select: none; overflow: hidden; text-overflow: ellipsis; }
|
.kfu-table thead th { padding: 10px 14px; font-size: 11px; font-weight: 600; color: var(--kfu-ink-3);
|
||||||
.kfu-table th:first-child, .kfu-table td:first-child { text-align: left; }
|
text-transform: uppercase; letter-spacing: .5px; border-bottom: 1px solid var(--kfu-border);
|
||||||
.kfu-table th .th-sub { display: block; font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--kt-ai-fg-faint); font-size: 10px; white-space: normal; }
|
background: var(--kfu-bg); text-align: right; white-space: nowrap; cursor: pointer; user-select: none;
|
||||||
.kfu-table th.sorted-asc::after { content: " ↑"; color: var(--kt-ai-link); }
|
position: sticky; top: 0; z-index: 1; }
|
||||||
.kfu-table th.sorted-desc::after { content: " ↓"; color: var(--kt-ai-link); }
|
.kfu-table thead th:first-child { text-align: left; }
|
||||||
.kfu-table td { padding: 9px 10px; border-bottom: 1px solid var(--kt-ai-divider); font-size: var(--kt-ai-text-sm); text-align: right;
|
.kfu-table thead th .th-sub { display: block; font-weight: 400; text-transform: none; letter-spacing: 0;
|
||||||
font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
font-size: 10px; color: var(--kfu-ink-3); opacity: .7; margin-top: 1px; }
|
||||||
.kfu-table td:first-child { text-align: left; }
|
.kfu-table thead th.sorted-asc::after { content: " ↑"; color: var(--kfu-accent); }
|
||||||
|
.kfu-table thead th.sorted-desc::after { content: " ↓"; color: var(--kfu-accent); }
|
||||||
.kfu-table tbody tr { cursor: pointer; transition: background .1s; }
|
.kfu-table tbody tr { cursor: pointer; transition: background .1s; }
|
||||||
.kfu-table tbody tr:hover { background: var(--kt-ai-bg-hover); }
|
.kfu-table tbody tr:hover { background: var(--kfu-bg); }
|
||||||
.kfu-table tbody tr.is-group td { background: var(--kt-ai-bg-soft); font-weight: 600; }
|
.kfu-table tbody tr.is-group { background: var(--kfu-neutral-bg); }
|
||||||
.kfu-table td .none { color: var(--kt-ai-fg-faint); }
|
.kfu-table tbody tr.is-group:hover { background: #E8EBEF; }
|
||||||
.kfu-table td .inf { color: var(--kt-ai-link); font-weight: 600; }
|
.kfu-table td { padding: 12px 14px; border-bottom: 1px solid var(--kfu-border); font-size: 14px;
|
||||||
|
text-align: right; font-variant-numeric: tabular-nums; vertical-align: middle; }
|
||||||
|
.kfu-table td:first-child { text-align: left; }
|
||||||
|
.kfu-table td:first-child .co-name { font-weight: 500; color: var(--kfu-ink); line-height: 1.3;
|
||||||
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; }
|
||||||
|
.kfu-table td:first-child .co-sub { font-size: 11px; color: var(--kfu-ink-3); margin-top: 1px; }
|
||||||
|
|
||||||
.zone-pill { display: inline-flex; align-items: center; gap: 5px; padding: 2px 10px; border-radius: var(--vibe-button-radius);
|
/* метка учебного режима */
|
||||||
font-size: var(--kt-ai-text-xs); font-weight: 600; }
|
.kfu-mock-chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
|
||||||
.zone-pill::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
|
border-radius: var(--kfu-radius-sm); background: var(--kfu-warn-bg); color: var(--kfu-warn);
|
||||||
.zone-pill.z-green { background: var(--kt-ai-status-ok-bg); color: var(--kt-ai-status-ok-fg); }
|
font-size: 12px; font-weight: 600; letter-spacing: .3px; text-transform: uppercase; white-space: nowrap; flex-shrink: 0; }
|
||||||
.zone-pill.z-yellow { background: var(--kt-ai-status-warn-bg); color: var(--kt-ai-status-warn-fg); }
|
|
||||||
.zone-pill.z-red { background: var(--kt-ai-status-risk-bg); color: var(--kt-ai-status-risk-fg); }
|
|
||||||
.zone-pill.z-nodefault { background: var(--kt-ai-bg-active); color: var(--kt-ai-fg-muted); }
|
|
||||||
.zone-pill.z-nodata { background: transparent; border: 1px dashed var(--kt-ai-border-strong); color: var(--kt-ai-fg-muted); }
|
|
||||||
|
|
||||||
.kfu-empty { padding: 48px 24px; text-align: center; color: var(--kt-ai-fg-muted); }
|
/* статус-метка */
|
||||||
.kfu-empty svg { width: 32px; height: 32px; margin-bottom: 12px; opacity: .5; }
|
.kfu-status { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500;
|
||||||
|
padding: 3px 10px; border-radius: 5px; white-space: nowrap; }
|
||||||
|
.kfu-status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
||||||
|
.kfu-status.s-red { background: var(--kfu-risk-bg); color: var(--kfu-risk); }
|
||||||
|
.kfu-status.s-yellow { background: var(--kfu-warn-bg); color: var(--kfu-warn); }
|
||||||
|
.kfu-status.s-green { background: var(--kfu-ok-bg); color: var(--kfu-ok); }
|
||||||
|
.kfu-status.s-nodefault { background: var(--kfu-neutral-bg); color: var(--kfu-neutral); }
|
||||||
|
.kfu-status.s-nodata { background: var(--kfu-neutral-bg); color: var(--kfu-neutral); }
|
||||||
|
|
||||||
.kfu-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 520px; max-width: 94vw; background: var(--kt-ai-bg-elevated);
|
/* ячейка коэффициента: число + точка */
|
||||||
border-left: 1px solid var(--kt-ai-border); box-shadow: -8px 0 24px rgb(12 10 9 / .08); z-index: 100;
|
.kfu-coef { display: inline-flex; align-items: baseline; gap: 6px; }
|
||||||
padding: var(--kt-ai-space-7); overflow-y: auto; display: none; }
|
.kfu-coef .cv { font-weight: 500; font-variant-numeric: tabular-nums; }
|
||||||
|
.kfu-coef .cd { width: 6px; height: 6px; border-radius: 50%; align-self: center; flex-shrink: 0; }
|
||||||
|
.kfu-coef .cd.c-green { background: var(--kfu-ok); }
|
||||||
|
.kfu-coef .cd.c-yellow { background: var(--kfu-warn); }
|
||||||
|
.kfu-coef .cd.c-red { background: var(--kfu-risk); }
|
||||||
|
.kfu-coef .cd.c-nodefault { background: var(--kfu-neutral); }
|
||||||
|
.kfu-coef .cd.c-nodata { background: var(--kfu-neutral); opacity: .4; }
|
||||||
|
.kfu-coef .cv.cv-red { color: var(--kfu-risk); }
|
||||||
|
.kfu-coef .cv.cv-yellow { color: var(--kfu-warn); }
|
||||||
|
.kfu-coef .cv-none { color: var(--kfu-ink-3); font-size: 13px; }
|
||||||
|
.kfu-cause { font-size: 12px; color: var(--kfu-ink-2); line-height: 1.3; }
|
||||||
|
.kfu-cause.caution { color: var(--kfu-warn); font-weight: 500; }
|
||||||
|
.kfu-cause.danger { color: var(--kfu-risk); font-weight: 500; }
|
||||||
|
.kfu-dash { color: var(--kfu-ink-3); }
|
||||||
|
|
||||||
|
/* ---------- блок группы ---------- */
|
||||||
|
.kfu-group-block { margin-top: 16px; background: var(--kfu-surface); border: 1px solid var(--kfu-border);
|
||||||
|
border-radius: var(--kfu-radius); box-shadow: var(--kfu-shadow); overflow: hidden; }
|
||||||
|
.kfu-group-header { padding: 12px 16px; background: var(--kfu-neutral-bg); border-bottom: 1px solid var(--kfu-border);
|
||||||
|
font-size: 12px; font-weight: 600; color: var(--kfu-ink-2); text-transform: uppercase; letter-spacing: .4px; }
|
||||||
|
.kfu-group-block .kfu-table thead { display: none; }
|
||||||
|
.kfu-group-block .kfu-table td:first-child { font-weight: 600; }
|
||||||
|
|
||||||
|
/* ---------- диагностика ---------- */
|
||||||
|
.kfu-diag { margin-top: 20px; background: var(--kfu-surface); border: 1px solid var(--kfu-border);
|
||||||
|
border-radius: var(--kfu-radius); box-shadow: var(--kfu-shadow); }
|
||||||
|
.kfu-diag summary { padding: 12px 16px; font-size: 13px; font-weight: 500; color: var(--kfu-ink-2);
|
||||||
|
cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px;
|
||||||
|
font-variant-numeric: tabular-nums; }
|
||||||
|
.kfu-diag summary::-webkit-details-marker { display: none; }
|
||||||
|
.kfu-diag summary::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--kfu-warn); }
|
||||||
|
.kfu-diag[open] summary::before { background: var(--kfu-accent); }
|
||||||
|
.kfu-diag-list { padding: 0 16px 12px; max-height: 200px; overflow-y: auto; list-style: none; margin: 0; }
|
||||||
|
.kfu-diag-list li { padding: 6px 0; border-top: 1px solid var(--kfu-border); font-size: 12px;
|
||||||
|
color: var(--kfu-ink-2); font-family: var(--kt-ai-font-mono); font-variant-numeric: tabular-nums; }
|
||||||
|
.kfu-diag-list li:first-child { border-top: none; }
|
||||||
|
|
||||||
|
/* ---------- первая загрузка ---------- */
|
||||||
|
.kfu-upload { display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||||
|
text-align: center; padding: 72px 32px; background: var(--kfu-surface); border: 1px solid var(--kfu-border);
|
||||||
|
border-radius: var(--kfu-radius); box-shadow: var(--kfu-shadow); }
|
||||||
|
.kfu-upload-icon { width: 56px; height: 56px; border-radius: 14px; background: var(--kfu-accent-soft);
|
||||||
|
color: var(--kfu-accent); display: flex; align-items: center; justify-content: center; margin-bottom: 20px; }
|
||||||
|
.kfu-upload-icon svg { width: 28px; height: 28px; }
|
||||||
|
.kfu-upload h2 { font-size: 22px; font-weight: 600; color: var(--kfu-ink); letter-spacing: -.015em; margin: 0 0 8px; }
|
||||||
|
.kfu-upload p { font-size: 15px; color: var(--kfu-ink-2); max-width: 400px; line-height: 1.5; margin: 0 0 24px; }
|
||||||
|
.kfu-upload-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
|
||||||
|
.kfu-upload-note { margin-top: 16px; font-size: 12px; color: var(--kfu-ink-3); }
|
||||||
|
|
||||||
|
/* ---------- drawer (карточка компании) ---------- */
|
||||||
|
.kfu-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 520px; max-width: 94vw;
|
||||||
|
background: var(--kfu-surface); border-left: 1px solid var(--kfu-border); box-shadow: var(--kfu-shadow-md);
|
||||||
|
z-index: 100; padding: 24px; overflow-y: auto; display: none; }
|
||||||
.kfu-drawer.open { display: block; animation: kfuSlide .18s ease-out; }
|
.kfu-drawer.open { display: block; animation: kfuSlide .18s ease-out; }
|
||||||
@keyframes kfuSlide { from { transform: translateX(24px); opacity: .4; } to { transform: none; opacity: 1; } }
|
@keyframes kfuSlide { from { transform: translateX(24px); opacity: .4; } to { transform: none; opacity: 1; } }
|
||||||
.kfu-drawer-head { display: flex; align-items: flex-start; gap: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-5); }
|
.kfu-drawer-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 20px; }
|
||||||
.kfu-drawer-head h2 { font-size: var(--kt-ai-text-lg); font-weight: 500; margin: 0 0 4px; letter-spacing: -.01em; }
|
.kfu-drawer-head h2 { font-size: 22px; font-weight: 600; color: var(--kfu-ink); letter-spacing: -.02em;
|
||||||
.kfu-drawer-head .kt-ai-icon-button { margin-left: auto; }
|
line-height: 1.2; margin: 0 0 4px; flex: 1; }
|
||||||
.kfu-drawer-section { margin-bottom: var(--kt-ai-space-6); }
|
.kfu-drawer-head .close-btn { margin-left: auto; flex-shrink: 0; width: 32px; height: 32px;
|
||||||
.kfu-drawer-section h3 { font-size: var(--kt-ai-text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .6px;
|
border-radius: var(--kfu-radius-sm); border: 1px solid var(--kfu-border); background: var(--kfu-surface);
|
||||||
color: var(--kt-ai-fg-muted); margin: 0 0 10px; }
|
cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--kfu-ink-2); }
|
||||||
.kfu-metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--kt-ai-space-3); }
|
.kfu-drawer-head .close-btn:hover { background: var(--kfu-bg); }
|
||||||
.kfu-metric { border: 1px solid var(--kt-ai-card-border); border-radius: var(--vibe-card-radius); padding: 10px 12px; background: var(--kt-ai-bg); }
|
.kfu-drawer-head .close-btn svg { width: 16px; height: 16px; }
|
||||||
.kfu-metric .mlabel { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-bottom: 4px; display: flex; align-items: center; justify-content: space-between; gap: 6px; }
|
.kfu-drawer-section { margin-bottom: 20px; }
|
||||||
.kfu-metric .mval { font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
|
.kfu-drawer-section h3 { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .6px;
|
||||||
.kfu-metric .mhint { font-size: 11px; color: var(--kt-ai-fg-faint); margin-top: 3px; font-variant-numeric: tabular-nums; }
|
color: var(--kfu-ink-3); margin: 0 0 10px; }
|
||||||
.kfu-formula { font-size: 11px; color: var(--kt-ai-fg-faint); margin-top: 5px; font-family: var(--kt-ai-font-mono); }
|
|
||||||
.kfu-sources { width: 100%; border-collapse: collapse; font-size: var(--kt-ai-text-xs); }
|
|
||||||
.kfu-sources td { padding: 5px 0; border-bottom: 1px dashed var(--kt-ai-divider); vertical-align: top; }
|
|
||||||
.kfu-sources td:last-child { text-align: right; font-family: var(--kt-ai-font-mono); color: var(--kt-ai-fg-muted); white-space: nowrap; padding-left: 12px; }
|
|
||||||
.kfu-scenario-lbl { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-bottom: 10px; }
|
|
||||||
.kfu-scenario-row { display: flex; gap: 10px; align-items: stretch; }
|
|
||||||
.kfu-scenario-row .kt-ai-input { flex: 1; font-variant-numeric: tabular-nums; }
|
|
||||||
.kfu-delta { font-size: var(--kt-ai-text-sm); margin-top: 10px; padding: 10px 12px; border-radius: var(--vibe-card-radius);
|
|
||||||
background: var(--kt-ai-status-info-bg); color: var(--kt-ai-status-info-fg); font-variant-numeric: tabular-nums; }
|
|
||||||
.kfu-delta b { font-weight: 700; }
|
|
||||||
.kfu-drawer-actions { display: flex; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-6); flex-wrap: wrap; }
|
|
||||||
.kfu-drawer-actions .kt-ai-btn { flex: 1; }
|
|
||||||
|
|
||||||
.kfu-mock-banner { display: flex; align-items: center; gap: var(--kt-ai-space-4); padding: 10px 16px; margin-bottom: var(--kt-ai-space-5);
|
/* коэффициент в drawer */
|
||||||
border-radius: var(--vibe-card-radius); background: var(--kt-ai-chip-default-bg); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-sm); }
|
.kfu-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
||||||
.kfu-mock-banner button { margin-left: auto; }
|
.kfu-metric { border: 1px solid var(--kfu-border); border-radius: var(--kfu-radius-sm); padding: 12px;
|
||||||
|
background: var(--kfu-bg); }
|
||||||
|
.kfu-metric .mlabel { font-size: 11px; font-weight: 600; color: var(--kfu-ink-3);
|
||||||
|
text-transform: uppercase; letter-spacing: .3px; margin-bottom: 6px; display: flex; align-items: center; gap: 5px; }
|
||||||
|
.kfu-metric .mlabel .sdot { width: 6px; height: 6px; border-radius: 50%; }
|
||||||
|
.kfu-metric .mval { font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -.01em;
|
||||||
|
color: var(--kfu-ink); }
|
||||||
|
.kfu-metric .mval.mval-red { color: var(--kfu-risk); }
|
||||||
|
.kfu-metric .mval.mval-yellow { color: var(--kfu-warn); }
|
||||||
|
.kfu-metric .mval.mval-none { color: var(--kfu-ink-3); font-size: 14px; font-weight: 500; }
|
||||||
|
.kfu-metric .mhint { font-size: 11px; color: var(--kfu-ink-3); margin-top: 4px; font-variant-numeric: tabular-nums; }
|
||||||
|
.kfu-metric .mwhy { font-size: 11px; color: var(--kfu-warn); margin-top: 3px; font-weight: 500; }
|
||||||
|
.kfu-metric .mformula { font-size: 10px; color: var(--kfu-ink-3); margin-top: 6px;
|
||||||
|
padding-top: 6px; border-top: 1px solid var(--kfu-border); font-family: var(--kt-ai-font-mono); }
|
||||||
|
|
||||||
|
/* исходные данные */
|
||||||
|
.kfu-sources { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
.kfu-sources td { padding: 7px 0; border-bottom: 1px solid var(--kfu-border); vertical-align: top; }
|
||||||
|
.kfu-sources td:last-child { text-align: right; font-family: var(--kt-ai-font-mono); font-size: 12px;
|
||||||
|
color: var(--kfu-ink-2); white-space: nowrap; padding-left: 12px; font-variant-numeric: tabular-nums; }
|
||||||
|
.kfu-sources .src-addr { font-size: 11px; color: var(--kfu-ink-3); font-family: var(--kt-ai-font-mono); }
|
||||||
|
|
||||||
|
/* сценарий */
|
||||||
|
.kfu-scenario-lbl { font-size: 13px; color: var(--kfu-ink-2); margin-bottom: 8px; line-height: 1.4; }
|
||||||
|
.kfu-scenario-lbl b { color: var(--kfu-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||||
|
.kfu-scenario-row { display: flex; gap: 8px; align-items: stretch; }
|
||||||
|
.kfu-scenario-row .kfu-input { flex: 1; font-variant-numeric: tabular-nums; font-size: 14px;
|
||||||
|
height: 38px; border-radius: var(--kfu-radius-sm); padding: 0 12px; border: 1px solid var(--kfu-border-2);
|
||||||
|
background: var(--kfu-surface); color: var(--kfu-ink); }
|
||||||
|
.kfu-scenario-row .kfu-input:focus { outline: none; border-color: var(--kfu-accent); box-shadow: 0 0 0 3px var(--kfu-accent-soft); }
|
||||||
|
.kfu-scenario-hint { font-size: 12px; color: var(--kfu-ink-3); margin-top: 8px; line-height: 1.4; }
|
||||||
|
|
||||||
|
/* раскрываемый раздел источников */
|
||||||
|
.kfu-sources-block summary { font-size: 12px; font-weight: 500; color: var(--kfu-ink-2); cursor: pointer;
|
||||||
|
padding: 8px 0; list-style: none; display: flex; align-items: center; gap: 6px; }
|
||||||
|
.kfu-sources-block summary::-webkit-details-marker { display: none; }
|
||||||
|
.kfu-sources-block summary::after { content: " →"; font-size: 12px; transition: transform .15s; }
|
||||||
|
.kfu-sources-block[open] summary::after { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
/* ---------- toast ---------- */
|
||||||
.kfu-toast-stack { position: fixed; bottom: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
|
.kfu-toast-stack { position: fixed; bottom: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
|
||||||
.kfu-toast { padding: 10px 16px; border-radius: 10px; background: var(--kt-ai-fg); color: var(--kt-ai-bg-elevated);
|
.kfu-toast { padding: 10px 16px; border-radius: 8px; background: var(--kfu-ink); color: #fff;
|
||||||
font-size: var(--kt-ai-text-sm); box-shadow: var(--kt-ai-card-shadow-hover); display: flex; align-items: center; gap: 8px;
|
font-size: 13px; box-shadow: var(--kfu-shadow-md); display: flex; align-items: center; gap: 8px;
|
||||||
animation: kfuToastIn .15s ease-out; }
|
animation: kfuToastIn .15s ease-out; max-width: 400px; }
|
||||||
.kfu-toast.err { background: var(--kt-ai-status-risk-fg); }
|
.kfu-toast.err { background: var(--kfu-risk); }
|
||||||
@keyframes kfuToastIn { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
|
@keyframes kfuToastIn { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
|
||||||
|
|
||||||
.kfu-footnote { margin-top: var(--kt-ai-space-5); font-size: 11px; color: var(--kt-ai-fg-faint); }
|
/* ---------- empty state ---------- */
|
||||||
|
.kfu-empty { padding: 40px 24px; text-align: center; color: var(--kfu-ink-3); font-size: 14px; }
|
||||||
@media (max-width: 860px) {
|
.kfu-empty svg { width: 28px; height: 28px; margin-bottom: 8px; opacity: .4; }
|
||||||
.kfu-shell { grid-template-columns: 1fr; }
|
|
||||||
.kt-ai-sidebar { display: none; }
|
|
||||||
.kfu-content { padding: var(--kt-ai-space-5); }
|
|
||||||
.kfu-metrics { grid-template-columns: 1fr; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="kt-ai-app">
|
<body class="kt-ai-app">
|
||||||
<div class="kfu-shell">
|
|
||||||
|
|
||||||
<aside class="kt-ai-sidebar">
|
<!-- верхняя полоса -->
|
||||||
<div class="kt-ai-brand-row">
|
<header class="kfu-topbar">
|
||||||
<div style="width:28px;height:28px;border-radius:8px;background:var(--kt-ai-primary);color:var(--kt-ai-fg-on-fill);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px">V</div>
|
<div class="kfu-brand">
|
||||||
<div style="line-height:1.15">
|
<div class="kfu-brand-mark">КФУ</div>
|
||||||
<strong style="font-size:13px">Vibe AI</strong><br>
|
<div class="kfu-brand-text">
|
||||||
<span style="font-size:11px;color:var(--kt-ai-fg-muted)">КФУ · финансовая устойчивость</span>
|
<b>Финансовый мониторинг</b>
|
||||||
|
<span>Факт 2025</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<div class="kfu-topbar-right">
|
||||||
<div class="kt-ai-nav-group">Разделы</div>
|
<span class="kfu-file-badge" id="fileBadge" hidden>
|
||||||
<a class="kt-ai-nav-item" data-active="true" href="#top">
|
<span class="dot"></span>
|
||||||
<svg class="kt-icon" aria-hidden="true"><use href="#dashboard"></use></svg>
|
<span id="fileBadgeText"></span>
|
||||||
<span>Анализ факта 2025</span>
|
</span>
|
||||||
</a>
|
<button class="kfu-btn" id="btnReplace" hidden>
|
||||||
</nav>
|
<svg class="kt-icon" aria-hidden="true"><use href="#upload"></use></svg>
|
||||||
<div style="margin-top:auto">
|
Заменить файл
|
||||||
<div class="kt-ai-nav-group">Правила</div>
|
|
||||||
<div style="padding:4px 8px;font-size:11px;color:var(--kt-ai-fg-faint);line-height:1.55">
|
|
||||||
К1 = (долг + прочие) / EBITDA<br>
|
|
||||||
К2 = EBITDA / проценты<br>
|
|
||||||
К3 = (долг + прочие) / капитал<br>
|
|
||||||
<span style="color:var(--kt-ai-status-ok-fg)">●</span> до цели ·
|
|
||||||
<span style="color:var(--kt-ai-status-warn-fg)">●</span> до порога ·
|
|
||||||
<span style="color:var(--kt-ai-status-risk-fg)">●</span> выше порога
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="kfu-main">
|
|
||||||
<header class="kt-ai-topbar" id="top">
|
|
||||||
<span style="font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted)">КФУ · факт 2025</span>
|
|
||||||
<div style="flex:1"></div>
|
|
||||||
<button class="kt-ai-btn" data-size="sm" id="btnExport" hidden>
|
|
||||||
<svg class="kt-icon kt-icon-sm" aria-hidden="true"><use href="#download"></use></svg>
|
|
||||||
Выгрузить отчёт .xlsx
|
|
||||||
</button>
|
</button>
|
||||||
|
<button class="kfu-btn primary" id="btnExport" hidden>
|
||||||
|
<svg class="kt-icon" aria-hidden="true"><use href="#download"></use></svg>
|
||||||
|
Выгрузить отчёт
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="kfu-content">
|
<div class="kfu-wrap">
|
||||||
<div class="kfu-title-row">
|
|
||||||
|
<!-- заголовок -->
|
||||||
|
<div class="kfu-header" id="headerBlock">
|
||||||
|
<div class="kfu-header-left">
|
||||||
<h1>Финансовая устойчивость</h1>
|
<h1>Финансовая устойчивость</h1>
|
||||||
<span class="kt-ai-meta" id="mSource">файл не загружен</span>
|
<p class="kfu-header-sub" id="headerSub"></p>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:8px;align-items:center">
|
||||||
|
<span class="kfu-mock-chip" id="mockChip" hidden>Учебный пример · синтетические данные</span>
|
||||||
|
<span class="kfu-recon-chip" id="reconChip" hidden>
|
||||||
|
<svg class="kt-icon" aria-hidden="true"><use href="#check"></use></svg>
|
||||||
|
<span id="reconChipText"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kfu-actions">
|
<!-- сводка -->
|
||||||
<label class="dropzone" id="dropzone" title="Выбрать .xlsx с исходными данными">
|
<div class="kfu-brief" id="brief" hidden>
|
||||||
<svg class="kt-icon kt-icon-lg" aria-hidden="true"><use href="#upload"></use></svg>
|
<div class="kfu-brief-item">
|
||||||
<span><b>Загрузить .xlsx</b><br><span class="kt-ai-meta">файл остаётся в браузере, на сервер не отправляется</span></span>
|
<span class="kfu-brief-dot risk"></span>
|
||||||
<input type="file" id="fileInput" accept=".xlsx,.xls">
|
<div><span class="kfu-brief-num" id="briefRed">0</span><div class="kfu-brief-lbl">Нарушения</div></div>
|
||||||
</label>
|
</div>
|
||||||
<button class="kt-ai-btn" data-variant="ghost" id="btnSample">
|
<div class="kfu-brief-item">
|
||||||
<svg class="kt-icon kt-icon-sm" aria-hidden="true"><use href="#spreadsheet"></use></svg>
|
<span class="kfu-brief-dot warn"></span>
|
||||||
Учебный пример
|
<div><span class="kfu-brief-num" id="briefYellow">0</span><div class="kfu-brief-lbl">Под наблюдением</div></div>
|
||||||
</button>
|
</div>
|
||||||
|
<div class="kfu-brief-item">
|
||||||
|
<span class="kfu-brief-dot ok"></span>
|
||||||
|
<div><span class="kfu-brief-num" id="briefGreen">0</span><span class="kfu-brief-num-b" id="briefGreenSub"></span><div class="kfu-brief-lbl">В норме</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="kfu-brief-item">
|
||||||
|
<span class="kfu-brief-dot neutral"></span>
|
||||||
|
<div><span class="kfu-brief-num" id="briefNoNorm">0</span><div class="kfu-brief-lbl">Без нормативов</div></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kfu-mock-banner" id="mockBanner" hidden>
|
<!-- поиск + фильтры -->
|
||||||
<svg class="kt-icon" aria-hidden="true"><use href="#info"></use></svg>
|
|
||||||
<span><b>Учебный пример.</b> Все компании и суммы синтетические, созданы только для демонстрации интерфейса.</span>
|
|
||||||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" id="btnClearMock">Очистить</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="kfu-kpis" id="kpis" hidden></div>
|
|
||||||
|
|
||||||
<div class="kfu-toolbar" id="toolbar" hidden>
|
<div class="kfu-toolbar" id="toolbar" hidden>
|
||||||
<div class="kt-ai-search-field">
|
<div class="kfu-search">
|
||||||
<input class="kt-ai-search" id="q" placeholder="Поиск по названию…">
|
<svg class="kt-icon" aria-hidden="true"><use href="#search"></use></svg>
|
||||||
|
<input type="text" id="q" placeholder="Поиск по названию…">
|
||||||
</div>
|
</div>
|
||||||
<select class="kt-ai-select" id="zoneFilter">
|
<button class="kfu-chip-filter active" data-zone="" id="zoneAll">Все</button>
|
||||||
<option value="">Все зоны</option>
|
<button class="kfu-chip-filter" data-zone="red" id="zoneRed">Нарушения</button>
|
||||||
<option value="green">Зелёные</option>
|
<button class="kfu-chip-filter" data-zone="yellow" id="zoneYellow">Под наблюдением</button>
|
||||||
<option value="yellow">Жёлтые</option>
|
<button class="kfu-chip-filter" data-zone="nodefault" id="zoneNoNorm">Без нормативов</button>
|
||||||
<option value="red">Красные</option>
|
<span class="kfu-table-count" id="tableCount"></span>
|
||||||
</select>
|
|
||||||
<select class="kt-ai-select" id="normFilter">
|
|
||||||
<option value="">Нормативы: все</option>
|
|
||||||
<option value="with">С нормативом</option>
|
|
||||||
<option value="without">Без норматива</option>
|
|
||||||
</select>
|
|
||||||
<label class="kfu-check-wrap" style="margin-left:auto">
|
|
||||||
<span class="kt-ai-checkbox"><input type="checkbox" id="onlyRisk"></span>
|
|
||||||
Только красные
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kfu-table-wrap" id="tableWrap" hidden>
|
<!-- таблица компаний -->
|
||||||
|
<div class="kfu-table-card" id="tableWrap" hidden>
|
||||||
<table class="kfu-table">
|
<table class="kfu-table">
|
||||||
<colgroup>
|
<colgroup><col><col><col><col><col><col></colgroup>
|
||||||
<col><col><col><col><col>
|
|
||||||
</colgroup>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th data-k="name">Объект</th>
|
<th data-k="name">Компания</th>
|
||||||
<th>Итог</th>
|
<th>Статус</th>
|
||||||
<th data-k="k1">К1<span class="th-sub">Debt/EBITDA</span></th>
|
<th data-k="k1">К1<span class="th-sub">Debt/EBITDA</span></th>
|
||||||
<th data-k="k2">К2<span class="th-sub">EBITDA/Int</span></th>
|
<th data-k="k2">К2<span class="th-sub">EBITDA/Int</span></th>
|
||||||
<th data-k="k3">К3<span class="th-sub">Debt/Equity</span></th>
|
<th data-k="k3">К3<span class="th-sub">Debt/Equity</span></th>
|
||||||
|
<th>Причина</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tbody"></tbody>
|
<tbody id="tbody"></tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="kfu-empty" id="emptyState" hidden>
|
<div class="kfu-empty" id="emptyState" hidden>
|
||||||
<svg class="kt-icon" aria-hidden="true"><use href="#search"></use></svg>
|
<svg class="kt-icon" aria-hidden="true"><use href="#search"></use></svg>
|
||||||
<div>Ничего не найдено. Измените фильтры или сбросьте запрос.</div>
|
<div>Ничего не найдено. Измените фильтры.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="kfu-footnote" id="footnote"></p>
|
<!-- блок группы -->
|
||||||
</div>
|
<div class="kfu-group-block" id="groupBlock" hidden>
|
||||||
</main>
|
<div class="kfu-group-header">Консолидированная группа</div>
|
||||||
|
<table class="kfu-table" style="border-radius:0">
|
||||||
|
<colgroup><col><col><col><col><col><col></colgroup>
|
||||||
|
<tbody id="groupTbody"></tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- первая загрузка -->
|
||||||
|
<div class="kfu-upload" id="uploadPanel">
|
||||||
|
<div class="kfu-upload-icon">
|
||||||
|
<svg class="kt-icon" aria-hidden="true"><use href="#upload"></use></svg>
|
||||||
|
</div>
|
||||||
|
<h2>Загрузите сводную книгу</h2>
|
||||||
|
<p>Получите коэффициенты, зоны риска и отчёт в .xlsx</p>
|
||||||
|
<div class="kfu-upload-actions">
|
||||||
|
<button class="kfu-btn primary" id="btnUpload" style="height:40px;padding:0 24px;font-size:14px">
|
||||||
|
<svg class="kt-icon" aria-hidden="true"><use href="#upload"></use></svg>
|
||||||
|
Загрузить .xlsx
|
||||||
|
</button>
|
||||||
|
<button class="kfu-btn" id="btnSample" style="height:40px;padding:0 20px;font-size:14px">
|
||||||
|
<svg class="kt-icon" aria-hidden="true"><use href="#spreadsheet"></use></svg>
|
||||||
|
Учебный пример
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-size:12px;color:var(--kfu-ink-3);margin:16px 0 0">Файл обрабатывается в браузере и не отправляется на сервер</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- диагностика -->
|
||||||
|
<details class="kfu-diag" id="diagBlock" hidden>
|
||||||
|
<summary id="diagSummary"></summary>
|
||||||
|
<ul class="kfu-diag-list" id="diagList"></ul>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- drawer -->
|
||||||
<aside class="kfu-drawer" id="drawer" role="dialog" aria-modal="false" aria-labelledby="drawerTitle">
|
<aside class="kfu-drawer" id="drawer" role="dialog" aria-modal="false" aria-labelledby="drawerTitle">
|
||||||
<div id="drawerBody"></div>
|
<div id="drawerBody"></div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="kfu-toast-stack" id="toasts"></div>
|
<div class="kfu-toast-stack" id="toasts"></div>
|
||||||
|
|
||||||
|
<input type="file" id="fileInput" accept=".xlsx,.xls" style="display:none">
|
||||||
|
|
||||||
<script src="kfu-client.js"></script>
|
<script src="kfu-client.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
474
kfu-client.js
474
kfu-client.js
@ -550,31 +550,13 @@
|
|||||||
function screenEntry(e) {
|
function screenEntry(e) {
|
||||||
return recomputeFor(e, S.scen[e.name] || 0);
|
return recomputeFor(e, S.scen[e.name] || 0);
|
||||||
}
|
}
|
||||||
function visibleEntries() {
|
function zonePriority(z) {
|
||||||
var q = S.q.toLowerCase();
|
var p = { red: 0, yellow: 1, nodata: 2, green: 3, nodefault: 4 }[z];
|
||||||
var out = [];
|
return p === undefined ? 9 : p;
|
||||||
for (var i = 0; i < S.entries.length; i++) {
|
|
||||||
var e = S.entries[i];
|
|
||||||
var sc = screenEntry(e);
|
|
||||||
if (q && e.name.toLowerCase().indexOf(q) === -1) continue;
|
|
||||||
var wzone = worst(sc);
|
|
||||||
if (S.zone) {
|
|
||||||
if (S.zone === "red" ? wzone !== "red" : wzone !== S.zone) continue;
|
|
||||||
}
|
|
||||||
function hasNumericNorm(x) {
|
|
||||||
var n = x.norms; if (!n) return false;
|
|
||||||
var keys = ["k1t", "k1th", "k2t", "k2th", "k3t", "k3th"];
|
|
||||||
for (var j = 0; j < keys.length; j++) if (typeof n[keys[j]] === "number") return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (S.norm === "with" && !hasNumericNorm(e)) continue;
|
|
||||||
if (S.norm === "without" && hasNumericNorm(e)) continue;
|
|
||||||
if (S.onlyRisk && !(sc.k1Zone === "red" || sc.k2Zone === "red" || sc.k3Zone === "red")) continue;
|
|
||||||
sc._name = e.name;
|
|
||||||
out.push(sc);
|
|
||||||
}
|
}
|
||||||
|
function filterSortList(list) {
|
||||||
var k = S.sort.k, d = S.sort.d;
|
var k = S.sort.k, d = S.sort.d;
|
||||||
out.sort(function (a, b) {
|
list.sort(function (a, b) {
|
||||||
var va = sortVal(a, k), vb = sortVal(b, k);
|
var va = sortVal(a, k), vb = sortVal(b, k);
|
||||||
if (va == null && vb == null) return 0;
|
if (va == null && vb == null) return 0;
|
||||||
if (va == null) return 1;
|
if (va == null) return 1;
|
||||||
@ -582,11 +564,8 @@
|
|||||||
if (typeof va === "string") return va.localeCompare(vb) * d;
|
if (typeof va === "string") return va.localeCompare(vb) * d;
|
||||||
return (va - vb) * d;
|
return (va - vb) * d;
|
||||||
});
|
});
|
||||||
if (S.entries.length) {
|
list.sort(function (a, b) { return zonePriority(worst(a)) - zonePriority(worst(b)); });
|
||||||
/* группа всегда последней */
|
return list;
|
||||||
out.sort(function (a, b) { return (a.group ? 1 : 0) - (b.group ? 1 : 0); });
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
function sortVal(e, k) {
|
function sortVal(e, k) {
|
||||||
if (k === "name") return e.name;
|
if (k === "name") return e.name;
|
||||||
@ -603,99 +582,218 @@
|
|||||||
if (k === "k3n") return (e.norms && e.norms.k3t) ? 1 : 0;
|
if (k === "k3n") return (e.norms && e.norms.k3t) ? 1 : 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
function pill(e, key) {
|
/* ---------- отображение: статус, коэффициенты, причина ---------- */
|
||||||
var zn = e[key + "Zone"];
|
function hasNumericNorm(e) {
|
||||||
if (!zn) return '<span class="zone-pill z-nodata">—</span>';
|
var n = e.norms; if (!n) return false;
|
||||||
var cls = { green: "z-green", yellow: "z-yellow", red: "z-red", nodefault: "z-nodefault", nodata: "z-nodata" }[zn];
|
var keys = ["k1t", "k1th", "k2t", "k2th", "k3t", "k3th"];
|
||||||
return "<span class=\"zone-pill " + cls + "\" title=\"" + esc(e[key + "ZoneLabel"] || "") + "\">" + zoneLabel(zn) + "</span>";
|
for (var i = 0; i < keys.length; i++) if (typeof n[keys[i]] === "number") return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
function normCell(e, key) {
|
function statusHtml(zone) {
|
||||||
var n = e.norms;
|
var cls = { green: "s-green", yellow: "s-yellow", red: "s-red", nodefault: "s-nodefault", nodata: "s-nodata" }[zone] || "s-nodata";
|
||||||
if (!n) return '<span class="none">—</span>';
|
return "<span class='kfu-status " + cls + "'>" + zoneLabel(zone) + "</span>";
|
||||||
var t = n[key + (key === "k1" ? "t" : key === "k2" ? "t" : "t")];
|
|
||||||
var th = n[key + "th"];
|
|
||||||
var tv = normNum(t), hv = normNum(th);
|
|
||||||
function f(x) { return x == null ? "N/A" : nfmt(x, 2); }
|
|
||||||
return esc(f(tv) + " / " + f(hv));
|
|
||||||
}
|
}
|
||||||
function render() {
|
function coefCell(e, key) {
|
||||||
var body = $("#tbody");
|
var v = e[key];
|
||||||
if (!S.source && !S.isMock) {
|
var zn = e[key + "Zone"] || "nodata";
|
||||||
$("#kpis").hidden = true; $("#toolbar").hidden = true; $("#tableWrap").hidden = true; $("#btnExport").hidden = true;
|
var dotCls = { green: "c-green", yellow: "c-yellow", red: "c-red", nodefault: "c-nodefault", nodata: "c-nodata" }[zn] || "c-nodata";
|
||||||
$("#mSource").textContent = "";
|
var valHtml;
|
||||||
$("#mockBanner").hidden = true;
|
if (v == null) {
|
||||||
return;
|
if (e.why && e.why[key] && /EBITDA/i.test(e.why[key]) && e.ebitda != null && e.ebitda <= 0) {
|
||||||
|
valHtml = "<span class='cv-none'>EBITDA ≤ 0</span>";
|
||||||
|
} else {
|
||||||
|
valHtml = "<span class='cv-none'>—</span>";
|
||||||
}
|
}
|
||||||
$("#kpis").hidden = false; $("#toolbar").hidden = false; $("#tableWrap").hidden = false; $("#btnExport").hidden = false;
|
} else {
|
||||||
$("#btnExport").title = "Снимок текущего расчёта: коэффициенты, нормативы, сверка и источники";
|
var cvCls = zn === "red" ? " cv-red" : (zn === "yellow" ? " cv-yellow" : "");
|
||||||
$("#mSource").textContent = S.isMock ? "учебный пример (синтетика)" : (S.source || "");
|
valHtml = "<span class='cv" + cvCls + "'>" + nfmt(v, 2) + "</span>";
|
||||||
$("#mockBanner").hidden = !S.isMock;
|
}
|
||||||
|
return "<span class='kfu-coef'>" + valHtml + "<span class='cd " + dotCls + "'></span></span>";
|
||||||
renderDiag();
|
}
|
||||||
renderKpis();
|
function causeFor(e) {
|
||||||
var rows = visibleEntries();
|
var parts = [];
|
||||||
if (!rows.length) { body.innerHTML = ""; $("#emptyState").hidden = false; return; }
|
/* «Без норматива» — отдельная фраза, без числовых сравнений */
|
||||||
$("#emptyState").hidden = true;
|
if (worst(e) === "nodefault" && !hasNumericNorm(e)) {
|
||||||
var html = "";
|
return "<span class='kfu-cause'>Нормативы не установлены</span>";
|
||||||
for (var i = 0; i < rows.length; i++) {
|
}
|
||||||
var e = rows[i];
|
if (e.ebitda != null && e.ebitda <= 0 && (e.k1Zone === "red" || e.k2Zone === "red")) {
|
||||||
|
parts.push("Отрицательная EBITDA");
|
||||||
|
}
|
||||||
|
var keys = ["k1", "k2", "k3"];
|
||||||
|
var missOrder = ["debt", "other", "ebitda", "intExp", "equity"];
|
||||||
|
var missName = { debt: "долг", other: "прочие/гарантии", ebitda: "EBITDA", intExp: "проценты", equity: "капитал" };
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var k = keys[i];
|
||||||
|
var zone = e[k + "Zone"] || "nodata";
|
||||||
|
if (zone === "green" || zone === "nodefault") continue;
|
||||||
|
if (zone === "nodata" || e[k] == null) {
|
||||||
|
/* конкретный недостающий показатель */
|
||||||
|
var fieldMiss = k === "k1" ? "debt" : k === "k2" ? "intExp" : "equity";
|
||||||
|
if (fieldMiss === "intExp" && e.ebitda != null && e.ebitda <= 0) continue; /* уже фразой выше */
|
||||||
|
if (fieldMiss === "debt" && e.debt == null) parts.push("нет долга");
|
||||||
|
else if (fieldMiss === "debt" && e.other == null) parts.push("нет прочих/гарантий");
|
||||||
|
else if (fieldMiss === "intExp") parts.push("нет процентов");
|
||||||
|
else if (fieldMiss === "equity") parts.push("нет капитала");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var th = normNum(e.norms && e.norms[k + "th"]);
|
||||||
|
var tg = normNum(e.norms && e.norms[k + "t"]);
|
||||||
|
var val = nfmt(e[k], 2);
|
||||||
|
if (zone === "red") {
|
||||||
|
if (k === "k2") parts.push("К2 " + val + " ниже порога " + nfmt(th, 2));
|
||||||
|
else parts.push("К" + (i + 1) + " " + val + " выше порога " + nfmt(th, 2));
|
||||||
|
} else if (zone === "yellow") {
|
||||||
|
if (k === "k2") parts.push("К2 " + val + " ниже цели " + nfmt(tg, 2));
|
||||||
|
else parts.push("К" + (i + 1) + " " + val + " выше цели " + nfmt(tg, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!parts.length) return "<span class='kfu-dash'>—</span>";
|
||||||
|
var w = worst(e);
|
||||||
|
var cls = w === "red" ? "danger" : (w === "yellow" ? "caution" : "");
|
||||||
|
return "<span class='kfu-cause " + cls + "'>" + esc(parts.join("; ")) + "</span>";
|
||||||
|
}
|
||||||
|
function tableRow(e, isGroup) {
|
||||||
var wzone = worst(e);
|
var wzone = worst(e);
|
||||||
var wcls = { green: "z-green", yellow: "z-yellow", red: "z-red", nodefault: "z-nodefault", nodata: "z-nodata" }[wzone] || "z-nodata";
|
var name = e._name || e.name;
|
||||||
html += "<tr data-name=\"" + esc(e._name || e.name) + "\" class=\"" + (e.group ? "is-group" : "") + "\">" +
|
return "<tr data-name=\"" + esc(name) + "\" class=\"" + (isGroup ? "is-group" : "") + "\">" +
|
||||||
"<td title=\"" + esc(e._name || e.name) + "\">" + esc(e._name || e.name) + (e.group ? " <span class='kt-ai-chip' data-tone='blue' style='margin-left:6px'>группа</span>" : "") + "</td>" +
|
"<td><div class='co-name' title=\"" + esc(name) + "\">" + esc(name) + "</div></td>" +
|
||||||
"<td><span class='zone-pill " + wcls + "'></span> " + zoneLabel(wzone) + "</td>" +
|
"<td>" + statusHtml(wzone) + "</td>" +
|
||||||
"<td>" + coefDisplay(e, "k1") + " " + pill(e, "k1") + "</td>" +
|
"<td>" + coefCell(e, "k1") + "</td>" +
|
||||||
"<td>" + coefDisplay(e, "k2") + " " + pill(e, "k2") + "</td>" +
|
"<td>" + coefCell(e, "k2") + "</td>" +
|
||||||
"<td>" + coefDisplay(e, "k3") + " " + pill(e, "k3") + "</td>" +
|
"<td>" + coefCell(e, "k3") + "</td>" +
|
||||||
|
"<td>" + (wzone === "green" ? "<span class='kfu-dash'>—</span>" : causeFor(e)) + "</td>" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- основной рендер ---------- */
|
||||||
|
function render() {
|
||||||
|
var hasData = !!(S.source || S.isMock);
|
||||||
|
/* первая загрузка */
|
||||||
|
if (!hasData) {
|
||||||
|
$("#headerSub").textContent = "";
|
||||||
|
$("#reconChip").hidden = true;
|
||||||
|
$("#brief").hidden = true;
|
||||||
|
$("#toolbar").hidden = true;
|
||||||
|
$("#tableWrap").hidden = true;
|
||||||
|
$("#groupBlock").hidden = true;
|
||||||
|
$("#diagBlock").hidden = true;
|
||||||
|
$("#uploadPanel").hidden = false;
|
||||||
|
$("#fileBadge").hidden = true;
|
||||||
|
$("#btnReplace").hidden = true;
|
||||||
|
$("#btnExport").hidden = true;
|
||||||
|
$("#mockChip").hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#uploadPanel").hidden = true;
|
||||||
|
$("#headerSub").hidden = false;
|
||||||
|
$("#brief").hidden = false;
|
||||||
|
$("#toolbar").hidden = false;
|
||||||
|
$("#tableWrap").hidden = false;
|
||||||
|
$("#btnReplace").hidden = false;
|
||||||
|
$("#btnExport").hidden = false;
|
||||||
|
$("#mockChip").hidden = !S.isMock;
|
||||||
|
|
||||||
|
/* файл-бейдж: реальное имя + постоянная метка учебного режима */
|
||||||
|
var shortName = S.source || "учебный пример";
|
||||||
|
if (shortName.length > 28) shortName = shortName.slice(0, 26) + "…";
|
||||||
|
$("#fileBadge").hidden = false;
|
||||||
|
$("#fileBadgeText").textContent = S.isMock ? shortName + " · учебный пример (синтетика)" : shortName;
|
||||||
|
|
||||||
|
/* данные: компании + группа, по экрану (сценарий) */
|
||||||
|
var comps = [], grp = null;
|
||||||
|
for (var i = 0; i < S.entries.length; i++) {
|
||||||
|
var se = screenEntry(S.entries[i]);
|
||||||
|
if (S.entries[i].group) grp = se; else comps.push(se);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* заголовок + счётчики: зелёные только при worst === "green" */
|
||||||
|
var r = 0, y = 0, g = 0, nn = 0, nd = 0, co = comps.length;
|
||||||
|
for (var ci = 0; ci < comps.length; ci++) {
|
||||||
|
var c = comps[ci];
|
||||||
|
var w = worst(c);
|
||||||
|
if (w === "red") r++;
|
||||||
|
else if (w === "yellow") y++;
|
||||||
|
else if (w === "green") g++;
|
||||||
|
else if (w === "nodefault") nn++;
|
||||||
|
else nd++;
|
||||||
|
}
|
||||||
|
var subParts = [];
|
||||||
|
if (r > 0) subParts.push("<b>" + r + "</b> " + plural(r, "компании", "компании", "компаний") + " нарушают нормативы");
|
||||||
|
if (y > 0) subParts.push("<b>" + y + "</b> " + plural(y, "компания требует", "компании требуют", "компаний требуют") + " внимания");
|
||||||
|
if (r === 0 && y === 0 && g === co) subParts.push("Все компании в норме");
|
||||||
|
else if (!subParts.length) subParts.push("Нарушений нет");
|
||||||
|
if (nn > 0) subParts.push("<b>" + nn + "</b> — " + plural(nn, "компания без нормативов", "компании без нормативов", "компаний без нормативов"));
|
||||||
|
if (nd > 0) subParts.push("<b>" + nd + "</b> — " + plural(nd, "компания с", "компании с", "компаний с") + " неполными данными");
|
||||||
|
$("#headerSub").innerHTML = subParts.join(", ");
|
||||||
|
|
||||||
|
/* сверка — исходные данные */
|
||||||
|
if (S.recon) {
|
||||||
|
$("#reconChip").hidden = false;
|
||||||
|
$("#reconChipText").textContent = "Сверка " + S.recon.ok + "/" + S.recon.total + " по исходным";
|
||||||
|
} else {
|
||||||
|
$("#reconChip").hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* сводка: зелёные = только в норме; без нормативов и нет данных — отдельно */
|
||||||
|
$("#briefRed").textContent = r;
|
||||||
|
$("#briefYellow").textContent = y;
|
||||||
|
$("#briefGreen").textContent = g;
|
||||||
|
$("#briefGreenSub").textContent = nd > 0 ? " + " + nd + " нет данных" : "";
|
||||||
|
$("#briefNoNorm").textContent = nn;
|
||||||
|
|
||||||
|
renderDiag();
|
||||||
|
|
||||||
|
/* компании (без группы) */
|
||||||
|
var filtered = S.q ? comps.filter(function (e) {
|
||||||
|
if (e.name.toLowerCase().indexOf(S.q.toLowerCase()) === -1) return false;
|
||||||
|
var wz = worst(e);
|
||||||
|
if (!S.zone) return true;
|
||||||
|
if (S.zone === "nodefault") return wz === "nodefault";
|
||||||
|
return wz === S.zone;
|
||||||
|
}) : comps;
|
||||||
|
filtered = filterSortList(filtered);
|
||||||
|
var body = $("#tbody");
|
||||||
|
if (!filtered.length) {
|
||||||
|
body.innerHTML = "";
|
||||||
|
$("#tbody").hidden = true;
|
||||||
|
$("#emptyState").hidden = false;
|
||||||
|
} else {
|
||||||
|
$("#tbody").hidden = false;
|
||||||
|
$("#emptyState").hidden = true;
|
||||||
|
var html = "";
|
||||||
|
for (var j = 0; j < filtered.length; j++) html += tableRow(filtered[j], false);
|
||||||
body.innerHTML = html;
|
body.innerHTML = html;
|
||||||
|
}
|
||||||
|
$("#tableCount").textContent = filtered.length + " из " + co;
|
||||||
|
|
||||||
|
/* блок группы: отдельно из S.entries, скрывается только под фильтром/поиском, несовместимым с её зоной */
|
||||||
|
var showGroup = !!grp && (!S.q || (grp.name.toLowerCase().indexOf(S.q.toLowerCase()) !== -1)) &&
|
||||||
|
(!S.zone || worst(grp) === S.zone);
|
||||||
|
if (showGroup) {
|
||||||
|
$("#groupBlock").hidden = false;
|
||||||
|
$("#groupTbody").innerHTML = tableRow(grp, true);
|
||||||
|
} else {
|
||||||
|
$("#groupBlock").hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
/* сортировка */
|
/* сортировка */
|
||||||
var ths = document.querySelectorAll(".kfu-table th[data-k]");
|
var ths = document.querySelectorAll("#tableWrap .kfu-table th[data-k]");
|
||||||
for (var t = 0; t < ths.length; t++) {
|
for (var t = 0; t < ths.length; t++) {
|
||||||
var k = ths[t].getAttribute("data-k");
|
var k = ths[t].getAttribute("data-k");
|
||||||
ths[t].className = k === S.sort.k ? (S.sort.d === 1 ? "sorted-asc" : "sorted-desc") : "";
|
ths[t].className = k === S.sort.k ? (S.sort.d === 1 ? "sorted-asc" : "sorted-desc") : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* активный фильтр-чип */
|
||||||
|
var chips = document.querySelectorAll(".kfu-chip-filter");
|
||||||
|
for (var c = 0; c < chips.length; c++) {
|
||||||
|
chips[c].classList.toggle("active", (chips[c].getAttribute("data-zone") || "") === S.zone);
|
||||||
}
|
}
|
||||||
function renderKpis() {
|
|
||||||
var kpis = $("#kpis");
|
|
||||||
var co = 0, grp = 0;
|
|
||||||
var g = 0, y = 0, r = 0, nd = 0, nn = 0;
|
|
||||||
function hasNorm(e) {
|
|
||||||
var n = e.norms; if (!n) return false;
|
|
||||||
var keys = ["k1t", "k1th", "k2t", "k2th", "k3t", "k3th"];
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var v = n[keys[i]];
|
|
||||||
if (typeof v === "number") return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
function plural(n, a, b, c) {
|
||||||
}
|
var n10 = n % 10, n100 = n % 100;
|
||||||
var grpZone = "";
|
if (n10 === 1 && n100 !== 11) return a;
|
||||||
for (var i = 0; i < S.entries.length; i++) {
|
if (n10 >= 2 && n10 <= 4 && (n100 < 12 || n100 > 14)) return b;
|
||||||
var e = S.entries[i];
|
return c;
|
||||||
var sc = screenEntry(e);
|
|
||||||
if (e.group) { grp++; grpZone = worst(sc); continue; }
|
|
||||||
co++;
|
|
||||||
var w = worst(sc);
|
|
||||||
if (w === "red") r++;
|
|
||||||
else if (w === "yellow") y++;
|
|
||||||
else if (w === "nodata") nd++;
|
|
||||||
else if (w === "nodefault") {
|
|
||||||
if (hasNorm(e)) g++; else nn++;
|
|
||||||
}
|
|
||||||
else g++;
|
|
||||||
}
|
|
||||||
var grpCls = { green: "var(--kt-ai-status-ok-fg)", yellow: "var(--kt-ai-status-warn-fg)", red: "var(--kt-ai-status-risk-fg)", nodata: "var(--kt-ai-fg-muted)", nodefault: "var(--kt-ai-fg-muted)" }[grpZone] || "";
|
|
||||||
var reconOk = S.recon ? S.recon.ok + " / " + S.recon.total : "—";
|
|
||||||
kpis.innerHTML =
|
|
||||||
"<div class='kt-ai-kpi'><span class='label'>Компании</span><span class='value'>" + co + "</span><span class='hint'>из файла</span></div>" +
|
|
||||||
"<div class='kt-ai-kpi'><span class='label'>Группа</span><span class='value'>" + grp + "</span><span class='hint'>консолидированная</span></div>" +
|
|
||||||
"<div class='kt-ai-kpi'><span class='label'>Зелёные</span><span class='value' style='color:var(--kt-ai-status-ok-fg)'>" + g + "</span><span class='hint'>все зоны в норме</span></div>" +
|
|
||||||
"<div class='kt-ai-kpi'><span class='label'>Жёлтые</span><span class='value' style='color:var(--kt-ai-status-warn-fg)'>" + y + "</span><span class='hint'>между целью и порогом</span></div>" +
|
|
||||||
"<div class='kt-ai-kpi'><span class='label'>Красные</span><span class='value' style='color:var(--kt-ai-status-risk-fg)'>" + r + "</span><span class='hint'>нарушен порог или EBITDA≤0</span></div>" +
|
|
||||||
(nn ? "<div class='kt-ai-kpi'><span class='label'>Без норматива</span><span class='value' style='color:var(--kt-ai-fg-muted)'>" + nn + "</span><span class='hint'>нет числовых норм</span></div>" : "") +
|
|
||||||
(nd ? "<div class='kt-ai-kpi'><span class='label'>Нет данных</span><span class='value' style='color:var(--kt-ai-fg-muted)'>" + nd + "</span><span class='hint'>отсутствуют исходные</span></div>" : "") +
|
|
||||||
(S.recon ? "<div class='kt-ai-kpi'><span class='label'>Сверка (G5:J20)</span><span class='value'>" + reconOk + "</span><span class='hint'>допуск 0,01 млн ₸</span></div>" : "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- drawer ---------- */
|
/* ---------- drawer ---------- */
|
||||||
@ -712,12 +810,24 @@
|
|||||||
function metricHtml(e, key, title, formula) {
|
function metricHtml(e, key, title, formula) {
|
||||||
var v = e[key];
|
var v = e[key];
|
||||||
var zn = e[key + "Zone"] || "nodata";
|
var zn = e[key + "Zone"] || "nodata";
|
||||||
var cls = { green: "var(--kt-ai-status-ok-fg)", yellow: "var(--kt-ai-status-warn-fg)", red: "var(--kt-ai-status-risk-fg)", nodefault: "var(--kt-ai-fg-muted)", nodata: "var(--kt-ai-fg-muted)" }[zn];
|
var dotCls = { green: "c-green", yellow: "c-yellow", red: "c-red", nodefault: "c-nodefault", nodata: "c-nodata" }[zn] || "c-nodata";
|
||||||
return "<div class='kfu-metric'><div class='mlabel'>" + title + " " + pill(e, key) + "</div>" +
|
var valCls = zn === "red" ? " mval-red" : (zn === "yellow" ? " mval-yellow" : "");
|
||||||
"<div class='mval' style='color:" + cls + "'>" + (v == null && e.why && e.why[key] && /EBITDA/i.test(e.why[key]) && e.ebitda != null && e.ebitda <= 0 ? "EBITDA ≤ 0" : fmtR(v).replace(/<[^>]+>/g, function (m) { return m === "<span class=\"none\">нет данных<\/span>" ? "<span class='none'>нет данных</span>" : m; })) + "</div>" +
|
var valHtml;
|
||||||
|
if (v == null) {
|
||||||
|
if (e.why && e.why[key] && /EBITDA/i.test(e.why[key]) && e.ebitda != null && e.ebitda <= 0) {
|
||||||
|
valHtml = "<div class='mval mval-none'>EBITDA ≤ 0</div>";
|
||||||
|
} else {
|
||||||
|
valHtml = "<div class='mval mval-none'>—</div>";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valHtml = "<div class='mval" + valCls + "'>" + nfmt(v, 2) + "</div>";
|
||||||
|
}
|
||||||
|
return "<div class='kfu-metric'>" +
|
||||||
|
"<div class='mlabel'>" + title + " <span class='sdot " + dotCls + "'></span></div>" +
|
||||||
|
valHtml +
|
||||||
"<div class='mhint'>" + normLine(e, key) + "</div>" +
|
"<div class='mhint'>" + normLine(e, key) + "</div>" +
|
||||||
(e.why && e.why[key] ? "<div class='mhint' style='color:var(--kt-ai-status-warn-fg)'>" + esc(e.why[key]) + "</div>" : "") +
|
(e.why && e.why[key] ? "<div class='mwhy'>" + esc(e.why[key]) + "</div>" : "") +
|
||||||
"<div class='kfu-formula'>" + formula + "</div></div>";
|
"<div class='mformula'>" + formula + "</div></div>";
|
||||||
}
|
}
|
||||||
function normLine(e, key) {
|
function normLine(e, key) {
|
||||||
var n = e.norms;
|
var n = e.norms;
|
||||||
@ -728,59 +838,74 @@
|
|||||||
}
|
}
|
||||||
function renderDrawer(en) {
|
function renderDrawer(en) {
|
||||||
var delta = S.scen[en.name] || 0;
|
var delta = S.scen[en.name] || 0;
|
||||||
/* экранная копия — coefFor с дельтой, база en не меняется */
|
|
||||||
var sc = recomputeFor(en, delta);
|
var sc = recomputeFor(en, delta);
|
||||||
var baseOther = en.other;
|
var baseOther = en.other;
|
||||||
var newOther = (baseOther == null ? null : baseOther + delta);
|
var newOther = (baseOther == null ? null : baseOther + delta);
|
||||||
|
var wzone = worst(sc);
|
||||||
var body = "";
|
var body = "";
|
||||||
body += "<div class='kfu-drawer-head'><div><h2 id='drawerTitle'>" + esc(en.name) + "</h2>" +
|
|
||||||
"<span class='kt-ai-meta'>" + (en.group ? "Консолидированная группа — считается отдельно, не складывается с компаниями" : "Портфельная компания") + "</span></div>" +
|
|
||||||
"<button class='kt-ai-icon-button' id='btnCloseDrawer' aria-label='Закрыть'><svg class='kt-icon' aria-hidden='true'><use href='#x'></use></svg></button></div>";
|
|
||||||
|
|
||||||
|
/* заголовок: имя + статус */
|
||||||
|
body += "<div class='kfu-drawer-head'><div><h2 id='drawerTitle'>" + esc(en.name) + "</h2>" +
|
||||||
|
statusHtml(wzone) + "</div>" +
|
||||||
|
"<button class='close-btn' id='btnCloseDrawer' aria-label='Закрыть'><svg class='kt-icon' aria-hidden='true'><use href='#x'></use></svg></button></div>";
|
||||||
|
|
||||||
|
/* коэффициенты */
|
||||||
body += "<div class='kfu-drawer-section'><h3>Коэффициенты</h3><div class='kfu-metrics'>" +
|
body += "<div class='kfu-drawer-section'><h3>Коэффициенты</h3><div class='kfu-metrics'>" +
|
||||||
metricHtml(sc, "k1", "К1", "(долг + прочие/гарантии) / EBITDA · меньше лучше") +
|
metricHtml(sc, "k1", "К1", "(долг + прочие/гарантии) / EBITDA") +
|
||||||
metricHtml(sc, "k2", "К2", "EBITDA / процентные расходы · больше лучше") +
|
metricHtml(sc, "k2", "К2", "EBITDA / процентные расходы") +
|
||||||
metricHtml(sc, "k3", "К3", "(долг + прочие/гарантии) / капитал · меньше лучше") +
|
metricHtml(sc, "k3", "К3", "(долг + прочие/гарантии) / капитал") +
|
||||||
"</div></div>";
|
"</div></div>";
|
||||||
|
|
||||||
body += "<div class='kfu-drawer-section'><h3>Исходные данные (млн ₸)</h3><table class='kfu-sources'>" +
|
/* сценарий */
|
||||||
|
body += "<div class='kfu-drawer-section'><h3>Сценарий</h3>" +
|
||||||
|
"<div class='kfu-scenario-lbl'>Прочие/гарантии: <b>" + fmtMoney(baseOther) + "</b> млн ₸ · изменение: <b>" + pctStr(delta) + "</b> млн ₸</div>" +
|
||||||
|
"<div class='kfu-scenario-row'><input class='kfu-input' id='scenInput' type='text' inputmode='decimal' value='" + pctStr(delta) + "'>" +
|
||||||
|
"<button class='kfu-btn' id='btnScenReset'>Вернуть</button></div>" +
|
||||||
|
"<div class='kfu-scenario-hint'>Сценарий обновляет коэффициенты, таблицу, счётчики и выгрузку. Сверка остаётся базовой.</div>" +
|
||||||
|
"</div>";
|
||||||
|
|
||||||
|
/* источники — раскрываемый блок */
|
||||||
|
body += "<details class='kfu-sources-block kfu-drawer-section'><summary>Исходные данные и формулы</summary>" +
|
||||||
|
"<table class='kfu-sources'>" +
|
||||||
srcRow("Долг", fmtMoney(en.debt), addrVal(en, "debt")) +
|
srcRow("Долг", fmtMoney(en.debt), addrVal(en, "debt")) +
|
||||||
srcRow("Прочие, в т.ч. гарантии", fmtMoney(newOther), addrVal(en, "other") + "<br><span class='kt-ai-meta'>в т.ч. сценарий " + pctStr(delta) + "</span>") +
|
srcRow("Прочие, в т.ч. гарантии", fmtMoney(newOther), addrVal(en, "other")) +
|
||||||
srcRow("EBITDA", fmtMoney(en.ebitda), addrVal(en, "ebitda")) +
|
srcRow("EBITDA", fmtMoney(en.ebitda), addrVal(en, "ebitda")) +
|
||||||
srcRow("Процентные расходы", fmtMoney(en.intExp), addrVal(en, "intexp")) +
|
srcRow("Процентные расходы", fmtMoney(en.intExp), addrVal(en, "intexp")) +
|
||||||
srcRow("Капитал", fmtMoney(en.equity), addrVal(en, "equity")) +
|
srcRow("Капитал", fmtMoney(en.equity), addrVal(en, "equity")) +
|
||||||
"</table></div>";
|
"</table>" +
|
||||||
|
"<div style='margin-top:10px;font-size:11px;color:var(--kfu-ink-3);line-height:1.5'>" +
|
||||||
|
"К1 = (долг + прочие/гарантии) / EBITDA<br>" +
|
||||||
|
"К2 = EBITDA / процентные расходы<br>" +
|
||||||
|
"К3 = (долг + прочие/гарантии) / капитал<br>" +
|
||||||
|
(en.group ? "Группа считается отдельно, не складывается с компаниями." : "Портфельная компания.") +
|
||||||
|
"</div></details>";
|
||||||
|
|
||||||
if (en.issues && en.issues.length) {
|
if (en.issues && en.issues.length) {
|
||||||
body += "<div class='kfu-drawer-section'><h3>Внимание</h3>" + en.issues.map(function (s) {
|
body += "<div class='kfu-drawer-section'><h3>Внимание</h3>" + en.issues.map(function (s) {
|
||||||
return "<div class='kfu-delta' style='margin-bottom:6px'>⚠ " + esc(s) + "</div>";
|
return "<div class='kfu-scenario-hint' style='color:var(--kfu-warn)'>" + esc(s) + "</div>";
|
||||||
}).join("") + "</div>";
|
}).join("") + "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
body += "<div class='kfu-drawer-section'><h3>Сценарий: изменение прочих/гарантий</h3>" +
|
|
||||||
"<div class='kfu-scenario-lbl'>Первичные прочие/гарантии: <b>" + fmtMoney(baseOther) + "</b> млн ₸ · изменение: <b>" + pctStr(delta) + "</b> млн ₸</div>" +
|
|
||||||
"<div class='kfu-scenario-row'><input class='kt-ai-input' id='scenInput' type='text' inputmode='decimal' value='" + pctStr(delta) + "'>" +
|
|
||||||
"<button class='kt-ai-btn' id='btnScenReset' data-variant='ghost'>Вернуть</button></div>" +
|
|
||||||
"<div class='kfu-delta'>Сценарий влияет на коэффициенты и выгрузку. Сверка показывает исходные данные.</div>" +
|
|
||||||
"</div>";
|
|
||||||
|
|
||||||
body += "<div class='kfu-drawer-actions'><button class='kt-ai-btn' data-variant='primary' id='btnCloseDrawer2'>Понятно</button></div>";
|
|
||||||
|
|
||||||
$("#drawerBody").innerHTML = body;
|
$("#drawerBody").innerHTML = body;
|
||||||
$("#btnCloseDrawer").onclick = closeDrawer;
|
$("#btnCloseDrawer").onclick = closeDrawer;
|
||||||
$("#btnCloseDrawer2").onclick = closeDrawer;
|
|
||||||
$("#btnScenReset").onclick = function () {
|
$("#btnScenReset").onclick = function () {
|
||||||
S.scen[en.name] = 0;
|
S.scen[en.name] = 0;
|
||||||
document.getElementById("scenInput").value = "0";
|
var si = document.getElementById("scenInput");
|
||||||
|
if (si) si.value = "0";
|
||||||
renderDrawer(en);
|
renderDrawer(en);
|
||||||
render();
|
render();
|
||||||
};
|
};
|
||||||
var inp = document.getElementById("scenInput");
|
var inp = document.getElementById("scenInput");
|
||||||
|
if (inp) {
|
||||||
inp.addEventListener("input", function () {
|
inp.addEventListener("input", function () {
|
||||||
var r = parseDelta(inp.value);
|
var r = parseDelta(inp.value);
|
||||||
inp.dataset.v = r.v;
|
inp.style.borderColor = r.ok ? "" : "var(--kfu-risk)";
|
||||||
inp.style.borderColor = r.ok ? "" : "var(--kt-ai-status-risk-fg)";
|
if (!r.ok) {
|
||||||
if (!r.ok) toast("Введите число (допускаются −, +, запятая)", true);
|
/* нечисловой ввод: последнее допустимое значение остаётся в расчёте, показываем ошибку */
|
||||||
|
if (!inp._err) { inp._err = true; toast("Введите число (допускаются −, +, запятая)", true); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inp._err = false;
|
||||||
clearTimeout(inp._t);
|
clearTimeout(inp._t);
|
||||||
inp._t = setTimeout(function () {
|
inp._t = setTimeout(function () {
|
||||||
S.scen[en.name] = r.v;
|
S.scen[en.name] = r.v;
|
||||||
@ -790,6 +915,16 @@
|
|||||||
if (ni) { ni.focus(); }
|
if (ni) { ni.focus(); }
|
||||||
}, 350);
|
}, 350);
|
||||||
});
|
});
|
||||||
|
inp.addEventListener("change", function () {
|
||||||
|
var r = parseDelta(inp.value);
|
||||||
|
if (!r.ok) {
|
||||||
|
/* при переводе фокуса восстанавливаем последнее допустимое значение */
|
||||||
|
inp.value = (S.scen[en.name] != null && S.scen[en.name] !== 0) ? pctStr(S.scen[en.name]) : "0";
|
||||||
|
inp.style.borderColor = "";
|
||||||
|
if (S.scen[en.name] != null && S.scen[en.name] !== 0) { renderDrawer(en); render(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* возвращает { v: число, ok: true } или { v: 0, ok: false } для нечислового */
|
/* возвращает { v: число, ok: true } или { v: 0, ok: false } для нечислового */
|
||||||
function parseDelta(s) {
|
function parseDelta(s) {
|
||||||
@ -820,7 +955,7 @@
|
|||||||
var a = e.addr;
|
var a = e.addr;
|
||||||
var sheet = a[key + "Sheet"];
|
var sheet = a[key + "Sheet"];
|
||||||
var val = a[key] || "";
|
var val = a[key] || "";
|
||||||
return esc(val) + (sheet ? "<br><span class='kt-ai-meta'>лист «" + esc(sheet) + "»</span>" : "");
|
return "<span class='src-addr'>" + esc(val) + (sheet ? " · " + esc(sheet) : "") + "</span>";
|
||||||
}
|
}
|
||||||
function srcRow(label, val, addr) {
|
function srcRow(label, val, addr) {
|
||||||
return "<tr><td>" + label + "</td><td>" + val + "<br>" + (addr || "") + "</td></tr>";
|
return "<tr><td>" + label + "</td><td>" + val + "<br>" + (addr || "") + "</td></tr>";
|
||||||
@ -938,12 +1073,13 @@
|
|||||||
|
|
||||||
function renderDiag() {
|
function renderDiag() {
|
||||||
if (S.diagErrors && S.diagErrors.length) {
|
if (S.diagErrors && S.diagErrors.length) {
|
||||||
$("#footnote").innerHTML = "<details class='kfu-diag'><summary>В книге " + S.diagErrors.length + " ошибок в посторонних ячейках — раскрыть</summary>" +
|
$("#diagBlock").hidden = false;
|
||||||
"<ul>" + S.diagErrors.map(function (d) {
|
$("#diagSummary").textContent = "В книге " + S.diagErrors.length + " ошибок в посторонних ячейках — раскрыть";
|
||||||
|
$("#diagList").innerHTML = S.diagErrors.map(function (d) {
|
||||||
return "<li>«" + esc(d.sheet) + "» " + esc(d.addr) + " → " + esc(d.val || "#err") + "</li>";
|
return "<li>«" + esc(d.sheet) + "» " + esc(d.addr) + " → " + esc(d.val || "#err") + "</li>";
|
||||||
}).join("") + "</ul></details>";
|
}).join("");
|
||||||
} else {
|
} else {
|
||||||
$("#footnote").innerHTML = "";
|
$("#diagBlock").hidden = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function applyResult(res, sourceName) {
|
function applyResult(res, sourceName) {
|
||||||
@ -960,35 +1096,52 @@
|
|||||||
|
|
||||||
/* ---------- инициализация ---------- */
|
/* ---------- инициализация ---------- */
|
||||||
function init() {
|
function init() {
|
||||||
|
var fileInput = $("#fileInput");
|
||||||
|
|
||||||
$("#fileInput").addEventListener("change", function (e) {
|
fileInput.addEventListener("change", function (e) {
|
||||||
if (e.target.files && e.target.files[0]) onFile(e.target.files[0]);
|
if (e.target.files && e.target.files[0]) onFile(e.target.files[0]);
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
});
|
});
|
||||||
var dz = $("#dropzone");
|
|
||||||
dz.addEventListener("dragover", function (e) { e.preventDefault(); dz.style.borderColor = "var(--kt-ai-primary)"; });
|
/* загрузить (первый экран) + заменить файл --> */
|
||||||
dz.addEventListener("dragleave", function () { dz.style.borderColor = ""; });
|
var btnUpload = $("#btnUpload");
|
||||||
dz.addEventListener("drop", function (e) {
|
if (btnUpload) btnUpload.addEventListener("click", function () { fileInput.click(); });
|
||||||
e.preventDefault(); dz.style.borderColor = "";
|
var btnReplace = $("#btnReplace");
|
||||||
|
if (btnReplace) btnReplace.addEventListener("click", function () { fileInput.click(); });
|
||||||
|
|
||||||
|
/* drag-and-drop на страницу --> */
|
||||||
|
document.addEventListener("dragover", function (e) { e.preventDefault(); });
|
||||||
|
document.addEventListener("drop", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
if (e.dataTransfer.files && e.dataTransfer.files[0]) onFile(e.dataTransfer.files[0]);
|
if (e.dataTransfer.files && e.dataTransfer.files[0]) onFile(e.dataTransfer.files[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#btnSample").addEventListener("click", function () {
|
$("#btnSample").addEventListener("click", function () {
|
||||||
var res = syntheticDemo();
|
var res = syntheticDemo();
|
||||||
applyResult(res, null);
|
applyResult(res, null);
|
||||||
toast("Загружен учебный пример (синтетические данные)");
|
toast("Загружен учебный пример (синтетические данные)");
|
||||||
});
|
});
|
||||||
$("#btnClearMock").addEventListener("click", function () {
|
|
||||||
S = { source: null, sheetNames: [], diag: [], entries: [], isMock: false, sort: { k: "name", d: 1 }, q: "", zone: "", norm: "", onlyRisk: false, scen: {} };
|
/* выгрузка отчёта (topbar, hidden до загрузки) */
|
||||||
render();
|
var be = $("#btnExport");
|
||||||
});
|
if (be) be.addEventListener("click", exportXlsx);
|
||||||
$("#btnExport").addEventListener("click", exportXlsx);
|
|
||||||
|
|
||||||
$("#q").addEventListener("input", function (e) { S.q = e.target.value; render(); });
|
$("#q").addEventListener("input", function (e) { S.q = e.target.value; render(); });
|
||||||
$("#zoneFilter").addEventListener("change", function (e) { S.zone = e.target.value; render(); });
|
|
||||||
$("#normFilter").addEventListener("change", function (e) { S.norm = e.target.value; render(); });
|
|
||||||
$("#onlyRisk").addEventListener("change", function (e) { S.onlyRisk = e.target.checked; render(); });
|
|
||||||
|
|
||||||
document.querySelectorAll(".kfu-table th[data-k]").forEach(function (th) {
|
/* фильтр-чипы */
|
||||||
|
var chips = document.querySelectorAll(".kfu-chip-filter");
|
||||||
|
for (var c = 0; c < chips.length; c++) {
|
||||||
|
chips[c].addEventListener("click", function () {
|
||||||
|
var zone = this.getAttribute("data-zone");
|
||||||
|
S.zone = zone || "";
|
||||||
|
for (var j = 0; j < chips.length; j++) chips[j].classList.remove("active");
|
||||||
|
this.classList.add("active");
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* сортировка в таблице компаний */
|
||||||
|
document.querySelectorAll("#tableWrap .kfu-table th[data-k]").forEach(function (th) {
|
||||||
th.addEventListener("click", function () {
|
th.addEventListener("click", function () {
|
||||||
var k = th.getAttribute("data-k");
|
var k = th.getAttribute("data-k");
|
||||||
if (S.sort.k === k) S.sort.d *= -1;
|
if (S.sort.k === k) S.sort.d *= -1;
|
||||||
@ -997,13 +1150,20 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* клик по строке --> */
|
||||||
$("#tbody").addEventListener("click", function (e) {
|
$("#tbody").addEventListener("click", function (e) {
|
||||||
var tr = e.target.closest("tr[data-name]");
|
var tr = e.target.closest("tr[data-name]");
|
||||||
if (tr) openDrawer(tr.getAttribute("data-name"));
|
if (tr) openDrawer(tr.getAttribute("data-name"));
|
||||||
});
|
});
|
||||||
|
$("#groupTbody").addEventListener("click", function (e) {
|
||||||
|
var tr = e.target.closest("tr[data-name]");
|
||||||
|
if (tr) openDrawer(tr.getAttribute("data-name"));
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener("keydown", function (e) {
|
document.addEventListener("keydown", function (e) {
|
||||||
if (e.key === "Escape") closeDrawer();
|
if (e.key === "Escape") closeDrawer();
|
||||||
});
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user