Админская панель экспорта отчётов в 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 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() {
|
||||||
|
|||||||
33
index.html
33
index.html
@ -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">
|
||||||
|
|||||||
59
style.css
59
style.css
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user