chto-ty-umeesh-ranee-ya-sozd/index.html
2026-08-10 10:36:12 +00:00

640 lines
32 KiB
HTML
Raw 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="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 { @apply bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed; }
.btn-secondary { @apply bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition-colors; }
.input-field { @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none; }
.card { @apply bg-white rounded-xl shadow-md p-6; }
.table-container { @apply overflow-x-auto rounded-lg border border-gray-200; }
.table { @apply min-w-full divide-y divide-gray-200; }
.table th { @apply px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase; }
.table td { @apply px-6 py-4 whitespace-nowrap text-sm text-gray-900; }
.table tr:hover { @apply bg-gray-50; }
.nav-link { @apply px-3 py-2 rounded-md text-sm font-medium hover:bg-primary-600; }
.nav-link.active { @apply bg-primary-800; }
</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('home'); return false;" class="nav-link" id="nav-home">Главная</a>
<a href="#" onclick="showPage('trainer'); return false;" class="nav-link" id="nav-trainer">Кабинет тренера</a>
<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>
</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">Заполнить анкету</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 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');
} 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');
showPage('home');
}
// 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) ? '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');
}
renderAll();
showPage('home');
</script>
</body>
</html>