207 lines
7.5 KiB
JavaScript
207 lines
7.5 KiB
JavaScript
let data = [];
|
||
let currentFilter = 'all';
|
||
|
||
async function loadData() {
|
||
try {
|
||
const response = await fetch('data.json');
|
||
const json = await response.json();
|
||
data = json.zamечания || [];
|
||
renderTable();
|
||
renderSummary();
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки данных:', error);
|
||
document.getElementById('table-body').innerHTML =
|
||
'<tr><td colspan="6" style="text-align:center;padding:40px;color:#dc3545;">Ошибка загрузки данных</td></tr>';
|
||
}
|
||
}
|
||
|
||
function compareDecisions(zam, sf) {
|
||
if (!zam || !sf) return 'partially';
|
||
|
||
const zamNorm = zam.toLowerCase().trim();
|
||
const sfNorm = sf.toLowerCase().trim();
|
||
|
||
if (zamNorm === sfNorm) return 'fully';
|
||
|
||
const zamWords = zamNorm.split(/[\s,;.]+/).filter(w => w.length > 3);
|
||
const sfWords = sfNorm.split(/[\s,;.]+/).filter(w => w.length > 3);
|
||
|
||
const matchCount = zamWords.filter(w => sfWords.includes(w)).length;
|
||
const matchPercent = matchCount / Math.max(zamWords.length, sfWords.length);
|
||
|
||
if (matchPercent >= 0.8) return 'fully';
|
||
if (matchPercent >= 0.4) return 'partially';
|
||
return 'partially';
|
||
}
|
||
|
||
function findAddedContent(zam, sf) {
|
||
if (!zam || !sf) return [];
|
||
|
||
const zamNorm = zam.toLowerCase();
|
||
const sfNorm = sf.toLowerCase();
|
||
|
||
const zamSentences = zamNorm.split(/[.!?]/).filter(s => s.trim().length > 10);
|
||
const sfSentences = sfNorm.split(/[.!?]/).filter(s => s.trim().length > 10);
|
||
|
||
const added = sfSentences.filter(sfSent =>
|
||
!zamSentences.some(zamSent => {
|
||
const words1 = sfSent.trim().split(/\s+/).filter(w => w.length > 3);
|
||
const words2 = zamSent.trim().split(/\s+/).filter(w => w.length > 3);
|
||
const match = words1.filter(w => words2.includes(w)).length;
|
||
return match / Math.max(words1.length, words2.length) > 0.6;
|
||
})
|
||
);
|
||
|
||
return added.slice(0, 3);
|
||
}
|
||
|
||
function getStatusHtml(status, added) {
|
||
const statusLabels = {
|
||
'fully': 'Полностью учтено',
|
||
'partially': 'Частично учтено',
|
||
'added': 'Добавлено'
|
||
};
|
||
|
||
let html = `<span class="status-badge status-${status}">${statusLabels[status]}</span>`;
|
||
|
||
if (added && added.length > 0) {
|
||
html += `<div class="changes-highlight">
|
||
<strong>Добавлено в Приложении 1_СФ:</strong>
|
||
<ul>${added.map(item => `<li>${item.trim()}.</li>`).join('')}</ul>
|
||
</div>`;
|
||
}
|
||
|
||
return html;
|
||
}
|
||
|
||
function renderTable(filter = 'all') {
|
||
const tbody = document.getElementById('table-body');
|
||
|
||
const filtered = filter === 'all'
|
||
? data
|
||
: data.filter(item => {
|
||
const status = compareDecisions(item.decision_zam, item.decision_sf);
|
||
const added = findAddedContent(item.decision_zam, item.decision_sf);
|
||
|
||
if (filter === 'fully') return status === 'fully';
|
||
if (filter === 'partially') return status === 'partially';
|
||
if (filter === 'added') return added.length > 0;
|
||
return true;
|
||
});
|
||
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;padding:40px;">Нет данных для отображения</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = filtered.map(item => {
|
||
const status = compareDecisions(item.decision_zam, item.decision_sf);
|
||
const added = findAddedContent(item.decision_zam, item.decision_sf);
|
||
|
||
return `
|
||
<tr>
|
||
<td class="col-id">${item.id}</td>
|
||
<td class="col-problem">${item.problem}</td>
|
||
<td class="col-recommendation">${item.recommendation}</td>
|
||
<td class="col-decision-zam">${item.decision_zam}</td>
|
||
<td class="col-decision-sf">${item.decision_sf}</td>
|
||
<td class="col-status">${getStatusHtml(status, added)}</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderSummary() {
|
||
const total = data.length;
|
||
let fullyCount = 0;
|
||
let partiallyCount = 0;
|
||
let addedCount = 0;
|
||
const addedItems = [];
|
||
|
||
data.forEach(item => {
|
||
const status = compareDecisions(item.decision_zam, item.decision_sf);
|
||
const added = findAddedContent(item.decision_zam, item.decision_sf);
|
||
|
||
if (status === 'fully') fullyCount++;
|
||
else partiallyCount++;
|
||
|
||
if (added.length > 0) {
|
||
addedCount++;
|
||
addedItems.push({ id: item.id, added });
|
||
}
|
||
});
|
||
|
||
document.getElementById('summary').innerHTML = `
|
||
<div class="summary-item">
|
||
<strong>${total}</strong>
|
||
<span>Всего замечаний</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<strong style="color:#d4edda">${fullyCount}</strong>
|
||
<span>Полностью учтено</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<strong style="color:#fff3cd">${partiallyCount}</strong>
|
||
<span>Частично учтено</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<strong style="color:#cce5ff">${addedCount}</strong>
|
||
<span>С дополнениями</span>
|
||
</div>
|
||
`;
|
||
|
||
const summaryDetails = document.getElementById('summary-details');
|
||
summaryDetails.innerHTML = `
|
||
<div class="summary-card">
|
||
<h3>📊 Общая статистика</h3>
|
||
<ul>
|
||
<li>Всего замечаний проанализировано: <strong>${total}</strong></li>
|
||
<li>Полностью учтены в Приложении 1_СФ: <strong>${fullyCount}</strong> (${((fullyCount/total)*100).toFixed(1)}%)</li>
|
||
<li>Частично учтены / изменены: <strong>${partiallyCount}</strong> (${((partiallyCount/total)*100).toFixed(1)}%)</li>
|
||
<li>Замечаний с новыми дополнениями: <strong>${addedCount}</strong></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="summary-card">
|
||
<h3>✨ Что добавлено в Приложении 1_СФ</h3>
|
||
${addedItems.length > 0 ? `
|
||
<ul>
|
||
${addedItems.map(item => `
|
||
<li>
|
||
<strong>Замечание ${item.id}:</strong>
|
||
<ul style="margin-top:6px;padding-left:20px;">
|
||
${item.added.map(a => `<li>${a.trim()}.</li>`).join('')}
|
||
</ul>
|
||
</li>
|
||
`).join('')}
|
||
</ul>
|
||
` : '<li>Новых положений не выявлено</li>'}
|
||
</div>
|
||
|
||
<div class="summary-card" style="grid-column:1/-1">
|
||
<h3>📋 Ключевые дополнения в Приложении 1_СФ по сравнению с ПКМ замечаниями</h3>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;">
|
||
${addedItems.length > 0 ? addedItems.map(item => `
|
||
<div style="background:#f8f9fa;padding:16px;border-radius:8px;border-left:3px solid #3498db;">
|
||
<strong style="color:#2c3e50;display:block;margin-bottom:8px;">Замечание ${item.id}</strong>
|
||
<ul style="padding-left:20px;">
|
||
${item.added.map(a => `<li style="margin:6px 0;font-size:13px;">${a.trim()}.</li>`).join('')}
|
||
</ul>
|
||
</div>
|
||
`).join('') : '<p style="grid-column:1/-1;text-align:center;color:#6c757d;padding:20px;">Все решения полностью соответствуют исходным замечаниям</p>'}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
currentFilter = btn.dataset.filter;
|
||
renderTable(currentFilter);
|
||
});
|
||
});
|
||
|
||
document.addEventListener('DOMContentLoaded', loadData);
|