diff --git a/app.js b/app.js index 7da83bc..fb69a13 100644 --- a/app.js +++ b/app.js @@ -431,6 +431,7 @@ function loadAnalytics() { const reviews = appData.reviews; const ideas = appData.ideas; const settings = appData.settings; + const profile = appData.profile; document.getElementById('akpiEmployees').textContent = settings.totalEmployees; document.getElementById('akpiParticipants').textContent = reviews.length; @@ -473,32 +474,190 @@ function loadAnalytics() { ${count} `).join(''); + + // Показываем админ-панель экспорта только администратору + const adminExportPanel = document.getElementById('adminExportPanel'); + const exportBtn = document.getElementById('exportBtn'); + const exportBtnLite = document.getElementById('exportBtnLite'); + + if (profile && profile.isAdmin) { + if (adminExportPanel) adminExportPanel.style.display = 'block'; + if (exportBtn) exportBtn.style.display = 'none'; // Скрываем обычную кнопку + if (exportBtnLite) exportBtnLite.style.display = 'none'; + } else { + if (adminExportPanel) adminExportPanel.style.display = 'none'; + if (exportBtn) exportBtn.style.display = 'inline-block'; + if (exportBtnLite) exportBtnLite.style.display = 'inline-block'; + } } -// Экспорт в 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(';')); +// Экспорт в Excel для администратора +function exportToExcel(type) { + const date = new Date().toISOString().split('T')[0]; - const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); + if (type === 'reviews') { + 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, + r.anonymous ? 'Анонимно' : (r.author || '—') + ].join(';')); + + const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); + downloadCSV(csv, `pulse_otzyvy_${date}.csv`); + } + if (type === 'ideas') { + const head = ['Название', 'Автор', 'Подразделение', 'Категория', 'Проблема', 'Решение', 'Эффект', 'Голоса', 'Статус', 'Дата']; + const body = appData.ideas.map(i => [ + i.title, + i.anonymous ? 'Анонимно' : i.author, + getDeptName(i.department), + getCategoryName(i.category), + '"' + i.problem.replace(/"/g, '""') + '"', + '"' + i.solution.replace(/"/g, '""') + '"', + '"' + (i.effect || '').replace(/"/g, '""') + '"', + i.votes, + getStatusName(i.status), + i.date + ].join(';')); + + const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); + downloadCSV(csv, `pulse idei_${date}.csv`); + } + + if (type === 'motivation') { + const head = ['Тип', 'Кому', 'От кого', 'Причина', 'Дата']; + const body = appData.motivation.thanks.map(t => [ + getThankTypeName(t.type), + t.to, + t.anonymous ? 'Анонимно' : t.from, + '"' + t.reason.replace(/"/g, '""') + '"', + t.date + ].join(';')); + + const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); + downloadCSV(csv, `pulse_blagodarnosti_${date}.csv`); + } + + if (type === 'kpi') { + const head = ['Компонент', 'Значение %']; + const body = Object.entries(appData.kpi.components).map(([key, value]) => [ + getKpiComponentName(key), + value + ].join(';')); + + body.push(['Общий КПД', appData.kpi.overall]); + + const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); + downloadCSV(csv, `pulse_kpd_${date}.csv`); + } + + if (type === 'reserve') { + const head = ['ФИО', 'Подразделение', 'Этап', 'Прогресс %']; + const body = appData.reserve.participants.map(p => [ + p.name, + p.dept, + p.stage, + p.progress + ].join(';')); + + const csv = '\uFEFF' + [head.join(';'), ...body].join('\n'); + downloadCSV(csv, `pulse_rezerv_${date}.csv`); + } + + if (type === 'all') { + // Создаём многостраничный Excel через JSON + const excelData = { + 'Отзывы': appData.reviews.map(r => ({ + 'Дата': r.date, + 'Подразделение': r.department, + 'Направление': getDirectionName(r.direction), + 'Оценка': r.rating, + 'Комментарий': r.comment, + 'Тип': r.type, + 'Статус': r.status, + 'Автор': r.anonymous ? 'Анонимно' : (r.author || '—') + })), + 'Идеи': appData.ideas.map(i => ({ + 'Название': i.title, + 'Автор': i.anonymous ? 'Анонимно' : i.author, + 'Подразделение': getDeptName(i.department), + 'Категория': getCategoryName(i.category), + 'Проблема': i.problem, + 'Решение': i.solution, + 'Голоса': i.votes, + 'Статус': getStatusName(i.status), + 'Дата': i.date + })), + 'Благодарности': appData.motivation.thanks.map(t => ({ + 'Кому': t.to, + 'От кого': t.anonymous ? 'Анонимно' : t.from, + 'Причина': t.reason, + 'Тип': getThankTypeName(t.type), + 'Дата': t.date + })), + 'КПД': [{ + 'Общий': appData.kpi.overall, + 'Задачи': appData.kpi.components.tasks, + 'Сроки': appData.kpi.components.deadlines, + 'Качество': appData.kpi.components.quality, + 'Инициатива': appData.kpi.components.initiative, + 'Команда': appData.kpi.components.team + }], + 'Резерв': appData.reserve.participants.map(p => ({ + 'ФИО': p.name, + 'Подразделение': p.dept, + 'Этап': p.stage, + 'Прогресс': p.progress + })) + }; + + // Для простоты выгружаем как JSON который можно открыть в Excel + const json = JSON.stringify(excelData, null, 2); + const blob = new Blob([json], { type: 'application/json;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `pulse_polnyj_otchet_${date}.json`; + a.click(); + URL.revokeObjectURL(url); + + alert('Полный отчёт скачан в формате JSON. Откройте его в Excel или импортируйте в систему.'); + return; + } + + alert('Отчёт скачан!'); +} + +function downloadCSV(csv, filename) { 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.download = filename; a.click(); URL.revokeObjectURL(url); -}); +} + +function getKpiComponentName(key) { + const names = { + tasks: 'Выполнение задач', + deadlines: 'Соблюдение сроков', + quality: 'Качество работы', + initiative: 'Инициативность', + team: 'Командная работа' + }; + return names[key] || key; +} + +window.exportToExcel = exportToExcel; +window.downloadCSV = downloadCSV; // Модальные окна function initModals() { diff --git a/index.html b/index.html index 787f430..174cab3 100644 --- a/index.html +++ b/index.html @@ -740,7 +740,38 @@ - + + + + +
diff --git a/style.css b/style.css index b9fc193..736d11c 100644 --- a/style.css +++ b/style.css @@ -812,6 +812,65 @@ textarea { font-size: 14px; } +/* Admin Export Panel */ +.admin-export-panel { + background: linear-gradient(135deg, #1A3A52, #2C5282); + color: var(--white); + padding: 24px; + border-radius: 12px; + box-shadow: var(--shadow-lg); + margin-bottom: 24px; +} + +.admin-export-panel h3 { + margin-bottom: 20px; + font-size: 18px; + color: var(--white); +} + +.export-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 12px; +} + +.export-option-btn { + display: flex; + align-items: center; + gap: 12px; + background: rgba(255,255,255,0.15); + color: var(--white); + border: 1px solid rgba(255,255,255,0.3); + padding: 14px 18px; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + transition: all 0.2s; + text-align: left; +} + +.export-option-btn:hover { + background: rgba(255,255,255,0.25); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.2); +} + +.export-icon { + font-size: 24px; +} + +.export-option-btn:last-child { + background: linear-gradient(135deg, var(--success), #059669); + grid-column: 1 / -1; + justify-content: center; + text-align: center; +} + +.export-option-btn:last-child:hover { + background: linear-gradient(135deg, #059669, #047857); +} + .analytics-grid { display: grid; gap: 20px;