362 lines
16 KiB
JavaScript
362 lines
16 KiB
JavaScript
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 = '<div class="empty">Ештеңе табылмады</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = filtered.map(function (l) {
|
||
return '<div class="law-item">' +
|
||
'<div class="law-title">' + l.title + '</div>' +
|
||
'<div class="law-meta"><span class="law-cat">' + l.cat + '</span>' + l.id + '</div>' +
|
||
(l.desc ? '<div class="law-desc">' + l.desc + '</div>' : '') +
|
||
'</div>';
|
||
}).join("");
|
||
}
|
||
|
||
function renderLawChips() {
|
||
var cats = {};
|
||
LAWS.forEach(function (l) { cats[l.cat] = true; });
|
||
var chips = document.getElementById("law-chips");
|
||
chips.innerHTML = '<button class="chip active" data-cat="">Барлығы</button>' +
|
||
Object.keys(cats).map(function (c) {
|
||
return '<button class="chip" data-cat="' + c + '">' + c + '</button>';
|
||
}).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 '<div class="structure-card">' +
|
||
'<div class="structure-avatar">' + s.emoji + '</div>' +
|
||
'<div class="structure-name">' + s.name + '</div>' +
|
||
'<div class="structure-role">' + s.role + '</div>' +
|
||
'<div class="structure-dept">' + s.dept + '</div>' +
|
||
(s.head ? '<div class="structure-head">👑 Басшылық</div>' : '') +
|
||
'</div>';
|
||
}).join("");
|
||
}
|
||
|
||
function renderVideos() {
|
||
var grid = document.getElementById("video-grid");
|
||
if (!VIDEOS.length) {
|
||
grid.innerHTML = '<div class="empty">Әзірге видео жоқ — офицер жүктеген кезде пайда болады</div>';
|
||
return;
|
||
}
|
||
grid.innerHTML = VIDEOS.map(function (v) {
|
||
return '<div class="video-card" onclick="openVideo(' + v.id + ')">' +
|
||
'<div class="video-thumbnail">' + (v.desc || "") + '</div>' +
|
||
'<div class="video-info">' +
|
||
'<div class="video-title">' + v.title + '</div>' +
|
||
'<div class="video-meta"><span>' + v.category + '</span><span class="video-duration">' + v.duration + '</span></div>' +
|
||
'</div></div>';
|
||
}).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 = '<video src="' + v.src + '" controls autoplay style="max-width:90vw;max-height:80vh;border-radius:10px"></video>' +
|
||
'<button style="padding:10px 24px;border:none;border-radius:8px;cursor:pointer;font-size:14px">Жабу (X)</button>';
|
||
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 '<button class="test-option" onclick="answer(' + i + ')">' + opt + '</button>';
|
||
}).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 '<div class="stat-card"><span class="stat-icon">' + s.icon + '</span>' +
|
||
'<span class="stat-value">' + s.val + '</span><span class="stat-label">' + s.label + '</span></div>';
|
||
}).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 = '<tr><td colspan="4" class="empty">Нәтиже жоқ</td></tr>';
|
||
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 '<tr><td>' + r.name + '</td><td>' + r.dept + '</td>' +
|
||
'<td><span class="score-badge ' + cls + '">' + pct + '% (' + r.score + '/' + r.total + ')</span></td>' +
|
||
'<td>' + r.date + '</td></tr>';
|
||
}).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();
|