sozday-veb-prilozhenie-kalku/script.js

340 lines
10 KiB
JavaScript
Raw 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.

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();