Админская панель экспорта отчётов в Excel

This commit is contained in:
Katipa 2026-08-21 10:59:37 +00:00
parent 46caa4805f
commit bc4c068813
3 changed files with 266 additions and 17 deletions

191
app.js
View File

@ -431,6 +431,7 @@ function loadAnalytics() {
const reviews = appData.reviews; const reviews = appData.reviews;
const ideas = appData.ideas; const ideas = appData.ideas;
const settings = appData.settings; const settings = appData.settings;
const profile = appData.profile;
document.getElementById('akpiEmployees').textContent = settings.totalEmployees; document.getElementById('akpiEmployees').textContent = settings.totalEmployees;
document.getElementById('akpiParticipants').textContent = reviews.length; document.getElementById('akpiParticipants').textContent = reviews.length;
@ -473,32 +474,190 @@ function loadAnalytics() {
<span class="category-count">${count}</span> <span class="category-count">${count}</span>
</div> </div>
`).join(''); `).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 // Экспорт в Excel для администратора
document.getElementById('exportBtn')?.addEventListener('click', () => { function exportToExcel(type) {
// Создаём CSV с BOM для Excel const date = new Date().toISOString().split('T')[0];
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'); 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 blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = 'pulse_feedback_' + new Date().toISOString().split('T')[0] + '.csv'; a.download = filename;
a.click(); a.click();
URL.revokeObjectURL(url); 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() { function initModals() {

View File

@ -740,7 +740,38 @@
<option value="finance">Финансы</option> <option value="finance">Финансы</option>
<option value="sales">Продажи</option> <option value="sales">Продажи</option>
</select> </select>
<button class="btn-secondary" id="exportBtn">📥 Выгрузить в Excel</button> <button class="btn-secondary" id="exportBtn" style="display: none;">📥 Выгрузить в Excel</button>
<button class="btn-secondary" id="exportBtnLite">📥 Выгрузить мои данные</button>
</div>
<div id="adminExportPanel" class="admin-export-panel" style="display: none;">
<h3>🔐 Экспорт данных (только для администратора)</h3>
<div class="export-options">
<button class="export-option-btn" onclick="exportToExcel('reviews')">
<span class="export-icon">📊</span>
<span>Отзывы сотрудников</span>
</button>
<button class="export-option-btn" onclick="exportToExcel('ideas')">
<span class="export-icon">💡</span>
<span>Идеи и предложения</span>
</button>
<button class="export-option-btn" onclick="exportToExcel('motivation')">
<span class="export-icon">🏆</span>
<span>Мотивация и благодарности</span>
</button>
<button class="export-option-btn" onclick="exportToExcel('kpi')">
<span class="export-icon">📈</span>
<span>КПД сотрудников</span>
</button>
<button class="export-option-btn" onclick="exportToExcel('reserve')">
<span class="export-icon">🎯</span>
<span>Кадровый резерв</span>
</button>
<button class="export-option-btn" onclick="exportToExcel('all')">
<span class="export-icon">📁</span>
<span>Полный отчёт (все листы)</span>
</button>
</div>
</div> </div>
<div class="analytics-grid"> <div class="analytics-grid">

View File

@ -812,6 +812,65 @@ textarea {
font-size: 14px; 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 { .analytics-grid {
display: grid; display: grid;
gap: 20px; gap: 20px;