433 lines
24 KiB
HTML
433 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="light">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Сводный дашборд — ИИ-агент КФУ</title>
|
||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||
<link rel="stylesheet" href="design-system/vibe-theme.css">
|
||
<style>
|
||
a.kt-ai-btn { text-decoration: none; }
|
||
.kfu-wrap { max-width: 1240px; margin: 0 auto; padding: 20px 18px 70px; }
|
||
.kfu-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 16px; margin-bottom: 16px; }
|
||
.kfu-head h1 { font-size: 22px; margin: 0; }
|
||
.kfu-head .meta { font-size: 13.5px; color: var(--kt-ai-fg-muted); }
|
||
.kfu-head .spacer { flex: 1; }
|
||
.kfu-cnt { font-size: 14px; font-weight: 600; }
|
||
.kfu-cnt b { color: var(--kt-ai-primary); }
|
||
.kfu-legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0 0 14px; font-size: 12.5px; color: var(--kt-ai-fg-muted); }
|
||
.kfu-legend span { display: inline-flex; align-items: center; gap: 6px; }
|
||
.kfu-dot { width: 10px; height: 10px; border-radius: 50%; }
|
||
.kfu-dot.green { background: var(--kt-ai-success-token); }
|
||
.kfu-dot.yellow { background: var(--kt-ai-warning, var(--kt-ai-warn-token)); }
|
||
.kfu-dot.red { background: var(--kt-ai-danger); }
|
||
.kfu-dot.none { background: var(--kt-ai-fg-faint, var(--kt-ai-fg-faint)); }
|
||
.kfu-table-wrap { border: 1px solid var(--kt-ai-card-border); border-radius: 12px; background: var(--kt-ai-card-bg); }
|
||
table.kfu-t { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 13px; }
|
||
.kfu-t th { text-align: left; padding: 8px 8px; border-bottom: 1px solid var(--kt-ai-card-border); font-weight: 600; font-size: 11.5px; color: var(--kt-ai-fg-muted); white-space: nowrap; background: var(--kt-ai-bg); overflow: hidden; text-overflow: ellipsis; }
|
||
.kfu-t td { padding: 8px 8px; border-bottom: 1px solid var(--kt-ai-card-border); vertical-align: top; overflow: hidden; text-overflow: clip; }
|
||
.kfu-t td.company-cell { white-space: normal; word-break: break-word; }
|
||
.kfu-sub { font-size: 12px; color: var(--kt-ai-fg-muted); margin-top: 2px; }
|
||
.kfu-toggle { background: none; border: 0; color: var(--kt-ai-primary); cursor: pointer; font-size: 12px; padding: 2px 0; margin-top: 4px; }
|
||
.kfu-toggle:hover { text-decoration: underline; }
|
||
.kfu-rowdetail { padding: 10px 14px 12px; background: var(--kt-ai-bg); }
|
||
.kfu-err { margin-top: 4px; font-size: 11.5px; color: var(--kt-ai-danger); max-width: 320px; }
|
||
.kfu-alem-note { font-size: 11px; color: var(--kt-ai-fg-faint, var(--kt-ai-fg-muted)); line-height: 1.55; margin-top: 10px; padding: 8px 12px; background: var(--kt-ai-card-bg); border: 1px dashed var(--kt-ai-card-border); border-radius: 10px; }
|
||
.kfu-alem-note code { font-size: 10.5px; color: var(--kt-ai-fg); }
|
||
.kfu-rowdetail .kfu-cell { margin-bottom: 6px; font-size: 12.5px; color: var(--kt-ai-fg); }
|
||
.kfu-rowdetail .lbl { font-size: 11.5px; color: var(--kt-ai-fg-muted); }
|
||
.kfu-t tr:last-child td { border-bottom: 0; }
|
||
.kfu-t td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12.5px; }
|
||
.kfu-t th.num { text-align: right; }
|
||
.kfu-t td.company { font-weight: 600; white-space: nowrap; max-width: 230px; overflow: hidden; text-overflow: ellipsis; }
|
||
.kfu-zone { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; white-space: nowrap; }
|
||
.kfu-flags { font-size: 12px; color: var(--kt-ai-fg-muted); max-width: 260px; }
|
||
.kfu-act { white-space: nowrap; }
|
||
.kfu-act a, .kfu-act button { background: none; border: 0; color: var(--kt-ai-primary); cursor: pointer; font-size: 12.5px; padding: 2px 4px; }
|
||
.kfu-note { font-size: 12px; color: var(--kt-ai-fg-muted); max-width: 240px; }
|
||
.kfu-status-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 500; white-space: nowrap; }
|
||
.kfu-st-done { background: color-mix(in srgb, var(--kt-ai-success-token) 14%, transparent); color: var(--kt-ai-success, var(--kt-ai-success-token)); }
|
||
.kfu-st-processing { background: color-mix(in srgb, var(--kt-ai-warning, var(--kt-ai-warn-token)) 14%, transparent); color: var(--kt-ai-warning, var(--kt-ai-warn-token)); }
|
||
.kfu-st-error { background: color-mix(in srgb, var(--kt-ai-danger) 12%, transparent); color: var(--kt-ai-danger); }
|
||
.kfu-st-no { background: var(--kt-ai-bg-active, var(--kt-ai-bg-active)); color: var(--kt-ai-fg-muted); }
|
||
.kfu-section { margin-top: 30px; }
|
||
.kfu-section h2 { font-size: 17px; margin: 0 0 12px; }
|
||
.kfu-setcard { border: 1px solid var(--kt-ai-card-border); border-radius: 12px; background: var(--kt-ai-card-bg); padding: 18px; display: grid; gap: 16px; }
|
||
.kfu-setgrid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
|
||
.kfu-comp { display: grid; grid-template-columns: 1.3fr 1fr 0.8fr auto; gap: 10px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--kt-ai-card-border); }
|
||
.kfu-comp:last-child { border-bottom: 0; }
|
||
.kfu-comp .in { width: 100%; }
|
||
.kfu-comp .name { font-weight: 600; font-size: 13px; }
|
||
.kfu-comp input.kt-ai-input { padding: 6px 10px; font-size: 13px; }
|
||
.kfu-actions { display: flex; gap: 6px; white-space: nowrap; }
|
||
.kfu-actions button { background: var(--kt-ai-bg-active, transparent); border: 1px solid var(--kt-ai-card-border); border-radius: 8px; padding: 5px 9px; font-size: 12px; cursor: pointer; color: var(--kt-ai-fg); }
|
||
.kfu-actions button:hover { border-color: var(--kt-ai-primary); color: var(--kt-ai-primary); }
|
||
.kfu-actions button.danger:hover { border-color: var(--kt-ai-danger); color: var(--kt-ai-danger); }
|
||
.kfu-addrow { display: grid; grid-template-columns: 1.3fr 1fr 0.8fr auto; gap: 10px; align-items: center; padding-top: 12px; }
|
||
.kfu-hidden-input { display: none; }
|
||
.kfu-pass { font-size: 12px; color: var(--kt-ai-fg-muted); margin-top: 6px; }
|
||
.kfu-pass code { background: var(--kt-ai-bg-active, var(--kt-ai-bg-active)); padding: 1px 5px; border-radius: 4px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<script src="design-system/icons/kt-ai-icons.js"></script>
|
||
<div class="kfu-wrap">
|
||
<div class="kfu-head">
|
||
<h1>Сводный дашборд</h1>
|
||
<span class="meta" id="periodMeta"></span>
|
||
<span class="spacer"></span>
|
||
<span class="kfu-cnt" id="cnt"></span>
|
||
<a class="kt-ai-btn" data-variant="primary" id="exportBtn" href="#">Скачать сводный отчёт</a>
|
||
<a class="kt-ai-btn" data-variant="ghost" id="logoutBtn" href="#">Выйти</a>
|
||
</div>
|
||
<div class="kfu-legend">
|
||
<span><i class="kfu-dot green"></i>зелёная</span>
|
||
<span><i class="kfu-dot yellow"></i>жёлтая</span>
|
||
<span><i class="kfu-dot red"></i>красная</span>
|
||
<span><i class="kfu-dot none"></i>без оценки</span>
|
||
<span style="margin-left:auto">Цифры и зоны — от агента Alem</span>
|
||
</div>
|
||
<div class="kfu-table-wrap">
|
||
<table class="kfu-t">
|
||
<colgroup>
|
||
<col style="width:19%"><col style="width:11%"><col style="width:9%"><col style="width:8%"><col style="width:9%">
|
||
<col style="width:10%"><col style="width:9%"><col style="width:6.5%"><col style="width:6.5%"><col style="width:6.5%"><col style="width:6%">
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th class="th-company">Компания</th>
|
||
<th>Статус</th>
|
||
<th>Загружен</th>
|
||
<th class="num">Долг</th>
|
||
<th class="num">EBITDA 12М</th>
|
||
<th class="num">Проц. расходы 12М</th>
|
||
<th class="num">Капитал, млрд ₸</th>
|
||
<th class="num">К1</th>
|
||
<th class="num">К2</th>
|
||
<th class="num">К3</th>
|
||
<th>Зона</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
<p class="kfu-alem-note">Как считается: дочка загружает файл → приложение создаёт сессию в Alem: <code>POST /api/v1/open-api/conversation</code> (заголовок <code>Authorization: Bearer …</code>, body <code>{template_id, title, user_id}</code>) → затем текст отчёта уходит в <code>POST /api/v1/open-api/conversation/{session_id}/send</code> (поле <code>content</code>) → ответ приходит SSE-потоком, итог — в последнем событии <code>message_result.detail.full_content</code>, которое приложение разбирает как JSON. Ключи и <code>template_id</code> задаются в настройках приложения, в код не попадают.</p>
|
||
<section class="kfu-section">
|
||
<h2>Настройки</h2>
|
||
<div class="kfu-setcard">
|
||
<div class="kfu-setgrid">
|
||
<label class="kt-ai-field"><span>Период отчётности</span>
|
||
<input class="kt-ai-input" id="setPeriod" placeholder="Q2-2026"></label>
|
||
<label class="kt-ai-field"><span>Срок предоставления (ДД.ММ.ГГГГ)</span>
|
||
<input class="kt-ai-input" id="setDeadline" placeholder="30.06.2026"></label>
|
||
<label class="kt-ai-field"><span>Телефон ведущего</span>
|
||
<input class="kt-ai-input" id="setPhone" placeholder="+7 (___) ___-__-__"></label>
|
||
</div>
|
||
<button class="kt-ai-btn" data-variant="primary" id="saveSettings">Сохранить настройки</button>
|
||
|
||
<div>
|
||
<h3 style="font-size:14px;margin:0 0 4px">Дочерние компании</h3>
|
||
<p style="font-size:12.5px;color:var(--kt-ai-fg-muted);margin:0 0 8px">Логин дочерней компании — её адрес в Aitu (например, <code>jandamir_z</code>). Пароль генерируется системой.</p>
|
||
<div id="compList"></div>
|
||
<div class="kfu-addrow">
|
||
<input class="kt-ai-input" id="addName" placeholder="Название компании">
|
||
<input class="kt-ai-input" id="addPerson" placeholder="Ответственный">
|
||
<input class="kt-ai-input" id="addPhone" placeholder="+7 ___">
|
||
<span></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="addCompany" type="button">Добавить</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 style="font-size:14px;margin:0 0 4px">Смена пароля сводящего</h3>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="regeneratePw" type="button">Сменить пароль</button>
|
||
<p class="kfu-pass" id="newPw"></p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<script>
|
||
(function () {
|
||
function fmtDate(iso) {
|
||
if (!iso) return "";
|
||
var d = new Date(iso);
|
||
var p = function (x) { return (x < 10 ? "0" : "") + x; };
|
||
return p(d.getDate()) + "." + p(d.getMonth() + 1) + " " + p(d.getHours()) + ":" + p(d.getMinutes());
|
||
}
|
||
function fmtDateFull(iso) {
|
||
if (!iso) return "";
|
||
var s = String(iso).trim();
|
||
var p2 = function (x) { return (x < 10 ? "0" : "") + x; };
|
||
var m = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})/);
|
||
if (m) return p2(+m[1]) + "." + p2(+m[2]) + "." + m[3];
|
||
var parts = s.split("-");
|
||
if (parts.length === 3) return parts[2] + "." + parts[1] + "." + parts[0];
|
||
var d = new Date(s);
|
||
if (isNaN(d.getTime())) return s;
|
||
return p2(d.getDate()) + "." + p2(d.getMonth() + 1) + "." + d.getFullYear();
|
||
}
|
||
function fmtNum(v) {
|
||
if (v === null || v === undefined || v === "") return "—";
|
||
var n = Number(v);
|
||
if (isNaN(n)) return esc(v);
|
||
return n.toLocaleString("ru-RU", { maximumFractionDigits: 2 });
|
||
}
|
||
function esc(x) {
|
||
return String(x == null ? "" : x).replace(/[&<>"']/g, function (c) {
|
||
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
||
});
|
||
}
|
||
function zoneHtml(row) {
|
||
var z = row.zone_company;
|
||
var cls = { "зелёная": "green", "жёлтая": "yellow", "красная": "red" }[z];
|
||
var dot = "<i class='kfu-dot " + (cls || "none") + "'></i>";
|
||
var flags = (row.flags || []).join("\n");
|
||
return "<div class='kfu-zone'>" + dot + "<span>" + esc(z || "без оценки") + "</span></div>" +
|
||
(flags ? "<div class='kfu-flags'>" + esc(flags) + "</div>" : "");
|
||
}
|
||
function stHtml(row) {
|
||
if (!row || !row.status || row.status === "not_submitted") return "<span class='kfu-status-pill kfu-st-no'>не сдан</span>";
|
||
var m = { done: ["kfu-st-done", "сдан"], processing: ["kfu-st-processing", "обрабатывается"], error: ["kfu-st-error", "ошибка расчёта"] };
|
||
var it = m[row.status] || ["kfu-st-no", esc(row.status)];
|
||
return "<span class='kfu-status-pill " + it[0] + "'>" + it[1] + "</span>";
|
||
}
|
||
|
||
var tbody = document.getElementById("tbody");
|
||
|
||
var ROW_DATA = [];
|
||
var ROW_IDX = 0;
|
||
|
||
function render(rows, b) {
|
||
var done = 0, proc = 0, err = 0;
|
||
rows.forEach(function (r) {
|
||
if (r.status === "done") done++;
|
||
else if (r.status === "processing") proc++;
|
||
else if (r.status === "error") err++;
|
||
});
|
||
var submitted = rows.filter(function (r) { return r.status === "done" || r.status === "error"; }).length;
|
||
document.getElementById("cnt").innerHTML =
|
||
esc(rows.length) + " компаний: <b>" + submitted + "</b> сдано (" + err + " с ошибкой расчёта), " + proc + " на расчёте";
|
||
document.getElementById("periodMeta").textContent = "период " + (b.period || "") + " · срок до " + fmtDateFull(b.deadline);
|
||
ROW_DATA = [];
|
||
ROW_IDX = 0;
|
||
var html = rows.map(function (r) {
|
||
var acts = [];
|
||
if (r.fileId) {
|
||
acts.push("<a href='api/file/" + r.fileId + "' target='_blank' download>файл</a>");
|
||
}
|
||
if (r.status === "processing" || r.status === "error") {
|
||
acts.push("<button onclick='kfRecalc(\"" + r.fileId + "\")'>пересчитать</button>");
|
||
}
|
||
var idx = ROW_IDX++;
|
||
ROW_DATA[idx] = r;
|
||
var hasDetail = !!(r.comment || (r.flags && r.flags.length) || r.agent_comment || r.error);
|
||
var actsStr = acts.length ? acts.join(" ") : "";
|
||
return "<tr>" +
|
||
"<td class='company company-cell' title='" + esc(r.company) + "'>" + esc(r.company) +
|
||
(hasDetail ? "<br><button class='kfu-toggle' onclick='kfToggle(" + idx + ")' id='tg" + idx + "'>подробнее</button>" : "") +
|
||
(r.error ? "<div class='kfu-err'>" + esc(r.error) + "</div>" : "") +
|
||
"</td>" +
|
||
"<td>" + stHtml(r) + "</td>" +
|
||
"<td>" + esc(fmtDate(r.uploadedAt)) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.debt) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.ebitda_ltm) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.interest_ltm) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.equity) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.k1) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.k2) + "</td>" +
|
||
"<td class='num'>" + fmtNum(r.k3) + "</td>" +
|
||
"<td>" + zoneHtml(r) + "</td>" +
|
||
"<td class='kfu-act'>" + actsStr + "</td>" +
|
||
"</tr>" +
|
||
(hasDetail ? "<tr class='kfu-detail-row' id='dt" + idx + "' hidden><td colspan='12' class='kfu-rowdetail'>" +
|
||
(r.comment ? "<div class='kfu-cell'><span class='lbl'>Комментарий дочки:</span> " + esc(r.comment) + "</div>" : "") +
|
||
(r.agent_comment ? "<div class='kfu-cell'><span class='lbl'>Замечания агента:</span> " + esc(r.agent_comment) + "</div>" : "") +
|
||
((r.flags && r.flags.length) ? "<div class='kfu-cell'><span class='lbl'>Признаки:</span><br>" + r.flags.map(esc).join("<br>") + "</div>" : "") +
|
||
(r.error ? "<div class='kfu-cell' style='color:var(--kt-ai-danger)'><span class='lbl'>Ошибка расчёта:</span> " + esc(r.error) + "</div>" : "") +
|
||
"</td></tr>" : "");
|
||
}).join("");
|
||
tbody.innerHTML = html;
|
||
document.getElementById("exportBtn").setAttribute("href", "api/export?t=" + Date.now());
|
||
}
|
||
|
||
function renderCompanies(list) {
|
||
var box = document.getElementById("compList");
|
||
box.innerHTML = list.map(function (c) {
|
||
return "<div class='kfu-comp' data-id='" + esc(c.id) + "' data-login='" + esc(c.login) + "'>" +
|
||
"<input class='kt-ai-input in name' value='" + esc(c.name) + "' placeholder='Название'>" +
|
||
"<input class='kt-ai-input in person' value='" + esc(c.person) + "' placeholder='Ответственный'>" +
|
||
"<input class='kt-ai-input in phone' value='" + esc(c.phone) + "' placeholder='+7 ___'>" +
|
||
"<span style='font-size:12.5px' class='lg' title='Логин для входа — передайте компании в Aitu'>" + esc(c.login) + "</span>" +
|
||
"<div class='kfu-actions'>" +
|
||
"<button class='act-save'>сохранить</button>" +
|
||
"<button class='act-pw'>пароль</button>" +
|
||
"<button class='act-toggle'>" + (c.active ? "отключить" : "включить") + "</button>" +
|
||
"</div>" +
|
||
"</div>";
|
||
}).join("");
|
||
Array.prototype.forEach.call(box.querySelectorAll(".kfu-comp"), function (rowEl) {
|
||
var id = rowEl.getAttribute("data-id");
|
||
var c_login = rowEl.getAttribute("data-login");
|
||
function collect() {
|
||
return {
|
||
name: rowEl.querySelector(".name").value,
|
||
person: rowEl.querySelector(".person").value,
|
||
phone: rowEl.querySelector(".phone").value,
|
||
login: c_login
|
||
};
|
||
}
|
||
function flash(msg) {
|
||
var el = document.createElement("p");
|
||
el.className = "kfu-pass";
|
||
el.textContent = msg;
|
||
box.after(el);
|
||
setTimeout(function () { el.remove(); }, 30000);
|
||
}
|
||
rowEl.querySelector(".act-save").addEventListener("click", function () {
|
||
var data = collect();
|
||
var body = { companies: window.__companies.map(function (c) {
|
||
if (c.id !== id) return c;
|
||
return { id: c.id, name: data.name, person: data.person, phone: data.phone, active: c.active };
|
||
}) };
|
||
fetch("api/settings", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(body)
|
||
}).then(function (r) { return r.json(); }).then(function (b) {
|
||
if (!b.ok) { alert(b.error || "Не удалось сохранить"); return; }
|
||
load();
|
||
});
|
||
});
|
||
rowEl.querySelector(".act-pw").addEventListener("click", function () {
|
||
fetch("api/user/" + id, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ action: "set_password" })
|
||
}).then(function (r) { return r.json(); }).then(function (b) {
|
||
if (!b.ok) { alert(b.error || "Ошибка"); return; }
|
||
flash("Новый пароль: " + b.tempPassword + " — передайте компании через Aitu.");
|
||
load();
|
||
});
|
||
});
|
||
rowEl.querySelector(".act-toggle").addEventListener("click", function () {
|
||
fetch("api/user/" + id, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ action: "toggle" })
|
||
}).then(function (r) { return r.json(); }).then(function (b) { load(); });
|
||
});
|
||
});
|
||
}
|
||
|
||
function load() {
|
||
Promise.all([
|
||
fetch("api/dashboard", { credentials: "same-origin" })
|
||
.then(function (r) { if (r.status === 401) { window.location.href = "index.html"; } return r.json(); }),
|
||
fetch("api/settings", { credentials: "same-origin" }).then(function (r) { return r.json(); })
|
||
]).then(function (pair) {
|
||
var b = pair[0], s = pair[1];
|
||
if (!b) return;
|
||
window.__companies = s.companies || [];
|
||
render(b.rows, b);
|
||
document.getElementById("setPeriod").value = s.period || "";
|
||
document.getElementById("setDeadline").value = fmtDateFull(s.deadline);
|
||
document.getElementById("setPhone").value = s.consolidatorPhone || "";
|
||
renderCompanies(s.companies || []);
|
||
});
|
||
}
|
||
|
||
window.kfToggle = function (i) {
|
||
var row = document.getElementById("dt" + i);
|
||
var btn = document.getElementById("tg" + i);
|
||
if (!row) return;
|
||
var open = row.hidden;
|
||
row.hidden = !open;
|
||
if (btn) btn.textContent = open ? "скрыть" : "подробнее";
|
||
};
|
||
|
||
window.kfRecalc = function (id) {
|
||
fetch("api/recalc?id=" + encodeURIComponent(id), { method: "POST" })
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (b) {
|
||
if (!b.ok) alert(b.error || "Не удалось пересчитать");
|
||
load();
|
||
});
|
||
};
|
||
|
||
document.getElementById("saveSettings").addEventListener("click", function () {
|
||
fetch("api/settings", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
period: document.getElementById("setPeriod").value,
|
||
deadline: function (v) {
|
||
var m = String(v || "").trim().match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})/);
|
||
if (!m) { var m2 = String(v || "").trim().match(/^(\d{4})-(\d{2})-(\d{2})/); if (m2) return m2[0]; return String(v || ""); }
|
||
return m[3] + "-" + m[2] + "-" + m[1];
|
||
}(document.getElementById("setDeadline").value),
|
||
consolidatorPhone: document.getElementById("setPhone").value
|
||
})
|
||
}).then(function (r) { return r.json(); }).then(function (b) {
|
||
if (b.ok) load(); else alert(b.error || "Не удалось сохранить");
|
||
});
|
||
});
|
||
|
||
document.getElementById("addCompany").addEventListener("click", function () {
|
||
var name = document.getElementById("addName").value.trim();
|
||
var phone = document.getElementById("addPhone").value.trim();
|
||
if (!name) { alert("Укажите название компании"); return; }
|
||
fetch("api/add-company", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ name: name, phone: phone, person: document.getElementById("addPerson").value.trim() })
|
||
}).then(function (r) { return r.json(); }).then(function (b) {
|
||
if (!b.ok) { alert(b.error || "Не удалось добавить компанию"); return; }
|
||
document.getElementById("addName").value = "";
|
||
document.getElementById("addPhone").value = "";
|
||
var el = document.createElement("p");
|
||
el.className = "kfu-pass";
|
||
el.textContent = "Компания добавлена. Логин: " + b.login + ", пароль: " + b.tempPassword + " — передайте через Aitu.";
|
||
document.getElementById("compList").after(el);
|
||
setTimeout(function () { el.remove(); }, 30000);
|
||
load();
|
||
});
|
||
});
|
||
|
||
document.getElementById("regeneratePw").addEventListener("click", function () {
|
||
var nw = prompt("Введите новый пароль сводящего (минимум 4 символа):");
|
||
if (!nw || nw.length < 4) { alert("Слишком короткий пароль"); return; }
|
||
if (nw !== window.prompt("Повторите новый пароль:")) { alert("Пароли не совпадают"); return; }
|
||
var cur = prompt("Введите текущий пароль сводящего:");
|
||
if (cur === null) return;
|
||
fetch("api/consolidator-password", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ password: cur })
|
||
}).then(function (r) { return r.json(); }).then(function (b) {
|
||
if (!b.ok) { alert(b.error || "Ошибка"); return; }
|
||
alert("Пароль изменён.");
|
||
});
|
||
});
|
||
|
||
document.getElementById("logoutBtn").addEventListener("click", function (e) {
|
||
e.preventDefault();
|
||
fetch("api/logout", { method: "POST" }).then(function () { window.location.href = "index.html"; });
|
||
});
|
||
|
||
fetch("api/me", { credentials: "same-origin" }).then(function (r) {
|
||
if (r.status === 401) { window.location.href = "index.html"; return; }
|
||
return r.json();
|
||
}).then(function (me) {
|
||
if (!me || me.role !== "consolidator") { window.location.href = "index.html"; return; }
|
||
load();
|
||
setInterval(function () { if (document.visibilityState === "visible") load(); }, 30000);
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html> |