tehnicheskoe-zadanie-na-razr/app.js

928 lines
31 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 `
<div class="idea-card" data-id="${idea.id}">
<div class="idea-header">
<div class="idea-title">${idea.title}</div>
<span class="idea-status status-${idea.status}">${statusLabels[idea.status]}</span>
</div>
<div class="idea-meta">
<span>👤 ${idea.anonymous ? 'Анонимно' : idea.author}</span>
<span>📅 ${formatDate(idea.date)}</span>
<span>🏢 ${getDeptName(idea.department)}</span>
</div>
<div class="idea-description">
<strong>Проблема:</strong> ${idea.problem}<br>
<strong>Решение:</strong> ${idea.solution}
</div>
<div class="idea-actions">
<button class="vote-btn" onclick="voteIdea(${idea.id})">
👍 <span class="vote-count">${idea.votes}</span>
</button>
<span class="idea-comments">💬 ${idea.comments} комментариев</span>
</div>
</div>
`;
}
// Загрузка идей
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 => `
<div class="history-item">
<div class="history-item-header">
<span class="history-item-title">${getDirectionName(r.direction)}</span>
<span class="history-item-date">${formatDate(r.date)}</span>
</div>
<div class="history-item-status">Статус: ${getStatusName(r.status)}</div>
</div>
`).join('') || '<p>Пока нет обращений</p>';
// Панель администратора (видна только если 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;
const profile = appData.profile;
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]) => `
<div class="problem-item">
<span class="problem-name">${getDirectionName(dir)}</span>
<span class="problem-count">${count}</span>
</div>
`).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]) => `
<div class="category-item">
<span class="category-name">${getCategoryName(cat)}</span>
<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 для администратора
function exportToExcel(type) {
const date = new Date().toISOString().split('T')[0];
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 = 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() {
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 = `
<div class="badge-avatar">${mot.employeeOfMonth.avatar}</div>
<div class="badge-name">${mot.employeeOfMonth.name}</div>
<div class="badge-dept">${mot.employeeOfMonth.dept}</div>
`;
}
}
if (mot.ideaOfMonth) {
const ideaEl = document.getElementById('ideaOfMonth');
if (ideaEl) {
ideaEl.innerHTML = `
<div class="idea-badge-title">${mot.ideaOfMonth.title}</div>
<div class="idea-badge-author">${mot.ideaOfMonth.author}</div>
`;
}
}
const thanksList = document.getElementById('thanksList');
if (thanksList && mot.thanks) {
thanksList.innerHTML = mot.thanks.map(t => `
<div class="thank-card">
<div class="thank-header">
<span class="thank-to">${t.to}</span>
<span class="thank-type">${getThankTypeName(t.type)}</span>
</div>
<div class="thank-reason">${t.reason}</div>
<div class="thank-meta">
<span>От: ${t.anonymous ? 'Анонимно' : t.from}</span>
<span>${formatDate(t.date)}</span>
</div>
</div>
`).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 => `
<div class="achievement-item">
<span class="achievement-icon">${a.icon}</span>
<span class="achievement-name">${a.name}</span>
<span class="achievement-count">x${a.count}</span>
</div>
`).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 => `
<div class="participant-card">
<div class="participant-avatar">👤</div>
<div class="participant-info">
<div class="participant-name">${p.name}</div>
<div class="participant-dept">${p.dept}</div>
<div class="participant-stage">Этап: ${p.stage}</div>
</div>
<div class="participant-progress">
<div class="progress-bar"><div class="progress-fill" style="width: ${p.progress}%"></div></div>
<span>${p.progress}%</span>
</div>
</div>
`).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;
}