ℹ модальное окно с деталями парсинга (параграфы, таблицы, ошибки, превью), v1.10
This commit is contained in:
+19
-2
@@ -147,6 +147,23 @@ class ContractsApp:
|
|||||||
|
|
||||||
supp_rows = [dict(zip(supps["columns"], r)) for r in supps["rows"]]
|
supp_rows = [dict(zip(supps["columns"], r)) for r in supps["rows"]]
|
||||||
|
|
||||||
|
def _file_done(name, elapsed, elements, errors, text):
|
||||||
|
paragraphs = sum(1 for e in elements if e.get("type") == "paragraph")
|
||||||
|
tables = sum(1 for e in elements if e.get("type") == "table")
|
||||||
|
table_rows = sum(len(e.get("rows", [])) for e in elements if e.get("type") == "table")
|
||||||
|
return {
|
||||||
|
"type": "file_done",
|
||||||
|
"name": name,
|
||||||
|
"time_s": elapsed,
|
||||||
|
"elements": len(elements),
|
||||||
|
"paragraphs": paragraphs,
|
||||||
|
"tables": tables,
|
||||||
|
"table_rows": table_rows,
|
||||||
|
"errors": errors,
|
||||||
|
"text_len": len(text) if text else 0,
|
||||||
|
"text_preview": text[:200] if text else "",
|
||||||
|
}
|
||||||
|
|
||||||
for s in supp_rows:
|
for s in supp_rows:
|
||||||
# Пропустить уже распарсенные
|
# Пропустить уже распарсенные
|
||||||
existing, _ = db.query(
|
existing, _ = db.query(
|
||||||
@@ -223,7 +240,7 @@ class ContractsApp:
|
|||||||
)
|
)
|
||||||
zelapsed = round(time_mod.time() - zf_start, 2)
|
zelapsed = round(time_mod.time() - zf_start, 2)
|
||||||
files_processed += 1
|
files_processed += 1
|
||||||
yield f"data: {json.dumps({'type': 'file_done', 'name': zname, 'time_s': zelapsed, 'elements': len(zelements)})}\n\n"
|
yield f"data: {json.dumps(_file_done(zname, zelapsed, zelements, zpr.get('errors',[]), ztext))}\n\n"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
yield f"data: {json.dumps({'type': 'file_error', 'name': zname, 'error': str(e)})}\n\n"
|
yield f"data: {json.dumps({'type': 'file_error', 'name': zname, 'error': str(e)})}\n\n"
|
||||||
|
|
||||||
@@ -244,7 +261,7 @@ class ContractsApp:
|
|||||||
)
|
)
|
||||||
elapsed = round(time_mod.time() - file_start, 2)
|
elapsed = round(time_mod.time() - file_start, 2)
|
||||||
files_processed += 1
|
files_processed += 1
|
||||||
yield f"data: {json.dumps({'type': 'file_done', 'name': s['filename'], 'time_s': elapsed, 'elements': len(elements)})}\n\n"
|
yield f"data: {json.dumps(_file_done(s['filename'], elapsed, elements, pr.get('errors',[]), text))}\n\n"
|
||||||
|
|
||||||
total_time = round(time_mod.time() - start_time, 2)
|
total_time = round(time_mod.time() - start_time, 2)
|
||||||
yield f"data: {json.dumps({'type': 'summary', 'total_time_s': total_time, 'total_bytes': total_bytes, 'files_processed': files_processed})}\n\n"
|
yield f"data: {json.dumps({'type': 'summary', 'total_time_s': total_time, 'total_bytes': total_bytes, 'files_processed': files_processed})}\n\n"
|
||||||
|
|||||||
@@ -42,12 +42,25 @@
|
|||||||
.empty-row td { color: var(--muted); text-align: center; padding: 24px; }
|
.empty-row td { color: var(--muted); text-align: center; padding: 24px; }
|
||||||
.remove-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 16px; line-height: 1; }
|
.remove-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 16px; line-height: 1; }
|
||||||
.remove-btn:hover { color: var(--destructive); }
|
.remove-btn:hover { color: var(--destructive); }
|
||||||
|
.info-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 14px; display: none; }
|
||||||
|
.info-btn:hover { color: var(--brand-primary); }
|
||||||
|
.info-btn.visible { display: inline; }
|
||||||
|
/* Modal */
|
||||||
|
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.4); z-index: 100; justify-content: center; align-items: center; }
|
||||||
|
.modal-overlay.open { display: flex; }
|
||||||
|
.modal { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 4px 24px rgba(0,0,0,.15); max-width: 520px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
||||||
|
.modal-header { padding: 14px 16px; border-bottom: 1px solid var(--brand-gray); display: flex; align-items: center; gap: 8px; font-weight: 600; }
|
||||||
|
.modal-body { padding: 16px; }
|
||||||
|
.modal-body .kv { display: flex; margin-bottom: 6px; font-size: 13px; }
|
||||||
|
.modal-body .kv .k { color: var(--muted); width: 110px; flex-shrink: 0; }
|
||||||
|
.modal-body .kv .v { word-break: break-all; }
|
||||||
|
.modal-body pre { background: var(--brand-grey-light); padding: 10px; border-radius: 6px; font-size: 12px; max-height: 200px; overflow-y: auto; white-space: pre-wrap; margin-top: 4px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
||||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.9</span></span>
|
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.10</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -62,7 +75,7 @@
|
|||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Имя</th><th style="width:130px;">Изменён</th><th style="width:90px;">Размер</th><th style="width:115px;">Статус</th><th style="width:30px;"></th></tr>
|
<tr><th>Имя</th><th style="width:130px;">Изменён</th><th style="width:90px;">Размер</th><th style="width:115px;">Статус</th><th style="width:55px;"></th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="fileTable"></tbody>
|
<tbody id="fileTable"></tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -75,6 +88,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Модальное окно Инфо -->
|
||||||
|
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
|
||||||
|
<div class="modal" onclick="event.stopPropagation()">
|
||||||
|
<div class="modal-header">
|
||||||
|
<i data-lucide="file-text" style="width:18px;height:18px;"></i>
|
||||||
|
<span id="modalTitle">Информация о файле</span>
|
||||||
|
<button class="remove-btn" onclick="closeModal()" style="margin-left:auto;font-size:18px;">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modalBody"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
|
|
||||||
@@ -110,7 +135,8 @@
|
|||||||
'<td style="font-size:12px;color:var(--muted);">' + formatDate(f.lastModified) + '</td>' +
|
'<td style="font-size:12px;color:var(--muted);">' + formatDate(f.lastModified) + '</td>' +
|
||||||
'<td class="num-cell" style="font-size:12px;color:var(--muted);">' + formatSize(f.size) + '</td>' +
|
'<td class="num-cell" style="font-size:12px;color:var(--muted);">' + formatSize(f.size) + '</td>' +
|
||||||
'<td class="status-cell"></td>' +
|
'<td class="status-cell"></td>' +
|
||||||
'<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить">×</button></td>' +
|
'<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить">×</button>' +
|
||||||
|
'<button class="info-btn" id="info_' + i + '" onclick="showInfo(' + i + ')" title="Инфо">ℹ</button></td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
@@ -290,6 +316,13 @@
|
|||||||
var row = findRowByName(d.name);
|
var row = findRowByName(d.name);
|
||||||
if (row) {
|
if (row) {
|
||||||
row.querySelector('.status-cell').innerHTML = '<span class="status-ok">✓ ' + d.time_s + 'с</span>';
|
row.querySelector('.status-cell').innerHTML = '<span class="status-ok">✓ ' + d.time_s + 'с</span>';
|
||||||
|
// Сохранить детали в fileQueue и показать ℹ
|
||||||
|
var idx = findIndexByName(d.name);
|
||||||
|
if (idx >= 0) {
|
||||||
|
fileQueue[idx].parseInfo = d;
|
||||||
|
var infoBtn = document.getElementById('info_' + idx);
|
||||||
|
if (infoBtn) infoBtn.classList.add('visible');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,8 +380,63 @@
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findIndexByName(name) {
|
||||||
|
for (var i = 0; i < fileQueue.length; i++) {
|
||||||
|
if (fileQueue[i].name === name) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Модальное окно ──────────────────────────────────────────
|
||||||
|
|
||||||
|
function showInfo(index) {
|
||||||
|
var f = fileQueue[index];
|
||||||
|
if (!f || !f.parseInfo) return;
|
||||||
|
var d = f.parseInfo;
|
||||||
|
|
||||||
|
document.getElementById('modalTitle').textContent = f.name;
|
||||||
|
|
||||||
|
var rows = [
|
||||||
|
['Тип', f.file ? f.file.type : (f.name.split('.').pop() || '—')],
|
||||||
|
['Размер', formatSize(f.size)],
|
||||||
|
['В архиве', f.zipName || '—'],
|
||||||
|
['Статус', 'parsed'],
|
||||||
|
['Время парсинга', d.time_s + 'с'],
|
||||||
|
['Элементов всего', d.elements],
|
||||||
|
['Параграфов', d.paragraphs],
|
||||||
|
['Таблиц', d.tables],
|
||||||
|
['Строк таблиц', d.table_rows],
|
||||||
|
['Текст (символов)', d.text_len],
|
||||||
|
];
|
||||||
|
|
||||||
|
var html = rows.map(function(r) {
|
||||||
|
return '<div class="kv"><span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span></div>';
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
if (d.errors && d.errors.length > 0) {
|
||||||
|
html += '<div style="margin-top:8px;color:var(--destructive);font-weight:600;">Ошибки парсера:</div>';
|
||||||
|
html += '<pre>' + d.errors.join('\n') + '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.text_preview) {
|
||||||
|
html += '<div style="margin-top:10px;font-weight:600;font-size:13px;">Превью текста:</div>';
|
||||||
|
html += '<pre>' + d.text_preview + '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('modalBody').innerHTML = html;
|
||||||
|
document.getElementById('modalOverlay').classList.add('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal(e) {
|
||||||
|
if (e && e.target !== document.getElementById('modalOverlay')) return;
|
||||||
|
document.getElementById('modalOverlay').classList.remove('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showInfo = showInfo;
|
||||||
|
window.closeModal = closeModal;
|
||||||
|
|
||||||
var style = document.createElement('style');
|
var style = document.createElement('style');
|
||||||
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }';
|
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }';
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|||||||
Reference in New Issue
Block a user