// ========== МОК-ДАННЫЕ ========== const COURSES = [ { id: 1, name: "Безопасная рабочая среда SWE 2", level: 2, periodicity: 12 }, { id: 2, name: "Безопасное вождение (Defensive Driving)", level: 2, periodicity: 24 }, { id: 3, name: "Безопасное выполнение земляных работ", level: 1, periodicity: 12 }, { id: 4, name: "Безопасное выполнение работ на лестницах", level: 1, periodicity: 12 }, { id: 5, name: "Безопасное выполнение работ на опорах", level: 1, periodicity: 12 }, { id: 6, name: "Безопасность рук", level: 2, periodicity: 12 }, { id: 7, name: "Изоляция источников энергии (LOTO)", level: 1, periodicity: 12 }, { id: 8, name: "Контроль усталости", level: 1, periodicity: 6 }, { id: 9, name: "Культура безопасности труда", level: 2, periodicity: 24 }, { id: 10, name: "Оказание первой медицинской помощи", level: 1, periodicity: 24 }, { id: 11, name: "Основы безопасности в замкнутом пространстве", level: 2, periodicity: 12 }, { id: 12, name: "Основы безопасности на высоте", level: 2, periodicity: 12 }, { id: 13, name: "Поведенческие аудиты безопасности", level: 2, periodicity: 12 }, { id: 14, name: "Предотвращение травматизма IPR", level: 1, periodicity: 12 }, { id: 15, name: "Предотвращение травматизма IPR (углублённый)", level: 2, periodicity: 12 }, { id: 16, name: "Расследование инцидентов", level: 1, periodicity: 12 }, { id: 17, name: "Управление опасностями и рисками", level: 1, periodicity: 12 }, { id: 18, name: "Устойчивое развитие и экология", level: 1, periodicity: 24 }, { id: 19, name: "Фасилитатор в HSE", level: 2, periodicity: 24 }, { id: 20, name: "Четырёхступенчатый контроль (1 ступень)", level: 1, periodicity: 12 }, { id: 21, name: "Четырёхступенчатый контроль (2 ступень)", level: 1, periodicity: 12 }, { id: 22, name: "Четырёхступенчатый контроль (3 ступень)", level: 1, periodicity: 12 }, { id: 23, name: "Четырёхступенчатый контроль (4 ступень)", level: 1, periodicity: 12 } ]; const CITIES = ["Алматы", "Астана", "Атырау", "Актау", "Караганда", "Шымкент"]; const DEPARTMENTS = ["HSE", "Производство", "Логистика", "Администрация", "Техническая служба", "Электроснабжение"]; const POSITIONS = ["Инженер HSE", "Мастер", "Электромонтёр", "Слесарь", "Водитель", "Начальник смены", "Техник"]; // Генерация мок-данных сотрудников function generateEmployees() { const employees = []; const lastNames = ["Иванов", "Петров", "Сидоров", "Козлов", "Новиков", "Морозов", "Волков", "Соколов", "Лебедев", "Кузнецов", "Попов", "Васильев", "Зайцев", "Михайлов", "Фёдоров", "Абдуллин", "Нурланов", "Сагиев", "Турсунов", "Касымов", "Жумабаев", "Ермеков", "Баймуханов", "Алиев", "Хасенов"]; const firstNames = ["Александр", "Дмитрий", "Сергей", "Андрей", "Михаил", "Ильяс", "Данияр", "Артём", "Руслан", "Нурсултан", "Айдар", "Ерлан", "Булат", "Тимур", "Арман", "Мадияр", "Самат", "Кайрат", "Ержан", "Аслан"]; const patronymics = ["Александрович", "Дмитриевич", "Сергеевич", "Андреевич", "Михайлович", "Ильясович", "Даниярович", "Артёмович", "Русланович", "Нурсултанович"]; let tab = 1001; for (let city of CITIES) { const count = 8 + Math.floor(Math.random() * 7); for (let i = 0; i < count; i++) { const emp = { tab: String(tab++), lastName: lastNames[Math.floor(Math.random() * lastNames.length)], firstName: firstNames[Math.floor(Math.random() * firstNames.length)], patronymic: patronymics[Math.floor(Math.random() * patronymics.length)], city: city, department: DEPARTMENTS[Math.floor(Math.random() * DEPARTMENTS.length)], position: POSITIONS[Math.floor(Math.random() * POSITIONS.length)], courses: [] }; emp.fullName = `${emp.lastName} ${emp.firstName} ${emp.patronymic}`; // Случайные курсы const courseCount = 5 + Math.floor(Math.random() * 10); const shuffled = [...COURSES].sort(() => 0.5 - Math.random()); for (let j = 0; j < Math.min(courseCount, COURSES.length); j++) { const course = shuffled[j]; const completed = Math.random() > 0.3; if (completed) { const monthsAgo = Math.floor(Math.random() * 36); const completedDate = new Date(); completedDate.setMonth(completedDate.getMonth() - monthsAgo); const nextDate = new Date(completedDate); nextDate.setMonth(nextDate.getMonth() + course.periodicity); const isOverdue = nextDate < new Date(); emp.courses.push({ courseId: course.id, courseName: course.name, level: course.level, completed: true, completedDate: completedDate.toISOString().split('T')[0], nextDate: nextDate.toISOString().split('T')[0], overdue: isOverdue, daysOverdue: isOverdue ? Math.floor((new Date() - nextDate) / 86400000) : 0 }); } else { emp.courses.push({ courseId: course.id, courseName: course.name, level: course.level, completed: false, completedDate: null, nextDate: null, overdue: false, daysOverdue: 0 }); } } employees.push(emp); } } return employees; } const EMPLOYEES = generateEmployees(); // ========== СОСТОЯНИЕ ========== let currentUser = null; let isAdmin = false; // ========== АВТОРИЗАЦИЯ ========== function doLogin() { const login = document.getElementById('login-id').value.trim(); const pass = document.getElementById('login-pass').value.trim(); if (!login) { alert('Введите табельный номер'); return; } // Админ if (login === 'admin' || login === '12345') { isAdmin = true; currentUser = { tab: 'admin', fullName: 'Администратор системы', role: 'admin' }; } else { // Поиск сотрудника const emp = EMPLOYEES.find(e => e.tab === login || e.fullName.toLowerCase().includes(login.toLowerCase())); if (!emp) { alert('Сотрудник не найден. Попробуйте: 1001-1050'); return; } isAdmin = false; currentUser = emp; } document.getElementById('login-screen').classList.add('hidden'); document.getElementById('main-app').classList.remove('hidden'); document.getElementById('user-info').textContent = `${currentUser.fullName} (${isAdmin ? 'Администратор' : 'Сотрудник'})`; if (isAdmin) { document.getElementById('admin-panel').classList.remove('hidden'); document.getElementById('employee-panel').classList.add('hidden'); showTab('dashboard'); renderDashboard(); renderCityStats(); } else { document.getElementById('admin-panel').classList.add('hidden'); document.getElementById('employee-panel').classList.remove('hidden'); renderEmployeeCourses(currentUser); } } function doLogout() { currentUser = null; isAdmin = false; document.getElementById('login-screen').classList.remove('hidden'); document.getElementById('main-app').classList.add('hidden'); document.getElementById('login-id').value = ''; document.getElementById('login-pass').value = ''; } // ========== ТАБЫ ========== function showTab(name) { document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden')); document.getElementById('tab-' + name).classList.remove('hidden'); document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('btn'); btn.classList.add('btn', 'btn-outline'); }); document.querySelector(`[data-tab="${name}"]`).classList.remove('btn-outline'); } // ========== ПОИСК ========== function searchEmployee() { const query = document.getElementById('search-input').value.trim(); const cityFilter = document.getElementById('filter-city').value; let results = EMPLOYEES; if (query) { results = results.filter(e => e.tab === query || e.fullName.toLowerCase().includes(query.toLowerCase()) ); } if (cityFilter) { results = results.filter(e => e.city === cityFilter); } renderSearchResults(results); } function renderSearchResults(employees) { const container = document.getElementById('search-results'); if (employees.length === 0) { container.innerHTML = '
| Таб.№ | ФИО | Город | Должность | Пройдено | Просрочено | Прогресс | '; html += ' |
|---|---|---|---|---|---|---|---|
| ${emp.tab} | ${emp.fullName} | ${emp.city} | ${emp.position} | ${completed}/${emp.courses.length} | ${overdue > 0 ? '❌ ' + overdue : '✅'} |
${progress}%
|
| Курс | Уровень | Завершён | Должен пройти | Просрочка |
|---|---|---|---|---|
| ${c.courseName} | Ур.${c.level} | ${formatDate(c.completedDate)} | ${formatDate(c.nextDate)} | ⚠️ ${c.daysOverdue} дн. |
| Курс | Уровень | Завершён | Следующее | |
|---|---|---|---|---|
| ${c.courseName} | Ур.${c.level} | ${formatDate(c.completedDate)} | ${formatDate(c.nextDate)} ${daysLeft <= 30 ? '⚠️ ' + daysLeft + ' дн.' : ''} |
Нет данных для выгрузки
'; } else { const headers = Object.keys(data[0]); html += '| ${h} | `).join('') + '
|---|
| ${row[h]} | `).join('') + '
Показано 100 из ${data.length} записей
`; } html += '