sdelay-sayt-tsifrovoy-pomosc/script.js

496 lines
29 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const DOC_TYPES = {
"NDA": {
icon: "lock",
desc: "Соглашение о неразглашении для переговорных партнёров и подрядчиков.",
fields: [
{ key: "party1", label: "Раскрывающая сторона", ph: "ТОО «Альфа Групп»" },
{ key: "party2", label: "Принимающая сторона", ph: "АО «ТехноКом»" },
{ key: "term", label: "Срок действия", type: "date", span: 2 },
{ key: "scope", label: "Сферa конфиденциальных данных", type: "textarea", span: 2, ph: "Прототипы, номенклатура, коммерческие условия…" },
{ key: "penalty", label: "Пеня за разглашение, %", type: "number", ph: "0.1" },
{ key: "currency", label: "Валюта", span: 2 }
]
},
"Договор аренды": {
icon: "building",
desc: "Аренда коммерческой недвижимости: офис, склад, участок.",
fields: [
{ key: "party1", label: "Арендодатель", ph: "ТОО «Недвижимость-К»" },
{ key: "party2", label: "Арендатор", ph: "ТОО «СтройМед»" },
{ key: "address", label: "Адрес объекта", span: 2, ph: "г. Алматы, пр. Абая 12, офис 415" },
{ key: "area", label: "Площадь, м²", type: "number", ph: "120" },
{ key: "rent", label: "Аренда, ₸/мес", type: "number", ph: "450000" },
{ key: "term", label: "Срок аренды", type: "date", span: 2 },
{ key: "deposit", label: "Залог (мес.)", type: "number", ph: "2" },
{ key: "purpose", label: "Назначение", span: 2 }
]
},
"Договор залога": {
icon: "scale",
desc: "Обеспечение обязательства залогом имущества или прав.",
fields: [
{ key: "party1", label: "Залогодатель", ph: "ТОО «АгроПлюс»" },
{ key: "party2", label: "Залоготребователь", ph: "БТА Банк" },
{ key: "property", label: "Издатель залога", span: 2, ph: "Земельный участок, кадастровый № 12:34:56:789" },
{ key: "value", label: "Стоимость залога, ₸", type: "number", ph: "8500000" },
{ key: "debt", label: "Обеспечиваемый долг, ₸", type: "number", ph: "6000000" },
{ key: "term", label: "Срок действия", type: "date", span: 2 },
{ key: "registration", label: "Договор будет зарегистрирован", type: "checkbox", span: 2 }
]
},
"Доверенность": {
icon: "user",
desc: "Одновременное полномочие на представительство.",
fields: [
{ key: "power", label: "Доверитель", span: 2, ph: "ТОО «Альфа Групп», БИН 1234567890" },
{ key: "attorney", label: "Представитель", ph: "Иванов И.И." },
{ key: "inn", label: "ИИН представителя", type: "number", ph: "980101300123" },
{ key: "powers", label: "Одновременные полномочия", type: "textarea", span: 2, ph: "Представление интересов в органах, подписание договоров…" },
{ key: "term", label: "Действительна", type: "date", span: 2 },
{ key: "notary", label: "С заверением нотариуса", type: "checkbox", span: 2 }
]
},
"Досудебная претензия": {
icon: "send",
desc: "Защита права до обращения в суд.",
fields: [
{ key: "claimant", label: "Кто предъявляет", ph: "ТОО «СтройМед»" },
{ key: "defendant", label: "Кому предъявляется", ph: "ТОО «Энергосбыт»" },
{ key: "reason", label: "Основание", type: "textarea", span: 2, ph: "Неисполнение договора № 12/26 от 15.02.2026, поставка задержана на 14 дней" },
{ key: "demand", label: "Требование", type: "textarea", span: 2, ph: "Устранить нарушение в 5 рабочих дней, компенсировать убытки 340 000 ₸" },
{ key: "deadline", label: "Срок устранения", type: "date", span: 2 },
{ key: "amount", label: "Сумма, ₸", type: "number", ph: "340000" },
{ key: "lawyer", label: "Фирма юриста", span: 2 }
]
}
};
const STATUS = {
"Черновик": { tone: "muted" },
"На согласовании": { tone: "warn" },
"На подписи": { tone: "blue" },
"Завершён": { tone: "ok" }
};
const DEMO_DOCS = [
{ id: "Д-1041/26", type: "NDA", counter: "ТОО «Казтелепорт»", created: "2026-09-18", due: "2026-09-28", status: "Черновик", data: { party1: "ТОО «Альфа Групп»", party2: "ТОО «Казтелепорт»", term: "2026-09-20", penalty: "0.1", currency: "₸" } },
{ id: "Д-1040/26", type: "Договор аренды", counter: "АО «Транстелеком»", created: "2026-09-17", due: "2026-09-25", status: "На согласовании", data: { party1: "ТОО «Недвижимость-К»", party2: "АО «Транстелеком»", address: "г. Алматы, ул. Толе би 40", area: "250", rent: "980000", term: "2026-10-01" } },
{ id: "Д-1039/26", type: "Договор залога", counter: "АО «АгроХолдинг»", created: "2026-09-16", due: "2026-09-30", status: "На согласовании", data: { party1: "АО «АгроХолдинг»", party2: "БТА Банк", value: "12400000", debt: "9800000" } },
{ id: "Д-1038/26", type: "Доверенность", counter: "ИП Маратбекова", created: "2026-09-16", due: "—", status: "Черновик", data: { power: "ИП Маратбекова А.С.", attorney: "Султанов Д.Ж.", powers: "Представление интересов в госорганах" } },
{ id: "Д-1037/26", type: "Досудебная претензия", counter: "ТОО «РемСтрой»", created: "2026-09-15", due: "2026-09-22", status: "На подписи", data: { claimant: "ТОО «СтройМед»", defendant: "ТОО «РемСтрой»", demand: "Компенсировать 1 250 000 ₸", deadline: "2026-09-22" } },
{ id: "Д-1036/26", type: "Договор аренды", counter: "ТОО «АйТи Сервис»", created: "2026-09-14", due: "2026-09-20", status: "Завершён", data: { party1: "ТОО «Недвижимость-К»", party2: "ТОО «АйТи Сервис»", area: "80", rent: "320000", term: "2026-10-01" } },
{ id: "Д-1035/26", type: "NDA", counter: "ТОО «Энергосервис»", created: "2026-09-12", due: "2026-09-26", status: "На согласовании", data: { party1: "ТОО «Альфа Групп»", party2: "ТОО «Энергосервис»", term: "2026-09-15" } },
{ id: "Д-1034/26", type: "Досудебная претензия", counter: "АО «КТЖ-Груз»", created: "2026-09-11", due: "2026-09-18", status: "На подписи", data: { claimant: "АО «Транстелеком»", defendant: "АО «КТЖ-Груз»", demand: "Взыскать пени 480 000 ₸" } },
{ id: "Д-1033/26", type: "Договор залога", counter: "ТОО «Жол Курылыс»", created: "2026-09-10", due: "2026-09-29", status: "На согласовании", data: { party1: "ТОО «Жол Курылыс»", party2: "Казахстан Кредит Bank", value: "4600000", debt: "3900000" } },
{ id: "Д-1032/26", type: "Доверенность", counter: "ТОО «СтройДеталь»", created: "2026-09-09", due: "—", status: "Завершён", data: { power: "ТОО «СтройДеталь»", attorney: "Абдуллинова Ж.А.", powers: "Подписание актов приёма-передачи" } },
{ id: "Д-1031/26", type: "Договор аренды", counter: "ТОО «МегаМаркет»", created: "2026-09-08", due: "—", status: "Завершён", data: { party1: "ТОО «Недвижимость-К»", party2: "ТОО «МегаМаркет»", area: "5400", rent: "21600000" } },
{ id: "Д-1030/26", type: "NDA", counter: "ТОО «АйТи Сервис»", created: "2026-09-07", due: "2026-09-21", status: "На согласовании", data: { party1: "ТОО «Альфа Групп»", party2: "ТОО «АйТи Сервис»", term: "2026-09-10" } },
{ id: "Д-1029/26", type: "Досудебная претензия", counter: "ТОО «ТрансЛогист»", created: "2026-09-05", due: "2026-09-15", status: "Завершён", data: { claimant: "ТОО «СтройМед»", defendant: "ТОО «ТрансЛогист»", demand: "Возвратить аванс 890 000 ₸" } },
{ id: "Д-1028/26", type: "Договор залога", counter: "ИП Сериков", created: "2026-09-04", due: "—", status: "Завершён", data: { party1: "ИП Сериков М.К.", party2: "Halyk Bank", value: "1500000", debt: "1200000" } },
{ id: "Д-1027/26", type: "NDA", counter: "АО «Казпочта»", created: "2026-09-03", due: "—", status: "Завершён", data: { party1: "ТОО «Альфа Групп»", party2: "АО «Казпочта»", term: "2026-09-05" } },
{ id: "Д-1026/26", type: "Доверенность", counter: "ТОО «АгроПлюс»", created: "2026-09-02", due: "—", status: "Завершён", data: { power: "ТОО «АгроПлюс»", attorney: "Бердибеков Н.Р.", powers: "Полномочия единоличного представителя" } },
{ id: "Д-1025/26", type: "Договор аренды", counter: "ТОО «Фабрика Керамика»", created: "2026-09-01", due: "—", status: "Завершён", data: { party1: "ТОО «Недвижимость-К»", party2: "ТОО «Фабрика Керамика»", area: "1200", rent: "4920000" } },
{ id: "Д-1024/26", type: "Досудебная претензия", counter: "ТОО «МеталлПром»", created: "2026-08-28", due: "—", status: "Завершён", data: { claimant: "АО «Транстелеком»", defendant: "ТОО «МеталлПром»", demand: "Компенсировать простой 3 100 000 ₸" } },
{ id: "Д-1023/26", type: "NDA", counter: "ТОО «Байтерек»", created: "2026-08-26", due: "—", status: "Завершён", data: { party1: "ТОО «Альфа Групп»", party2: "ТОО «Байтерек»", term: "2026-08-28" } },
{ id: "Д-1022/26", type: "Договор аренды", counter: "АО «Транстелеком»", created: "2026-08-24", due: "2026-09-14", status: "На согласовании", data: { party1: "ТОО «Недвижимость-К»", party2: "АО «Транстелеком»", area: "64", rent: "260000" } },
{ id: "Д-1021/26", type: "Договор залога", counter: "ТОО «АвтоТрейд»", created: "2026-08-22", due: "—", status: "Завершён", data: { party1: "ТОО «АвтоТрейд»", party2: "Freedom Finance", value: "7200000", debt: "6000000" } },
{ id: "Д-1020/26", type: "Досудебная претензия", counter: "ТОО «СтильДом»", created: "2026-08-20", due: "2026-09-05", status: "Завершён", data: { claimant: "ТОО «СтройМед»", defendant: "ТОО «СтильДом»", demand: "Устранить дефекты, штраф 220 000 ₸" } },
{ id: "Д-1019/26", type: "Доверенность", counter: "ТОО «АйТи Сервис»", created: "2026-08-18", due: "—", status: "Завершён", data: { power: "ТОО «АйТи Сервис»", attorney: "Ким Д.С.", powers: "Финансовые и кадровые действия" } },
{ id: "Д-1018/26", type: "NDA", counter: "ТОО «Энергосервис»", created: "2026-08-15", due: "—", status: "Завершён", data: { party1: "ТОО «Альфа Групп»", party2: "ТОО «Энергосервис»", term: "2026-08-17" } }
];
const WEEKS = [
{ label: "25.07", value: 2 },
{ label: "01.08", value: 4 },
{ label: "08.08", value: 3 },
{ label: "15.08", value: 5 },
{ label: "22.08", value: 4 },
{ label: "29.08", value: 6 }
];
let docs = DEMO_DOCS.map(d => ({ ...d, demo: true }));
let userDocs = JSON.parse(localStorage.getItem("ai_legal_docs") || "[]");
docs = userDocs.concat(docs);
let currentView = "dashboard";
let activeType = null;
let filterStatus = "all";
let filterType = "all";
let searchQuery = "";
function esc(s) {
return String(s || "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}
function fmtDate(iso) {
if (!iso || iso === "—") return "—";
const [y, m, d] = iso.split("-");
return `${d}.${m}.${y}`;
}
function fmtMoney(n) {
const num = Number(n);
if (!Number.isFinite(num)) return esc(n);
return num.toLocaleString("ru-RU");
}
function statusPill(status) {
const s = STATUS[status] || { tone: "info" };
return `<span class="kt-ai-status-pill" data-status="${s.tone}">${esc(status)}</span>`;
}
function iconSvg(name) {
return `<svg class="kt-icon" aria-hidden="true"><use href="#${name}"></use></svg>`;
}
/* ======================= НАВИГАЦИЯ ======================= */
function setView(name) {
currentView = name;
document.querySelectorAll("#view .view").forEach(v => v.classList.toggle("is-active", v.id === "view-" + name));
document.querySelectorAll(".kt-ai-nav-item[data-view]").forEach(n => n.dataset.active = String(n.dataset.view === name));
const titles = {
dashboard: ["Обзор", "Документы юридической службы · сентябрь 2026"],
docs: ["Документы", "5 типов — от NDA до досудебной претензии"],
approval: ["Согласование", "Все документы службы по этапам"]
};
document.getElementById("pageTitle").textContent = titles[name][0];
document.getElementById("pageSub").textContent = titles[name][1];
if (name === "approval") { renderApproval(); }
closeSidebar();
}
document.querySelectorAll(".kt-ai-nav-item[data-view]").forEach(btn => {
btn.addEventListener("click", () => setView(btn.dataset.view));
});
/* ======================= Сайдбар (мобильный) ======================= */
function openSidebar() { document.getElementById("sidebar").dataset.open = "true"; }
function closeSidebar() { delete document.getElementById("sidebar").dataset.open; }
document.getElementById("btnBurger").addEventListener("click", e => {
e.stopPropagation();
const sb = document.getElementById("sidebar");
if (sb.dataset.open === "true") closeSidebar(); else { openSidebar(); }
});
document.addEventListener("click", e => {
const sb = document.getElementById("sidebar");
if (sb.dataset.open === "true" && !sb.contains(e.target)) closeSidebar();
});
/* ======================= Обзор ======================= */
function renderDocTypes(container, all) {
const types = Object.keys(DOC_TYPES);
const show = all ? types : types.slice(0, 5);
container.innerHTML = show.map(t => {
const d = DOC_TYPES[t];
const count = docs.filter(x => x.type === t).length;
return `<article class="kt-ai-card doc-card" data-type="${esc(t)}" tabindex="0" role="button">
<span class="doc-ic">${iconSvg(d.icon)}</span>
<h3>${esc(t)}</h3>
<p>${esc(d.desc)}</p>
<span class="doc-open">${iconSvg("chevron-right")} Создать · ${count}</span>
</article>`;
}).join("");
container.querySelectorAll(".doc-card").forEach(card => {
const open = () => openDocForm(card.dataset.type);
card.addEventListener("click", open);
card.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); } });
});
}
function renderRecent() {
const recent = docs.slice(0, 5);
document.getElementById("dashRecent").innerHTML = recent.map(d => `
<tr data-id="${esc(d.id)}" style="cursor:pointer">
<td class="row-num">${esc(d.id)}</td>
<td>${esc(d.type)}</td>
<td>${esc(d.counter)}</td>
<td>${fmtDate(d.created)}</td>
<td>${statusPill(d.status)}</td>
</tr>`).join("");
document.querySelectorAll("#dashRecent tr").forEach(tr => {
tr.addEventListener("click", () => {
const d = docs.find(x => x.id === tr.dataset.id);
openDocDetail(d);
setView("approval");
});
});
}
function renderChart() {
const max = Math.max(...WEEKS.map(w => w.value));
const bars = WEEKS.map(w => `
<div class="cc-bar" data-kt-tip data-kt-tip-title="${w.label}" data-kt-tip-rows="документы|${w.value}">
<i style="height:${(w.value / max) * 100}%" aria-hidden="true"></i>
</div>`).join("");
const axis = WEEKS.map(w => `<span>${w.label}</span>`).join("");
const total = WEEKS.reduce((s, w) => s + w.value, 0);
document.getElementById("chartMonthly").innerHTML = `
<div class="cc-head">
<div>
<div class="cc-title">Документов за 6 недель</div>
<div class="cc-total"><span class="num kt-ai-num">${total}</span><span class="unit">шт.</span></div>
</div>
</div>
<div class="cc-bars">${bars}</div>
<div class="cc-axis cc-axis-cat">${axis}</div>`;
}
function renderDashboard() {
renderDocTypes(document.getElementById("docTypes"), false);
renderDocTypes(document.getElementById("docTypesAll"), true);
renderRecent();
renderChart();
}
/* ======================= Согласование ======================= */
function typeOptions() {
return ["all"].concat(Object.keys(DOC_TYPES)).map(t =>
`<option value="${esc(t)}">${t === "all" ? "Все" : esc(t)}</option>`).join("");
}
function renderApproval() {
const typeSel = document.getElementById("filterType");
if (!typeSel.options.length) typeSel.innerHTML = typeOptions();
let rows = docs.slice();
if (filterStatus !== "all") rows = rows.filter(d => d.status === filterStatus);
if (filterType !== "all") rows = rows.filter(d => d.type === filterType);
if (searchQuery) {
const q = searchQuery.toLowerCase();
rows = rows.filter(d => (d.id + " " + d.counter + " " + d.type).toLowerCase().includes(q));
}
const tbody = document.getElementById("approvalRows");
document.getElementById("approvalEmpty").style.display = rows.length ? "none" : "flex";
tbody.innerHTML = rows.map(d => `
<tr data-id="${esc(d.id)}" style="cursor:pointer">
<td class="row-num">${esc(d.id)}</td>
<td>${esc(d.type)}</td>
<td>${esc(d.counter)}</td>
<td>${fmtDate(d.created)}</td>
<td>${fmtDate(d.due)}</td>
<td>${statusPill(d.status)}</td>
</tr>`).join("");
tbody.querySelectorAll("tr").forEach(tr => {
tr.addEventListener("click", () => {
const d = docs.find(x => x.id === tr.dataset.id);
if (d) openDocDetail(d);
});
});
const byStatus = { "Черновик": 0, "На согласовании": 0, "На подписи": 0, "Завершён": 0 };
docs.forEach(d => { byStatus[d.status] = (byStatus[d.status] || 0) + 1; });
const summary = `Всего ${docs.length}: ${byStatus["Черновик"]} в черновике, ${byStatus["На согласовании"]} на согласовании, ${byStatus["На подписи"]} на подписи, ${byStatus["Завершён"]} завершено.`;
document.getElementById("approvalSummary").textContent = summary;
const month = docs.filter(d => d.created >= "2026-09-01").length;
const kpiEl = document.getElementById("kpiMonth");
if (kpiEl) kpiEl.textContent = String(month);
document.getElementById("kpiApproval").textContent = String(byStatus["На согласовании"] + byStatus["На подписи"]);
document.getElementById("kpiDone").textContent = String(byStatus["Завершён"]);
}
document.getElementById("filterStatus").addEventListener("change", e => {
filterStatus = e.target.value;
renderApproval();
});
document.getElementById("filterType").addEventListener("change", e => {
filterType = e.target.value;
renderApproval();
});
/* ======================= Поиск ======================= */
let searchTimer = null;
document.getElementById("topSearch").addEventListener("input", e => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
searchQuery = e.target.value.trim();
if (currentView !== "approval") setView("approval");
else renderApproval();
}, 250);
});
/* ======================= Drawer: форма нового ======================= */
function fieldHtml(f, val) {
const span = f.span ? ` data-span="${f.span}"` : "";
const v = val != null ? esc(val) : "";
const ph = f.ph ? ` placeholder="${esc(f.ph)}"` : "";
if (f.type === "textarea") {
return `<div class="kt-ai-field"${span}><label for="f-${f.key}">${esc(f.label)}</label><textarea class="kt-ai-textarea" id="f-${f.key}" data-key="${f.key}"${ph}>${v}</textarea></div>`;
}
if (f.type === "checkbox") {
const checked = val ? " checked" : "";
return `<div class="kt-ai-field"${span} style="flex-direction:row;align-items:center;gap:10px"><label class="kt-ai-checkbox" style="margin:0"><input type="checkbox" id="f-${f.key}" data-key="${f.key}"${checked}>${esc(f.label)}</label></div>`;
}
const inputType = f.type || "text";
return `<div class="kt-ai-field"${span}><label for="f-${f.key}">${esc(f.label)}</label><input class="kt-ai-input" type="${esc(inputType)}" id="f-${f.key}" data-key="${f.key}"${ph} value="${v}"></div>`;
}
function openDocForm(type) {
activeType = type;
const d = DOC_TYPES[type];
document.getElementById("drawerTitle").textContent = "Новый " + type.toLowerCase();
document.getElementById("drawerSub").textContent = "Заполните поля — ассистент сформирует проект с внутренними ограничениями";
const body = document.getElementById("drawerBody");
body.innerHTML = `<div class="field-grid">${d.fields.map(f => fieldHtml(f, null)).join("")}</div>`;
const gen = document.getElementById("btnGenerate");
gen.disabled = false;
gen.removeAttribute("data-loading");
gen.querySelector("#btnGenerateLabel").textContent = "Сформировать документ с помощью AI";
gen.querySelector(".ai-orb-dot").hidden = true;
closeSidebar();
document.getElementById("drawer").style.display = "";
document.getElementById("drawer").focus();
}
function openDocDetail(doc) {
activeType = null;
document.getElementById("drawerTitle").textContent = doc.id;
document.getElementById("drawerSub").textContent = `${doc.type} · ${doc.counter}`;
const d = DOC_TYPES[doc.type];
const rows = d.fields.map(f => {
const raw = doc.data && doc.data[f.key];
let val = raw != null ? raw : (doc.counter);
if (f.type === "date") val = fmtDate(raw);
else if (f.type === "number" && f.key !== "penalty") val = fmtMoney(raw);
else if (f.type === "checkbox") val = raw ? "Да" : "Нет";
const span = f.span ? ` style="grid-column:span ${f.span}"` : "";
return `<div${span}><div class="kt-ai-field" style="margin:0"><label>${esc(f.label)}</label><div class="kt-ai-num" style="font-size:var(--kt-ai-text-sm);margin-top:2px">${esc(val || "—")}</div></div></div>`;
}).join("");
document.getElementById("drawerBody").innerHTML = `
<div class="field-grid">${rows}</div>
<div class="rec-box">
<div class="rec-line"><span class="k">Статус</span><span class="v">${statusPill(doc.status)}</span></div>
<div class="rec-line"><span class="k">Срок виз</span><span class="v">${fmtDate(doc.due)}</span></div>
<div class="rec-line"><span class="k">Создан</span><span class="v">${fmtDate(doc.created)}</span></div>
</div>`;
const gen = document.getElementById("btnGenerate");
gen.disabled = true;
gen.querySelector("#btnGenerateLabel").textContent = "Сформировать документ с помощью AI";
gen.querySelector(".ai-orb-dot").hidden = true;
gen.removeAttribute("data-loading");
document.getElementById("drawer").style.display = "";
document.getElementById("drawer").focus();
}
function closeDrawer() {
document.getElementById("drawer").style.display = "none";
activeType = null;
}
document.getElementById("drawerClose").addEventListener("click", closeDrawer);
document.getElementById("drawerCancel").addEventListener("click", closeDrawer);
document.addEventListener("keydown", e => { if (e.key === "Escape") closeDrawer(); });
document.getElementById("btnNew").addEventListener("click", () => openDocForm("NDA"));
/* ======================= Генерация (демо ИИ) ======================= */
function aiRecommendations(type, data) {
const lines = [];
const num = (k) => Number(data[k]);
switch (type) {
case "NDA":
lines.push(["Срок", data.term ? `3 года до ${fmtDate(data.term)}` : "не задан"]);
lines.push(["Пеня", (data.penalty || "—") + " % от убытков"]);
lines.push(["Юрисдикция", "суд Алматы, право РК"]);
break;
case "Договор аренды":
lines.push(["Ставка", data.rent ? `${fmtMoney(data.rent)} ₸/мес` : "—"]);
lines.push(["Залог", (data.deposit || "—") + " мес. аренды"]);
break;
case "Договор залога":
lines.push(["Объект", data.property || "—"]);
lines.push(["Залог", data.value ? `${fmtMoney(data.value)} ₸` : "—"]);
lines.push(["Долг", data.debt ? `${fmtMoney(data.debt)} ₸` : "—"]);
break;
case "Доверенность":
lines.push(["Форма", data.notary ? "нотариальная" : "простая письменная"]);
lines.push(["Полномочия", data.powers ? "с перечислением" : "—"]);
lines.push(["Передача", "не допускается"]);
break;
case "Досудебная претензия":
lines.push(["Срок устранения", data.deadline ? fmtDate(data.deadline) : "10 рабочих дней"]);
lines.push(["Сумма", data.amount ? `${fmtMoney(data.amount)} ₸` : "—"]);
lines.push(["Основание", "ст. 376 ГК РК, досудебный порядок"]);
break;
}
return lines;
}
document.getElementById("btnGenerate").addEventListener("click", () => {
if (!activeType || !DOC_TYPES[activeType]) return;
const type = activeType;
const gen = document.getElementById("btnGenerate");
gen.disabled = true;
gen.setAttribute("data-loading", "true");
gen.querySelector("#btnGenerateLabel").textContent = "Ассистент формирует документ…";
gen.querySelector(".ai-orb-dot").hidden = false;
const data = {};
document.querySelectorAll("#drawerBody [data-key]").forEach(inp => {
data[inp.dataset.key] = inp.type === "checkbox" ? inp.checked : inp.value.trim();
});
setTimeout(() => {
gen.removeAttribute("data-loading");
gen.querySelector("#btnGenerateLabel").textContent = "Сформировать документ с помощью AI";
gen.querySelector(".ai-orb-dot").hidden = true;
const counter = data.party2 || data.attorney || data.defendant || "—";
const idNum = 1041 + userDocs.length;
const id = `Д-${idNum}/26`;
const today = new Date().toISOString().slice(0, 10);
const doc = {
id, type, counter, created: today, due: fmtToIso(Date.now() + 10 * 86400000),
status: "Черновик", data, demo: false
};
docs.unshift(doc);
userDocs.unshift(docToStore(doc));
localStorage.setItem("ai_legal_docs", JSON.stringify(userDocs));
const recs = aiRecommendations(type, data);
const recBox = recs.map(([k, v2]) => `<div class="rec-line"><span class="k">${k}</span><span class="v">${esc(v2)}</span></div>`).join("");
document.getElementById("drawerBody").insertAdjacentHTML("beforeend", `
<div class="ai-result is-shown">
<div class="kt-ai-banner" data-tone="ok">
<svg class="kt-icon" aria-hidden="true"><use href="#check-circle"></use></svg>
<div>Документ <strong>${esc(id)}</strong> сформирован и добавлен в «Согласование» со статусом «Черновик».</div>
</div>
<div class="rec-box">${recBox}</div>
</div>`);
toast(`Документ ${id} создан`, "ok");
if (currentView === "dashboard") renderDashboard();
if (currentView === "approval") renderApproval();
}, 1600);
});
function fmtToIso(ms) { return new Date(ms).toISOString().slice(0, 10); }
function docToStore(d) {
return { id: d.id, type: d.type, counter: d.counter, created: d.created, due: d.due, status: d.status, data: d.data };
}
/* ======================= Toast ======================= */
function toast(msg, tone) {
const el = document.createElement("div");
el.className = "kt-ai-toast";
if (tone) el.dataset.tone = tone;
el.textContent = msg;
document.getElementById("toasts").appendChild(el);
setTimeout(() => el.remove(), 4000);
}
/* ======================= Start ======================= */
renderDashboard();
renderApproval();
setView("dashboard");