compliance-tv/script.js

362 lines
16 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.

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();