ubt-tarih-sayt-dayyndalu-ush/index.html

954 lines
37 KiB
HTML
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.

<!DOCTYPE html>
<html lang="kk">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ҰБТ Тарих — Дайындық</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📚</text></svg>">
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7}
*{box-sizing:border-box;margin:0;padding:0}
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
.hero{background:var(--ink);color:var(--white);padding:100px 24px}
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px}
.hero p{font-size:20px;color:#9aa3b2;max-width:600px;margin-bottom:32px}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none;cursor:pointer;border:none}
.btn:hover{opacity:0.9}
.section{padding:80px 24px}
.section h2{font-size:36px;font-weight:700;margin-bottom:24px}
.card{background:var(--gray-100);border-radius:16px;padding:32px;margin-bottom:16px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px}
.stat-card{background:var(--cyan-50);border-radius:12px;padding:24px;text-align:center}
.stat-num{font-size:48px;font-weight:800;color:var(--ink)}
.stat-label{color:var(--gray-500);margin-top:8px}
input,select,textarea{width:100%;padding:12px;border:1px solid var(--gray-100);border-radius:8px;margin-bottom:16px;font-size:17px}
input:focus,select:focus,textarea:focus{outline:2px solid var(--cyan);border-color:transparent}
.nav{background:var(--ink);padding:12px 16px;position:sticky;top:0;z-index:100}
.nav-content{max-width:1140px;margin:0 auto;display:flex;justify-content:space-between;align-items:center;position:relative}
.nav-logo{color:var(--white);font-weight:800;font-size:20px;text-decoration:none;z-index:101}
.nav-links{display:flex;gap:16px}
.nav-links a{color:var(--white);text-decoration:none;font-weight:600;font-size:14px;white-space:nowrap}
.nav-links a:hover{color:var(--cyan)}
.nav-links a.admin-only{display:none}
.mobile-menu-btn{display:none;background:none;border:none;color:var(--white);font-size:28px;cursor:pointer;padding:4px}
.mobile-menu{display:none;position:fixed;top:52px;left:0;right:0;background:var(--ink);padding:16px;z-index:99;box-shadow:0 4px 12px rgba(0,0,0,0.3);max-height:80vh;overflow-y:auto}
.mobile-menu.show{display:block}
.mobile-menu a{display:block;color:var(--white);padding:14px 0;text-decoration:none;font-size:17px;border-bottom:1px solid rgba(255,255,255,0.08)}
.mobile-menu a:last-child{border-bottom:none}
.mobile-menu a:hover{color:var(--cyan)}
.mobile-menu a.admin-only{display:none}
.mobile-menu-btn{display:none;background:none;border:none;color:var(--white);font-size:24px;cursor:pointer}
.mobile-menu{display:none;position:fixed;top:60px;left:0;right:0;background:var(--ink);padding:20px;z-index:99;box-shadow:0 4px 12px rgba(0,0,0,0.3)}
.mobile-menu a{display:block;color:var(--white);padding:16px 0;text-decoration:none;font-size:18px;border-bottom:1px solid rgba(255,255,255,0.1)}
.mobile-menu a:hover{color:var(--cyan)}
.page{display:none}
.page.active{display:block}
.topic-card{background:linear-gradient(135deg,var(--ink),#1a1f29);color:var(--white);border-radius:16px;padding:24px;cursor:pointer;transition:transform 0.2s}
.topic-card:hover{transform:translateY(-4px)}
.topic-card h3{font-size:20px;margin-bottom:12px}
.topic-card p{color:#9aa3b2;font-size:14px}
.video-card{border-radius:16px;overflow:hidden;background:var(--gray-100}
.video-placeholder{width:100%;height:200px;background:linear-gradient(135deg,var(--cyan-50),var(--gray-100));display:flex;align-items:center;justify-content:center;font-size:48px}
.video-info{padding:16px}
.video-info h4{font-size:18px;margin-bottom:8px}
.video-info p{color:var(--gray-500);font-size:14px}
.test-question{background:var(--white);border:1px solid var(--gray-100);border-radius:12px;padding:20px;margin-bottom:16px}
.test-question h4{font-size:18px;margin-bottom:16px}
.test-option{display:block;padding:12px;border:1px solid var(--gray-100);border-radius:8px;margin-bottom:8px;cursor:pointer}
.test-option:hover{background:var(--cyan-50)}
.test-option.selected{background:var(--cyan);border-color:var(--cyan)}
.test-option.correct{background:#22c55e;border-color:#22c55e;color:white}
.test-option.wrong{background:#ef4444;border-color:#ef4444;color:white}
.upload-area{border:2px dashed var(--gray-100);border-radius:16px;padding:40px;text-align:center;background:var(--gray-100;cursor:pointer}
.upload-area:hover{border-color:var(--cyan)}
.badge{display:inline-block;padding:4px 12px;border-radius:20px;font-size:12px;font-weight:600;background:var(--cyan-50);color:var(--ink)}
.progress-bar{height:8px;background:var(--gray-100);border-radius:4px;overflow:hidden}
.progress-fill{height:100%;background:var(--cyan);transition:width 0.3s}
@media(max-width:640px){
.hero h1{font-size:28px}.hero p{font-size:15px}.section h2{font-size:22px}
.nav-links{display:none}.mobile-menu-btn{display:block}
.container{padding:32px 14px}.card{padding:16px;border-radius:12px}.topic-card{padding:14px;border-radius:12px}
.stat-num{font-size:32px}.stat-label{font-size:13px}.btn{padding:12px 18px;font-size:15px;border-radius:8px}
input,select,textarea{font-size:16px;padding:12px;border-radius:8px;margin-bottom:12px}
.test-option{padding:12px;font-size:15px;border-radius:6px;margin-bottom:6px}
.video-placeholder{height:180px}.nav{padding:10px 14px}.nav-logo{font-size:17px}
.grid{grid-template-columns:1fr;gap:12px}.upload-area{padding:24px 16px}
.topic-card h3{font-size:17px}.topic-card p{font-size:13px}
.badge{font-size:11px;padding:3px 10px;border-radius:16px}
.test-question{padding:16px;border-radius:10px;margin-bottom:12px}
#prevBtn,#nextBtn,#finishBtn{flex:1;padding:14px}
}
</style>
</head>
<body>
<nav class="nav">
<div class="nav-content">
<a href="#" class="nav-logo" onclick="showPage('home');return false">📚 ҰБТ Тарих</a>
<button class="mobile-menu-btn" onclick="toggleMobileMenu()">☰</button>
<div class="nav-links">
<a href="#" onclick="showPage('home');return false">Тесттер</a>
<a href="#" onclick="showPage('videos');return false">Видео</a>
<a href="#" onclick="showPage('dashboard');return false">Дашборд</a>
<a href="#" onclick="showPage('admin');return false" class="admin-only" id="navAdminLink">Админ</a>
<a href="#" onclick="showPage('login');return false" id="loginLink">Кіру</a>
</div>
</div>
<div class="mobile-menu" id="mobileMenu">
<a href="#" onclick="showPage('home');toggleMobileMenu()">Тесттер</a>
<a href="#" onclick="showPage('videos');toggleMobileMenu()">Видео</a>
<a href="#" onclick="showPage('dashboard');toggleMobileMenu()">Дашборд</a>
<a href="#" onclick="showPage('admin');toggleMobileMenu()" class="admin-only" id="mobileNavAdminLink">Админ</a>
<a href="#" onclick="showPage('login');toggleMobileMenu()" id="mobileLoginLink">Кіру</a>
</div>
</nav>
<!-- LOGIN PAGE -->
<div id="login" class="page">
<div class="container" style="max-width:400px;margin-top:60px">
<div class="card">
<h2 style="text-align:center;margin-bottom:24px">Кіру</h2>
<form id="loginForm">
<input type="tel" id="loginPhone" placeholder="+7 (___) ___-__-__" required>
<input type="password" id="loginPassword" placeholder="Құпиясөз" required>
<button type="submit" class="btn" style="width:100%">Кіру</button>
</form>
<p style="text-align:center;margin-top:16px;color:var(--gray-500)">
Әлі тіркелмегенсіз бе? <a href="#" onclick="showPage('register');return false" style="color:var(--cyan)">Тіркеу</a>
</p>
</div>
</div>
</div>
<!-- REGISTER PAGE -->
<div id="register" class="page">
<div class="container" style="max-width:400px;margin-top:60px">
<div class="card">
<h2 style="text-align:center;margin-bottom:24px">Тіркеу</h2>
<form id="registerForm">
<input type="tel" id="regPhone" placeholder="+7 (___) ___-__-__" required>
<input type="text" id="regName" placeholder="Аты-жөніңіз" required>
<input type="password" id="regPassword" placeholder="Құпиясөз" required>
<button type="submit" class="btn" style="width:100%">Тіркеу</button>
</form>
<p style="text-align:center;margin-top:16px;color:var(--gray-500)">
Аккаунт бар ма? <a href="#" onclick="showPage('login');return false" style="color:var(--cyan)">Кіру</a>
</p>
</div>
</div>
</div>
<!-- HOME PAGE -->
<div id="home" class="page active">
<section class="hero">
<div class="container" style="padding:40px 24px">
<h1>ҰБТ Тарихқа Дайындық</h1>
<p>Тест тапсырмалары, видео-сабақтар және жеке прогресс бақылау жүйесі</p>
<a href="#topics" class="btn">Бастау</a>
</div>
</section>
<section id="topics" class="section">
<div class="container">
<h2>Тақырыптар</h2>
<div class="grid" id="topicsGrid"></div>
</div>
</section>
<section class="section" style="background:var(--cyan-50)">
<div class="container">
<h2>Жаңа Тесттер</h2>
<div class="grid" id="recentTests"></div>
</div>
</section>
</div>
<!-- VIDEOS PAGE -->
<div id="videos" class="page">
<div class="container">
<h2 style="margin-bottom:24px">Видео Сабақтар</h2>
<div class="grid" id="videosGrid"></div>
<div id="noVideos" style="display:none;text-align:center;padding:40px">
<div style="font-size:48px;margin-bottom:16px">🎥</div>
<h3 style="margin-bottom:8px">Видео жоқ</h3>
<p style="color:var(--gray-500)">Админ панельден видео файл жүктеңіз</p>
</div>
</div>
</div>
<!-- TEST PAGE -->
<div id="test" class="page">
<div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px">
<h2 id="testTitle">Тест</h2>
<div>
<span class="badge" id="questionNum">1/20</span>
</div>
</div>
<div class="progress-bar" style="margin-bottom:24px">
<div class="progress-fill" id="testProgress" style="width:0%"></div>
</div>
<div id="testContent"></div>
<div style="margin-top:24px;display:flex;gap:12px">
<button class="btn" id="prevBtn" onclick="prevQuestion()" style="background:var(--gray-100)">Артқа</button>
<button class="btn" id="nextBtn" onclick="nextQuestion()">Келесі</button>
<button class="btn" id="finishBtn" onclick="finishTest()" style="display:none;background:#22c55e">Аяқтау</button>
</div>
</div>
</div>
<!-- DASHBOARD PAGE -->
<div id="dashboard" class="page">
<div class="container">
<h2 style="margin-bottom:24px">Менің Прогрессім</h2>
<div class="grid" style="margin-bottom:40px">
<div class="stat-card">
<div class="stat-num" id="dashTests">0</div>
<div class="stat-label">Өтілген тесттер</div>
</div>
<div class="stat-card">
<div class="stat-num" id="dashPercent">0%</div>
<div class="stat-label">Дұрыс жауаптар</div>
</div>
<div class="stat-card">
<div class="stat-num" id="dashTime">0с</div>
<div class="stat-label">Дайындық уақыты</div>
</div>
</div>
<h3 style="margin-bottom:16px">Соңғы Нәтижелер</h3>
<div id="resultsList"></div>
</div>
</div>
<!-- ADMIN PAGE -->
<div id="admin" class="page">
<div class="container">
<h2 style="margin-bottom:24px">Админ Панель</h2>
<div class="card" style="margin-bottom:24px">
<h3 style="margin-bottom:16px">📊 Оқушылар</h3>
<div id="studentsList"></div>
</div>
<div class="card" style="margin-bottom:24px">
<h3 style="margin-bottom:16px">Тест Жүктеу</h3>
<div class="upload-area" onclick="document.getElementById('testFile').click()">
<div style="font-size:48px;margin-bottom:16px">📁</div>
<p>Тест файлы таңдау (Excel/CSV)</p>
<input type="file" id="testFile" accept=".xlsx,.xls,.csv" style="display:none" onchange="handleTestUpload(this)">
</div>
<div id="testUploadResult" style="margin-top:16px;color:var(--gray-500)"></div>
</div>
<div class="card" style="margin-bottom:24px">
<h3 style="margin-bottom:16px">Видео Жүктеу</h3>
<div class="upload-area" onclick="document.getElementById('videoFile').click()">
<div style="font-size:48px;margin-bottom:16px">🎥</div>
<p>Видео файл таңдау (MP4)</p>
<input type="file" id="videoFile" accept=".mp4,.avi,.mov" style="display:none" onchange="handleVideoUpload(this)">
</div>
<div id="videoUploadResult" style="margin-top:16px;color:var(--gray-500)"></div>
</div>
<div class="card">
<h3 style="margin-bottom:16px">Барлық Тесттер</h3>
<div id="adminTestsList"></div>
</div>
</div>
</div>
<script>
// MOCK DATA - бастапқы деректер
const TOPICS = [
{ id: 1, title: "Қазақстан Ежелгі Дәуірде", desc: "Алғашқы адамдар, қола дәуірі, сақтар", tests: 5 },
{ id: 2, title: "Орта Ғасырлардағы Қазақстан", desc: "Түркі қағанаты, Қарахан мемлекеті", tests: 8 },
{ id: 3, title: "Қазақ Хандығы", desc: "Қазақ хандығының құрылуы, дамуы", tests: 12 },
{ id: 4, title: "Ресей Империясы", desc: "Қазақстан Ресей құрамында", tests: 7 },
{ id: 5, title: "Кеңес Дәуірі", desc: "Қазан төңкерісі, КСРО кезеңі", tests: 15 },
{ id: 6, title: "Тәуелсіз Қазақстан", desc: "1991 жылдан бері", tests: 6 }
];
const MOCK_TESTS = [
{ id: 1, topicId: 1, title: "Ежелгі Қазақстан", questions: 20, time: "20 мин" },
{ id: 2, topicId: 2, title: "Орта Ғасырлар", questions: 25, time: "25 мин" },
{ id: 3, topicId: 3, title: "Қазақ Хандығы", questions: 30, time: "30 мин" }
];
const MOCK_VIDEOS = [
{ id: 1, title: "Қазақ Хандығының Құрылуы", duration: "15:30", topic: "Қазақ Хандығы" },
{ id: 2, title: "Сақтардың Мәдениеті", duration: "12:45", topic: "Ежелгі Дәуір" },
{ id: 3, title: "Тәуелсіздік Жолы", duration: "20:15", topic: "Тәуелсіз Қазақстан" }
];
const SAMPLE_QUESTIONS = [
{ q: "Қазақ хандығы қай жылы құрылды?", options: ["1465", "1456", "1564", "1380"], correct: 0 },
{ q: "Алтын адам қай жерден табылды?", options: ["Есік қорғаны", "Берел қорғаны", "Тамғалы", "Отырар"], correct: 0 },
{ q: "Қазақтың тұңғыш ханы кім?", options: ["Керей", "Жәнібек", "Қасым", "Хақназар"], correct: 0 },
{ q: "Ұлы Жібек жолы қай ғасырда дамыды?", options: ["V-XV ғғ", "III-VII ғғ", "X-XV ғғ", "I-III ғғ"], correct: 2 },
{ q: "Абылай хан қай жылы туылды?", options: ["1711", "1723", "1705", "1718"], correct: 0 }
];
let currentUser = null;
let currentTest = null;
let currentQuestionIndex = 0;
let userAnswers = [];
let testStartTime = null;
const ADMIN_PHONE = '77474700532';
const ADMIN_PASS = 'Aa123456';
const ADMIN_NAME = 'Бижігіт Айбек';
// USER DATA - локальды сақтау
function getUserData() {
if (!currentUser) return null;
const data = localStorage.getItem('ubt_user_' + currentUser);
return data ? JSON.parse(data) : { tests: [], results: [], totalTime: 0 };
}
function saveUserData(data) {
if (!currentUser) return;
localStorage.setItem('ubt_user_' + currentUser, JSON.stringify(data));
}
function getAllStudents() {
const users = JSON.parse(localStorage.getItem('ubt_students') || '{}');
return users;
}
function saveStudent(phone, data) {
const users = JSON.parse(localStorage.getItem('ubt_students') || '{}');
users[phone] = data;
localStorage.setItem('ubt_students', JSON.stringify(users));
}
function banStudent(phone) {
const users = JSON.parse(localStorage.getItem('ubt_students') || '{}');
if (users[phone]) {
users[phone].banned = true;
localStorage.setItem('ubt_students', JSON.stringify(users));
return true;
}
return false;
}
function unbanStudent(phone) {
const users = JSON.parse(localStorage.getItem('ubt_students') || '{}');
if (users[phone]) {
users[phone].banned = false;
localStorage.setItem('ubt_students', JSON.stringify(users));
return true;
}
return false;
}
// PAGE NAVIGATION
function showPage(pageId) {
// Check admin-only pages
if (pageId === 'admin' && currentUser !== 'admin') {
alert('⛔ Админ панельге кіру рұқсаты жоқ!');
showPage('home');
return;
}
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(pageId).classList.add('active');
if (pageId === 'dashboard') updateDashboard();
if (pageId === 'admin') {
if (currentUser === 'admin') {
updateAdminList();
renderStudentsList();
} else {
showPage('home');
return;
}
}
if (pageId === 'home') { renderTopics(); renderRecentTests(); }
window.scrollTo(0, 0);
}
function toggleMobileMenu() {
const menu = document.getElementById('mobileMenu');
menu.classList.toggle('show');
}
function updateAdminLinks() {
const isAdmin = currentUser === 'admin';
document.querySelectorAll('.admin-only').forEach(el => {
el.style.display = isAdmin ? 'block' : 'none';
});
}
// RENDER TOPICS
async function renderTopics() {
const grid = document.getElementById('topicsGrid');
if (!grid) return;
try {
const res = await fetch('/api/tests');
const tests = await res.json();
const topicCounts = {};
tests.forEach(t => {
topicCounts[t.topicId] = (topicCounts[t.topicId] || 0) + 1;
});
grid.innerHTML = TOPICS.map(t => {
const count = topicCounts[t.id] || 0;
const hasTests = count > 0;
return `
<div class="topic-card" style="${hasTests ? '' : 'opacity:0.6;cursor:not-allowed'}"
onclick="${hasTests ? 'showTopicTests(' + t.id + ')' : ''}">
<h3>${t.title}</h3>
<p>${t.desc}</p>
<div style="margin-top:16px">
<span class="badge" style="background:${hasTests ? 'var(--cyan-50)' : '#e5e7eb'}">
${count} тест${hasTests ? '' : ' (жоқ)'}
</span>
</div>
</div>
`}).join('');
} catch (e) {
grid.innerHTML = TOPICS.map(t => `
<div class="topic-card">
<h3>${t.title}</h3>
<p>${t.desc}</p>
<div style="margin-top:16px"><span class="badge">0 тест</span></div>
</div>
`).join('');
}
}
async function showTopicTests(topicId) {
try {
const res = await fetch('/api/tests');
const tests = await res.json();
const topicTests = tests.filter(t => t.topicId === topicId);
if (topicTests.length === 0) {
alert('📭 Бұл тақырыпта әлі тест жоқ.\n\nАдминге хабарлаңыз немесе өзіңіз админ ретінде кіріп, тест жүктеңіз.');
return;
}
if (topicTests.length === 1) {
startServerTest(topicTests[0].id);
return;
}
const choice = prompt('Тақырыпта ' + topicTests.length + ' тест бар:\n\n' +
topicTests.map((t, i) => (i+1) + ') ' + t.title).join('\n') + '\n\nТест нөмірін енгізіңіз (1-' + topicTests.length + '):');
if (choice) {
const idx = parseInt(choice) - 1;
if (idx >= 0 && idx < topicTests.length) {
startServerTest(topicTests[idx].id);
}
}
} catch (e) {
alert('Қате: ' + e.message);
}
}
// RENDER RECENT TESTS
async function renderRecentTests() {
const grid = document.getElementById('recentTests');
if (!grid) return;
try {
const res = await fetch('/api/tests');
const tests = await res.json();
if (tests.length === 0) {
grid.innerHTML = `
<div class="card" style="grid-column:1/-1;text-align:center;padding:40px">
<div style="font-size:48px;margin-bottom:16px">📭</div>
<h3 style="margin-bottom:8px">Тест жоқ</h3>
<p style="color:var(--gray-500);margin-bottom:16px">Админ панельден тест файлы жүктеңіз</p>
<button class="btn" onclick="showPage('login')">Админ кіру</button>
</div>
`;
return;
}
grid.innerHTML = tests.slice(0, 3).map(t => `
<div class="card" onclick="startServerTest(${t.id})" style="cursor:pointer">
<h4 style="margin-bottom:8px">${t.title}</h4>
<p style="color:var(--gray-500);font-size:14px">${t.questions.length} сұрақ</p>
</div>
`).join('');
} catch (e) {
grid.innerHTML = MOCK_TESTS.slice(0, 3).map(t => `
<div class="card" onclick="startTestById(${t.id})" style="cursor:pointer">
<h4 style="margin-bottom:8px">${t.title}</h4>
<p style="color:var(--gray-500);font-size:14px">${t.questions} сұрақ • ${t.time}</p>
</div>
`).join('');
}
}
// RENDER VIDEOS
async function renderVideos() {
const grid = document.getElementById('videosGrid');
const noVideos = document.getElementById('noVideos');
if (!grid) return;
try {
const res = await fetch('/api/videos');
const videos = await res.json();
if (videos.length === 0) {
grid.innerHTML = '';
if (noVideos) noVideos.style.display = 'block';
return;
}
if (noVideos) noVideos.style.display = 'none';
grid.innerHTML = videos.map(v => `
<div class="video-card">
<video controls style="width:100%;height:200px;background:#000">
<source src="/api/video/${v.filename}" type="video/mp4">
</video>
<div class="video-info">
<h4>${v.title}</h4>
<p>${v.topic}</p>
</div>
</div>
`).join('');
} catch (e) {
if (noVideos) noVideos.style.display = 'block';
grid.innerHTML = '';
}
}
// TEST FUNCTIONS
function startTest(topicId) {
const topic = TOPICS.find(t => t.id === topicId);
currentTest = {
id: topicId,
title: topic.title,
questions: SAMPLE_QUESTIONS.map(q => ({...q}))
};
startTestFlow();
}
function startTestById(testId) {
const test = MOCK_TESTS.find(t => t.id === testId);
currentTest = {
id: test.id,
title: test.title,
questions: SAMPLE_QUESTIONS.map(q => ({...q}))
};
startTestFlow();
}
async function startServerTest(testId) {
try {
const res = await fetch('/api/tests');
const tests = await res.json();
const test = tests.find(t => t.id === testId);
if (test) {
currentTest = { id: test.id, title: test.title, questions: test.questions };
startTestFlow();
}
} catch (e) {
alert('Тест жүктелмеді: ' + e.message);
}
}
function startTestFlow() {
currentQuestionIndex = 0;
userAnswers = new Array(currentTest.questions.length).fill(null);
testStartTime = Date.now();
showPage('test');
document.getElementById('testTitle').textContent = currentTest.title;
renderQuestion();
}
function renderQuestion() {
const q = currentTest.questions[currentQuestionIndex];
const total = currentTest.questions.length;
const percent = ((currentQuestionIndex + 1) / total) * 100;
document.getElementById('questionNum').textContent = `${currentQuestionIndex + 1}/${total}`;
document.getElementById('testProgress').style.width = percent + '%';
const content = document.getElementById('testContent');
content.innerHTML = `
<div class="test-question">
<h4>${q.q}</h4>
${q.options.map((opt, i) => `
<div class="test-option ${userAnswers[currentQuestionIndex] === i ? 'selected' : ''}"
onclick="selectAnswer(${i})">${opt}</div>
`).join('')}
</div>
`;
document.getElementById('prevBtn').style.display = currentQuestionIndex === 0 ? 'none' : 'block';
document.getElementById('nextBtn').style.display = currentQuestionIndex === total - 1 ? 'none' : 'block';
document.getElementById('finishBtn').style.display = currentQuestionIndex === total - 1 ? 'block' : 'none';
}
function selectAnswer(index) {
userAnswers[currentQuestionIndex] = index;
renderQuestion();
}
function nextQuestion() {
if (currentQuestionIndex < currentTest.questions.length - 1) {
currentQuestionIndex++;
renderQuestion();
}
}
function prevQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
renderQuestion();
}
}
async function finishTest() {
const correct = userAnswers.filter((a, i) => a === currentTest.questions[i].correct).length;
const total = currentTest.questions.length;
const percent = Math.round((correct / total) * 100);
const timeSpent = Math.floor((Date.now() - testStartTime) / 1000);
// Save result to server
if (currentUser) {
try {
await fetch('/api/result', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: currentUser,
test: currentTest.title,
correct: correct,
total: total,
percent: percent,
time: timeSpent,
date: new Date().toISOString()
})
});
} catch (e) {
console.error('Failed to save result:', e);
}
}
// Show results
const content = document.getElementById('testContent');
content.innerHTML = `
<div class="card" style="text-align:center;padding:40px">
<div style="font-size:64px;margin-bottom:16px">${percent >= 60 ? '🎉' : '📚'}</div>
<h3 style="margin-bottom:8px">Тест Аяқталды!</h3>
<p style="font-size:32px;font-weight:800;color:var(--cyan);margin-bottom:16px">${percent}%</p>
<p style="color:var(--gray-500);margin-bottom:24px">${correct}/${total} дұрыс жауап</p>
<button class="btn" onclick="showPage('dashboard')">Дашбордқа</button>
</div>
`;
document.getElementById('finishBtn').style.display = 'none';
document.getElementById('nextBtn').style.display = 'none';
}
// DASHBOARD
async function updateDashboard() {
if (!currentUser) {
document.getElementById('dashTests').textContent = '0';
document.getElementById('dashPercent').textContent = '0%';
document.getElementById('dashTime').textContent = '0с';
return;
}
let userData = { tests: [], totalTime: 0 };
try {
const res = await fetch('/api/user/' + encodeURIComponent(currentUser));
userData = await res.json();
} catch (e) {
userData = getUserData() || { tests: [], totalTime: 0 };
}
const testsCount = userData.tests?.length || 0;
const totalTime = userData.totalTime || 0;
let avgPercent = 0;
if (userData.tests && userData.tests.length > 0) {
avgPercent = Math.round(userData.tests.reduce((sum, r) => sum + (r.percent || 0), 0) / userData.tests.length);
}
document.getElementById('dashTests').textContent = testsCount;
document.getElementById('dashPercent').textContent = avgPercent + '%';
document.getElementById('dashTime').textContent = formatTime(totalTime);
const resultsList = document.getElementById('resultsList');
if (userData.tests && userData.tests.length > 0) {
resultsList.innerHTML = userData.tests.slice(-5).reverse().map(r => `
<div class="card" style="margin-bottom:12px;padding:16px">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<h4 style="margin-bottom:4px">${r.test}</h4>
<p style="color:var(--gray-500);font-size:14px">${new Date(r.date).toLocaleDateString()}</p>
</div>
<div style="text-align:right">
<div style="font-size:24px;font-weight:800;color:${r.percent >= 60 ? '#22c55e' : '#ef4444'}">${r.percent}%</div>
</div>
</div>
</div>
`).join('');
} else {
resultsList.innerHTML = '<p style="color:var(--gray-500);text-align:center;padding:40px">Әлі тест тапсырылмаған</p>';
}
}
function formatTime(seconds) {
if (seconds < 60) return seconds + 'с';
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return m + 'м ' + s + 'с';
}
// ADMIN FUNCTIONS
function renderStudentsList() {
if (currentUser !== 'admin') {
document.getElementById('studentsList').innerHTML = '<p style="color:#ef4444;text-align:center;padding:20px">⛔ Админ емессіз</p>';
return;
}
const students = getAllStudents();
const list = document.getElementById('studentsList');
const studentPhones = Object.keys(students);
if (studentPhones.length === 0) {
list.innerHTML = '<p style="color:var(--gray-500);text-align:center;padding:20px">Әлі ешкім тіркелген жоқ</p>';
return;
}
list.innerHTML = studentPhones.map(phone => {
const s = students[phone];
const isBanned = s.banned === true;
const testsCount = (s.tests || []).length;
return `
<div style="padding:16px;border:1px solid ${isBanned ? '#ef4444' : 'var(--gray-100)'};border-radius:12px;margin-bottom:12px;background:${isBanned ? '#fef2f2' : 'var(--white)'}">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px">
<div style="flex:1;min-width:200px">
<div style="font-weight:700;font-size:16px">${s.name || 'Аты жоқ'}</div>
<div style="color:var(--gray-500);font-size:14px">${phone}</div>
<div style="font-size:13px;color:var(--gray-500);margin-top:4px">
Тіркелген: ${new Date(s.registeredAt).toLocaleDateString('ru-RU')} • Тест: ${testsCount}
</div>
</div>
<div style="display:flex;gap:8px;align-items:center">
${isBanned
? `<span class="badge" style="background:#ef4444;color:white">Блокталған</span>
<button class="btn" onclick="unbanStudent('${phone}')" style="background:#22c55e;padding:8px 16px;font-size:14px">Ашу</button>`
: `<span class="badge" style="background:#22c55e;color:white">Белсенді</span>
<button class="btn" onclick="banStudent('${phone}')" style="background:#ef4444;padding:8px 16px;font-size:14px">Блоктау</button>`
}
</div>
</div>
</div>
`;
}).join('');
}
function banStudent(phone) {
if (!confirm(phone + ' нөмірін блоктау шынымен керек пе?')) return;
if (banStudent(phone)) {
alert('Оқушы блокталды');
renderStudentsList();
}
}
function unbanStudent(phone) {
if (unbanStudent(phone)) {
alert('Оқушының бұғаты алынды');
renderStudentsList();
}
}
async function updateAdminList() {
if (currentUser !== 'admin') {
document.getElementById('adminTestsList').innerHTML = '<p style="color:#ef4444;text-align:center;padding:20px">⛔ Админ емессіз</p>';
return;
}
try {
const res = await fetch('/api/tests');
const tests = await res.json();
const list = document.getElementById('adminTestsList');
if (tests.length === 0) {
list.innerHTML = '<p style="color:var(--gray-500);text-align:center;padding:20px">Тест жоқ</p>';
return;
}
list.innerHTML = tests.map(t => `
<div style="padding:12px;border-bottom:1px solid var(--gray-100);display:flex;justify-content:space-between">
<span>${t.title}</span>
<span class="badge">${t.questions.length} сұрақ</span>
</div>
`).join('');
} catch (e) {
// Fallback to mock
}
}
async function handleTestUpload(input) {
if (currentUser !== ADMIN_USER) {
alert('⛔ Тек админ жүктей алады');
return;
}
const file = input.files[0];
if (!file) return;
const title = prompt('Тест атауы:', file.name.split('.')[0]);
if (!title) return;
const formData = new FormData();
formData.append('file', file);
formData.append('title', title);
formData.append('topicId', '1');
document.getElementById('testUploadResult').textContent = '⏳ Жүктелуде...';
try {
const res = await fetch('/api/upload-test', { method: 'POST', body: formData });
const data = await res.json();
if (data.ok) {
document.getElementById('testUploadResult').textContent = `✅ Жүктелді! ${data.count} сұрақ табылды`;
updateAdminList();
} else {
document.getElementById('testUploadResult').textContent = '❌ Қате: ' + data.error;
}
} catch (e) {
document.getElementById('testUploadResult').textContent = '❌ Қате: ' + e.message;
}
}
async function handleVideoUpload(input) {
if (currentUser !== ADMIN_USER) {
alert('⛔ Тек админ жүктей алады');
return;
}
const file = input.files[0];
if (!file) return;
const title = prompt('Видео атауы:', file.name.split('.')[0]);
if (!title) return;
const topic = prompt('Тақырыбы:', 'Жалпы');
const formData = new FormData();
formData.append('file', file);
formData.append('title', title);
formData.append('topic', topic || 'Жалпы');
document.getElementById('videoUploadResult').textContent = '⏳ Жүктелуде...';
try {
const res = await fetch('/api/upload-video', { method: 'POST', body: formData });
const data = await res.json();
if (data.ok) {
document.getElementById('videoUploadResult').textContent = `✅ Жүктелді! ${Math.round(data.video.size/1024)}KB`;
renderVideos();
} else {
document.getElementById('videoUploadResult').textContent = '❌ Қате: ' + data.error;
}
} catch (e) {
document.getElementById('videoUploadResult').textContent = '❌ Қате: ' + e.message;
}
}
// PHONE MASK
document.getElementById('regPhone').addEventListener('input', function(e) {
let x = e.target.value.replace(/\D/g, '').substring(0, 11);
if (x.startsWith('8')) x = '7' + x.substring(1);
if (!x.startsWith('7')) x = '7' + x;
let formatted = '+' + x;
if (x.length > 1) formatted = '+' + x.substring(0, 1) + ' (' + x.substring(1, 4);
if (x.length > 4) formatted = '+' + x.substring(0, 1) + ' (' + x.substring(1, 4) + ') ' + x.substring(4, 7);
if (x.length > 7) formatted = '+' + x.substring(0, 1) + ' (' + x.substring(1, 4) + ') ' + x.substring(4, 7) + '-' + x.substring(7, 9);
if (x.length > 9) formatted = '+' + x.substring(0, 1) + ' (' + x.substring(1, 4) + ') ' + x.substring(4, 7) + '-' + x.substring(7, 9) + '-' + x.substring(9, 11);
e.target.value = formatted;
});
document.getElementById('loginPhone').addEventListener('input', document.getElementById('regPhone').oninput);
// AUTH
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
const phone = document.getElementById('loginPhone').value.replace(/\D/g, '');
const password = document.getElementById('loginPassword').value;
// Admin check
if (phone === ADMIN_PHONE && password === ADMIN_PASS) {
currentUser = 'admin';
const adminName = ADMIN_NAME;
document.getElementById('loginLink').textContent = '👑 ' + adminName;
document.getElementById('mobileLoginLink').textContent = '👑 ' + adminName;
updateAdminLinks();
showPage('admin');
renderStudentsList();
alert('Қош келдіңіз, ' + adminName + '!');
return;
}
// Check if banned
const students = JSON.parse(localStorage.getItem('ubt_students') || '{}');
if (students[phone] && students[phone].banned) {
alert('🚫 Сіздің аккаунтыңыз бұғатталған.\nТехникалық қолдауге хабарласыңыз.');
return;
}
// Student auth
if (students[phone] && students[phone].password === password) {
currentUser = phone;
const name = students[phone].name || phone;
document.getElementById('loginLink').textContent = '👤 ' + name;
document.getElementById('mobileLoginLink').textContent = '👤 ' + name;
updateAdminLinks();
showPage('home');
alert('Қош келдіңіз, ' + name + '!');
} else {
alert('Нөмір немесе құпиясөз қате');
}
});
document.getElementById('registerForm').addEventListener('submit', function(e) {
e.preventDefault();
const phone = document.getElementById('regPhone').value.replace(/\D/g, '');
const name = document.getElementById('regName').value;
const password = document.getElementById('regPassword').value;
if (phone.length < 11) {
alert('Телефон нөмірін дұрыс енгізіңіз (+7...)');
return;
}
const students = JSON.parse(localStorage.getItem('ubt_students') || '{}');
if (students[phone]) {
alert('Бұл нөмір тіркелген!');
return;
}
students[phone] = {
phone: phone,
name: name,
password: password,
registeredAt: new Date().toISOString(),
banned: false
};
localStorage.setItem('ubt_students', JSON.stringify(students));
alert('Тіркелдіңіз! Енді кіріңіз');
showPage('login');
});
// INIT
renderTopics();
renderRecentTests();
renderVideos();
updateAdminLinks();
</script>
</body>
</html>