674 lines
34 KiB
HTML
674 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Адаптационная программа стажёров</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
primary: {
|
||
50: '#e6f0ff', 100: '#cce0ff', 200: '#99c2ff', 300: '#66a3ff',
|
||
400: '#3385ff', 500: '#0066ff', 600: '#0052cc', 700: '#003d99',
|
||
800: '#002966', 900: '#001433'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style>
|
||
.btn-primary { background-color: #0052cc; color: white; font-weight: 500; padding: 0.5rem 1rem; border-radius: 0.5rem; transition: background-color 0.2s; }
|
||
.btn-primary:hover { background-color: #003d99; }
|
||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
.btn-secondary { background-color: #e5e7eb; color: #1f2937; font-weight: 500; padding: 0.5rem 1rem; border-radius: 0.5rem; transition: background-color 0.2s; }
|
||
.btn-secondary:hover { background-color: #d1d5db; }
|
||
.input-field { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.5rem; outline: none; }
|
||
.input-field:focus { ring: 2px; ring-color: #0066ff; border-color: #0066ff; }
|
||
.card { background-color: white; border-radius: 0.75rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); padding: 1.5rem; }
|
||
.table-container { overflow-x: auto; border-radius: 0.5rem; border: 1px solid #e5e7eb; }
|
||
.table { width: 100%; border-collapse: collapse; }
|
||
.table thead tr { background-color: #f9fafb; }
|
||
.table th { padding: 0.75rem 1.5rem; text-align: left; font-size: 0.75rem; font-weight: 500; color: #6b7280; text-transform: uppercase; }
|
||
.table td { padding: 1rem 1.5rem; font-size: 0.875rem; color: #111827; }
|
||
.table tbody tr:hover { background-color: #f9fafb; }
|
||
.nav-link { padding: 0.5rem 0.75rem; border-radius: 0.375rem; font-size: 0.875rem; font-weight: 500; transition: background-color 0.2s; }
|
||
.nav-link:hover { background-color: #0052cc; }
|
||
.nav-link.active { background-color: #002966; }
|
||
</style>
|
||
</head>
|
||
<body class="min-h-screen bg-gray-50">
|
||
<header class="bg-primary-700 text-white shadow-lg">
|
||
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
||
<a href="#" onclick="showPage('home'); return false;" class="text-xl font-bold">📚 Адаптация стажёров</a>
|
||
<nav class="flex items-center gap-4">
|
||
<a href="#" onclick="showPage('progress'); return false;" class="nav-link" id="nav-progress">Прогресс</a>
|
||
<a href="#" onclick="showPage('exam'); return false;" class="nav-link" id="nav-exam">Экзамен</a>
|
||
<a href="#" onclick="showPage('whatsapp'); return false;" class="nav-link" id="nav-whatsapp">WhatsApp</a>
|
||
<span id="userInfo" class="text-sm text-primary-200 hidden"></span>
|
||
<button id="logoutBtn" onclick="logout()" class="text-sm bg-primary-800 hover:bg-primary-900 px-3 py-2 rounded hidden">Выйти</button>
|
||
<button id="trainerLoginBtn" onclick="showPage('trainer'); return false;" class="text-sm bg-primary-800 hover:bg-primary-900 px-3 py-2 rounded">🔐 Тренер</button>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="max-w-7xl mx-auto px-4 py-8">
|
||
<!-- HOME PAGE -->
|
||
<div id="homePage" class="page">
|
||
<div class="bg-gradient-to-r from-primary-600 to-primary-800 text-white py-12 rounded-xl mb-8 text-center">
|
||
<h1 id="programTitle" class="text-4xl font-bold mb-2">Адаптационная программа стажёров</h1>
|
||
<p id="programDesc" class="text-xl text-primary-100">Программа профессиональной адаптации</p>
|
||
</div>
|
||
|
||
<div class="card mb-8">
|
||
<h3 class="text-lg font-semibold mb-4">Путь стажёра</h3>
|
||
<div class="flex flex-wrap justify-center gap-4">
|
||
<div class="flex flex-col items-center p-4 bg-primary-100 text-primary-700 rounded-lg">
|
||
<span class="text-3xl mb-2">📝</span><span class="text-sm">Анкета</span>
|
||
</div>
|
||
<div class="w-12 h-1 bg-gray-300 self-center"></div>
|
||
<div class="flex flex-col items-center p-4 bg-gray-100 text-gray-400 rounded-lg" id="stage2">
|
||
<span class="text-3xl mb-2">👥</span><span class="text-sm">Группы</span>
|
||
</div>
|
||
<div class="w-12 h-1 bg-gray-300 self-center"></div>
|
||
<div class="flex flex-col items-center p-4 bg-gray-100 text-gray-400 rounded-lg" id="stage3">
|
||
<span class="text-3xl mb-2">📚</span><span class="text-sm">Обучение</span>
|
||
</div>
|
||
<div class="w-12 h-1 bg-gray-300 self-center"></div>
|
||
<div class="flex flex-col items-center p-4 bg-gray-100 text-gray-400 rounded-lg" id="stage4">
|
||
<span class="text-3xl mb-2">✅</span><span class="text-sm">Экзамен</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-8">
|
||
<div class="flex flex-col md:flex-row justify-between items-center gap-6">
|
||
<div>
|
||
<h3 id="statusText" class="text-2xl font-bold text-gray-900 mb-2">🟢 Сбор анкет открыт</h3>
|
||
<p id="statusDate" class="text-gray-600"></p>
|
||
</div>
|
||
<a href="#" onclick="showPage('form'); return false;" id="formBtn" class="btn-primary text-lg px-8 py-3 inline-block">Заполнить анкету</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid md:grid-cols-3 gap-6 mb-8">
|
||
<div class="card text-center">
|
||
<span class="text-4xl mb-4 block">📋</span>
|
||
<h3 class="text-lg font-semibold mb-2">Анкета стажёра</h3>
|
||
<p class="text-gray-600 text-sm">Анкету заполняет руководитель стажёра</p>
|
||
</div>
|
||
<div class="card text-center">
|
||
<span class="text-4xl mb-4 block">⚖️</span>
|
||
<h3 class="text-lg font-semibold mb-2">Честное деление</h3>
|
||
<p class="text-gray-600 text-sm">Группы формируются автоматически</p>
|
||
</div>
|
||
<div class="card text-center">
|
||
<span class="text-4xl mb-4 block">📱</span>
|
||
<h3 class="text-lg font-semibold mb-2">Уведомление WhatsApp</h3>
|
||
<p class="text-gray-600 text-sm">Тренер отправит ссылку на чат</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card text-center">
|
||
<h3 class="text-xl font-semibold mb-4">Вы тренер?</h3>
|
||
<p class="text-gray-600 mb-6">Войдите в кабинет тренера для управления программой</p>
|
||
<button onclick="showPage('trainer'); return false;" class="btn-primary text-lg px-8 py-3">🔐 Вход для тренера</button>
|
||
</div>
|
||
|
||
<div id="groupsList" class="card mb-8 hidden">
|
||
<h3 class="text-2xl font-bold mb-6">Список групп</h3>
|
||
<div id="groupsContent" class="grid md:grid-cols-2 gap-6"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- FORM PAGE -->
|
||
<div id="formPage" class="page hidden max-w-2xl mx-auto">
|
||
<div class="card">
|
||
<h2 class="text-2xl font-bold mb-6">Анкета стажёра</h2>
|
||
<form id="traineeForm" class="space-y-4">
|
||
<div class="grid md:grid-cols-2 gap-4">
|
||
<input name="lastName" placeholder="Фамилия *" required class="input-field">
|
||
<input name="firstName" placeholder="Имя *" required class="input-field">
|
||
</div>
|
||
<input name="email" type="email" placeholder="Почта *" required class="input-field">
|
||
<div class="grid md:grid-cols-2 gap-4">
|
||
<input name="division" placeholder="Подразделение *" required class="input-field">
|
||
<input name="position" placeholder="Должность *" required class="input-field">
|
||
</div>
|
||
<input name="phone" placeholder="Телефон *" required class="input-field">
|
||
<input name="supervisorName" placeholder="ФИО руководителя *" required class="input-field">
|
||
<input name="supervisorContact" placeholder="Контакты руководителя *" required class="input-field">
|
||
<select name="city" required class="input-field">
|
||
<option value="">Город *</option>
|
||
<option>Алматы</option><option>Астана</option><option>Шымкент</option>
|
||
<option>Караганда</option><option>Актобе</option><option>Тараз</option>
|
||
<option>Павлодар</option><option>Усть-Каменогорск</option><option>Семей</option>
|
||
<option>Атырау</option><option>Орал</option><option>Кызылорда</option>
|
||
<option>Костанай</option><option>Петропавловск</option><option>Актау</option>
|
||
<option>Другой</option>
|
||
</select>
|
||
<button type="submit" class="btn-primary w-full">Отправить</button>
|
||
</form>
|
||
<div id="formSuccess" class="hidden text-center py-8">
|
||
<div class="text-6xl mb-4">✅</div>
|
||
<h3 class="text-2xl font-bold mb-4">Анкета отправлена!</h3>
|
||
<button onclick="showPage('home')" class="btn-primary">На главную</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TRAINER PAGE -->
|
||
<div id="trainerPage" class="page hidden">
|
||
<div id="loginForm" class="max-w-md mx-auto card">
|
||
<h2 class="text-2xl font-bold mb-6 text-center">Вход для тренеров</h2>
|
||
<form onsubmit="login(event)" class="space-y-4">
|
||
<input id="loginUsername" placeholder="Логин" required class="input-field">
|
||
<input id="loginPassword" type="password" placeholder="Пароль" required class="input-field">
|
||
<p id="loginError" class="text-red-600 text-sm hidden"></p>
|
||
<button type="submit" class="btn-primary w-full">Войти</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="trainerContent" class="hidden">
|
||
<h1 class="text-3xl font-bold mb-6">Кабинет тренера</h1>
|
||
<div class="flex gap-2 mb-6 border-b">
|
||
<button onclick="showTab('settings')" class="tab-btn px-4 py-2 border-b-2 border-primary-600 text-primary-600" data-tab="settings">Настройки</button>
|
||
<button onclick="showTab('trainers')" class="tab-btn px-4 py-2" data-tab="trainers">Тренеры</button>
|
||
<button onclick="showTab('rules')" class="tab-btn px-4 py-2" data-tab="rules">Правила</button>
|
||
<button onclick="showTab('trainees')" class="tab-btn px-4 py-2" data-tab="trainees">Стажёры</button>
|
||
</div>
|
||
|
||
<div id="tab-settings" class="tab-content card space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium mb-1">Название программы</label>
|
||
<input id="settingTitle" onchange="updateProgram()" class="input-field">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium mb-1">Описание</label>
|
||
<textarea id="settingDesc" onchange="updateProgram()" class="input-field" rows="3"></textarea>
|
||
</div>
|
||
<div class="grid md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium mb-1">Дата начала</label>
|
||
<input id="settingStart" type="date" onchange="updateProgram()" class="input-field">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium mb-1">Дата окончания</label>
|
||
<input id="settingEnd" type="date" onchange="updateProgram()" class="input-field">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium mb-1">Количество групп</label>
|
||
<input id="settingGroups" type="number" min="1" max="10" onchange="updateProgram()" class="input-field w-32">
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<input id="settingOpen" type="checkbox" onchange="updateProgram()" class="w-4 h-4">
|
||
<label>Сбор анкет открыт</label>
|
||
</div>
|
||
<div class="pt-4 border-t">
|
||
<button id="formGroupsBtn" onclick="formGroups()" class="btn-primary">Сформировать группы</button>
|
||
<button id="resetGroupsBtn" onclick="resetGroups()" class="btn-secondary hidden">Открыть заново</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-trainers" class="tab-content card hidden">
|
||
<h2 class="text-xl font-bold mb-4">Тренеры</h2>
|
||
<div class="table-container">
|
||
<table class="table">
|
||
<thead><tr><th>Имя</th><th>Логин</th><th>Пароль</th></tr></thead>
|
||
<tbody id="trainersTable"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-rules" class="tab-content card hidden">
|
||
<h2 class="text-xl font-bold mb-4">Правила программы</h2>
|
||
<textarea id="rulesText" onchange="updateRules()" class="input-field w-full" rows="12"></textarea>
|
||
</div>
|
||
|
||
<div id="tab-trainees" class="tab-content card hidden">
|
||
<h2 class="text-xl font-bold mb-4">Стажёры (<span id="traineeCount">0</span>)</h2>
|
||
<div class="table-container">
|
||
<table class="table">
|
||
<thead><tr><th>ФИО</th><th>Почта</th><th>Подразделение</th><th>Город</th><th>Группа</th></tr></thead>
|
||
<tbody id="traineesTable"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PROGRESS PAGE -->
|
||
<div id="progressPage" class="page hidden">
|
||
<h1 class="text-3xl font-bold mb-6">Прогресс</h1>
|
||
<div class="card mb-6">
|
||
<h2 class="text-xl font-bold mb-4">Добавить занятие</h2>
|
||
<div class="flex gap-4">
|
||
<input id="sessionName" placeholder="Название" class="input-field flex-1">
|
||
<input id="sessionDate" type="date" class="input-field">
|
||
<button onclick="addSession()" class="btn-primary">Добавить</button>
|
||
</div>
|
||
<div id="sessionsList" class="flex flex-wrap gap-2 mt-4"></div>
|
||
</div>
|
||
<div id="progressTable" class="card overflow-x-auto hidden">
|
||
<table class="table" id="progressTableContent"></table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- EXAM PAGE -->
|
||
<div id="examPage" class="page hidden">
|
||
<h1 class="text-3xl font-bold mb-6">Итоговый экзамен</h1>
|
||
<div class="card mb-6">
|
||
<div class="flex items-center gap-3 mb-4">
|
||
<input id="examEnabled" type="checkbox" onchange="updateExam()" class="w-4 h-4">
|
||
<label>Этап экзамена активен</label>
|
||
</div>
|
||
<textarea id="examRules" onchange="updateExamRules()" class="input-field w-full" rows="6"></textarea>
|
||
</div>
|
||
<div class="card mb-6">
|
||
<div class="grid md:grid-cols-3 gap-4">
|
||
<div class="bg-gray-50 p-4 text-center rounded">
|
||
<div id="statTotal" class="text-3xl font-bold">0</div>
|
||
<div class="text-sm text-gray-600">Всего стажёров</div>
|
||
</div>
|
||
<div class="bg-primary-50 p-4 text-center rounded">
|
||
<div id="statPassed" class="text-3xl font-bold text-primary-600">0</div>
|
||
<div class="text-sm text-primary-600">Сдали</div>
|
||
</div>
|
||
<div class="bg-green-50 p-4 text-center rounded">
|
||
<div id="statThreshold" class="text-3xl font-bold text-green-600">0</div>
|
||
<div class="text-sm text-green-600">Прошли порог (≥9)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<table class="table">
|
||
<thead><tr><th>ФИО</th><th>Группа</th><th>Балл</th><th>Статус</th></tr></thead>
|
||
<tbody id="examTable"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- WHATSAPP PAGE -->
|
||
<div id="whatsappPage" class="page hidden">
|
||
<h1 class="text-3xl font-bold mb-6">Рассылка WhatsApp</h1>
|
||
<div class="card mb-6">
|
||
<p class="text-sm text-gray-600 mb-2">Шаблон: {name}, {group}, {title}</p>
|
||
<textarea id="waTemplate" onchange="updateTemplate()" class="input-field w-full" rows="4"></textarea>
|
||
</div>
|
||
<div class="card">
|
||
<table class="table">
|
||
<thead><tr><th>ФИО</th><th>Группа</th><th>Телефон</th><th>Действие</th></tr></thead>
|
||
<tbody id="waTable"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<script>
|
||
// DATA STORE
|
||
const CITIES = ['Алматы','Астана','Шымкент','Караганда','Актобе','Тараз','Павлодар','Усть-Каменогорск','Семей','Атырау','Орал','Кызылорда','Костанай','Петропавловск','Актау','Другой'];
|
||
let data = JSON.parse(localStorage.getItem('internData')) || {
|
||
program: { title: 'Адаптационная программа стажёров', description: 'Программа профессиональной адаптации', startDate: '', endDate: '', collectionOpen: true, groupsFormed: false, examEnabled: false, numberOfGroups: 2 },
|
||
trainers: [{id:1,name:'Тренер 1',login:'trainer1',password:'pass1'},{id:2,name:'Тренер 2',login:'trainer2',password:'pass2'}],
|
||
programRules: 'Правила:\n1. Посещаемость обязательна\n2. ДЗ сдавать вовремя',
|
||
examRules: 'Экзамен:\n1. 10 вопросов\n2. Порог: 9 баллов (90%)',
|
||
sessions: [],
|
||
trainees: [],
|
||
waTemplate: 'Здравствуйте, {name}! Вы в группе {group} программы "{title}". Чат: https://chat.whatsapp.com/...'
|
||
};
|
||
let currentUser = null;
|
||
|
||
function saveData() {
|
||
localStorage.setItem('internData', JSON.stringify(data));
|
||
renderAll();
|
||
}
|
||
|
||
// NAVIGATION
|
||
function showPage(page) {
|
||
document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
|
||
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
||
document.getElementById(page + 'Page').classList.remove('hidden');
|
||
const navLink = document.getElementById('nav-' + page);
|
||
if (navLink) navLink.classList.add('active');
|
||
|
||
if (page === 'trainer') {
|
||
if (currentUser) {
|
||
document.getElementById('loginForm').classList.add('hidden');
|
||
document.getElementById('trainerContent').classList.remove('hidden');
|
||
}
|
||
}
|
||
}
|
||
|
||
function showTab(tab) {
|
||
document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden'));
|
||
document.querySelectorAll('.tab-btn').forEach(b => {
|
||
b.classList.remove('border-b-2', 'border-primary-600', 'text-primary-600');
|
||
});
|
||
document.getElementById('tab-' + tab).classList.remove('hidden');
|
||
document.querySelector('[data-tab="' + tab + '"]').classList.add('border-b-2', 'border-primary-600', 'text-primary-600');
|
||
}
|
||
|
||
// AUTH
|
||
function login(e) {
|
||
e.preventDefault();
|
||
const login = document.getElementById('loginUsername').value;
|
||
const password = document.getElementById('loginPassword').value;
|
||
const trainer = data.trainers.find(t => t.login === login && t.password === password);
|
||
if (trainer) {
|
||
currentUser = trainer;
|
||
localStorage.setItem('currentUser', JSON.stringify(trainer));
|
||
document.getElementById('loginForm').classList.add('hidden');
|
||
document.getElementById('trainerContent').classList.remove('hidden');
|
||
document.getElementById('userInfo').textContent = '👤 ' + trainer.name;
|
||
document.getElementById('userInfo').classList.remove('hidden');
|
||
document.getElementById('logoutBtn').classList.remove('hidden');
|
||
document.getElementById('trainerLoginBtn').classList.add('hidden');
|
||
document.getElementById('nav-progress').classList.remove('hidden');
|
||
document.getElementById('nav-exam').classList.remove('hidden');
|
||
document.getElementById('nav-whatsapp').classList.remove('hidden');
|
||
} else {
|
||
document.getElementById('loginError').textContent = 'Неверный логин или пароль';
|
||
document.getElementById('loginError').classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
function logout() {
|
||
currentUser = null;
|
||
localStorage.removeItem('currentUser');
|
||
document.getElementById('trainerContent').classList.add('hidden');
|
||
document.getElementById('loginForm').classList.remove('hidden');
|
||
document.getElementById('userInfo').classList.add('hidden');
|
||
document.getElementById('logoutBtn').classList.add('hidden');
|
||
document.getElementById('trainerLoginBtn').classList.remove('hidden');
|
||
document.getElementById('nav-progress').classList.add('hidden');
|
||
document.getElementById('nav-exam').classList.add('hidden');
|
||
document.getElementById('nav-whatsapp').classList.add('hidden');
|
||
showPage('home');
|
||
}
|
||
|
||
function updateNav() {
|
||
if (currentUser) {
|
||
document.getElementById('trainerLoginBtn').classList.add('hidden');
|
||
document.getElementById('nav-progress').classList.remove('hidden');
|
||
document.getElementById('nav-exam').classList.remove('hidden');
|
||
document.getElementById('nav-whatsapp').classList.remove('hidden');
|
||
} else {
|
||
document.getElementById('trainerLoginBtn').classList.remove('hidden');
|
||
document.getElementById('nav-progress').classList.add('hidden');
|
||
document.getElementById('nav-exam').classList.add('hidden');
|
||
document.getElementById('nav-whatsapp').classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// FORM
|
||
document.getElementById('traineeForm').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
if (!data.program.collectionOpen || data.program.groupsFormed) {
|
||
alert('Приём анкет закрыт');
|
||
return;
|
||
}
|
||
const formData = new FormData(e.target);
|
||
const trainee = {
|
||
id: Date.now(),
|
||
firstName: formData.get('firstName'),
|
||
lastName: formData.get('lastName'),
|
||
email: formData.get('email'),
|
||
division: formData.get('division'),
|
||
position: formData.get('position'),
|
||
phone: formData.get('phone'),
|
||
supervisorName: formData.get('supervisorName'),
|
||
supervisorContact: formData.get('supervisorContact'),
|
||
city: formData.get('city'),
|
||
groupId: null,
|
||
attendance: {},
|
||
homeworkStatus: {},
|
||
examScore: null
|
||
};
|
||
data.trainees.push(trainee);
|
||
saveData();
|
||
e.target.classList.add('hidden');
|
||
document.getElementById('formSuccess').classList.remove('hidden');
|
||
});
|
||
|
||
// RENDER
|
||
function renderAll() {
|
||
// Home
|
||
document.getElementById('programTitle').textContent = data.program.title;
|
||
document.getElementById('programDesc').textContent = data.program.description;
|
||
document.getElementById('statusText').textContent = (data.program.collectionOpen && !data.program.groupsFormed) ? '🟢 Сбор анкет открыт' : '🔴 Сбор анкет закрыт';
|
||
document.getElementById('statusDate').textContent = data.program.endDate ? 'Окончание: ' + new Date(data.program.endDate).toLocaleDateString('ru-RU') : '';
|
||
document.getElementById('formBtn').style.display = (data.program.collectionOpen && !data.program.groupsFormed) ? 'inline-block' : 'none';
|
||
|
||
// Stages
|
||
const stage = data.program.examEnabled ? 4 : (data.program.groupsFormed ? 3 : (data.program.collectionOpen ? 1 : 2));
|
||
for (let i = 1; i <= 4; i++) {
|
||
const el = document.getElementById('stage' + i);
|
||
if (i <= stage) {
|
||
el.classList.remove('bg-gray-100', 'text-gray-400');
|
||
el.classList.add('bg-primary-100', 'text-primary-700');
|
||
}
|
||
}
|
||
|
||
// Groups
|
||
if (data.program.groupsFormed) {
|
||
document.getElementById('groupsList').classList.remove('hidden');
|
||
const groups = {};
|
||
data.trainees.forEach(t => {
|
||
const gid = t.groupId || 'Без группы';
|
||
if (!groups[gid]) groups[gid] = [];
|
||
groups[gid].push(t);
|
||
});
|
||
document.getElementById('groupsContent').innerHTML = Object.entries(groups).map(([gid, members]) =>
|
||
'<div class="border p-4 rounded"><h4 class="font-bold text-primary-700 mb-2">Группа ' + gid + '</h4>' +
|
||
members.map(m => '<div class="flex justify-between text-sm"><span>' + m.lastName + ' ' + m.firstName + '</span><span class="text-gray-500">' + m.city + '</span></div>').join('') +
|
||
'</div>'
|
||
).join('');
|
||
} else {
|
||
document.getElementById('groupsList').classList.add('hidden');
|
||
}
|
||
|
||
// Settings
|
||
document.getElementById('settingTitle').value = data.program.title;
|
||
document.getElementById('settingDesc').value = data.program.description;
|
||
document.getElementById('settingStart').value = data.program.startDate;
|
||
document.getElementById('settingEnd').value = data.program.endDate;
|
||
document.getElementById('settingGroups').value = data.program.numberOfGroups;
|
||
document.getElementById('settingOpen').checked = data.program.collectionOpen;
|
||
document.getElementById('formGroupsBtn').classList.toggle('hidden', data.program.groupsFormed);
|
||
document.getElementById('resetGroupsBtn').classList.toggle('hidden', !data.program.groupsFormed);
|
||
|
||
// Trainers table
|
||
document.getElementById('trainersTable').innerHTML = data.trainers.map((t, i) =>
|
||
'<tr>' +
|
||
'<td><input value="' + t.name + '" onchange="updateTrainer(' + i + ',\'name\',this.value)" class="input-field"></td>' +
|
||
'<td><input value="' + t.login + '" onchange="updateTrainer(' + i + ',\'login\',this.value)" class="input-field"></td>' +
|
||
'<td><input value="' + t.password + '" onchange="updateTrainer(' + i + ',\'password\',this.value)" class="input-field"></td>' +
|
||
'</tr>'
|
||
).join('');
|
||
|
||
// Rules
|
||
document.getElementById('rulesText').value = data.programRules;
|
||
|
||
// Trainees
|
||
document.getElementById('traineeCount').textContent = data.trainees.length;
|
||
document.getElementById('traineesTable').innerHTML = data.trainees.map(t =>
|
||
'<tr><td>' + t.lastName + ' ' + t.firstName + '</td><td>' + t.email + '</td><td>' + t.division + '</td><td>' + t.city + '</td><td>' + (t.groupId ? 'Гр.'+t.groupId : '-') + '</td></tr>'
|
||
).join('');
|
||
|
||
// Sessions
|
||
document.getElementById('sessionsList').innerHTML = data.sessions.map(s =>
|
||
'<div class="bg-primary-100 px-3 py-2 rounded flex items-center gap-2">' +
|
||
'<div><div class="font-medium text-sm">' + s.name + '</div><div class="text-xs text-gray-500">' + s.date + '</div></div>' +
|
||
'<button onclick="removeSession(' + s.id + ')" class="text-red-600">✕</button></div>'
|
||
).join('');
|
||
|
||
// Progress table
|
||
if (data.sessions.length > 0 && data.trainees.length > 0) {
|
||
document.getElementById('progressTable').classList.remove('hidden');
|
||
let html = '<thead><tr><th>ФИО</th><th>Группа</th>';
|
||
data.sessions.forEach(s => html += '<th class="text-center">' + s.name.split(' ')[0] + '</th>');
|
||
html += '<th>Комментарий</th></tr></thead><tbody>';
|
||
data.trainees.forEach(t => {
|
||
html += '<tr><td>' + t.lastName + ' ' + t.firstName + '</td><td>' + (t.groupId ? 'Гр.'+t.groupId : '-') + '</td>';
|
||
data.sessions.forEach(s => {
|
||
const present = t.attendance[s.id] || false;
|
||
const hw = t.homeworkStatus[s.id] || '';
|
||
html += '<td class="text-center"><label class="flex items-center justify-center gap-1 text-xs">' +
|
||
'<input type="checkbox" ' + (present ? 'checked' : '') + ' onchange="updateAttendance(' + t.id + ',' + s.id + ',this.checked)">' +
|
||
(present ? '✓' : '✗') + '</label>' +
|
||
'<select onchange="updateHomework(' + t.id + ',' + s.id + ',this.value)" class="text-xs border rounded px-1">' +
|
||
'<option value="">ДЗ</option><option value="done" ' + (hw==='done'?'selected':'') + '>✓</option>' +
|
||
'<option value="late" ' + (hw==='late'?'selected':'') + '>!</option>' +
|
||
'<option value="missing" ' + (hw==='missing'?'selected':'') + '>✗</option></select></td>';
|
||
});
|
||
html += '<td><input value="' + (t.comment||'') + '" onchange="updateComment(' + t.id + ',this.value)" class="input-field text-sm" placeholder="..."></td></tr>';
|
||
});
|
||
html += '</tbody>';
|
||
document.getElementById('progressTableContent').innerHTML = html;
|
||
} else {
|
||
document.getElementById('progressTable').classList.add('hidden');
|
||
}
|
||
|
||
// Exam
|
||
document.getElementById('examEnabled').checked = data.program.examEnabled;
|
||
document.getElementById('examRules').value = data.examRules;
|
||
const passed = data.trainees.filter(t => t.examScore !== null && t.examScore >= 9).length;
|
||
const withScore = data.trainees.filter(t => t.examScore !== null).length;
|
||
document.getElementById('statTotal').textContent = data.trainees.length;
|
||
document.getElementById('statPassed').textContent = withScore;
|
||
document.getElementById('statThreshold').textContent = passed;
|
||
document.getElementById('examTable').innerHTML = data.trainees.map(t => {
|
||
const status = t.examScore === null ? {text:'Не оценён',color:'text-gray-500'} : (t.examScore >= 9 ? {text:'Сдан',color:'text-green-600'} : {text:'Не сдан',color:'text-red-600'});
|
||
return '<tr><td>' + t.lastName + ' ' + t.firstName + '</td><td>' + (t.groupId ? 'Гр.'+t.groupId : '-') + '</td>' +
|
||
'<td><input type="number" min="0" max="10" value="' + (t.examScore||'') + '" onchange="updateExamScore(' + t.id + ',this.value)" class="input-field w-24"></td>' +
|
||
'<td class="' + status.color + '">' + status.text + '</td></tr>';
|
||
}).join('');
|
||
|
||
// WhatsApp
|
||
document.getElementById('waTemplate').value = data.waTemplate;
|
||
document.getElementById('waTable').innerHTML = data.trainees.map(t => {
|
||
const msg = data.waTemplate.replace(/{name}/g, t.lastName+' '+t.firstName).replace(/{group}/g, t.groupId?'Группа '+t.groupId:'Не распределён').replace(/{title}/g, data.program.title);
|
||
return '<tr><td>' + t.lastName + ' ' + t.firstName + '</td><td>' + (t.groupId ? 'Гр.'+t.groupId : '-') + '</td><td>' + t.phone + '</td>' +
|
||
'<td><button onclick="openWa(\'' + t.phone + '\',\'' + encodeURIComponent(msg) + '\')" class="btn-primary text-sm">Написать</button></td></tr>';
|
||
}).join('');
|
||
}
|
||
|
||
// ACTIONS
|
||
function updateProgram() {
|
||
data.program.title = document.getElementById('settingTitle').value;
|
||
data.program.description = document.getElementById('settingDesc').value;
|
||
data.program.startDate = document.getElementById('settingStart').value;
|
||
data.program.endDate = document.getElementById('settingEnd').value;
|
||
data.program.numberOfGroups = parseInt(document.getElementById('settingGroups').value) || 1;
|
||
data.program.collectionOpen = document.getElementById('settingOpen').checked;
|
||
saveData();
|
||
}
|
||
|
||
function updateTrainer(index, field, value) {
|
||
data.trainers[index][field] = value;
|
||
saveData();
|
||
}
|
||
|
||
function updateRules() {
|
||
data.programRules = document.getElementById('rulesText').value;
|
||
saveData();
|
||
}
|
||
|
||
function formGroups() {
|
||
if (!confirm('Сформировать группы?')) return;
|
||
const num = data.program.numberOfGroups;
|
||
const trainees = [...data.trainees].sort((a,b) => a.division.localeCompare(b.division) || a.city.localeCompare(b.city));
|
||
const groups = Array.from({length: num}, () => []);
|
||
trainees.forEach((t, i) => groups[i % num].push(t.id));
|
||
data.trainees = data.trainees.map(t => {
|
||
const idx = groups.findIndex(g => g.includes(t.id));
|
||
return {...t, groupId: idx >= 0 ? idx + 1 : null};
|
||
});
|
||
data.program.groupsFormed = true;
|
||
data.program.collectionOpen = false;
|
||
saveData();
|
||
}
|
||
|
||
function resetGroups() {
|
||
if (!confirm('Сбросить группы и прогресс?')) return;
|
||
data.trainees = data.trainees.map(t => ({...t, groupId: null, attendance: {}, homeworkStatus: {}, examScore: null}));
|
||
data.program.groupsFormed = false;
|
||
data.program.examEnabled = false;
|
||
saveData();
|
||
}
|
||
|
||
function addSession() {
|
||
const name = document.getElementById('sessionName').value;
|
||
const date = document.getElementById('sessionDate').value;
|
||
if (name && date) {
|
||
data.sessions.push({id: Date.now(), name, date});
|
||
document.getElementById('sessionName').value = '';
|
||
document.getElementById('sessionDate').value = '';
|
||
saveData();
|
||
}
|
||
}
|
||
|
||
function removeSession(id) {
|
||
data.sessions = data.sessions.filter(s => s.id !== id);
|
||
saveData();
|
||
}
|
||
|
||
function updateAttendance(tid, sid, present) {
|
||
const t = data.trainees.find(x => x.id === tid);
|
||
if (t) { t.attendance[sid] = present; saveData(); }
|
||
}
|
||
|
||
function updateHomework(tid, sid, status) {
|
||
const t = data.trainees.find(x => x.id === tid);
|
||
if (t) { t.homeworkStatus[sid] = status; saveData(); }
|
||
}
|
||
|
||
function updateComment(tid, value) {
|
||
const t = data.trainees.find(x => x.id === tid);
|
||
if (t) { t.comment = value; saveData(); }
|
||
}
|
||
|
||
function updateExam() {
|
||
data.program.examEnabled = document.getElementById('examEnabled').checked;
|
||
saveData();
|
||
}
|
||
|
||
function updateExamRules() {
|
||
data.examRules = document.getElementById('examRules').value;
|
||
saveData();
|
||
}
|
||
|
||
function updateExamScore(tid, score) {
|
||
const t = data.trainees.find(x => x.id === tid);
|
||
if (t) { t.examScore = score ? parseInt(score) : null; saveData(); }
|
||
}
|
||
|
||
function updateTemplate() {
|
||
data.waTemplate = document.getElementById('waTemplate').value;
|
||
saveData();
|
||
}
|
||
|
||
function openWa(phone, msg) {
|
||
const clean = phone.replace(/\D/g, '');
|
||
window.open('https://wa.me/' + clean + '?text=' + msg, '_blank');
|
||
}
|
||
|
||
// INIT
|
||
const savedUser = localStorage.getItem('currentUser');
|
||
if (savedUser) {
|
||
currentUser = JSON.parse(savedUser);
|
||
document.getElementById('userInfo').textContent = '👤 ' + currentUser.name;
|
||
document.getElementById('userInfo').classList.remove('hidden');
|
||
document.getElementById('logoutBtn').classList.remove('hidden');
|
||
}
|
||
updateNav();
|
||
renderAll();
|
||
showPage('home');
|
||
</script>
|
||
</body>
|
||
</html>
|