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 `${esc(status)}`; } function iconSvg(name) { return ``; } /* ======================= НАВИГАЦИЯ ======================= */ 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 `
${iconSvg(d.icon)}

${esc(t)}

${esc(d.desc)}

${iconSvg("chevron-right")} Создать · ${count}
`; }).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 => ` ${esc(d.id)} ${esc(d.type)} ${esc(d.counter)} ${fmtDate(d.created)} ${statusPill(d.status)} `).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 => `
`).join(""); const axis = WEEKS.map(w => `${w.label}`).join(""); const total = WEEKS.reduce((s, w) => s + w.value, 0); document.getElementById("chartMonthly").innerHTML = `
Документов за 6 недель
${total}шт.
${bars}
${axis}
`; } 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 => ``).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 => ` ${esc(d.id)} ${esc(d.type)} ${esc(d.counter)} ${fmtDate(d.created)} ${fmtDate(d.due)} ${statusPill(d.status)} `).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 `
`; } if (f.type === "checkbox") { const checked = val ? " checked" : ""; return `
`; } const inputType = f.type || "text"; return `
`; } 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 = `
${d.fields.map(f => fieldHtml(f, null)).join("")}
`; 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 `
${esc(val || "—")}
`; }).join(""); document.getElementById("drawerBody").innerHTML = `
${rows}
Статус${statusPill(doc.status)}
Срок виз${fmtDate(doc.due)}
Создан${fmtDate(doc.created)}
`; 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]) => `
${k}${esc(v2)}
`).join(""); document.getElementById("drawerBody").insertAdjacentHTML("beforeend", `
Документ ${esc(id)} сформирован и добавлен в «Согласование» со статусом «Черновик».
${recBox}
`); 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");