496 lines
29 KiB
JavaScript
496 lines
29 KiB
JavaScript
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 => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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");
|