560 lines
24 KiB
HTML
560 lines
24 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: #f5f7fa;
|
||
--card: #ffffff;
|
||
--text: #1f2937;
|
||
--muted: #6b7280;
|
||
--border: #e5e7eb;
|
||
--primary: #2563eb;
|
||
--primary-hover: #1d4ed8;
|
||
--success: #10b981;
|
||
--warning: #f59e0b;
|
||
--danger: #ef4444;
|
||
--info: #3b82f6;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.5; padding: 24px; }
|
||
.container { max-width: 1400px; margin: 0 auto; }
|
||
h1 { font-size: 28px; font-weight: 700; margin-bottom: 24px; color: var(--text); }
|
||
|
||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||
.stat-card { background: var(--card); border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid var(--border); }
|
||
.stat-value { font-size: 32px; font-weight: 700; color: var(--text); }
|
||
.stat-label { font-size: 14px; color: var(--muted); margin-top: 4px; }
|
||
|
||
.controls { background: var(--card); border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid var(--border); }
|
||
.search-row { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
|
||
.search-input { flex: 1; min-width: 280px; padding: 12px 16px; border: 1px solid var(--border); border-radius: 8px; font-size: 15px; outline: none; transition: border-color 0.2s; }
|
||
.search-input:focus { border-color: var(--primary); }
|
||
.btn { padding: 12px 20px; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
|
||
.btn-primary { background: var(--primary); color: white; }
|
||
.btn-primary:hover { background: var(--primary-hover); }
|
||
.btn-secondary { background: #f3f4f6; color: var(--text); border: 1px solid var(--border); }
|
||
.btn-secondary:hover { background: #e5e7eb; }
|
||
.btn-danger { background: var(--danger); color: white; }
|
||
.btn-danger:hover { background: #dc2626; }
|
||
.btn-sm { padding: 6px 12px; font-size: 13px; }
|
||
|
||
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
|
||
.chip { padding: 8px 16px; border-radius: 20px; font-size: 14px; cursor: pointer; border: 1px solid var(--border); background: var(--card); transition: all 0.2s; }
|
||
.chip:hover { border-color: var(--primary); }
|
||
.chip.active { background: var(--primary); color: white; border-color: var(--primary); }
|
||
|
||
.table-container { background: var(--card); border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid var(--border); 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: #f9fafb; font-weight: 600; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--muted); }
|
||
tr { transition: background 0.15s; }
|
||
tr:hover { background: #f9fafb; }
|
||
tr.expiring-soon { background: #fef3c7; }
|
||
tr.expired { background: #fee2e2; }
|
||
tr.suspended { background: #f3f4f6; }
|
||
|
||
.status-badge { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; text-transform: uppercase; }
|
||
.status-active { background: #d1fae5; color: #065f46; }
|
||
.status-expiring { background: #fef3c7; color: #92400e; }
|
||
.status-expired { background: #fee2e2; color: #991b1b; }
|
||
.status-suspended { background: #e5e7eb; color: #374151; }
|
||
|
||
.actions { display: flex; gap: 8px; }
|
||
.action-btn { padding: 6px 10px; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.15s; }
|
||
.action-btn-edit { background: #dbeafe; color: var(--primary); }
|
||
.action-btn-edit:hover { background: #bfdbfe; }
|
||
.action-btn-delete { background: #fee2e2; color: var(--danger); }
|
||
.action-btn-delete:hover { background: #fecaca; }
|
||
|
||
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
|
||
.modal-overlay.open { display: flex; }
|
||
.modal { background: var(--card); border-radius: 16px; padding: 32px; width: 100%; max-width: 520px; max-height: 90vh; overflow-y: auto; }
|
||
.modal h2 { font-size: 22px; margin-bottom: 24px; }
|
||
.form-group { margin-bottom: 16px; }
|
||
.form-group label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; color: var(--text); }
|
||
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; font-size: 15px; outline: none; }
|
||
.form-group input:focus, .form-group select:focus { border-color: var(--primary); }
|
||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||
.form-actions { display: flex; gap: 12px; margin-top: 24px; }
|
||
|
||
.tabs { display: flex; gap: 8px; margin-bottom: 16px; }
|
||
.tab { padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: 600; border: 1px solid transparent; }
|
||
.tab.active { background: var(--primary); color: white; }
|
||
.tab:hover:not(.active) { background: #f3f4f6; }
|
||
|
||
.log-container { background: var(--card); border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid var(--border); }
|
||
.log-entry { padding: 10px 0; border-bottom: 1px solid var(--border); font-size: 14px; }
|
||
.log-entry:last-child { border-bottom: none; }
|
||
.log-time { color: var(--muted); font-size: 12px; }
|
||
.log-action { font-weight: 600; }
|
||
.log-action.create { color: var(--success); }
|
||
.log-action.edit { color: var(--info); }
|
||
.log-action.delete { color: var(--danger); }
|
||
|
||
.empty-state { text-align: center; padding: 60px 20px; color: var(--muted); }
|
||
.empty-state p { font-size: 16px; }
|
||
|
||
@media (max-width: 768px) {
|
||
body { padding: 16px; }
|
||
.form-row { grid-template-columns: 1fr; }
|
||
table { font-size: 13px; }
|
||
th, td { padding: 10px 12px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Реестр договоров аренды</h1>
|
||
|
||
<div class="stats" id="stats"></div>
|
||
|
||
<div class="controls">
|
||
<div class="search-row">
|
||
<input type="text" class="search-input" id="searchInput" placeholder="Поиск по контрагенту, объекту, ответственному или статусу...">
|
||
<button class="btn btn-primary" onclick="openModal()">+ Добавить договор</button>
|
||
<button class="btn btn-secondary" onclick="exportCSV()">Выгрузить CSV</button>
|
||
</div>
|
||
<div class="chips" id="statusChips"></div>
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<div class="tab active" data-tab="registry" onclick="switchTab('registry')">Реестр</div>
|
||
<div class="tab" data-tab="log" onclick="switchTab('log')">Журнал изменений</div>
|
||
</div>
|
||
|
||
<div id="registryTab">
|
||
<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-state" id="emptyState" style="display: none;">
|
||
<p>Нет договоров для отображения</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="logTab" style="display: none;">
|
||
<div class="log-container" id="logContainer"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="modalOverlay">
|
||
<div class="modal">
|
||
<h2 id="modalTitle">Добавить договор</h2>
|
||
<form id="contractForm">
|
||
<input type="hidden" id="contractId">
|
||
<div class="form-group">
|
||
<label for="counterparty">Контрагент *</label>
|
||
<input type="text" id="counterparty" required placeholder="Например: ТОО «Алатау Сервис»">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="object">Объект аренды *</label>
|
||
<input type="text" id="object" required placeholder="Например: офис в Алматы">
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="amount">Сумма (₸) *</label>
|
||
<input type="number" id="amount" required placeholder="150000">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="responsible">Ответственный *</label>
|
||
<input type="text" id="responsible" required placeholder="ФИО">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="startDate">Дата начала *</label>
|
||
<input type="date" id="startDate" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="endDate">Дата окончания *</label>
|
||
<input type="date" id="endDate" required>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="status">Статус *</label>
|
||
<select id="status" required>
|
||
<option value="active">Действующий</option>
|
||
<option value="expiring">Скоро истекает</option>
|
||
<option value="expired">Истёк</option>
|
||
<option value="suspended">Приостановлен</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||
<button type="button" class="btn btn-secondary" onclick="closeModal()">Отмена</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const STORAGE_KEY = 'lease_contracts';
|
||
const LOG_KEY = 'lease_log';
|
||
const EXPIRING_DAYS = 30;
|
||
|
||
const demoData = [
|
||
{ id: 1, counterparty: 'ТОО «Алатау Сервис»', object: 'Офис, Алматы, ул. Абая 150', amount: 450000, startDate: '2025-01-15', endDate: '2026-01-14', responsible: 'Иванов А.С.', status: 'active' },
|
||
{ id: 2, counterparty: 'ТОО «Qazaq Office»', object: 'Склад, Астана, пр. Сарыарка 22', amount: 680000, startDate: '2025-06-01', endDate: '2026-05-31', responsible: 'Петрова М.К.', status: 'active' },
|
||
{ id: 3, counterparty: 'ИП Садыкова Г.Т.', object: 'Помещение, Шымкент, ул. Толе би 45', amount: 220000, startDate: '2024-03-01', endDate: '2025-08-31', responsible: 'Алиев Н.Р.', status: 'expiring' },
|
||
{ id: 4, counterparty: 'ТОО «Steppe Trade»', object: 'Торговая точка, Караганда, ул. Ленина 88', amount: 350000, startDate: '2025-09-01', endDate: '2026-08-31', responsible: 'Смирнов Д.В.', status: 'active' },
|
||
{ id: 5, counterparty: 'ТОО «Байтау»', object: 'Офис, Алматы, пр. Достык 100', amount: 520000, startDate: '2023-01-01', endDate: '2024-12-31', responsible: 'Иванов А.С.', status: 'expired' },
|
||
{ id: 6, counterparty: 'ИП Нурланов', object: 'Склад, Астана, ул. Кенесары 55', amount: 380000, startDate: '2025-02-01', endDate: '2026-01-31', responsible: 'Петрова М.К.', status: 'active' },
|
||
{ id: 7, counterparty: 'ТОО «KazRent»', object: 'Помещение, Алматы, ул. Сатпаева 30', amount: 750000, startDate: '2024-06-01', endDate: '2025-05-31', responsible: 'Алиев Н.Р.', status: 'suspended' },
|
||
{ id: 8, counterparty: 'ТОО «Алматы Строй»', object: 'Офис, Алматы, пр. Абая 200', amount: 480000, startDate: '2025-10-01', endDate: '2025-09-15', responsible: 'Смирнов Д.В.', status: 'expiring' }
|
||
];
|
||
|
||
let contracts = [];
|
||
let log = [];
|
||
let currentFilter = 'all';
|
||
let searchQuery = '';
|
||
|
||
function loadData() {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
if (stored) {
|
||
contracts = JSON.parse(stored);
|
||
} else {
|
||
contracts = [...demoData];
|
||
saveData();
|
||
}
|
||
|
||
const storedLog = localStorage.getItem(LOG_KEY);
|
||
if (storedLog) {
|
||
log = JSON.parse(storedLog);
|
||
} else {
|
||
log = [
|
||
{ time: new Date().toISOString(), action: 'create', what: 'Инициализация реестра', who: 'Система' }
|
||
];
|
||
saveLog();
|
||
}
|
||
}
|
||
|
||
function saveData() {
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(contracts));
|
||
}
|
||
|
||
function saveLog() {
|
||
localStorage.setItem(LOG_KEY, JSON.stringify(log));
|
||
}
|
||
|
||
function addLogEntry(action, what, who = 'Пользователь') {
|
||
log.unshift({
|
||
time: new Date().toISOString(),
|
||
action,
|
||
what,
|
||
who
|
||
});
|
||
saveLog();
|
||
}
|
||
|
||
function getStatusLabel(status) {
|
||
const labels = {
|
||
active: 'Действующий',
|
||
expiring: 'Скоро истекает',
|
||
expired: 'Истёк',
|
||
suspended: 'Приостановлен'
|
||
};
|
||
return labels[status] || status;
|
||
}
|
||
|
||
function getStatusClass(status) {
|
||
return 'status-' + status;
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
const d = new Date(dateStr);
|
||
const day = String(d.getDate()).padStart(2, '0');
|
||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||
const year = d.getFullYear();
|
||
return `${day}.${month}.${year}`;
|
||
}
|
||
|
||
function getRowClass(contract) {
|
||
if (contract.status === 'expired') return 'expired';
|
||
if (contract.status === 'suspended') return 'suspended';
|
||
if (contract.status === 'expiring') return 'expiring-soon';
|
||
return '';
|
||
}
|
||
|
||
function calculateStats() {
|
||
const stats = { total: contracts.length, active: 0, expiring: 0, expired: 0, suspended: 0 };
|
||
contracts.forEach(c => {
|
||
if (stats[c.status] !== undefined) stats[c.status]++;
|
||
});
|
||
return stats;
|
||
}
|
||
|
||
function renderStats() {
|
||
const stats = calculateStats();
|
||
const container = document.getElementById('stats');
|
||
container.innerHTML = `
|
||
<div class="stat-card">
|
||
<div class="stat-value">${stats.total}</div>
|
||
<div class="stat-label">Всего договоров</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value">${stats.active}</div>
|
||
<div class="stat-label">Действующий</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value">${stats.expiring}</div>
|
||
<div class="stat-label">Скоро истекает</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value">${stats.expired}</div>
|
||
<div class="stat-label">Истёк</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value">${stats.suspended}</div>
|
||
<div class="stat-label">Приостановлен</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderChips() {
|
||
const chips = [
|
||
{ value: 'all', label: 'Все' },
|
||
{ value: 'active', label: 'Действующий' },
|
||
{ value: 'expiring', label: 'Скоро истекает' },
|
||
{ value: 'expired', label: 'Истёк' },
|
||
{ value: 'suspended', label: 'Приостановлен' }
|
||
];
|
||
const container = document.getElementById('statusChips');
|
||
container.innerHTML = chips.map(chip => `
|
||
<div class="chip ${currentFilter === chip.value ? 'active' : ''}"
|
||
onclick="setFilter('${chip.value}')">${chip.label}</div>
|
||
`).join('');
|
||
}
|
||
|
||
function setFilter(value) {
|
||
currentFilter = value;
|
||
renderChips();
|
||
renderTable();
|
||
}
|
||
|
||
function filterContracts() {
|
||
let filtered = [...contracts];
|
||
|
||
if (currentFilter !== 'all') {
|
||
filtered = filtered.filter(c => c.status === currentFilter);
|
||
}
|
||
|
||
if (searchQuery) {
|
||
const q = searchQuery.toLowerCase();
|
||
filtered = filtered.filter(c =>
|
||
c.counterparty.toLowerCase().includes(q) ||
|
||
c.object.toLowerCase().includes(q) ||
|
||
c.responsible.toLowerCase().includes(q) ||
|
||
getStatusLabel(c.status).toLowerCase().includes(q)
|
||
);
|
||
}
|
||
|
||
return filtered;
|
||
}
|
||
|
||
function renderTable() {
|
||
const filtered = filterContracts();
|
||
const tbody = document.getElementById('tableBody');
|
||
const emptyState = document.getElementById('emptyState');
|
||
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = '';
|
||
emptyState.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
emptyState.style.display = 'none';
|
||
tbody.innerHTML = filtered.map(c => `
|
||
<tr class="${getRowClass(c)}">
|
||
<td>${escapeHtml(c.counterparty)}</td>
|
||
<td>${escapeHtml(c.object)}</td>
|
||
<td>${formatNumber(c.amount)}</td>
|
||
<td>${formatDate(c.startDate)} — ${formatDate(c.endDate)}</td>
|
||
<td>${escapeHtml(c.responsible)}</td>
|
||
<td><span class="status-badge ${getStatusClass(c)}">${getStatusLabel(c.status)}</span></td>
|
||
<td>
|
||
<div class="actions">
|
||
<button class="action-btn action-btn-edit" onclick="editContract(${c.id})">Ред.</button>
|
||
<button class="action-btn action-btn-delete" onclick="deleteContract(${c.id})">Удалить</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function renderLog() {
|
||
const container = document.getElementById('logContainer');
|
||
container.innerHTML = log.map(entry => `
|
||
<div class="log-entry">
|
||
<span class="log-time">${new Date(entry.time).toLocaleString('ru-RU')}</span> —
|
||
<span class="log-action ${entry.action}">${entry.action === 'create' ? 'Создание' : entry.action === 'edit' ? 'Редактирование' : 'Удаление'}</span>:
|
||
${escapeHtml(entry.what)} (Ответственный: ${escapeHtml(entry.who)})
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
function formatNumber(num) {
|
||
return new Intl.NumberFormat('ru-RU').format(num);
|
||
}
|
||
|
||
function openModal(contractId = null) {
|
||
const overlay = document.getElementById('modalOverlay');
|
||
const title = document.getElementById('modalTitle');
|
||
const form = document.getElementById('contractForm');
|
||
|
||
if (contractId) {
|
||
const contract = contracts.find(c => c.id === contractId);
|
||
if (!contract) return;
|
||
|
||
title.textContent = 'Редактировать договор';
|
||
document.getElementById('contractId').value = contract.id;
|
||
document.getElementById('counterparty').value = contract.counterparty;
|
||
document.getElementById('object').value = contract.object;
|
||
document.getElementById('amount').value = contract.amount;
|
||
document.getElementById('responsible').value = contract.responsible;
|
||
document.getElementById('startDate').value = contract.startDate;
|
||
document.getElementById('endDate').value = contract.endDate;
|
||
document.getElementById('status').value = contract.status;
|
||
} else {
|
||
title.textContent = 'Добавить договор';
|
||
form.reset();
|
||
document.getElementById('contractId').value = '';
|
||
}
|
||
|
||
overlay.classList.add('open');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modalOverlay').classList.remove('open');
|
||
}
|
||
|
||
function editContract(id) {
|
||
openModal(id);
|
||
}
|
||
|
||
function deleteContract(id) {
|
||
const contract = contracts.find(c => c.id === id);
|
||
if (!contract) return;
|
||
|
||
if (!confirm(`Вы уверены, что хотите удалить договор с контрагентом "${contract.counterparty}"?`)) {
|
||
return;
|
||
}
|
||
|
||
addLogEntry('delete', `Удалён договор: ${contract.counterparty} (${contract.object})`, contract.responsible);
|
||
contracts = contracts.filter(c => c.id !== id);
|
||
saveData();
|
||
renderAll();
|
||
}
|
||
|
||
function exportCSV() {
|
||
const headers = ['Контрагент', 'Объект', 'Сумма', 'Дата начала', 'Дата окончания', 'Ответственный', 'Статус'];
|
||
const rows = filterContracts().map(c => [
|
||
c.counterparty,
|
||
c.object,
|
||
c.amount.toString(),
|
||
formatDate(c.startDate),
|
||
formatDate(c.endDate),
|
||
c.responsible,
|
||
getStatusLabel(c.status)
|
||
]);
|
||
|
||
const csvContent = '\uFEFF' + [headers.join(';'), ...rows.map(r => r.join(';'))].join('\n');
|
||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'reestr_dogovorov.csv';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function switchTab(tab) {
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelector(`[data-tab="${tab}"]`).classList.add('active');
|
||
|
||
if (tab === 'registry') {
|
||
document.getElementById('registryTab').style.display = 'block';
|
||
document.getElementById('logTab').style.display = 'none';
|
||
} else {
|
||
document.getElementById('registryTab').style.display = 'none';
|
||
document.getElementById('logTab').style.display = 'block';
|
||
renderLog();
|
||
}
|
||
}
|
||
|
||
function renderAll() {
|
||
renderStats();
|
||
renderChips();
|
||
renderTable();
|
||
}
|
||
|
||
document.getElementById('contractForm').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
const id = document.getElementById('contractId').value;
|
||
const contractData = {
|
||
counterparty: document.getElementById('counterparty').value,
|
||
object: document.getElementById('object').value,
|
||
amount: parseFloat(document.getElementById('amount').value),
|
||
responsible: document.getElementById('responsible').value,
|
||
startDate: document.getElementById('startDate').value,
|
||
endDate: document.getElementById('endDate').value,
|
||
status: document.getElementById('status').value
|
||
};
|
||
|
||
if (id) {
|
||
const index = contracts.findIndex(c => c.id == id);
|
||
if (index !== -1) {
|
||
const old = contracts[index];
|
||
contractData.id = old.id;
|
||
contracts[index] = contractData;
|
||
addLogEntry('edit', `Изменён договор: ${contractData.counterparty} (${contractData.object})`, contractData.responsible);
|
||
}
|
||
} else {
|
||
const newId = Date.now();
|
||
contracts.push({ id: newId, ...contractData });
|
||
addLogEntry('create', `Создан договор: ${contractData.counterparty} (${contractData.object})`, contractData.responsible);
|
||
}
|
||
|
||
saveData();
|
||
closeModal();
|
||
renderAll();
|
||
});
|
||
|
||
document.getElementById('searchInput').addEventListener('input', function(e) {
|
||
searchQuery = e.target.value.trim();
|
||
renderTable();
|
||
});
|
||
|
||
document.getElementById('modalOverlay').addEventListener('click', function(e) {
|
||
if (e.target === e.currentTarget) {
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
loadData();
|
||
renderAll();
|
||
</script>
|
||
</body>
|
||
</html>
|