246 lines
9.1 KiB
HTML
246 lines
9.1 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Калькулятор премий</title>
|
||
<style>
|
||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
|
||
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
|
||
.hero{background:var(--ink);color:var(--white)}
|
||
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px}
|
||
.hero p{font-size:20px;color:#9aa3b2;max-width:600px;margin-bottom:32px}
|
||
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none}
|
||
.btn:hover{background:#1be5ff}
|
||
.section h2{font-size:36px;font-weight:700;margin-bottom:24px}
|
||
.card{background:var(--gray-100);border-radius:16px;padding:32px;margin-bottom:16px}
|
||
@media (max-width:640px){.hero h1{font-size:36px}.section h2{font-size:28px}.container{padding:48px 20px}}
|
||
|
||
.form-group{margin-bottom:16px}
|
||
.form-group label{display:block;margin-bottom:8px;font-weight:600}
|
||
.form-group input,.form-group select{width:100%;padding:12px;border:2px solid var(--gray-100);border-radius:8px;font-size:16px}
|
||
.form-group input:focus,.form-group select:focus{outline:none;border-color:var(--cyan)}
|
||
.btn-add{background:var(--cyan);color:var(--ink);padding:12px 24px;border:none;border-radius:8px;font-weight:700;cursor:pointer;margin-bottom:24px}
|
||
.btn-add:hover{background:#1be5ff}
|
||
.table-container{overflow-x:auto}
|
||
table{width:100%;border-collapse:collapse;margin-top:16px}
|
||
th,td{padding:12px;text-align:left;border-bottom:2px solid var(--gray-100)}
|
||
th{background:var(--gray-100);font-weight:600}
|
||
.btn-remove{background:#ff4757;color:white;border:none;padding:6px 12px;border-radius:6px;cursor:pointer}
|
||
.btn-remove:hover{background:#ff3344}
|
||
.result-section{margin-top:32px;padding:24px;background:var(--cyan-50);border-radius:12px}
|
||
.result-section h3{margin-bottom:16px}
|
||
.result-item{display:flex;justify-content:space-between;margin-bottom:8px;padding:8px;border-bottom:1px solid var(--gray-100)}
|
||
.result-item.excluded{color:#ff4757}
|
||
.result-item.included{color:#2ed573}
|
||
.total{font-weight:700;font-size:20px;margin-top:16px;padding-top:16px;border-top:2px solid var(--ink)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<section class="hero">
|
||
<div class="container">
|
||
<h1>Калькулятор премий</h1>
|
||
<p>Автоматический расчет премий с учетом испытательного срока. Исключает работников с незаконченным испытательным сроком и пересчитывает фактические рабочие дни.</p>
|
||
<a class="btn" href="#calculator">Начать расчет</a>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="calculator" class="section">
|
||
<div class="container">
|
||
<h2>Добавьте работников</h2>
|
||
|
||
<div class="card">
|
||
<div class="form-group">
|
||
<label for="fullName">ФИО работника</label>
|
||
<input type="text" id="fullName" placeholder="Иванов Иван Иванович">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="hireDate">Дата приема на работу</label>
|
||
<input type="date" id="hireDate">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="salary">Фактический оклад (₽)</label>
|
||
<input type="number" id="salary" placeholder="50000">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="workedDays">Фактически отработанные рабочие дни</label>
|
||
<input type="number" id="workedDays" placeholder="22">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="reportMonth">Отчетный месяц</label>
|
||
<input type="month" id="reportMonth">
|
||
</div>
|
||
|
||
<button class="btn-add" onclick="addWorker()">Добавить работника</button>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table id="workersTable">
|
||
<thead>
|
||
<tr>
|
||
<th>ФИО</th>
|
||
<th>Дата приема</th>
|
||
<th>Оклад (₽)</th>
|
||
<th>Отработано дней</th>
|
||
<th>Отчетный месяц</th>
|
||
<th>Действие</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="workersBody">
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<button class="btn" onclick="calculateBonus()" style="margin-top:24px">Рассчитать премию</button>
|
||
|
||
<div id="results" class="result-section" style="display:none">
|
||
<h3>Результаты расчета</h3>
|
||
<div id="resultsList"></div>
|
||
<div id="totalResult" class="total"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<script>
|
||
let workers = [];
|
||
|
||
function addWorker() {
|
||
const fullName = document.getElementById('fullName').value;
|
||
const hireDate = document.getElementById('hireDate').value;
|
||
const salary = parseFloat(document.getElementById('salary').value);
|
||
const workedDays = parseInt(document.getElementById('workedDays').value);
|
||
const reportMonth = document.getElementById('reportMonth').value;
|
||
|
||
if (!fullName || !hireDate || isNaN(salary) || isNaN(workedDays) || !reportMonth) {
|
||
alert('Заполните все поля');
|
||
return;
|
||
}
|
||
|
||
workers.push({
|
||
id: Date.now(),
|
||
fullName,
|
||
hireDate,
|
||
salary,
|
||
workedDays,
|
||
reportMonth
|
||
});
|
||
|
||
updateTable();
|
||
clearForm();
|
||
}
|
||
|
||
function removeWorker(id) {
|
||
workers = workers.filter(w => w.id !== id);
|
||
updateTable();
|
||
}
|
||
|
||
function updateTable() {
|
||
const tbody = document.getElementById('workersBody');
|
||
tbody.innerHTML = workers.map(w => `
|
||
<tr>
|
||
<td>${w.fullName}</td>
|
||
<td>${formatDate(w.hireDate)}</td>
|
||
<td>${w.salary.toLocaleString('ru-RU')}</td>
|
||
<td>${w.workedDays}</td>
|
||
<td>${formatMonth(w.reportMonth)}</td>
|
||
<td><button class="btn-remove" onclick="removeWorker(${w.id})">Удалить</button></td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function clearForm() {
|
||
document.getElementById('fullName').value = '';
|
||
document.getElementById('hireDate').value = '';
|
||
document.getElementById('salary').value = '';
|
||
document.getElementById('workedDays').value = '';
|
||
document.getElementById('reportMonth').value = '';
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
const date = new Date(dateStr);
|
||
return date.toLocaleDateString('ru-RU');
|
||
}
|
||
|
||
function formatMonth(monthStr) {
|
||
const [year, month] = monthStr.split('-');
|
||
const months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
|
||
'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
|
||
return `${months[parseInt(month)-1]} ${year}`;
|
||
}
|
||
|
||
function calculateBonus() {
|
||
if (workers.length === 0) {
|
||
alert('Добавьте хотя бы одного работника');
|
||
return;
|
||
}
|
||
|
||
const results = [];
|
||
let totalBonus = 0;
|
||
|
||
workers.forEach(worker => {
|
||
const hireDate = new Date(worker.hireDate);
|
||
const probationEndDate = new Date(hireDate);
|
||
probationEndDate.setMonth(probationEndDate.getMonth() + 1);
|
||
|
||
const [reportYear, reportMonth] = worker.reportMonth.split('-').map(Number);
|
||
const reportEndDate = new Date(reportYear, reportMonth, 0);
|
||
|
||
const isProbationEnded = probationEndDate <= reportEndDate;
|
||
|
||
let includedDays = 0;
|
||
let bonusAmount = 0;
|
||
let excludedReason = '';
|
||
|
||
if (!isProbationEnded) {
|
||
excludedReason = `Испытательный срок до ${formatDate(probationEndDate.toISOString().split('T')[0])}`;
|
||
} else {
|
||
const daysInMonth = reportEndDate.getDate();
|
||
const daysBeforeProbation = Math.max(0, Math.ceil((probationEndDate - new Date(reportYear, reportMonth-1, 1)) / (1000*60*60*24)));
|
||
includedDays = daysInMonth - daysBeforeProbation;
|
||
|
||
const dailyRate = worker.salary / daysInMonth;
|
||
bonusAmount = dailyRate * includedDays;
|
||
}
|
||
|
||
results.push({
|
||
...worker,
|
||
isProbationEnded,
|
||
includedDays,
|
||
bonusAmount,
|
||
excludedReason,
|
||
probationEndDate: formatDate(probationEndDate.toISOString().split('T')[0])
|
||
});
|
||
|
||
totalBonus += bonusAmount;
|
||
});
|
||
|
||
displayResults(results, totalBonus);
|
||
}
|
||
|
||
function displayResults(results, totalBonus) {
|
||
const resultsDiv = document.getElementById('results');
|
||
const resultsList = document.getElementById('resultsList');
|
||
const totalResult = document.getElementById('totalResult');
|
||
|
||
resultsList.innerHTML = results.map(r => `
|
||
<div class="result-item ${r.isProbationEnded ? 'included' : 'excluded'}">
|
||
<div>
|
||
<strong>${r.fullName}</strong><br>
|
||
<small>${r.isProbationEnded ? `Включен: ${r.includedDays} дней` : `Исключен: ${r.excludedReason}`}</small>
|
||
</div>
|
||
<div>${r.isProbationEnded ? r.bonusAmount.toLocaleString('ru-RU') + ' ₽' : '—'}</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
totalResult.innerHTML = `Итого к выплате: ${totalBonus.toLocaleString('ru-RU')} ₽`;
|
||
resultsDiv.style.display = 'block';
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |