// Pulse App - Main JavaScript // Инициализация при загрузке document.addEventListener('DOMContentLoaded', () => { initNavigation(); initRatingInputs(); loadDashboard(); loadIdeas(); loadProfile(); initForms(); initFilters(); initModals(); renderCharts(); loadMotivation(); loadKPI(); loadReserve(); }); // Навигация между секциями function initNavigation() { const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const section = link.dataset.section; showSection(section); navLinks.forEach(l => l.classList.remove('active')); link.classList.add('active'); }); }); } function showSection(sectionId) { document.querySelectorAll('.section').forEach(s => s.classList.remove('active')); document.getElementById(sectionId).classList.add('active'); if (sectionId === 'analytics') { loadAnalytics(); } } window.showSection = showSection; // Рейтинг (звезды) function initRatingInputs() { const ratingContainers = document.querySelectorAll('.rating-input'); ratingContainers.forEach(container => { const stars = container.querySelectorAll('.rating-star'); const hiddenInput = container.parentElement.querySelector('input[type="hidden"]'); stars.forEach(star => { star.addEventListener('click', () => { const value = parseInt(star.dataset.value); stars.forEach((s, i) => { s.classList.toggle('active', i < value); }); if (hiddenInput) { hiddenInput.value = value; } container.dataset.value = value; }); star.addEventListener('mouseenter', () => { const value = parseInt(star.dataset.value); stars.forEach((s, i) => { s.classList.toggle('active', i < value); }); }); star.addEventListener('mouseleave', () => { const currentValue = parseInt(container.dataset.value || 0); stars.forEach((s, i) => { s.classList.toggle('active', i < currentValue); }); }); }); }); } // Загрузка дэшборда function loadDashboard() { const reviews = appData.reviews; const ideas = appData.ideas; // KPI document.getElementById('kpiReviews').textContent = reviews.length; const avgRating = reviews.length > 0 ? (reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length).toFixed(1) : '0.0'; document.getElementById('kpiAvgRating').textContent = avgRating; document.getElementById('kpiIdeas').textContent = ideas.length; document.getElementById('kpiImplemented').textContent = ideas.filter(i => i.status === 'done').length; document.getElementById('kpiResolved').textContent = reviews.filter(r => r.status === 'done').length; const engagementIndex = appData.settings.engagementIndex; document.getElementById('kpiEngagement').textContent = engagementIndex; const trendEl = document.getElementById('kpiTrend'); const trend = appData.settings.engagementTrend; trendEl.textContent = trend === 'up' ? '↑ рост' : trend === 'down' ? '↓ снижение' : '→ без изменений'; // Последние идеи const ideasList = document.getElementById('ideasList'); const recentIdeas = ideas.slice(0, 3); ideasList.innerHTML = recentIdeas.map(idea => createIdeaCard(idea)).join(''); } // Создание карточки идеи function createIdeaCard(idea) { const statusLabels = { new: 'Новая', review: 'На рассмотрении', accepted: 'Принята', implement: 'В реализации', done: 'Реализована', rejected: 'Отклонена' }; return `
${idea.title}
${statusLabels[idea.status]}
👤 ${idea.anonymous ? 'Анонимно' : idea.author} 📅 ${formatDate(idea.date)} 🏢 ${getDeptName(idea.department)}
Проблема: ${idea.problem}
Решение: ${idea.solution}
💬 ${idea.comments} комментариев
`; } // Загрузка идей function loadIdeas() { const container = document.getElementById('ideasContainer'); container.innerHTML = appData.ideas.map(idea => createIdeaCard(idea)).join(''); } // Голосование за идею function voteIdea(id) { const idea = appData.ideas.find(i => i.id === id); if (idea && !idea.hasVoted) { idea.votes++; idea.hasVoted = true; saveData(appData); loadIdeas(); // Визуальное подтверждение const btn = event.target.closest('.vote-btn'); btn.classList.add('voted'); btn.querySelector('.vote-count').textContent = idea.votes; } } window.voteIdea = voteIdea; // Фильтры идей function initFilters() { const filterBtns = document.querySelectorAll('.filter-btn'); filterBtns.forEach(btn => { btn.addEventListener('click', () => { filterBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const status = btn.dataset.status; const container = document.getElementById('ideasContainer'); const filtered = status === 'all' ? appData.ideas : appData.ideas.filter(i => i.status === status); container.innerHTML = filtered.map(idea => createIdeaCard(idea)).join(''); }); }); } // Формы function initForms() { // Форма обратной связи document.getElementById('feedbackForm').addEventListener('submit', (e) => { e.preventDefault(); const review = { id: Date.now(), date: new Date().toISOString().split('T')[0], direction: document.getElementById('fbDirection').value, rating: parseInt(document.getElementById('fbRating').value) || 0, type: document.getElementById('fbType').value, comment: document.getElementById('fbComment').value, suggestion: document.getElementById('fbSuggestion').value, anonymous: document.getElementById('fbAnonymous').checked, department: appData.profile.department.toLowerCase().split(' ')[0], status: 'new' }; appData.reviews.push(review); saveData(appData); document.getElementById('feedbackForm').style.display = 'none'; document.getElementById('feedbackSuccess').style.display = 'block'; loadDashboard(); loadProfile(); }); // Форма идеи document.getElementById('ideaForm').addEventListener('submit', (e) => { e.preventDefault(); const idea = { id: Date.now(), title: document.getElementById('ideaTitle').value, problem: document.getElementById('ideaProblem').value, solution: document.getElementById('ideaSolution').value, effect: document.getElementById('ideaEffect').value, category: document.getElementById('ideaCategory').value, department: document.getElementById('ideaDept').value, votes: 0, comments: 0, status: 'new', anonymous: document.getElementById('ideaAnonymous').checked, author: appData.profile.name, date: new Date().toISOString().split('T')[0], hasVoted: false }; appData.ideas.push(idea); saveData(appData); closeModal('ideaModal'); loadIdeas(); loadDashboard(); alert('Идея отправлена на рассмотрение!'); e.target.reset(); }); // Форма благодарности document.getElementById('thankFormEl').addEventListener('submit', (e) => { e.preventDefault(); const thank = { id: Date.now(), to: document.getElementById('thankTo').value, from: document.getElementById('thankAnonymous').checked ? 'Анонимно' : appData.profile.name, reason: document.getElementById('thankReason').value, type: document.getElementById('thankType').value, date: new Date().toISOString().split('T')[0], anonymous: document.getElementById('thankAnonymous').checked }; appData.motivation.thanks.unshift(thank); saveData(appData); document.getElementById('thankForm').style.display = 'none'; loadMotivation(); alert('Благодарность отправлена!'); }); // Цели КПД document.getElementById('kpiGoalsForm').addEventListener('submit', (e) => { e.preventDefault(); appData.kpi.goals = [ document.getElementById('kpiGoal1').value, document.getElementById('kpiGoal2').value, document.getElementById('kpiGoal3').value ]; saveData(appData); alert('Цели сохранены!'); }); // Заявка в кадровый резерв document.getElementById('reserveFormEl').addEventListener('submit', (e) => { e.preventDefault(); const application = { id: Date.now(), position: document.getElementById('reservePosition').value, experience: document.getElementById('reserveExperience').value, motivation: document.getElementById('reserveMotivation').value, skills: document.getElementById('reserveSkills').value, date: new Date().toISOString().split('T')[0], status: 'new' }; appData.reserve.applications.push(application); saveData(appData); document.getElementById('reserveForm').style.display = 'none'; alert('Заявка подана! HR свяжется с вами.'); }); // Форма автоматизации document.getElementById('automationForm').addEventListener('submit', (e) => { e.preventDefault(); const automation = { id: Date.now(), process: document.getElementById('autoProcess').value, department: document.getElementById('autoDept').value, problem: document.getElementById('autoProblem').value, solution: document.getElementById('autoSolution').value, effect: document.getElementById('autoEffect').value, date: new Date().toISOString().split('T')[0], status: 'new' }; appData.automation.push(automation); saveData(appData); alert('Предложение по автоматизации отправлено!'); e.target.reset(); }); // Опросы адаптации document.querySelectorAll('[data-survey]').forEach(btn => { btn.addEventListener('click', () => { const surveyType = btn.dataset.survey; document.getElementById('adaptationSurvey').style.display = 'block'; document.getElementById('surveyTitle').textContent = surveyType === 'week1' ? 'Опрос: Первая неделя' : surveyType === 'month1' ? 'Опрос: 1 месяц' : 'Опрос: После испытательного срока'; document.querySelectorAll('.survey-rating').forEach(r => r.dataset.value = 0); document.querySelectorAll('.rating-star').forEach(s => s.classList.remove('active')); document.getElementById('surveyComment').value = ''; }); }); document.getElementById('adaptationForm').addEventListener('submit', (e) => { e.preventDefault(); const ratings = document.querySelectorAll('.survey-rating'); let tasks = 0, support = 0; ratings.forEach(r => { if (r.dataset.field === 'tasks') tasks = parseInt(r.dataset.value); if (r.dataset.field === 'support') support = parseInt(r.dataset.value); }); const survey = { id: Date.now(), type: 'adaptation', date: new Date().toISOString().split('T')[0], tasks, support, comment: document.getElementById('surveyComment').value }; appData.surveys.push(survey); saveData(appData); document.getElementById('adaptationSurvey').style.display = 'none'; alert('Спасибо за участие в опросе!'); }); } // Профиль function loadProfile() { const profile = appData.profile; document.getElementById('profileName').textContent = profile.name; document.getElementById('userName').textContent = profile.name.split(' ')[0]; document.getElementById('profileDept').textContent = profile.department; document.getElementById('profilePosition').textContent = profile.position; document.getElementById('profileTenure').textContent = 'Стаж: ' + profile.tenure; const myReviews = appData.reviews.filter(r => !r.anonymous); document.getElementById('statReviews').textContent = myReviews.length; document.getElementById('statIdeas').textContent = appData.ideas.filter(i => !i.anonymous && i.author === profile.name).length; document.getElementById('statSurveys').textContent = appData.surveys.length; const historyList = document.getElementById('profileHistory'); historyList.innerHTML = myReviews.slice(0, 5).map(r => `
${getDirectionName(r.direction)} ${formatDate(r.date)}
Статус: ${getStatusName(r.status)}
`).join('') || '

Пока нет обращений

'; // Панель администратора (видна только если isAdmin = true) const adminPanel = document.getElementById('adminPanel'); if (adminPanel && profile.isAdmin) { adminPanel.style.display = 'block'; } } // Экспорт всех данных для админа function exportAllData() { const data = { reviews: appData.reviews, ideas: appData.ideas, motivation: appData.motivation, kpi: appData.kpi, reserve: appData.reserve }; const json = JSON.stringify(data, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'pulse_admin_export_' + new Date().toISOString().split('T')[0] + '.json'; a.click(); URL.revokeObjectURL(url); } window.exportAllData = exportAllData; // Аналитика function loadAnalytics() { const reviews = appData.reviews; const ideas = appData.ideas; const settings = appData.settings; document.getElementById('akpiEmployees').textContent = settings.totalEmployees; document.getElementById('akpiParticipants').textContent = reviews.length; document.getElementById('akpiResponse').textContent = Math.round((reviews.length / settings.totalEmployees) * 100) + '%'; const avgRating = reviews.length > 0 ? (reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length).toFixed(1) : '0.0'; document.getElementById('akpiAvgRating').textContent = avgRating; // Проблемные направления const directionCounts = {}; reviews.forEach(r => { directionCounts[r.direction] = (directionCounts[r.direction] || 0) + 1; }); const sortedDirections = Object.entries(directionCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 5); document.getElementById('problemAreas').innerHTML = sortedDirections.map(([dir, count]) => `
${getDirectionName(dir)} ${count}
`).join(''); // Идеи по категориям const categoryCounts = {}; ideas.forEach(i => { categoryCounts[i.category] = (categoryCounts[i.category] || 0) + 1; }); document.getElementById('ideasByCategory').innerHTML = Object.entries(categoryCounts) .sort((a, b) => b[1] - a[1]) .map(([cat, count]) => `
${getCategoryName(cat)} ${count}
`).join(''); } // Экспорт в Excel document.getElementById('exportBtn')?.addEventListener('click', () => { // Создаём CSV с BOM для Excel const head = ['Дата', 'Подразделение', 'Направление', 'Оценка', 'Комментарий', 'Тип', 'Статус']; const body = appData.reviews.map(r => [ r.date, r.department, getDirectionName(r.direction), r.rating, '"' + r.comment.replace(/"/g, '""') + '"', r.type, r.status ].join(';')); const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'pulse_feedback_' + new Date().toISOString().split('T')[0] + '.csv'; a.click(); URL.revokeObjectURL(url); }); // Модальные окна function initModals() { document.getElementById('newIdeaBtn').addEventListener('click', () => { document.getElementById('ideaModal').classList.add('active'); }); } window.closeModal = function(modalId) { document.getElementById(modalId).classList.remove('active'); }; // Мотивация function loadMotivation() { const mot = appData.motivation; if (mot.employeeOfMonth) { const empEl = document.getElementById('employeeOfMonth'); if (empEl) { empEl.innerHTML = `
${mot.employeeOfMonth.avatar}
${mot.employeeOfMonth.name}
${mot.employeeOfMonth.dept}
`; } } if (mot.ideaOfMonth) { const ideaEl = document.getElementById('ideaOfMonth'); if (ideaEl) { ideaEl.innerHTML = `
${mot.ideaOfMonth.title}
${mot.ideaOfMonth.author}
`; } } const thanksList = document.getElementById('thanksList'); if (thanksList && mot.thanks) { thanksList.innerHTML = mot.thanks.map(t => `
${t.to} ${getThankTypeName(t.type)}
${t.reason}
От: ${t.anonymous ? 'Анонимно' : t.from} ${formatDate(t.date)}
`).join(''); } } window.showThankForm = function() { document.getElementById('thankForm').style.display = 'block'; document.getElementById('thankFormEl').reset(); }; window.nominateEmployee = function() { alert('Форма номинации будет доступна в конце месяца'); }; window.loadAchievements = function() { const achievements = appData.motivation.achievements; const html = achievements.map(a => `
${a.icon} ${a.name} x${a.count}
`).join(''); alert('Ваши достижения:\n\n' + achievements.map(a => `${a.icon} ${a.name}: ${a.count}`).join('\n')); }; function getThankTypeName(type) { const names = { help: 'За помощь', mentor: 'За наставничество', initiative: 'За инициативу', team: 'За команду', other: 'Другое' }; return names[type] || type; } // КПД function loadKPI() { const kpi = appData.kpi; document.getElementById('kpiValue').textContent = kpi.overall; document.getElementById('kpiCirclePath').style.strokeDasharray = `${kpi.overall}, 100`; const kpiChartCanvas = document.getElementById('kpiChartCanvas'); if (kpiChartCanvas) { const ctx = kpiChartCanvas.getContext('2d'); drawLineChart(ctx, kpi.history, '#4A90E2', 400, 200); } } // Кадровый резерв function loadReserve() { const participants = appData.reserve.participants; const list = document.getElementById('participantsList'); if (list) { list.innerHTML = participants.map(p => `
👤
${p.name}
${p.dept}
Этап: ${p.stage}
${p.progress}%
`).join(''); } } window.showReserveForm = function() { document.getElementById('reserveForm').style.display = 'block'; document.getElementById('reserveForm').scrollIntoView({ behavior: 'smooth' }); }; window.resetFeedbackForm = function() { document.getElementById('feedbackForm').reset(); document.getElementById('feedbackForm').style.display = 'flex'; document.getElementById('feedbackSuccess').style.display = 'none'; document.querySelectorAll('.rating-star').forEach(s => s.classList.remove('active')); }; // Простые графики на Canvas function renderCharts() { // График вовлечённости (простая имитация) const engagementCanvas = document.getElementById('engagementCanvas'); if (engagementCanvas) { const ctx = engagementCanvas.getContext('2d'); const data = [65, 68, 72, 70, 75, 78]; drawLineChart(ctx, data, '#4A90E2', 300, 200); } // Рейтинг по направлениям const ratingCanvas = document.getElementById('ratingCanvas'); if (ratingCanvas) { const ctx = ratingCanvas.getContext('2d'); const data = [4.2, 3.8, 4.5, 4.0, 3.9]; drawBarChart(ctx, data, ['#4A90E2', '#5DBCD2', '#00B8CC', '#10B981', '#F59E0B'], 300, 200); } } function drawLineChart(ctx, data, color, width, height) { ctx.canvas.width = width; ctx.canvas.height = height; ctx.clearRect(0, 0, width, height); const step = width / (data.length - 1); const max = Math.max(...data); const min = Math.min(...data); const range = max - min || 1; ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 3; ctx.lineJoin = 'round'; data.forEach((val, i) => { const x = i * step; const y = height - ((val - min) / range) * (height - 40) - 20; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.stroke(); // Точки data.forEach((val, i) => { const x = i * step; const y = height - ((val - min) / range) * (height - 40) - 20; ctx.beginPath(); ctx.fillStyle = color; ctx.arc(x, y, 5, 0, Math.PI * 2); ctx.fill(); }); } function drawBarChart(ctx, data, colors, width, height) { ctx.canvas.width = width; ctx.canvas.height = height; ctx.clearRect(0, 0, width, height); const barWidth = (width - 40) / data.length - 10; const max = Math.max(...data); data.forEach((val, i) => { const x = 20 + i * (barWidth + 10); const barHeight = (val / max) * (height - 40); const y = height - barHeight - 20; ctx.fillStyle = colors[i % colors.length]; ctx.fillRect(x, y, barWidth, barHeight); // Значение ctx.fillStyle = '#333'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(val.toFixed(1), x + barWidth/2, y - 5); }); } // Вспомогательные функции function formatDate(dateStr) { const d = new Date(dateStr); return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long' }); } function getDirectionName(dir) { const names = { adaptation: 'Адаптация', training: 'Обучение', discipline: 'Трудовая дисциплина', automation: 'Автоматизация', innovation: 'Инновации', other: 'Другое' }; return names[dir] || dir; } function getDeptName(dept) { const names = { hr: 'HR', it: 'IT', finance: 'Финансы', procurement: 'Закупки', sales: 'Продажи', support: 'Поддержка', other: 'Другое' }; return names[dept] || dept; } function getStatusName(status) { const names = { new: 'Новое', review: 'На рассмотрении', accepted: 'Принято', implement: 'В работе', done: 'Решено', rejected: 'Отклонено' }; return names[status] || status; } function getCategoryName(cat) { const names = { process: 'Оптимизация процессов', automation: 'Автоматизация', cost: 'Снижение затрат', quality: 'Повышение качества', safety: 'Безопасность', other: 'Другое' }; return names[cat] || cat; }