var LAWS = []; var STRUCTURE = []; var VIDEOS = []; var QUESTIONS = [ { q: "Сыйлықты декларациясыз қабылдауға болатын ең жоғары шекті мөлшері:", options: ["5 АЕК", "10 АЕК", "20 АЕК", "Кез келген"], correct: 0 }, { q: "Мүдделер қақтығысы туындағанда не істеу керек?", options: ["Өзім шешемін", "Басшыға және комплаенс қызметіне хабарлау", "Елемеу", "Жұмыстан кету"], correct: 1 }, { q: "Комплаенс-тестілеуден қаншалықты жиі өту керек?", options: ["3 жылда бір", "Қалауымша", "Жыл сайын", "Тек жұмысқа қабылданғанда"], correct: 2 }, { q: "Серіктестерден ақшалай сыйлық қабылдауға бола ма?", options: ["10 000 теңгеге дейін", "Басшының рұқсатымен", "Кез келген мөлшерде тыйым салынады", "Болады, егер декларацияласа"], correct: 2 }, { q: "5 АЕК-тен қымбат сыйлықпен не істеу керек?", options: ["Өзіме қалдыру", "Әріптестерге беру", "Декларациялап, ұйымға тапсыру", "Қайтару"], correct: 2 }, { q: "Құпия ақпаратқа не жатады?", options: ["Тек қаржылық есептер", "Тек жеке деректер", "Қаржылық деректер, ЖД, коммерциялық құпия", "Тек келісімшарттар"], correct: 2 }, { q: "Сырттай қоса атқару:", options: ["Толық тыйым салынады", "Шектеусіз рұқсат", "Басшының келісімімен рұқсат", "Тек демалыс күндері"], correct: 2 }, { q: "Сыбайлас жемқорлық туралы хабарлау арналары:", options: ["Тек жеке басшыға", "Сенім телефоны, email, басшы", "Тек анонимді", "Хабарлауға болмайды"], correct: 1 }, { q: "Сыбайлас жемқорлыққа қарсы оқыту міндетті:", options: ["Тек басшыларға", "Тек жаңа қызметкерлерге", "Барлық қызметкерлерге", "Тек бухгалтерияға"], correct: 2 }, { q: "Комплаенс ережесін бұзу неге әкеледі?", options: ["Ауызша ескерту", "Тәртіптік жауапкершілік, жұмыстан босатуға дейін", "Ешқандай салдарға", "Тек айыппұлға"], correct: 1 } ]; var currentQuestion = 0; var score = 0; var testTaken = false; var userName = ""; var userDept = ""; var mockResults = []; var results = []; var resultsBackendReady = false; function getResults() { return resultsBackendReady ? results : mockResults; } function loadData() { Promise.all([ fetch("/api/laws").then(function (r) { return r.json(); }).then(function (d) { LAWS = d; }), fetch("/api/structure").then(function (r) { return r.json(); }).then(function (d) { STRUCTURE = d; }), fetch("/api/videos").then(function (r) { return r.json(); }).then(function (d) { VIDEOS = d; }) ]).then(function () { renderLawChips(); renderLaws(); renderStructure(); renderVideos(); }).catch(function () {}); } function renderLaws() { var query = (document.getElementById("law-search").value || "").toLowerCase(); var activeCat = document.querySelector(".chip.active"); var cat = activeCat ? activeCat.dataset.cat : ""; var list = document.getElementById("law-list"); var filtered = LAWS.filter(function (l) { var matchQ = l.title.toLowerCase().indexOf(query) !== -1 || l.desc.toLowerCase().indexOf(query) !== -1; var matchC = !cat || l.cat === cat; return matchQ && matchC; }); if (!filtered.length) { list.innerHTML = '
Ештеңе табылмады
'; return; } list.innerHTML = filtered.map(function (l) { return '
' + '
' + l.title + '
' + '
' + l.cat + '' + l.id + '
' + (l.desc ? '
' + l.desc + '
' : '') + '
'; }).join(""); } function renderLawChips() { var cats = {}; LAWS.forEach(function (l) { cats[l.cat] = true; }); var chips = document.getElementById("law-chips"); chips.innerHTML = '' + Object.keys(cats).map(function (c) { return ''; }).join(""); chips.querySelectorAll(".chip").forEach(function (chip) { chip.addEventListener("click", function () { chips.querySelectorAll(".chip").forEach(function (c) { c.classList.remove("active"); }); chip.classList.add("active"); renderLaws(); }); }); } function renderStructure() { var grid = document.getElementById("structure-grid"); grid.innerHTML = STRUCTURE.map(function (s) { return '
' + '
' + s.emoji + '
' + '
' + s.name + '
' + '
' + s.role + '
' + '
' + s.dept + '
' + (s.head ? '
👑 Басшылық
' : '') + '
'; }).join(""); } function renderVideos() { var grid = document.getElementById("video-grid"); if (!VIDEOS.length) { grid.innerHTML = '
Әзірге видео жоқ — офицер жүктеген кезде пайда болады
'; return; } grid.innerHTML = VIDEOS.map(function (v) { return '
' + '
' + (v.desc || "") + '
' + '
' + '
' + v.title + '
' + '
' + v.category + '' + v.duration + '
' + '
'; }).join(""); } function openVideo(id) { var v = VIDEOS.find(function (x) { return x.id === id; }); if (!v) return; if (v.src) { var overlay = document.createElement("div"); overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:999;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px"; overlay.innerHTML = '' + ''; overlay.querySelector("button").addEventListener("click", function () { overlay.remove(); }); overlay.addEventListener("click", function (e) { if (e.target === overlay) overlay.remove(); }); document.body.appendChild(overlay); } else { alert("🎬 «" + v.title + "»\n\nРеалды нұсқада мұнда видео ойнайды (файлды офицер жүктеген кезде)."); } } function initTest() { currentQuestion = 0; score = 0; testTaken = false; userName = document.getElementById("test-name").value.trim(); userDept = document.getElementById("test-dept").value.trim(); if (!userName || !userDept) { alert("Аты-жөніңізді және бөлімді толтырыңыз"); return; } document.getElementById("test-start").style.display = "none"; document.getElementById("test-quiz").style.display = "block"; document.getElementById("test-result").style.display = "none"; showQuestion(); } function showQuestion() { var q = QUESTIONS[currentQuestion]; document.getElementById("q-current").textContent = currentQuestion + 1; document.getElementById("q-total").textContent = QUESTIONS.length; document.getElementById("question-text").textContent = q.q; document.getElementById("test-feedback").textContent = ""; document.getElementById("test-options").innerHTML = q.options.map(function (opt, i) { return ''; }).join(""); } function answer(selectedIndex) { if (testTaken) return; testTaken = true; var q = QUESTIONS[currentQuestion]; var options = document.querySelectorAll(".test-option"); var feedback = document.getElementById("test-feedback"); if (selectedIndex === q.correct) { options[selectedIndex].classList.add("correct"); feedback.textContent = "✅ Дұрыс!"; feedback.style.color = "#48bb78"; score++; } else { options[selectedIndex].classList.add("wrong"); options[q.correct].classList.add("correct"); feedback.textContent = "❌ Қате"; feedback.style.color = "#f56565"; } setTimeout(function () { currentQuestion++; if (currentQuestion < QUESTIONS.length) { testTaken = false; showQuestion(); } else { finishTest(); } }, 2000); } function finishTest() { var percent = Math.round((score / QUESTIONS.length) * 100); document.getElementById("test-quiz").style.display = "none"; document.getElementById("test-result").style.display = "block"; document.getElementById("result-score").textContent = score + " / " + QUESTIONS.length; document.getElementById("result-percent").textContent = percent + "%"; var msg = percent >= 90 ? "🎉 Тамаша! Сіз комплаенс саясатын жақсы білесіз." : percent >= 70 ? "👍 Жақсы нәтиже. Жеке тақырыптарды қайталау ұсынылады." : "⚠️ Оқуды қайта өтіп, материалдарды зерделеуді ұсынамыз."; document.getElementById("result-message").textContent = msg; saveResult(userName, userDept, score, QUESTIONS.length); } function saveResult(name, dept, sc, total) { var rec = { name: name, dept: dept, score: sc, total: total }; fetch("/api/results", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(rec) }).then(function () { return fetch("/api/results").then(function (r) { return r.json(); }); }).then(function (d) { if (Array.isArray(d)) { results = d; resultsBackendReady = true; renderDashboard(); } }).catch(function () {}); } function renderStats() { var rows = getResults(); var total = rows.length; var passed = rows.filter(function (r) { return r.score / r.total >= 0.7; }).length; var avg = total ? Math.round(rows.reduce(function (s, r) { return s + r.score / r.total * 100; }, 0) / total) : 0; var stats = document.getElementById("stats-grid"); stats.innerHTML = [ { icon: "👥", val: total, label: "Тест тапсырған" }, { icon: "✅", val: passed, label: "70%+ нәтижемен" }, { icon: "🎯", val: avg + "%", label: "Орташа нәтиже" }, { icon: "📈", val: topPerformer(rows), label: "Үздік қызметкер" } ].map(function (s) { return '
' + s.icon + '' + '' + s.val + '' + s.label + '
'; }).join(""); } function topPerformer(rows) { if (!rows.length) return "—"; var best = rows.reduce(function (a, b) { return (a.score / a.total) >= (b.score / b.total) ? a : b; }); return best.name; } function renderResultsTable() { var query = (document.getElementById("dash-search").value || "").toLowerCase(); var rows = getResults().filter(function (r) { return r.name.toLowerCase().indexOf(query) !== -1 || r.dept.toLowerCase().indexOf(query) !== -1; }).slice().reverse(); var tbody = document.querySelector("#results-table tbody"); if (!rows.length) { tbody.innerHTML = 'Нәтиже жоқ'; return; } tbody.innerHTML = rows.map(function (r) { var pct = Math.round(r.score / r.total * 100); var cls = pct >= 90 ? "score-good" : pct >= 70 ? "score-mid" : "score-low"; return '' + r.name + '' + r.dept + '' + '' + pct + '% (' + r.score + '/' + r.total + ')' + '' + r.date + ''; }).join(""); } function renderDashboard() { renderStats(); renderResultsTable(); } function exportCsv() { var rows = getResults(); var head = ["Қызметкер", "Бөлім", "Нәтиже", "Дата"]; var body = rows.map(function (r) { return [r.name, r.dept, r.score + "/" + r.total, r.date] .map(function (v) { return '"' + String(v).replace(/"/g, '""') + '"'; }).join(";"); }); var csv = "\uFEFF" + [head.join(";"), ...body].join("\n"); var a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" })); a.download = "test_results.csv"; a.click(); } function tryBackend() { fetch("/api/results").then(function (r) { return r.json(); }).then(function (data) { if (data && Array.isArray(data)) { results = data; resultsBackendReady = true; renderDashboard(); } }).catch(function () {}); } document.getElementById("law-search").addEventListener("input", renderLaws); document.getElementById("dash-search").addEventListener("input", renderResultsTable); document.getElementById("export-btn").addEventListener("click", exportCsv); document.getElementById("start-test-btn").addEventListener("click", initTest); document.getElementById("restart-test-btn").addEventListener("click", function () { document.getElementById("test-start").style.display = "block"; document.getElementById("test-result").style.display = "none"; }); document.getElementById("complaint-form").addEventListener("submit", function (e) { e.preventDefault(); var form = e.target; var data = { category: form.category.value, text: form.text.value }; fetch("/api/complaints", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then(function () { form.reset(); showFormSuccess(); }).catch(function () { form.reset(); showFormSuccess(); }); }); function showFormSuccess() { var note = document.querySelector(".form-note"); var old = note.textContent; note.textContent = "✅ Өтініш жіберілді!"; setTimeout(function () { note.textContent = old; }, 4000); } document.getElementById("video-form").addEventListener("submit", function (e) { e.preventDefault(); var form = e.target; var btn = form.querySelector("button"); var file = form.file.files[0]; if (!file) { alert("Видео файлды таңдаңыз"); return; } var fd = new FormData(); fd.append("title", form.title.value); fd.append("file", file); btn.disabled = true; btn.textContent = "Жүктелуде..."; fetch("/api/upload", { method: "POST", body: fd }) .then(function (r) { return r.json(); }) .then(function (d) { if (d.ok) { alert("✅ Видео жүктелді!"); form.reset(); loadData(); } else { alert("Қате: файл жүктелмеді"); } }) .catch(function () { alert("Сервер қолжетімсіз"); }) .finally(function () { btn.disabled = false; btn.textContent = "Жүктеу"; }); }); document.querySelectorAll(".nav-link").forEach(function (link) { link.addEventListener("click", function (e) { e.preventDefault(); document.querySelectorAll(".nav-link").forEach(function (l) { l.classList.remove("active"); }); link.classList.add("active"); document.querySelector(link.getAttribute("href")).scrollIntoView({ behavior: "smooth" }); }); }); renderDashboard(); loadData(); tryBackend();