nado-sozdat-po-marketingovom/index.html

632 lines
31 KiB
HTML
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.

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>МАЦ — Маркетинговый анализ цен</title>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #f5f5f7; color: #1d1d1f; line-height: 1.5; }
header { background: linear-gradient(135deg, #007AFF 0%, #00C7BE 100%); color: white; padding: 32px 24px; text-align: center; }
header h1 { font-size: clamp(24px, 4vw, 36px); margin-bottom: 8px; }
header p { opacity: 0.9; font-size: 16px; }
.container { max-width: 1400px; margin: 0 auto; padding: 24px; }
.section-num { display: inline-block; background: #007AFF; color: white; width: 32px; height: 32px; border-radius: 50%; text-align: center; line-height: 32px; font-weight: 700; margin-right: 12px; }
.section-title { font-size: 22px; margin-bottom: 20px; color: #1d1d1f; display: flex; align-items: center; }
.card { background: white; border-radius: 16px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-bottom: 16px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; margin-bottom: 6px; font-weight: 500; font-size: 14px; color: #6e6e73; }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 12px; border: 1px solid #d2d2d7; border-radius: 10px; font-size: 15px; }
.form-group textarea { min-height: 100px; resize: vertical; }
.btn { padding: 12px 24px; border: none; border-radius: 10px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.2s; margin-right: 8px; margin-bottom: 8px; }
.btn-primary { background: #007AFF; color: white; }
.btn-primary:hover { background: #0056b3; }
.btn-success { background: #34C759; color: white; }
.btn-danger { background: #FF3B30; color: white; padding: 8px 16px; font-size: 13px; }
.btn-secondary { background: #8e8e93; color: white; }
.btn-outline { background: white; border: 2px solid #007AFF; color: #007AFF; }
table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 14px; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e5ea; }
th { background: #f5f5f7; font-weight: 600; font-size: 13px; color: #6e6e73; text-transform: uppercase; }
tr:hover { background: #f9f9fb; }
.checkbox-group { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; padding: 16px; background: #f9f9fb; border-radius: 10px; }
.checkbox-group input[type="checkbox"] { width: 20px; height: 20px; margin-top: 2px; }
.checkbox-group label { font-size: 14px; line-height: 1.4; cursor: pointer; }
.price-highlight { background: #d4edda; font-weight: 600; }
.empty { text-align: center; padding: 40px; color: #86868b; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-bottom: 24px; }
.stat { background: white; padding: 20px; border-radius: 12px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.stat-value { font-size: 32px; font-weight: 700; color: #007AFF; }
.stat-label { font-size: 13px; color: #6e6e73; margin-top: 4px; }
.conclusion-box { background: #f0f8ff; border: 2px solid #007AFF; border-radius: 12px; padding: 20px; margin-top: 20px; }
.signature-block { margin-top: 40px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
.signature-line { border-top: 1px solid #1d1d1f; padding-top: 8px; margin-top: 40px; }
.file-list { margin-top: 16px; }
.file-item { display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9fb; border-radius: 8px; margin-bottom: 8px; }
.file-item a { color: #007AFF; text-decoration: none; font-weight: 500; }
.file-item a:hover { text-decoration: underline; }
.file-meta { font-size: 12px; color: #86868b; margin-left: 12px; }
.upload-area { border: 2px dashed #d2d2d7; border-radius: 12px; padding: 24px; text-align: center; background: #fafafa; cursor: pointer; transition: all 0.2s; margin-bottom: 16px; }
.upload-area:hover { border-color: #007AFF; background: #f0f8ff; }
.upload-area.dragover { border-color: #34C759; background: #e8f5e9; }
.upload-icon { font-size: 48px; margin-bottom: 12px; }
.email-log { margin-top: 16px; max-height: 300px; overflow-y: auto; }
.email-item { padding: 12px; background: #f0f8ff; border-left: 4px solid #007AFF; margin-bottom: 8px; border-radius: 4px; }
.email-item h4 { margin-bottom: 4px; font-size: 14px; }
.email-item p { font-size: 13px; color: #6e6e73; margin: 4px 0; }
.status-badge { display: inline-block; padding: 4px 8px; background: #34C759; color: white; border-radius: 4px; font-size: 11px; }
@media print {
.btn, .no-print, .upload-area { display: none !important; }
.card { box-shadow: none; border: 1px solid #ddd; }
}
</style>
</head>
<body>
<header>
<h1>📊 МАЦ — Маркетинговый анализ цен</h1>
<p>Форма по Приложению 2 (Самрук-Қазына)</p>
</header>
<div class="container">
<div class="no-print" style="text-align: right; margin-bottom: 20px;">
<button class="btn btn-primary" onclick="window.print()">🖨 Печать / PDF</button>
<button class="btn btn-success" onclick="generateDocx()">📄 Сформировать DOCX</button>
<button class="btn btn-outline" onclick="sendEmails()">✉️ Отправить запросы КП</button>
</div>
<!-- Раздел 1: Входные данные -->
<div class="card">
<h2 class="section-title"><span class="section-num">1</span>Входные данные</h2>
<form id="inputDataForm">
<div class="form-row">
<div class="form-group">
<label>Код ЕНС ТРУ</label>
<input type="text" name="ensCode" placeholder="XX.XX.XX-XXX.X">
</div>
<div class="form-group">
<label>Объем</label>
<input type="text" name="volume" placeholder="с указанием единицы измерения">
</div>
</div>
<div class="form-group">
<label>Место поставки</label>
<input type="text" name="deliveryPlace" placeholder="адрес доставки">
</div>
<div class="form-group">
<label>Техническая спецификация</label>
<textarea name="techSpec" placeholder="описание товара/работы/услуги"></textarea>
</div>
<div class="form-group">
<label>📎 Прикрепить файлы (ТЗ, спецификации, письма)</label>
<div class="upload-area" id="uploadArea1" onclick="document.getElementById('fileInput1').click()">
<div class="upload-icon">📎</div>
<div>Нажмите для загрузки или перетащите файлы сюда</div>
<div style="font-size: 12px; color: #86868b; margin-top: 8px;">DOCX, XLSX, PDF до 10 МБ</div>
</div>
<input type="file" id="fileInput1" multiple accept=".doc,.docx,.xls,.xlsx,.pdf" style="display: none;" onchange="handleFiles(this.files, 'Входные данные')">
</div>
<div class="file-list" id="filesSection1"></div>
<button type="submit" class="btn btn-primary">Сохранить</button>
</form>
<div id="inputDataDisplay" style="margin-top: 20px; padding: 16px; background: #f9f9fb; border-radius: 10px; display: none;"></div>
</div>
<!-- Раздел 2: Список рассылки -->
<div class="card">
<h2 class="section-title"><span class="section-num">2</span>Список рассылки и поставщики</h2>
<div class="no-print" style="margin-bottom: 16px; padding: 16px; background: #fff3cd; border-radius: 10px;">
<strong>✉️ Авто-рассылка:</strong> укажите email в контактах поставщика (например: info@company.kz) → нажмите «Отправить запросы КП» → письма сформируются для всех поставщиков.
</div>
<form id="supplierForm" style="margin-bottom: 20px;">
<div class="form-row">
<div class="form-group">
<label>Наименование компании</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>Резидент/нерезидент</label>
<select name="resident" required>
<option value="Резидент">Резидент РК</option>
<option value="Нерезидент">Нерезидент</option>
</select>
</div>
<div class="form-group">
<label>Статус</label>
<select name="status" required>
<option value="Производитель">Производитель</option>
<option value="Дилер">Дилер</option>
<option value="Поставщик">Поставщик</option>
<option value="ТПФ">ТПФ</option>
<option value="Другое">Другое</option>
</select>
</div>
</div>
<div class="form-group">
<label>Контактные данные (веб-сайт, телефон, адрес, <strong>email</strong>)</label>
<input type="text" name="contacts" placeholder="info@company.kz, +7 XXX XXX-XX-XX, example.kz">
</div>
<div class="form-group">
<label>📎 Прикрепить файлы (КП, уставные документы)</label>
<div class="upload-area" onclick="document.getElementById('fileInput2').click()">
<div class="upload-icon">📎</div>
<div>Нажмите для загрузки файлов</div>
</div>
<input type="file" id="fileInput2" multiple accept=".doc,.docx,.xls,.xlsx,.pdf" style="display: none;" onchange="handleFiles(this.files, 'Поставщики')">
</div>
<div class="file-list" id="filesSection2"></div>
<button type="submit" class="btn btn-success">Добавить поставщика</button>
</form>
<table>
<thead>
<tr><th>Компания</th><th>Резидентство</th><th>Статус</th><th>Контакты</th><th class="no-print">Действия</th></tr>
</thead>
<tbody id="suppliersTable"></tbody>
</table>
<div id="suppliersEmpty" class="empty">Нет поставщиков</div>
</div>
<!-- Раздел 3: Ценовые предложения -->
<div class="card">
<h2 class="section-title"><span class="section-num">3</span>Таблица полученных ценовых/коммерческих предложений</h2>
<form id="offerForm" style="margin-bottom: 20px;">
<div class="form-row">
<div class="form-group">
<label>Поставщик</label>
<select name="supplierId" id="supplierSelect" required></select>
</div>
<div class="form-group">
<label>Номер запроса</label>
<input type="text" name="requestNumber" placeholder="№ письма/запроса">
</div>
<div class="form-group">
<label>Цена за ед. (тенге, без НДС)</label>
<input type="number" name="price" required placeholder="15000">
</div>
<div class="form-group">
<label>Дата получения</label>
<input type="date" name="date">
</div>
</div>
<div class="form-group">
<label>📎 Прикрепить файлы (КП, письма)</label>
<div class="upload-area" onclick="document.getElementById('fileInput3').click()">
<div class="upload-icon">📎</div>
<div>Нажмите для загрузки файлов</div>
</div>
<input type="file" id="fileInput3" multiple accept=".doc,.docx,.xls,.xlsx,.pdf" style="display: none;" onchange="handleFiles(this.files, 'Предложения')">
</div>
<div class="file-list" id="filesSection3"></div>
<button type="submit" class="btn btn-success">Добавить предложение</button>
</form>
<table>
<thead>
<tr><th>Поставщик</th><th>№ запроса</th><th>Цена (тенге)</th><th>Дата</th><th class="no-print">Действия</th></tr>
</thead>
<tbody id="offersTable"></tbody>
</table>
<div id="offersEmpty" class="empty">Нет предложений</div>
</div>
<!-- Раздел 4: Проверка по статье 31 -->
<div class="card">
<h2 class="section-title"><span class="section-num">4</span>Заключение по проверке (ст. 31 Порядка закупок)</h2>
<div class="checkbox-group">
<input type="checkbox" id="check1">
<label for="check1">Потенциальный поставщик или его субподрядчик НЕ состоит в реестре недобросовестных участников</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="check2">
<label for="check2">НЕ включены в перечень организаций, связанных с финансированием терроризма</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="check3">
<label for="check3">НЕ является юридическим лицом из государства с льготным налогообложением</label>
</div>
<button class="btn btn-primary no-print" onclick="saveChecks()">Сохранить проверку</button>
</div>
<!-- Раздел 5: Расчеты и анализ -->
<div class="card">
<h2 class="section-title"><span class="section-num">5</span>Расчеты и анализ</h2>
<div class="stats">
<div class="stat">
<div class="stat-value" id="statSuppliers">0</div>
<div class="stat-label">Поставщиков</div>
</div>
<div class="stat">
<div class="stat-value" id="statOffers">0</div>
<div class="stat-label">Предложений</div>
</div>
<div class="stat">
<div class="stat-value" id="statMinPrice">0</div>
<div class="stat-label">Мин. цена</div>
</div>
<div class="stat">
<div class="stat-value" id="statMaxPrice">0</div>
<div class="stat-label">Макс. цена</div>
</div>
<div class="stat">
<div class="stat-value" id="statAvgPrice">0</div>
<div class="stat-label">Средняя цена</div>
</div>
</div>
<table>
<thead>
<tr><th>Поставщик</th><th>Цена за ед. (тенге)</th><th>Маркетинговая цена</th><th>Отклонение</th></tr>
</thead>
<tbody id="analysisTable"></tbody>
</table>
</div>
<!-- Раздел 6: Заключение -->
<div class="card">
<h2 class="section-title"><span class="section-num">6</span>Заключение с выводами и рекомендациями</h2>
<div class="conclusion-box">
<p id="conclusionText" style="white-space: pre-line; line-height: 1.8;"></p>
<p style="margin-top: 20px; font-style: italic; color: #6e6e73;">На основании цен потенциальных поставщиков/производителей определены маркетинговые цены без учета НДС (КП представлены).</p>
</div>
<div class="signature-block">
<div>
<p><strong>Инициатор потребности:</strong></p>
<div class="signature-line">__________________ / _________________</div>
<p style="font-size: 13px; color: #6e6e73; margin-top: 8px;">(подпись) (ФИО)</p>
</div>
<div>
<p><strong>Исполнитель:</strong></p>
<div class="signature-line">__________________ / _________________</div>
<p style="font-size: 13px; color: #6e6e73; margin-top: 8px;">(подпись) (ФИО)</p>
</div>
</div>
<div style="margin-top: 40px;">
<p><strong>Утверждено:</strong></p>
<div class="signature-line">__________________ / _________________</div>
<p style="font-size: 13px; color: #6e6e73; margin-top: 8px;">(подпись) (ФИО)</p>
<p style="margin-top: 16px;">«___» __________ 2026 г.</p>
</div>
</div>
<!-- Журнал рассылки -->
<div class="card no-print">
<h2 class="section-title">✉️ Журнал отправки запросов</h2>
<div id="emailLog" class="email-log">
<p style="color: #86868b;">Запросы ещё не отправлялись</p>
</div>
</div>
<!-- Все файлы -->
<div class="card">
<h2 class="section-title">📎 Все файлы</h2>
<div class="file-list" id="allFilesList"></div>
</div>
</div>
<script>
let data = { inputData: null, suppliers: [], offers: [], checks: { c1: false, c2: false, c3: false }, files: [], emails: [] };
async function loadData() {
const r = await fetch('/api/data');
data = await r.json();
if (data.inputData) displayInputData();
renderSuppliers();
renderOffers();
renderAnalysis();
updateSupplierSelect();
document.getElementById('check1').checked = data.checks.c1;
document.getElementById('check2').checked = data.checks.c2;
document.getElementById('check3').checked = data.checks.c3;
renderFiles();
renderEmails();
generateConclusion();
}
function displayInputData() {
const d = data.inputData;
document.getElementById('inputDataDisplay').innerHTML =
'<strong>Код ЕНС:</strong> ' + (d.ensCode || '—') +
' | <strong>Объем:</strong> ' + (d.volume || '—') +
' | <strong>Место поставки:</strong> ' + (d.deliveryPlace || '—') +
(d.techSpec ? '<br><strong>Спецификация:</strong> ' + d.techSpec : '');
document.getElementById('inputDataDisplay').style.display = 'block';
}
function renderSuppliers() {
const tbody = document.getElementById('suppliersTable');
const empty = document.getElementById('suppliersEmpty');
if (data.suppliers.length === 0) { empty.style.display = 'block'; tbody.innerHTML = ''; return; }
empty.style.display = 'none';
tbody.innerHTML = data.suppliers.map(s =>
'<tr><td>' + s.name + '</td><td>' + s.resident + '</td><td>' + s.status + '</td><td>' + (s.contacts || '—') +
'</td><td class="no-print"><button class="btn btn-danger" onclick="deleteSupplier(' + s.id + ')">Удалить</button></td></tr>'
).join('');
}
function renderOffers() {
const tbody = document.getElementById('offersTable');
const empty = document.getElementById('offersEmpty');
if (data.offers.length === 0) { empty.style.display = 'block'; tbody.innerHTML = ''; return; }
empty.style.display = 'none';
tbody.innerHTML = data.offers.map(o => {
const s = data.suppliers.find(x => x.id === o.supplierId);
return '<tr><td>' + (s ? s.name : '—') + '</td><td>' + (o.requestNumber || '—') +
'</td><td>' + Number(o.price).toLocaleString() + '</td><td>' + (o.date || '—') +
'</td><td class="no-print"><button class="btn btn-danger" onclick="deleteOffer(' + o.id + ')">Удалить</button></td></tr>';
}).join('');
}
function renderAnalysis() {
const prices = data.offers.map(o => Number(o.price));
const min = prices.length ? Math.min(...prices) : 0;
const max = prices.length ? Math.max(...prices) : 0;
const avg = prices.length ? Math.round(prices.reduce((a,b) => a+b, 0) / prices.length) : 0;
document.getElementById('statSuppliers').textContent = data.suppliers.length;
document.getElementById('statOffers').textContent = data.offers.length;
document.getElementById('statMinPrice').textContent = min.toLocaleString() + ' ₸';
document.getElementById('statMaxPrice').textContent = max.toLocaleString() + ' ₸';
document.getElementById('statAvgPrice').textContent = avg.toLocaleString() + ' ₸';
const tbody = document.getElementById('analysisTable');
tbody.innerHTML = data.offers.map(o => {
const s = data.suppliers.find(x => x.id === o.supplierId);
const p = Number(o.price);
const dev = min > 0 ? ((p - min) / min * 100).toFixed(1) : 0;
const isMin = p === min && min > 0;
return '<tr' + (isMin ? ' class="price-highlight"' : '') +
'><td>' + (s ? s.name : '—') + '</td><td>' + p.toLocaleString() +
'</td><td>' + (isMin ? min.toLocaleString() + ' ✓' : '—') +
'</td><td>' + (dev > 0 ? '+' + dev + '%' : '0%') + '</td></tr>';
}).join('');
generateConclusion(min, avg);
}
function renderFiles() {
const allFiles = document.getElementById('allFilesList');
const section1 = document.getElementById('filesSection1');
const section2 = document.getElementById('filesSection2');
const section3 = document.getElementById('filesSection3');
const inputFiles = data.files.filter(f => f.section === 'Входные данные');
const supplierFiles = data.files.filter(f => f.section === 'Поставщики');
const offerFiles = data.files.filter(f => f.section === 'Предложения');
const outputFiles = data.files.filter(f => f.section === 'Выходные документы');
function renderFileList(files, container) {
if (files.length === 0) { container.innerHTML = ''; return; }
container.innerHTML = files.map(f =>
'<div class="file-item">' +
'<a href="' + f.path + '" download>📄 ' + f.name + '</a>' +
'<span class="file-meta">' + new Date(f.date).toLocaleDateString() + '</span>' +
'<button class="btn btn-danger no-print" onclick="deleteFile(\'' + f.path.split('/').pop() + '\')">✕</button>' +
'</div>'
).join('');
}
renderFileList(inputFiles, section1);
renderFileList(supplierFiles, section2);
renderFileList(offerFiles, section3);
renderFileList(outputFiles, allFiles);
}
function renderEmails() {
const log = document.getElementById('emailLog');
if (!data.emails || data.emails.length === 0) {
log.innerHTML = '<p style="color: #86868b;">Запросы ещё не отправлялись</p>';
return;
}
log.innerHTML = data.emails.slice().reverse().map(e =>
'<div class="email-item">' +
'<h4>✉️ ' + e.supplierName + ' <span class="status-badge">' + e.status + '</span></h4>' +
'<p><strong>Кому:</strong> ' + e.to + '</p>' +
'<p><strong>Тема:</strong> ' + e.subject + '</p>' +
'<p><strong>Дата:</strong> ' + new Date(e.date).toLocaleString() + '</p>' +
'</div>'
).join('');
}
async function handleFiles(files, section) {
if (!files.length) return;
const formData = new FormData();
for (const f of files) {
formData.append('file', f);
}
formData.append('section', section);
const r = await fetch('/api/upload', { method: 'POST', body: formData });
const result = await r.json();
if (result.ok) {
loadData();
}
}
async function deleteFile(fileName) {
if (!confirm('Удалить файл?')) return;
await fetch('/api/files/' + fileName, { method: 'DELETE' });
loadData();
}
async function generateDocx() {
const btn = event.target;
btn.disabled = true;
btn.textContent = 'Формирование...';
try {
const r = await fetch('/api/generate-docx', { method: 'POST' });
const result = await r.json();
if (result.ok) {
window.location.href = result.file.path;
loadData();
} else {
alert('Ошибка: ' + result.error);
}
} catch (e) {
alert('Ошибка формирования: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = '📄 Сформировать DOCX';
}
}
async function sendEmails() {
if (data.suppliers.length === 0) {
alert('Сначала добавьте поставщиков в раздел 2');
return;
}
const deadline = prompt('Укажите срок ответа (например: до 25.09.2026):', 'до ' + new Date(Date.now() + 7*24*60*60*1000).toLocaleDateString('ru-RU'));
if (!deadline) return;
const btn = event.target;
btn.disabled = true;
btn.textContent = 'Отправка...';
try {
const r = await fetch('/api/send-emails', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deadline })
});
const result = await r.json();
if (result.ok) {
alert('Запросы сформированы и отправлены ' + result.count + ' поставщикам!\n\n(В реальной системе письма уйдут на email из контактов. Сейчас — симуляция с записью в журнал.)');
loadData();
} else {
alert('Ошибка: ' + result.error);
}
} catch (e) {
alert('Ошибка отправки: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = '✉️ Отправить запросы КП';
}
}
function generateConclusion(min, avg) {
const minOffer = data.offers.find(o => Number(o.price) === min);
const s = minOffer ? data.suppliers.find(x => x.id === minOffer.supplierId) : null;
const allChecked = data.checks.c1 && data.checks.c2 && data.checks.c3;
let text = 'На основании проведенного маркетингового анализа цен:\n\n';
text += '1. Запрошено коммерческих предложений: ' + data.offers.length + ' шт.\n';
text += '2. Количество потенциальных поставщиков: ' + data.suppliers.length + ' ед.\n';
text += '3. Диапазон цен: от ' + (min || 0).toLocaleString() + ' до ' + (avg ? Math.max(...data.offers.map(o => Number(o.price))) : 0).toLocaleString() + ' тенге без НДС.\n';
text += '4. Средняя рыночная цена: ' + (avg || 0).toLocaleString() + ' тенге без НДС.\n\n';
if (minOffer && s) {
text += 'РЕКОМЕНДАЦИЯ: Наиболее выгодное предложение от ' + s.name + ' (' + s.status.toLowerCase() + ', ' + s.resident.toLowerCase() + ') по цене ' + min.toLocaleString() + ' тенге.\n\n';
}
if (allChecked) {
text += 'ПРОВЕРКА ПО СТ. 31: Все пункты подтверждены — поставщик соответствует требованиям Порядка закупок.\n';
} else {
text += 'ПРОВЕРКА ПО СТ. 31: Требуется проверка отдельных пунктов.\n';
}
document.getElementById('conclusionText').textContent = text;
}
function updateSupplierSelect() {
document.getElementById('supplierSelect').innerHTML = '<option value="">Выберите поставщика</option>' +
data.suppliers.map(s => '<option value="' + s.id + '">' + s.name + ' (' + s.status + ')</option>').join('');
}
document.getElementById('inputDataForm').addEventListener('submit', async (e) => {
e.preventDefault();
const f = e.target;
await fetch('/api/inputData', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ensCode: f.ensCode.value, volume: f.volume.value, deliveryPlace: f.deliveryPlace.value, techSpec: f.techSpec.value })
});
loadData();
});
document.getElementById('supplierForm').addEventListener('submit', async (e) => {
e.preventDefault();
const f = e.target;
await fetch('/api/suppliers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: f.name.value, resident: f.resident.value, status: f.status.value, contacts: f.contacts.value })
});
f.reset();
loadData();
});
document.getElementById('offerForm').addEventListener('submit', async (e) => {
e.preventDefault();
const f = e.target;
await fetch('/api/offers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ supplierId: Number(f.supplierId.value), requestNumber: f.requestNumber.value, price: Number(f.price.value), date: f.date.value })
});
f.reset();
loadData();
});
async function deleteSupplier(id) {
if (!confirm('Удалить поставщика и все его предложения?')) return;
await fetch('/api/suppliers/' + id, { method: 'DELETE' });
loadData();
}
async function deleteOffer(id) {
if (!confirm('Удалить предложение?')) return;
await fetch('/api/offers/' + id, { method: 'DELETE' });
loadData();
}
async function saveChecks() {
await fetch('/api/checks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
c1: document.getElementById('check1').checked,
c2: document.getElementById('check2').checked,
c3: document.getElementById('check3').checked
})
});
loadData();
alert('Проверка сохранена');
}
const uploadAreas = document.querySelectorAll('.upload-area');
uploadAreas.forEach(area => {
area.addEventListener('dragover', (e) => {
e.preventDefault();
area.classList.add('dragover');
});
area.addEventListener('dragleave', () => {
area.classList.remove('dragover');
});
area.addEventListener('drop', (e) => {
e.preventDefault();
area.classList.remove('dragover');
const input = area.parentElement.querySelector('input[type="file"]');
if (input) {
input.files = e.dataTransfer.files;
const event = new Event('change');
input.dispatchEvent(event);
}
});
});
loadData();
</script>
</body>
</html>