ℹ модальное окно с деталями парсинга (параграфы, таблицы, ошибки, превью), v1.10

This commit is contained in:
2026-06-17 07:19:38 +04:00
parent 4474190cae
commit f627572abb
2 changed files with 110 additions and 5 deletions
+19 -2
View File
@@ -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"
+91 -3
View File
@@ -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);