";
if (state.user.role === "admin") {
var newCount = newItemsCount();
if (newCount > 0) {
html += '
';
html += '
🔔 Жаңа хабарламалар: ' + newCount + "
";
html += '
Шағым жәшігіне және жеке хаттарға жаңа өтініштер келді. Жауап беру үшін «Шағым жәшігі» немесе «Дашборд» бөліміне өтіңіз.
';
html += '';
html += "
";
}
}
html += '
📢 Хабарландырулар
'; if (state.content && state.content.announcements.length) {
state.content.announcements.forEach(function (a) {
html += '
' + esc(a.title) + '
📅 ' + a.date + '
' + esc(a.text) + "
";
});
} else {
html += '
Хабарландыру жоқ
';
}
if (state.user.role === "admin") {
html += '
➕ Жаңа хабарландыру
';
html += '
';
html += '';
html += '';
html += '
';
html += '
';
}
el.innerHTML = html;
}
async function addAnnouncement() {
var title = document.getElementById("annTitle").value.trim();
var text = document.getElementById("annText").value.trim();
var date = document.getElementById("annDate").value || new Date().toISOString().slice(0, 10);
if (!title || !text) { alert("Тақырып пен мәтінді толтырыңыз"); return; }
if (state.offline) {
var list = state.content.announcements;
list.unshift({ id: Date.now(), title: title, text: text, date: date });
renderHome(document.getElementById("content"));
return;
}
var res = await apiPost("/api/announcements", { title: title, text: text, date: date });
if (res.ok) {
await loadServerData();
renderHome(document.getElementById("content"));
} else alert(res.error || "Қате");
}
/* ---------------- БАСШЫЛЫҚ ҚҰРЫЛЫМЫ ---------------- */
function renderOrg(el) {
var org = state.content ? state.content.org : [];
var html = pageTitle("🏛️ Басшылық құрылымы");
var levels = {};
org.forEach(function (o) {
if (!levels[o.level]) levels[o.level] = [];
levels[o.level].push(o);
});
Object.keys(levels).sort(function (a, b) { return a - b; }).forEach(function (lv) {
html += '
▼<\/div>\s*$/, "");
el.innerHTML = html;
}
/* ---------------- МЕМЛЕКЕТТІК ОРГАНДАР ---------------- */
function renderContacts(el) {
var list = state.content && state.content.contacts ? state.content.contacts : [];
var html = pageTitle("📞 Мемлекеттік органдар — Алматы қаласы");
html += '
Комплаенс мәселелері бойынша хабарласуға болатын мемлекеттік органдардың ресми байланыс деректері.
";
el.innerHTML = html;
}
/* ---------------- ВИДЕО ---------------- */
function renderVideo(el) {
var vids = state.content ? state.content.videos : [];
var html = pageTitle("🎬 Видео сабақтар");
html += '
';
vids.forEach(function (v) {
html += '
';
html += '
▶
';
html += "
" + esc(v.title) + '
' + esc(v.desc) + '
⏱ ' + v.dur + "
";
html += "
";
});
html += "
";
el.innerHTML = html;
}
function openVideo(id) {
var v = state.content.videos.find(function (x) { return x.id === id; });
if (!v) return;
var url = prompt("Видео сілтемесін қойыңыз (https://...)", v.link || "https://www.youtube.com/watch?v=dQw4w9WgXcQ");
if (url && state.offline) { v.link = url; }
if (url) window.open(url, "_blank");
}
/* ---------------- ЗАҢДАР ---------------- */
function renderLaws(el) {
var laws = state.content ? state.content.laws : [];
var cats = [];
laws.forEach(function (l) { if (cats.indexOf(l.cat) < 0) cats.push(l.cat); });
var html = pageTitle("⚖️ Заң нормалары мен комплаенс заңдар");
var gr = state.content && state.content.giftRules ? state.content.giftRules : null;
if (gr) {
html += '
';
html += '
🎁 Сыйлықтар ережесі
';
html += '
';
html += '
✅ Рұқсат етілген
';
gr.allowed.forEach(function (t) { html += '
' + esc(t) + "
"; });
html += "
";
html += '
🚫 Тыйым салынған
';
gr.forbidden.forEach(function (t) { html += '
' + esc(t) + "
"; });
html += "
";
html += "
";
}
html += '
';
html += '';
cats.forEach(function (c) { html += '"; });
html += "
";
html += '';
el.innerHTML = html;
filterLaws("all");
}
function filterLaws(cat) {
var laws = state.content ? state.content.laws : [];
document.querySelectorAll(".tab").forEach(function (t) { t.classList.toggle("active", t.dataset.cat === cat); });
var list = cat === "all" ? laws : laws.filter(function (l) { return l.cat === cat; });
var html = "";
list.forEach(function (l) {
html += '
' + esc(l.title) + '
' + esc(l.cat) + "
" + esc(l.text) + "
";
});
document.getElementById("lawList").innerHTML = html || '
Табылмады
';
}
/* ---------------- ИНСАЙДЕРЛІК АҚПАРАТ ---------------- */
function renderInsider(el) {
var list = state.content && state.content.insider ? state.content.insider : [];
var html = pageTitle("🔐 Инсайдерлік ақпарат және құпиялылық");
html += '
Медиа саласында инсайдерлік ақпаратпен жұмыс — ең маңызды талаптардың бірі. Төмендегі ережелерді оқып, тест тапсыру ұсынылады.
';
list.forEach(function (item, i) {
html += '
';
html += '
' + (i + 1) + ". " + esc(item.title) + "
";
html += "
" + esc(item.text) + "
";
html += "
";
});
html += '
';
html += '
📝 Материалды оқыдыңыз ба?
';
html += '
«Инсайдерлік ақпарат және құпиялылық» тестін тапсырып, біліміңізді тексеріңіз.
';
html += '';
html += "
";
el.innerHTML = html;
}
/* ---------------- ШАҒЫМ ЖӘШІГІ ---------------- */
function renderComplaints(el) {
var html = pageTitle("✉️ Шағым жәшігі");
html += '
';
html += '
Жаңа шағым / өтініш
';
html += '
';
html += '';
html += '';
html += '';
html += '';
html += '
';
html += '
🔒 Комплаенс офицерге жеке хат
';
html += '
';
html += '';
html += '';
html += '';
html += '';
html += '
';
html += "
";
html += '
Менің шағымдарым' + (state.user.role === "admin" ? " және барлық өтініштер" : "") + "
";
html += '';
html += '
Менің жеке хаттарым (комплаенс офицерге)
';
html += '';
el.innerHTML = html;
renderComplaintList();
renderMyDmList();
}
function renderMyDmList() {
var list = (state.directMessages || []).slice().sort(function (a, b) { return b.id - a.id; });
if (state.user.role !== "admin") {
list = list.filter(function (m) { return m.authorLogin === state.user.login; });
}
var html = "";
list.forEach(function (m) {
var st = { "new": "Жаңа", "work": "Қаралуда", "done": "Шешілді" }[m.status] || m.status;
html += '
";
}
async function setComplaintStatus(id, status) {
if (state.offline) {
var c = state.complaints.find(function (x) { return x.id === id; });
if (c) { c.status = status; lsSet("complaints", state.complaints); }
renderComplaintList();
return;
}
var res = await apiPost("/api/complaints/" + id + "/status", { status: status });
if (res.ok) { await loadServerData(); renderComplaintList(); }
else alert(res.error || "Қате");
}
async function sendDirectMsg() {
var category = document.getElementById("dmCat").value;
var text = document.getElementById("dmText").value.trim();
var anonymous = document.getElementById("dmAnon").checked;
if (!text) { document.getElementById("dmMsg").innerHTML = '
";
}
/* ---------------- ҚЫЗМЕТКЕРЛЕР (АККАУНТТАР, админ) ---------------- */
async function renderUsers(el) {
var html = pageTitle("👥 Қызметкерлер және аккаунттар");
html += '
Логиндер мен құпия сөздер users.json файлында сақталады. Мұнда жаңа қызметкер қосуға, құпия сөз ауыстыруға және аккаунтты өшіруге болады.
';
html += '
➕ Жаңа қызметкер қосу
';
html += '';
html += '';
html += '
';
html += '';
html += '';
html += '
';
html += '';
html += '
';
html += '';
el.innerHTML = html;
loadUsersList();
}
async function loadUsersList() {
var users;
if (state.offline) {
users = MOCK_USERS;
} else {
var r = await apiGet("/api/users");
if (!r.ok) return;
users = r.data;
}
var html = '
";
document.getElementById("usersList").innerHTML = html;
}
async function addUser() {
var login = document.getElementById("uLogin").value.trim();
var name = document.getElementById("uName").value.trim();
var pass = document.getElementById("uPass").value.trim();
var role = document.getElementById("uRole").value;
var msg = document.getElementById("uMsg");
if (!login || !name || !pass) { msg.innerHTML = '
Барлық өрістерді толтырыңыз
'; return; }
if (state.offline) {
MOCK_USERS.push({ login: login, name: name, password: pass, role: role });
msg.innerHTML = '
✅ Қосылды (демо)
';
loadUsersList();
return;
}
var res = await apiPost("/api/users", { login: login, name: name, password: pass, role: role });
if (res.ok) {
msg.innerHTML = '
';
}
function startTest(id) {
state.curTest = id;
state.curTestAnswers = {};
renderTests(document.getElementById("content"));
}
function renderTestRun(el) {
var t = state.content.tests.find(function (x) { return x.id === state.curTest; });
if (!t) { state.curTest = null; renderTests(el); return; }
var html = '';
html += '
' + esc(t.title) + "
";
t.questions.forEach(function (q, qi) {
html += '
' + (qi + 1) + ". " + esc(q.q) + "
";
q.options.forEach(function (op, oi) {
html += '";
});
html += "
";
});
html += '';
el.innerHTML = html;
}
function pickAnswer(elm, qi, oi) {
state.curTestAnswers[qi] = oi;
document.querySelectorAll('.q-option[data-q="' + qi + '"]').forEach(function (o) {
o.classList.toggle("selected", o.dataset.o === String(oi));
});
}
async function submitTest() {
var t = state.content.tests.find(function (x) { return x.id === state.curTest; });
var total = t.questions.length;
var answered = Object.keys(state.curTestAnswers).length;
if (answered < total) { alert("Барлық сұрақтарға жауап беріңіз (" + answered + "/" + total + ")"); return; }
var answers = t.questions.map(function (q, qi) { return state.curTestAnswers[qi]; });
if (state.offline) {
var correct = 0;
t.questions.forEach(function (q, qi) { if (state.curTestAnswers[qi] === q.correct) correct++; });
var pct = Math.round(correct / total * 100);
state.testResults.push({ testId: t.id, login: state.user.login, name: state.user.name, score: pct, date: new Date().toISOString().slice(0, 10) });
lsSet("testResults", state.testResults);
showTestResult(pct, correct, total);
return;
}
var res = await apiPost("/api/test-results", { testId: t.id, answers: answers });
if (res.ok) {
await loadServerData();
showTestResult(res.score, res.correct, res.total);
} else alert(res.error || "Қате");
}
function showTestResult(pct, score, total) {
state.curTest = null;
var el = document.getElementById("content");
var html = '
';
html += '
' + pct + "%
";
html += "
Дұрыс жауаптар: " + score + " / " + total + "
";
html += '
' + (pct >= 70 ? "✅ Тест сәтті өтілді!" : "Тест өтілмеді, қайта тапсыруға болады") + "
";
html += '';
html += "
";
el.innerHTML = html;
}
/* ---------------- ДАШБОРД (АДМИН) ---------------- */
async function renderStats(el) {
var html = pageTitle("📊 Дашборд — статистика");
var data;
if (state.offline) {
data = computeStats();
} else {
var r = await apiGet("/api/stats");
if (r.ok) data = r.data; else data = computeStats();
}
if (!data) { el.innerHTML = '
Деректер жоқ
'; return; }
html += '
';
html += '
' + data.users + "
Қызметкерлер
";
html += '
' + data.complaintsTotal + "
Шағымдар (барлығы)
";
html += '
' + data.complaintsOpen + "
Ашық шағымдар
";
html += '
' + data.testsTotal + "
Тест тапсырулар
";
html += "
";
if (state.user.role === "admin") {
html += '
';
html += '
' + (data.dmTotal || 0) + "
Жеке хаттар (барлығы)
";
html += '
' + (data.dmOpen || 0) + "
Жауап күтіп тұрған хаттар
";
html += '
' + (data.dmDone || 0) + "
Шешілген хаттар
";
html += "
";
}
html += '
';
html += '
Шағымдар санаты бойынша
';
html += '
Тест өту көрсеткіші
';
html += "
";
html += '
Тест нәтижелері бойынша орташа балл
';
html += '
Соңғы шағымдар
';
html += '
🔒 Комплаенс офицерге жеке хаттар
';
el.innerHTML = html;
var catData = data.byCategory;
var catMax = Math.max.apply(null, catData.map(function (c) { return c.count; }).concat([1]));
var catHtml = "";
catData.forEach(function (c) {
var w = Math.max(8, Math.round(c.count / catMax * 100));
catHtml += '
';
var passHtml = "";
["done", "work", "new"].forEach(function (s) {
var c = data.byStatus[s] || 0;
var w = data.complaintsTotal ? Math.max(8, Math.round(c / data.complaintsTotal * 100)) : 8;
var color = s === "done" ? "green" : s === "work" ? "orange" : "red";
var name = s === "done" ? "Шешілді" : s === "work" ? "Қаралуда" : "Жаңа";
passHtml += '