Админская панель экспорта отчётов в Excel
This commit is contained in:
parent
46caa4805f
commit
bc4c068813
191
app.js
191
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() {
|
||||
<span class="category-count">${count}</span>
|
||||
</div>
|
||||
`).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() {
|
||||
|
||||
33
index.html
33
index.html
@ -740,7 +740,38 @@
|
||||
<option value="finance">Финансы</option>
|
||||
<option value="sales">Продажи</option>
|
||||
</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 class="analytics-grid">
|
||||
|
||||
59
style.css
59
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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user