547 lines
26 KiB
HTML
547 lines
26 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 {
|
||
--bg: #0f1218;
|
||
--card: #1a1d26;
|
||
--card-hover: #222632;
|
||
--text: #f4f4f5;
|
||
--text-dim: #a1a1aa;
|
||
--accent: #00b8cc;
|
||
--accent-hover: #00d9e8;
|
||
--danger: #ef4444;
|
||
--success: #22c55e;
|
||
--warning: #f59e0b;
|
||
--info: #3b82f6;
|
||
--border: #2d3142;
|
||
}
|
||
* { margin: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
padding: 24px;
|
||
}
|
||
.container { max-width: 1400px; margin: 0 auto; }
|
||
h1 { font-size: 28px; margin-bottom: 8px; }
|
||
.subtitle { color: var(--text-dim); margin-bottom: 24px; }
|
||
|
||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
||
.stat { background: var(--card); padding: 16px; border-radius: 12px; text-align: center; }
|
||
.stat-value { font-size: 32px; font-weight: 700; color: var(--accent); }
|
||
.stat-label { font-size: 13px; color: var(--text-dim); margin-top: 4px; }
|
||
.stat.in-work .stat-value { color: var(--info); }
|
||
.stat.closed .stat-value { color: var(--success); }
|
||
.stat.overdue .stat-value { color: var(--danger); }
|
||
.stat.maintenance .stat-value { color: var(--warning); }
|
||
|
||
.controls { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 24px; align-items: center; }
|
||
.search { flex: 1; min-width: 280px; padding: 12px 16px; background: var(--card); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 14px; }
|
||
.search::placeholder { color: var(--text-dim); }
|
||
.btn { padding: 12px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
||
.btn-primary { background: var(--accent); color: #000; }
|
||
.btn-primary:hover { background: var(--accent-hover); }
|
||
.btn-danger { background: var(--danger); color: #fff; }
|
||
.btn-secondary { background: var(--card); color: var(--text); border: 1px solid var(--border); }
|
||
.btn-secondary:hover { background: var(--card-hover); }
|
||
|
||
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; }
|
||
.chip { padding: 8px 16px; background: var(--card); border: 1px solid var(--border); border-radius: 20px; cursor: pointer; font-size: 13px; transition: all 0.2s; }
|
||
.chip:hover { background: var(--card-hover); }
|
||
.chip.active { background: var(--accent); color: #000; border-color: var(--accent); }
|
||
|
||
.table-container { background: var(--card); border-radius: 12px; overflow: hidden; margin-bottom: 24px; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th, td { padding: 14px 16px; text-align: left; border-bottom: 1px solid var(--border); }
|
||
th { background: rgba(0,0,0,0.2); font-weight: 600; font-size: 13px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; }
|
||
tr { cursor: pointer; transition: background 0.2s; }
|
||
tr:hover { background: var(--card-hover); }
|
||
.status-badge { padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
|
||
.status-in-work { background: rgba(59, 130, 246, 0.2); color: #60a5fa; }
|
||
.status-closed { background: rgba(34, 197, 94, 0.2); color: #4ade80; }
|
||
.status-overdue { background: rgba(239, 68, 68, 0.2); color: #f87171; }
|
||
.status-maintenance { background: rgba(245, 158, 11, 0.2); color: #fbbf24; }
|
||
|
||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); display: none; align-items: center; justify-content: center; z-index: 100; }
|
||
.modal-overlay.open { display: flex; }
|
||
.modal { background: var(--card); border-radius: 16px; padding: 32px; max-width: 680px; width: 90%; max-height: 90vh; overflow-y: auto; }
|
||
.modal h2 { margin-bottom: 24px; font-size: 22px; }
|
||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||
.form-group { margin-bottom: 16px; }
|
||
.form-group.full { grid-column: 1 / -1; }
|
||
label { display: block; margin-bottom: 6px; font-size: 13px; color: var(--text-dim); }
|
||
input, select, textarea { width: 100%; padding: 10px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; }
|
||
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
|
||
.modal-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 24px; }
|
||
|
||
.log-section { background: var(--card); border-radius: 12px; padding: 24px; margin-bottom: 24px; }
|
||
.log-section h3 { margin-bottom: 16px; font-size: 18px; }
|
||
.log-list { max-height: 200px; overflow-y: auto; }
|
||
.log-item { padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; color: var(--text-dim); }
|
||
.log-item:last-child { border-bottom: none; }
|
||
.log-time { color: var(--accent); margin-right: 8px; }
|
||
|
||
.confirm-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); display: none; align-items: center; justify-content: center; z-index: 200; }
|
||
.confirm-overlay.open { display: flex; }
|
||
.confirm { background: var(--card); border-radius: 16px; padding: 32px; max-width: 420px; width: 90%; text-align: center; }
|
||
.confirm h3 { margin-bottom: 12px; font-size: 20px; }
|
||
.confirm p { color: var(--text-dim); margin-bottom: 24px; }
|
||
.confirm-actions { display: flex; gap: 12px; justify-content: center; }
|
||
|
||
.empty { text-align: center; padding: 60px 20px; color: var(--text-dim); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Учёт служебных автомобилей</h1>
|
||
<p class="subtitle">Система учёта путевых листов автопарка</p>
|
||
|
||
<div class="stats" id="stats"></div>
|
||
|
||
<div class="controls">
|
||
<input type="text" class="search" id="search" placeholder="Поиск по водителю, автомобилю, маршруту или номеру путевого листа...">
|
||
<button class="btn btn-primary" id="btnAdd">Добавить путевой лист</button>
|
||
<button class="btn btn-secondary" id="btnExport">Выгрузить в CSV</button>
|
||
</div>
|
||
|
||
<div class="chips" id="chips"></div>
|
||
|
||
<div class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>№ ПЛ</th>
|
||
<th>Дата</th>
|
||
<th>Автомобиль</th>
|
||
<th>Водитель</th>
|
||
<th>Маршрут</th>
|
||
<th>Пробег (км)</th>
|
||
<th>Статус</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody"></tbody>
|
||
</table>
|
||
<div class="empty" id="empty" style="display:none">Нет записей</div>
|
||
</div>
|
||
|
||
<div class="log-section">
|
||
<h3>Журнал изменений</h3>
|
||
<div class="log-list" id="logList"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="modalOverlay">
|
||
<div class="modal">
|
||
<h2 id="modalTitle">Путевой лист</h2>
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Номер путевого листа</label>
|
||
<input type="text" id="fNumber" placeholder="ПЛ-2024-001">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Дата</label>
|
||
<input type="date" id="fDate">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Автомобиль</label>
|
||
<input type="text" id="fCar" placeholder="Toyota Camry">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Водитель</label>
|
||
<input type="text" id="fDriver" placeholder="ФИО">
|
||
</div>
|
||
<div class="form-group full">
|
||
<label>Маршрут</label>
|
||
<input type="text" id="fRoute" placeholder="Алматы - Астана">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Начальный километраж (км)</label>
|
||
<input type="number" id="fStartKm" placeholder="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Конечный километраж (км)</label>
|
||
<input type="number" id="fEndKm" placeholder="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Выдача топлива (л)</label>
|
||
<input type="number" id="fFuelGiven" placeholder="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Расход топлива (л)</label>
|
||
<input type="number" id="fFuelUsed" placeholder="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Следующее ТО</label>
|
||
<input type="date" id="fNextTo">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Статус</label>
|
||
<select id="fStatus">
|
||
<option value="в работе">в работе</option>
|
||
<option value="закрыт">закрыт</option>
|
||
<option value="просрочен">просрочен</option>
|
||
<option value="требуется ТО">требуется ТО</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group full">
|
||
<label>Комментарий</label>
|
||
<textarea id="fNote" rows="3" placeholder="Примечания"></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-secondary" id="btnCancel">Отмена</button>
|
||
<button class="btn btn-primary" id="btnSave">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="confirm-overlay" id="confirmOverlay">
|
||
<div class="confirm">
|
||
<h3>Удаление записи</h3>
|
||
<p>Вы уверены, что хотите удалить этот путевой лист? Это действие нельзя отменить.</p>
|
||
<div class="confirm-actions">
|
||
<button class="btn btn-secondary" id="btnConfirmCancel">Отмена</button>
|
||
<button class="btn btn-danger" id="btnConfirmDelete">Удалить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const STORAGE_KEY = 'auto_fleet_data';
|
||
const LOG_KEY = 'auto_fleet_log';
|
||
|
||
const demoData = [
|
||
{ id: 1, number: 'ПЛ-2024-089', date: '2024-12-15', car: 'Toyota Camry', driver: 'Ерлан Ахметов', route: 'Алматы - Астана', startKm: 45230, endKm: 46180, mileage: 950, fuelGiven: 85, fuelUsed: 78, nextTo: '2025-01-10', status: 'в работе', note: 'Плановая поездка' },
|
||
{ id: 2, number: 'ПЛ-2024-090', date: '2024-12-18', car: 'Hyundai Elantra', driver: 'Айдос Каримов', route: 'Алматы - Шымкент', startKm: 32100, endKm: 32680, mileage: 580, fuelGiven: 52, fuelUsed: 48, nextTo: '2025-02-15', status: 'закрыт', note: '' },
|
||
{ id: 3, number: 'ПЛ-2024-091', date: '2024-12-20', car: 'Kia Sorento', driver: 'Гульмира Омарова', route: 'Астана - Караганда', startKm: 28450, endKm: 28650, mileage: 200, fuelGiven: 25, fuelUsed: 22, nextTo: '2024-12-28', status: 'требуется ТО', note: 'Замена масла' },
|
||
{ id: 4, number: 'ПЛ-2024-092', date: '2024-12-10', car: 'Toyota Camry', driver: 'Нурлан Сериков', route: 'Алматы - Талдыкорган', startKm: 45100, endKm: 45450, mileage: 350, fuelGiven: 35, fuelUsed: 32, nextTo: '2025-01-10', status: 'закрыт', note: '' },
|
||
{ id: 5, number: 'ПЛ-2024-093', date: '2024-12-05', car: 'Hyundai Elantra', driver: 'Руслан Баймуханов', route: 'Шымкент - Туркестан', startKm: 31800, endKm: 31950, mileage: 150, fuelGiven: 18, fuelUsed: 15, nextTo: '2025-02-15', status: 'просрочен', note: 'Задержка с возвратом' },
|
||
{ id: 6, number: 'ПЛ-2024-094', date: '2024-12-22', car: 'Kia Sorento', driver: 'Алия Нурпеисова', route: 'Астана - Кокшетау', startKm: 28700, endKm: 29050, mileage: 350, fuelGiven: 40, fuelUsed: 36, nextTo: '2025-01-05', status: 'в работе', note: 'Делегация' }
|
||
];
|
||
|
||
let data = loadData();
|
||
let currentFilter = 'все';
|
||
let currentId = null;
|
||
let deleteId = null;
|
||
|
||
function loadData() {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
if (stored) return JSON.parse(stored);
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(demoData));
|
||
return [...demoData];
|
||
}
|
||
|
||
function saveData() {
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
}
|
||
|
||
function loadLog() {
|
||
const stored = localStorage.getItem(LOG_KEY);
|
||
return stored ? JSON.parse(stored) : [];
|
||
}
|
||
|
||
function addLog(action) {
|
||
const log = loadLog();
|
||
log.unshift({ time: new Date().toISOString(), action });
|
||
localStorage.setItem(LOG_KEY, JSON.stringify(log.slice(0, 50)));
|
||
renderLog();
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
if (!dateStr) return '';
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||
}
|
||
|
||
function getStatusClass(status) {
|
||
const map = { 'в работе': 'status-in-work', 'закрыт': 'status-closed', 'просрочен': 'status-overdue', 'требуется ТО': 'status-maintenance' };
|
||
return map[status] || '';
|
||
}
|
||
|
||
function getStats() {
|
||
const stats = { total: data.length, inWork: 0, closed: 0, overdue: 0, maintenance: 0 };
|
||
data.forEach(d => {
|
||
if (d.status === 'в работе') stats.inWork++;
|
||
else if (d.status === 'закрыт') stats.closed++;
|
||
else if (d.status === 'просрочен') stats.overdue++;
|
||
else if (d.status === 'требуется ТО') stats.maintenance++;
|
||
});
|
||
return stats;
|
||
}
|
||
|
||
function renderStats() {
|
||
const s = getStats();
|
||
document.getElementById('stats').innerHTML = `
|
||
<div class="stat"><div class="stat-value">${s.total}</div><div class="stat-label">Всего</div></div>
|
||
<div class="stat in-work"><div class="stat-value">${s.inWork}</div><div class="stat-label">В работе</div></div>
|
||
<div class="stat closed"><div class="stat-value">${s.closed}</div><div class="stat-label">Закрыт</div></div>
|
||
<div class="stat overdue"><div class="stat-value">${s.overdue}</div><div class="stat-label">Просрочен</div></div>
|
||
<div class="stat maintenance"><div class="stat-value">${s.maintenance}</div><div class="stat-label">Требуется ТО</div></div>
|
||
`;
|
||
}
|
||
|
||
function renderChips() {
|
||
const chips = ['все', 'в работе', 'закрыт', 'просрочен', 'требуется ТО'];
|
||
document.getElementById('chips').innerHTML = chips.map(c =>
|
||
`<div class="chip ${c === currentFilter ? 'active' : ''}" data-filter="${c}">${c}</div>`
|
||
).join('');
|
||
document.querySelectorAll('.chip').forEach(el => {
|
||
el.addEventListener('click', () => {
|
||
currentFilter = el.dataset.filter;
|
||
renderChips();
|
||
renderTable();
|
||
});
|
||
});
|
||
}
|
||
|
||
function getFilteredData() {
|
||
const query = document.getElementById('search').value.toLowerCase();
|
||
return data.filter(d => {
|
||
const matchFilter = currentFilter === 'все' || d.status === currentFilter;
|
||
const matchSearch = !query ||
|
||
d.driver.toLowerCase().includes(query) ||
|
||
d.car.toLowerCase().includes(query) ||
|
||
d.route.toLowerCase().includes(query) ||
|
||
d.number.toLowerCase().includes(query);
|
||
return matchFilter && matchSearch;
|
||
});
|
||
}
|
||
|
||
function renderTable() {
|
||
const filtered = getFilteredData();
|
||
const tbody = document.getElementById('tableBody');
|
||
const empty = document.getElementById('empty');
|
||
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = '';
|
||
empty.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
empty.style.display = 'none';
|
||
tbody.innerHTML = filtered.map(d => `
|
||
<tr data-id="${d.id}">
|
||
<td>${d.number}</td>
|
||
<td>${formatDate(d.date)}</td>
|
||
<td>${d.car}</td>
|
||
<td>${d.driver}</td>
|
||
<td>${d.route}</td>
|
||
<td>${d.mileage}</td>
|
||
<td><span class="status-badge ${getStatusClass(d.status)}">${d.status}</span></td>
|
||
</tr>
|
||
`).join('');
|
||
|
||
tbody.querySelectorAll('tr').forEach(tr => {
|
||
tr.addEventListener('click', (e) => {
|
||
if (e.target.closest('.status-badge')) return;
|
||
const id = parseInt(tr.dataset.id);
|
||
openCard(id);
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderLog() {
|
||
const log = loadLog();
|
||
document.getElementById('logList').innerHTML = log.length ? log.map(l =>
|
||
`<div class="log-item"><span class="log-time">${new Date(l.time).toLocaleString('ru-RU')}</span>${l.action}</div>`
|
||
).join('') : '<div class="log-item">Нет записей</div>';
|
||
}
|
||
|
||
function openModal(id = null) {
|
||
currentId = id;
|
||
const overlay = document.getElementById('modalOverlay');
|
||
const title = document.getElementById('modalTitle');
|
||
|
||
if (id) {
|
||
const d = data.find(x => x.id === id);
|
||
title.textContent = 'Редактирование путевого листа';
|
||
document.getElementById('fNumber').value = d.number;
|
||
document.getElementById('fDate').value = d.date;
|
||
document.getElementById('fCar').value = d.car;
|
||
document.getElementById('fDriver').value = d.driver;
|
||
document.getElementById('fRoute').value = d.route;
|
||
document.getElementById('fStartKm').value = d.startKm;
|
||
document.getElementById('fEndKm').value = d.endKm;
|
||
document.getElementById('fFuelGiven').value = d.fuelGiven;
|
||
document.getElementById('fFuelUsed').value = d.fuelUsed;
|
||
document.getElementById('fNextTo').value = d.nextTo;
|
||
document.getElementById('fStatus').value = d.status;
|
||
document.getElementById('fNote').value = d.note || '';
|
||
} else {
|
||
title.textContent = 'Новый путевой лист';
|
||
document.getElementById('fNumber').value = '';
|
||
document.getElementById('fDate').value = new Date().toISOString().slice(0, 10);
|
||
document.getElementById('fCar').value = '';
|
||
document.getElementById('fDriver').value = '';
|
||
document.getElementById('fRoute').value = '';
|
||
document.getElementById('fStartKm').value = '';
|
||
document.getElementById('fEndKm').value = '';
|
||
document.getElementById('fFuelGiven').value = '';
|
||
document.getElementById('fFuelUsed').value = '';
|
||
document.getElementById('fNextTo').value = '';
|
||
document.getElementById('fStatus').value = 'в работе';
|
||
document.getElementById('fNote').value = '';
|
||
}
|
||
|
||
overlay.classList.add('open');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modalOverlay').classList.remove('open');
|
||
currentId = null;
|
||
}
|
||
|
||
function saveForm() {
|
||
const number = document.getElementById('fNumber').value.trim();
|
||
const date = document.getElementById('fDate').value;
|
||
const car = document.getElementById('fCar').value.trim();
|
||
const driver = document.getElementById('fDriver').value.trim();
|
||
const route = document.getElementById('fRoute').value.trim();
|
||
const startKm = parseInt(document.getElementById('fStartKm').value) || 0;
|
||
const endKm = parseInt(document.getElementById('fEndKm').value) || 0;
|
||
const fuelGiven = parseInt(document.getElementById('fFuelGiven').value) || 0;
|
||
const fuelUsed = parseInt(document.getElementById('fFuelUsed').value) || 0;
|
||
const nextTo = document.getElementById('fNextTo').value;
|
||
const status = document.getElementById('fStatus').value;
|
||
const note = document.getElementById('fNote').value.trim();
|
||
|
||
if (!number || !date || !car || !driver) {
|
||
alert('Заполните обязательные поля: номер, дата, автомобиль, водитель');
|
||
return;
|
||
}
|
||
|
||
if (currentId) {
|
||
const idx = data.findIndex(x => x.id === currentId);
|
||
const old = data[idx];
|
||
data[idx] = { ...old, number, date, car, driver, route, startKm, endKm, mileage: endKm - startKm, fuelGiven, fuelUsed, nextTo, status, note };
|
||
addLog(`Изменён ПЛ ${number}: ${old.status} → ${status}, водитель: ${driver}`);
|
||
} else {
|
||
const newId = Math.max(...data.map(d => d.id), 0) + 1;
|
||
data.unshift({ id: newId, number, date, car, driver, route, startKm, endKm, mileage: endKm - startKm, fuelGiven, fuelUsed, nextTo, status, note });
|
||
addLog(`Добавлен ПЛ ${number}, водитель: ${driver}, статус: ${status}`);
|
||
}
|
||
|
||
saveData();
|
||
closeModal();
|
||
renderAll();
|
||
}
|
||
|
||
function openCard(id) {
|
||
const d = data.find(x => x.id === id);
|
||
const html = `
|
||
<div style="font-size:14px;line-height:1.8">
|
||
<p><strong>Номер:</strong> ${d.number}</p>
|
||
<p><strong>Дата:</strong> ${formatDate(d.date)}</p>
|
||
<p><strong>Автомобиль:</strong> ${d.car}</p>
|
||
<p><strong>Водитель:</strong> ${d.driver}</p>
|
||
<p><strong>Маршрут:</strong> ${d.route}</p>
|
||
<p><strong>Километраж:</strong> ${d.startKm} → ${d.endKm} км (пробег: ${d.mileage} км)</p>
|
||
<p><strong>Топливо:</strong> выдано ${d.fuelGiven} л, израсходовано ${d.fuelUsed} л</p>
|
||
<p><strong>Следующее ТО:</strong> ${formatDate(d.nextTo) || 'не назначено'}</p>
|
||
<p><strong>Статус:</strong> <span class="status-badge ${getStatusClass(d.status)}">${d.status}</span></p>
|
||
<p><strong>Комментарий:</strong> ${d.note || '—'}</p>
|
||
</div>
|
||
`;
|
||
|
||
const overlay = document.getElementById('modalOverlay');
|
||
document.getElementById('modalTitle').textContent = 'Карточка путевого листа';
|
||
document.querySelector('.modal .form-grid').innerHTML = html;
|
||
document.querySelector('.modal .form-grid').className = '';
|
||
document.querySelector('.modal-actions').style.display = 'none';
|
||
overlay.classList.add('open');
|
||
|
||
const editBtn = document.createElement('button');
|
||
editBtn.className = 'btn btn-primary';
|
||
editBtn.textContent = 'Редактировать';
|
||
editBtn.style.marginRight = '8px';
|
||
editBtn.onclick = () => { overlay.classList.remove('open'); openModal(id); };
|
||
|
||
const delBtn = document.createElement('button');
|
||
delBtn.className = 'btn btn-danger';
|
||
delBtn.textContent = 'Удалить';
|
||
delBtn.onclick = () => { overlay.classList.remove('open'); confirmDelete(id); };
|
||
|
||
const cancelBtn = document.createElement('button');
|
||
cancelBtn.className = 'btn btn-secondary';
|
||
cancelBtn.textContent = 'Закрыть';
|
||
cancelBtn.style.marginLeft = '8px';
|
||
cancelBtn.onclick = () => { overlay.classList.remove('open'); renderAll(); };
|
||
|
||
const actions = document.createElement('div');
|
||
actions.className = 'modal-actions';
|
||
actions.style.display = 'flex';
|
||
actions.appendChild(editBtn);
|
||
actions.appendChild(delBtn);
|
||
actions.appendChild(cancelBtn);
|
||
|
||
document.querySelector('.modal').insertBefore(actions, document.querySelector('.modal').lastElementChild);
|
||
}
|
||
|
||
function confirmDelete(id) {
|
||
deleteId = id;
|
||
document.getElementById('confirmOverlay').classList.add('open');
|
||
}
|
||
|
||
function executeDelete() {
|
||
const d = data.find(x => x.id === deleteId);
|
||
if (d) {
|
||
addLog(`Удалён ПЛ ${d.number}, водитель: ${d.driver}`);
|
||
data = data.filter(x => x.id !== deleteId);
|
||
saveData();
|
||
renderAll();
|
||
}
|
||
document.getElementById('confirmOverlay').classList.remove('open');
|
||
deleteId = null;
|
||
}
|
||
|
||
function exportCsv() {
|
||
const head = ['Номер ПЛ', 'Дата', 'Автомобиль', 'Водитель', 'Маршрут', 'Нач. км', 'Кон. км', 'Пробег', 'Выдано топлива', 'Расход', 'След. ТО', 'Статус', 'Комментарий'];
|
||
const body = data.map(d => [
|
||
d.number, formatDate(d.date), d.car, d.driver, d.route, d.startKm, d.endKm, d.mileage, d.fuelGiven, d.fuelUsed, formatDate(d.nextTo), d.status, d.note || ''
|
||
].map(v => '"' + String(v ?? '').replace(/"/g, '""') + '"').join(';'));
|
||
const csv = '\uFEFF' + [head.join(';'), ...body].join('\n');
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
|
||
a.download = 'putevye_listy.csv';
|
||
a.click();
|
||
addLog('Выгрузка данных в CSV');
|
||
}
|
||
|
||
function renderAll() {
|
||
renderStats();
|
||
renderChips();
|
||
renderTable();
|
||
renderLog();
|
||
}
|
||
|
||
document.getElementById('btnAdd').onclick = () => openModal();
|
||
document.getElementById('btnCancel').onclick = closeModal;
|
||
document.getElementById('btnSave').onclick = saveForm;
|
||
document.getElementById('btnExport').onclick = exportCsv;
|
||
document.getElementById('btnConfirmDelete').onclick = executeDelete;
|
||
document.getElementById('btnConfirmCancel').onclick = () => {
|
||
document.getElementById('confirmOverlay').classList.remove('open');
|
||
deleteId = null;
|
||
};
|
||
document.getElementById('search').addEventListener('input', renderTable);
|
||
|
||
document.getElementById('modalOverlay').addEventListener('click', (e) => {
|
||
if (e.target === e.currentTarget) closeModal();
|
||
});
|
||
document.getElementById('confirmOverlay').addEventListener('click', (e) => {
|
||
if (e.target === e.currentTarget) {
|
||
document.getElementById('confirmOverlay').classList.remove('open');
|
||
deleteId = null;
|
||
}
|
||
});
|
||
|
||
renderAll();
|
||
</script>
|
||
</body>
|
||
</html>
|