document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('wfmForm');
const resultsSection = document.getElementById('results');
const historyList = document.getElementById('historyList');
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const uploadStatus = document.getElementById('uploadStatus');
const monthlyResults = document.getElementById('monthlyResults');
const tabs = document.querySelectorAll('.tab');
const modes = {
manual: document.getElementById('manualMode'),
upload: document.getElementById('uploadMode'),
monthly: document.getElementById('monthlyMode')
};
tabs.forEach(tab => {
tab.addEventListener('click', function() {
const mode = this.dataset.mode;
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
Object.keys(modes).forEach(k => modes[k].classList.remove('active'));
modes[mode].classList.add('active');
if (mode === 'monthly') {
loadMonthlyResults();
}
});
});
form.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(form);
const params = {};
formData.forEach((value, key) => {
if (value !== '') {
params[key] = parseFloat(value);
}
});
try {
const response = await fetch('/api/calculate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
});
const results = await response.json();
displayResults(results);
loadHistory();
resultsSection.style.display = 'block';
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
} catch (error) {
alert('Ошибка расчёта: ' + error.message);
}
});
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
async function handleFile(file) {
uploadStatus.innerHTML = '
Загрузка файла...
';
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.ok) {
uploadStatus.innerHTML = '✅ Файл загружен! Найдено месяцев: ' + result.months.length + '
';
if (result.months.length > 0) {
displayMonthlyResults(result.months, result.globalParams);
tabs[2].click();
}
} else {
uploadStatus.innerHTML = 'Ошибка: ' + result.error + '
';
}
} catch (error) {
uploadStatus.innerHTML = 'Ошибка загрузки: ' + error.message + '
';
}
}
function displayResults(results) {
if (results.incoming) {
document.getElementById('resIncoming').textContent = results.incoming.fte;
document.getElementById('resIncomingSL').textContent = 'SL: ' + results.incoming.sl + '%';
} else {
document.getElementById('resIncoming').textContent = '-';
document.getElementById('resIncomingSL').textContent = '';
}
if (results.outgoing) {
document.getElementById('resOutgoing').textContent = results.outgoing.fte;
} else {
document.getElementById('resOutgoing').textContent = '-';
}
if (results.tickets) {
document.getElementById('resTickets').textContent = results.tickets.fte;
} else {
document.getElementById('resTickets').textContent = '-';
}
if (results.chats) {
document.getElementById('resChats').textContent = results.chats.fte;
} else {
document.getElementById('resChats').textContent = '-';
}
document.getElementById('resTotalSchedule').textContent = results.total.fteInSchedule.toFixed(1);
document.getElementById('resTotalWithAbsence').textContent = results.total.fteWithAbsence.toFixed(1);
}
function displayMonthlyResults(months, params) {
const totalFTE = months.reduce((sum, m) => sum + m.results.total.fteWithAbsence, 0);
const avgFTE = totalFTE / months.length;
let html = `
Итого за период
${avgFTE.toFixed(1)}
Среднее FTE в штате
${totalFTE.toFixed(1)}
Суммарно FTE (все месяцы)
${months.length}
Месяцев в расчёте
Детализация по месяцам
| Месяц |
Входящие |
Исходящие |
Тикеты |
Чаты |
FTE в графике |
FTE в штате |
`;
months.forEach(m => {
html += `
| ${m.month} |
${m.incomingCalls.toLocaleString('ru-RU')} |
${m.outgoingCalls ? m.outgoingCalls.toLocaleString('ru-RU') : '-'} |
${m.tickets ? m.tickets.toLocaleString('ru-RU') : '-'} |
${m.chats ? m.chats.toLocaleString('ru-RU') : '-'} |
${m.results.total.fteInSchedule.toFixed(1)} |
${m.results.total.fteWithAbsence.toFixed(1)} |
`;
});
html += `
`;
monthlyResults.innerHTML = html;
}
async function loadHistory() {
try {
const response = await fetch('/api/history');
const history = await response.json();
if (history.length === 0) {
historyList.innerHTML = 'История пуста
';
return;
}
historyList.innerHTML = history.map(calc => {
const date = new Date(calc.date).toLocaleString('ru-RU');
const total = calc.results.total.fteWithAbsence.toFixed(1);
return `
${total} FTE
${date}
Вх: ${calc.results.incoming ? calc.results.incoming.fte : '-'} |
Исх: ${calc.results.outgoing ? calc.results.outgoing.fte : '-'} |
Чаты: ${calc.results.chats ? calc.results.chats.fte : '-'}
`;
}).join('');
} catch (error) {
console.error('Ошибка загрузки истории:', error);
}
}
async function loadMonthlyResults() {
try {
const response = await fetch('/api/monthly');
const data = await response.json();
if (!data || data.length === 0) {
monthlyResults.innerHTML = `
Нет данных
Загрузите Excel файл во вкладке "Загрузить Excel" для расчёта по месяцам
`;
return;
}
displayMonthlyResults(data, {});
} catch (error) {
console.error('Ошибка:', error);
monthlyResults.innerHTML = 'Ошибка загрузки данных
';
}
}
loadHistory();
});