340 lines
10 KiB
JavaScript
340 lines
10 KiB
JavaScript
let appData = {
|
||
presets: [],
|
||
history: []
|
||
};
|
||
|
||
let selectedPresetId = null;
|
||
|
||
async function loadData() {
|
||
try {
|
||
const res = await fetch('/api/data');
|
||
const data = await res.json();
|
||
appData = data;
|
||
|
||
if (appData.presets.length === 0) {
|
||
appData.presets.push({
|
||
id: Date.now(),
|
||
name: 'Областной',
|
||
variableCosts: 15921.2583,
|
||
avgCapacity: 9.4317,
|
||
fixedCosts: 8129.6707
|
||
});
|
||
}
|
||
|
||
if (appData.history === undefined) {
|
||
appData.history = [];
|
||
}
|
||
|
||
saveData();
|
||
} catch (e) {
|
||
console.error('Ошибка загрузки:', e);
|
||
if (appData.presets.length === 0) {
|
||
appData.presets.push({
|
||
id: Date.now(),
|
||
name: 'Областной',
|
||
variableCosts: 15921.2583,
|
||
avgCapacity: 9.4317,
|
||
fixedCosts: 8129.6707
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
async function saveData() {
|
||
try {
|
||
await fetch('/api/data', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(appData)
|
||
});
|
||
} catch (e) {
|
||
console.error('Ошибка сохранения:', e);
|
||
}
|
||
}
|
||
|
||
function formatNumber(num) {
|
||
return num.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||
}
|
||
|
||
function formatNumberFull(num) {
|
||
return num.toLocaleString('ru-RU', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
|
||
}
|
||
|
||
function renderPresetsSelect() {
|
||
const select = document.getElementById('presetSelect');
|
||
select.innerHTML = '';
|
||
|
||
appData.presets.forEach(preset => {
|
||
const option = document.createElement('option');
|
||
option.value = preset.id;
|
||
option.textContent = preset.name;
|
||
select.appendChild(option);
|
||
});
|
||
|
||
if (selectedPresetId && appData.presets.find(p => p.id === selectedPresetId)) {
|
||
select.value = selectedPresetId;
|
||
} else if (appData.presets.length > 0) {
|
||
selectedPresetId = appData.presets[0].id;
|
||
select.value = selectedPresetId;
|
||
}
|
||
}
|
||
|
||
function renderPresetsSettings() {
|
||
const container = document.getElementById('presetsContainer');
|
||
container.innerHTML = '';
|
||
|
||
appData.presets.forEach(preset => {
|
||
const card = document.createElement('div');
|
||
card.className = 'preset-card';
|
||
card.innerHTML = `
|
||
<div class="preset-header">
|
||
<h3>
|
||
<input type="text" value="${preset.name}" data-preset-id="${preset.id}" data-field="name" class="preset-name-input" style="font-size: 16px; font-weight: 600; border: none; background: transparent; width: 200px;">
|
||
</h3>
|
||
<button class="btn-delete" data-preset-id="${preset.id}">Удалить</button>
|
||
</div>
|
||
<div class="preset-inputs">
|
||
<div class="form-group">
|
||
<label>Переменные затраты (₸):</label>
|
||
<input type="number" step="0.0001" value="${preset.variableCosts}" data-preset-id="${preset.id}" data-field="variableCosts">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Средняя пропускная способность (Мбит/с):</label>
|
||
<input type="number" step="0.0001" value="${preset.avgCapacity}" data-preset-id="${preset.id}" data-field="avgCapacity">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Постоянные затраты (₸):</label>
|
||
<input type="number" step="0.0001" value="${preset.fixedCosts}" data-preset-id="${preset.id}" data-field="fixedCosts">
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.appendChild(card);
|
||
});
|
||
|
||
container.querySelectorAll('.btn-delete').forEach(btn => {
|
||
btn.addEventListener('click', (e) => {
|
||
const id = parseInt(e.target.dataset.presetId);
|
||
if (appData.presets.length > 1) {
|
||
appData.presets = appData.presets.filter(p => p.id !== id);
|
||
if (selectedPresetId === id) {
|
||
selectedPresetId = appData.presets[0].id;
|
||
}
|
||
renderPresetsSettings();
|
||
} else {
|
||
alert('Должен остаться хотя бы один тип расчета');
|
||
}
|
||
});
|
||
});
|
||
|
||
container.querySelectorAll('input').forEach(input => {
|
||
input.addEventListener('input', (e) => {
|
||
const id = parseInt(e.target.dataset.presetId);
|
||
const field = e.target.dataset.field;
|
||
const preset = appData.presets.find(p => p.id === id);
|
||
if (preset) {
|
||
if (field === 'name') {
|
||
preset.name = e.target.value;
|
||
} else {
|
||
preset[field] = parseFloat(e.target.value) || 0;
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderHistory() {
|
||
const tbody = document.querySelector('#historyTable tbody');
|
||
const noHistory = document.getElementById('noHistory');
|
||
|
||
if (appData.history.length === 0) {
|
||
tbody.innerHTML = '';
|
||
noHistory.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
noHistory.style.display = 'none';
|
||
tbody.innerHTML = '';
|
||
|
||
const reversed = [...appData.history].reverse();
|
||
|
||
reversed.forEach(record => {
|
||
const row = document.createElement('tr');
|
||
const date = new Date(record.date);
|
||
const dateStr = date.toLocaleDateString('ru-RU');
|
||
|
||
row.innerHTML = `
|
||
<td>${dateStr}</td>
|
||
<td>${record.presetName}</td>
|
||
<td>${formatNumber(record.speed)} Мбит/с</td>
|
||
<td>${formatNumber(record.result)} ₸</td>
|
||
`;
|
||
tbody.appendChild(row);
|
||
});
|
||
}
|
||
|
||
function calculateCost() {
|
||
const speedInput = document.getElementById('speedInput');
|
||
const speed = parseFloat(speedInput.value);
|
||
|
||
const preset = appData.presets.find(p => p.id === selectedPresetId);
|
||
if (!preset) {
|
||
showError('Выберите тип расчета');
|
||
return null;
|
||
}
|
||
|
||
if (isNaN(speed) || speed <= 0) {
|
||
showError('Введите корректное значение скорости (больше 0)');
|
||
return null;
|
||
}
|
||
|
||
if (preset.avgCapacity <= 0) {
|
||
showError('Средняя пропускная способность должна быть больше 0. Проверьте настройки.');
|
||
return null;
|
||
}
|
||
|
||
const variablePart = (preset.variableCosts / preset.avgCapacity) * speed;
|
||
const total = variablePart + preset.fixedCosts;
|
||
|
||
return {
|
||
variablePart,
|
||
fixedPart: preset.fixedCosts,
|
||
total,
|
||
speed,
|
||
preset
|
||
};
|
||
}
|
||
|
||
function showResult(calc) {
|
||
const resultBlock = document.getElementById('resultBlock');
|
||
const resultValue = document.getElementById('resultValue');
|
||
const variablePart = document.getElementById('variablePart');
|
||
const fixedPart = document.getElementById('fixedPart');
|
||
const speedDisplay = document.getElementById('speedDisplay');
|
||
const formulaText = document.getElementById('formulaText');
|
||
|
||
resultValue.textContent = formatNumber(calc.total) + ' ₸';
|
||
variablePart.textContent = formatNumber(calc.variablePart) + ' ₸';
|
||
fixedPart.textContent = formatNumber(calc.fixedPart) + ' ₸';
|
||
speedDisplay.textContent = formatNumber(calc.speed) + ' Мбит/с';
|
||
|
||
formulaText.textContent =
|
||
`(${formatNumberFull(calc.preset.variableCosts)} / ${formatNumberFull(calc.preset.avgCapacity)}) × ${formatNumber(calc.speed)} + ${formatNumberFull(calc.preset.fixedCosts)} = ${formatNumber(calc.total)}`;
|
||
|
||
resultBlock.style.display = 'block';
|
||
}
|
||
|
||
function showError(message) {
|
||
const existingError = document.querySelector('.error-message');
|
||
if (existingError) {
|
||
existingError.remove();
|
||
}
|
||
|
||
const errorDiv = document.createElement('div');
|
||
errorDiv.className = 'error-message';
|
||
errorDiv.textContent = message;
|
||
|
||
const calculatorCard = document.querySelector('.calculator');
|
||
calculatorCard.insertBefore(errorDiv, calculatorCard.firstChild);
|
||
|
||
setTimeout(() => {
|
||
errorDiv.remove();
|
||
}, 5000);
|
||
}
|
||
|
||
function addToHistory(calc) {
|
||
const record = {
|
||
date: new Date().toISOString(),
|
||
presetId: calc.preset.id,
|
||
presetName: calc.preset.name,
|
||
speed: calc.speed,
|
||
variableCosts: calc.preset.variableCosts,
|
||
avgCapacity: calc.preset.avgCapacity,
|
||
fixedCosts: calc.preset.fixedCosts,
|
||
result: calc.total
|
||
};
|
||
|
||
appData.history.push(record);
|
||
|
||
if (appData.history.length > 100) {
|
||
appData.history = appData.history.slice(-100);
|
||
}
|
||
|
||
saveData();
|
||
renderHistory();
|
||
}
|
||
|
||
document.getElementById('calculateBtn').addEventListener('click', () => {
|
||
const calc = calculateCost();
|
||
if (calc) {
|
||
showResult(calc);
|
||
addToHistory(calc);
|
||
}
|
||
});
|
||
|
||
document.getElementById('speedInput').addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') {
|
||
document.getElementById('calculateBtn').click();
|
||
}
|
||
});
|
||
|
||
document.getElementById('settingsBtn').addEventListener('click', () => {
|
||
renderPresetsSettings();
|
||
document.getElementById('settingsModal').classList.add('on');
|
||
});
|
||
|
||
document.getElementById('closeModal').addEventListener('click', () => {
|
||
document.getElementById('settingsModal').classList.remove('on');
|
||
});
|
||
|
||
document.getElementById('saveSettingsBtn').addEventListener('click', () => {
|
||
saveData();
|
||
renderPresetsSelect();
|
||
document.getElementById('settingsModal').classList.remove('on');
|
||
});
|
||
|
||
document.getElementById('addPresetBtn').addEventListener('click', () => {
|
||
const name = prompt('Введите название типа расчета:');
|
||
if (name && name.trim()) {
|
||
appData.presets.push({
|
||
id: Date.now(),
|
||
name: name.trim(),
|
||
variableCosts: 0,
|
||
avgCapacity: 1,
|
||
fixedCosts: 0
|
||
});
|
||
renderPresetsSettings();
|
||
}
|
||
});
|
||
|
||
document.getElementById('clearHistoryBtn').addEventListener('click', () => {
|
||
if (confirm('Вы уверены, что хотите очистить историю расчетов?')) {
|
||
appData.history = [];
|
||
saveData();
|
||
renderHistory();
|
||
}
|
||
});
|
||
|
||
document.getElementById('presetSelect').addEventListener('change', (e) => {
|
||
selectedPresetId = parseInt(e.target.value);
|
||
});
|
||
|
||
document.getElementById('settingsModal').addEventListener('click', (e) => {
|
||
if (e.target === document.getElementById('settingsModal')) {
|
||
document.getElementById('settingsModal').classList.remove('on');
|
||
}
|
||
});
|
||
|
||
async function init() {
|
||
await loadData();
|
||
renderPresetsSelect();
|
||
renderHistory();
|
||
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const speed = urlParams.get('speed');
|
||
if (speed) {
|
||
document.getElementById('speedInput').value = speed;
|
||
}
|
||
}
|
||
|
||
init();
|