v1.0.178: Фаза 1.1 — statusToHTML. Статусы из HTML-строк в структуры {kind,pct,text,count,elapsed}. Чистая функция statusToHTML(st). renderTable использует statusToHTML(f.status).
This commit is contained in:
+37
-14
@@ -72,6 +72,29 @@ function resetStepper(fromId) {
|
||||
// Утилиты (formatSize, formatDate, moveUp, moveDown, escHtml, fmtDate) —
|
||||
// вынесены в app_utils.js для облегчения анализа и отладки.
|
||||
|
||||
/**
|
||||
* statusToHTML(status) — Чистая функция: структура → HTML (Фаза 1).
|
||||
*
|
||||
* Вход: { kind, pct?, text?, count?, elapsed? } — ни одного HTML-тега.
|
||||
* kind = 'uploading' | 'uploaded' | 'unzipping' | 'parsing' | 'parsed' | 'error' | ''
|
||||
* Выход: безопасная HTML-строка (статусы не содержат пользовательских данных).
|
||||
*
|
||||
* ПАТТЕРН (decoupling-final-plan.md): отделяем данные от представления.
|
||||
* state хранит чистые данные, statusToHTML — чистая функция рендеринга.
|
||||
*/
|
||||
function statusToHTML(st) {
|
||||
if (!st || !st.kind) return '';
|
||||
switch (st.kind) {
|
||||
case 'uploading': return '↑ ' + (st.pct || 0) + '%';
|
||||
case 'uploaded': return '<span class="status-ok">✓</span>';
|
||||
case 'unzipping': return '⏳ распаковка...';
|
||||
case 'parsing': return '⏳ парсинг...';
|
||||
case 'parsed': return '<span class="status-ok">✓ ' + (st.count || 0) + ' эл.' + (st.elapsed ? ' (' + st.elapsed + 'с)' : '') + '</span>';
|
||||
case 'error': return '<span class="status-err">✗ ' + (st.text || 'Неизвестная ошибка') + '</span>';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
if (state.files.length === 0) {
|
||||
fileTable.innerHTML = '<tr class="empty-row"><td colspan="7">Нет файлов — выберите .docx / .pdf</td></tr>';
|
||||
@@ -83,7 +106,7 @@ function renderTable() {
|
||||
escHtml(f.name) + '</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="status-cell">' + (f.status || '') + '</td>' +
|
||||
'<td class="status-cell">' + statusToHTML(f.status) + '</td>' +
|
||||
'<td><button class="info-btn' + (f.doc_id ? ' visible' : '') + '" onclick="showText(' + i + ')" title="Текст / Распарсено"><i data-lucide="file-text" style="width:16px;height:16px;"></i></button></td>' +
|
||||
'<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить"><i data-lucide="trash-2" style="width:16px;height:16px;"></i></button></td>' +
|
||||
'</tr>' +
|
||||
@@ -182,7 +205,7 @@ fileInput.addEventListener('change', async function() {
|
||||
|
||||
if (isZip) {
|
||||
// ZIP: показать временную строку, распаковать, убрать
|
||||
var zipEntry = { name: f.name, lastModified: f.lastModified, size: f.size, status: '⏳ распаковка...' };
|
||||
var zipEntry = { name: f.name, lastModified: f.lastModified, size: f.size, status: { kind: 'unzipping' } };
|
||||
state.files.push(zipEntry);
|
||||
var zipIdx = state.files.length - 1;
|
||||
render(state);
|
||||
@@ -220,7 +243,7 @@ fileInput.addEventListener('change', async function() {
|
||||
var zEntry = {
|
||||
name: zf.filename, size: zf.size,
|
||||
doc_id: zf.doc_id, uploaded: true,
|
||||
status: '⏳ парсинг...'
|
||||
status: { kind: 'parsing' }
|
||||
};
|
||||
state.files.push(zEntry);
|
||||
var zIdx = state.files.length - 1;
|
||||
@@ -231,23 +254,23 @@ fileInput.addEventListener('change', async function() {
|
||||
var zpr = zf.parsed;
|
||||
if (zpr && zpr.status === 'parsed') {
|
||||
state.files[zIdx].parsed = true;
|
||||
state.files[zIdx].status = '<span class="status-ok">✓ ' + zpr.element_count + ' эл.</span>';
|
||||
state.files[zIdx].status = { kind: 'parsed', count: zpr.element_count };
|
||||
} else if (zpr && zpr.status === 'error') {
|
||||
state.files[zIdx].status = '<span class="status-err">✗ ' + (zpr.error || 'ошибка парсинга') + '</span>';
|
||||
state.files[zIdx].status = { kind: 'error', text: zpr.error || 'ошибка парсинга' };
|
||||
} else {
|
||||
state.files[zIdx].status = '<span class="status-err">✗ Неизвестная ошибка</span>';
|
||||
state.files[zIdx].status = { kind: 'error', text: 'Неизвестная ошибка' };
|
||||
}
|
||||
render(state);
|
||||
}
|
||||
} catch(ze) {
|
||||
state.files[zipIdx].status = '<span class="status-err">✗ ZIP: ' + ze.message + '</span>';
|
||||
state.files[zipIdx].status = { kind: 'error', text: 'ZIP: ' + ze.message };
|
||||
render(state);
|
||||
}
|
||||
continue; // ZIP обработан, переходим к следующему файлу
|
||||
}
|
||||
|
||||
// Обычный файл (не ZIP)
|
||||
var entry = { name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: '↑ 0%' };
|
||||
var entry = { name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'uploading', pct: 0 } };
|
||||
var existingIdx = -1;
|
||||
for (var j = 0; j < state.files.length; j++) {
|
||||
if (state.files[j].name === f.name) { existingIdx = j; break; }
|
||||
@@ -264,13 +287,13 @@ fileInput.addEventListener('change', async function() {
|
||||
|
||||
try {
|
||||
var resp = await uploadFile(f, function(pct) {
|
||||
state.files[rowIdx].status = '↑ ' + pct + '%';
|
||||
state.files[rowIdx].status = { kind: 'uploading', pct: pct };
|
||||
render(state);
|
||||
});
|
||||
// Python API returns lowercase keys (doc_id, contract_id, parsed)
|
||||
if (resp && resp.contract_id) state.contractId = resp.contract_id;
|
||||
state.files[rowIdx].doc_id = resp.doc_id;
|
||||
state.files[rowIdx].status = '<span class="status-ok">✓</span>';
|
||||
state.files[rowIdx].status = { kind: 'uploaded' };
|
||||
state.files[rowIdx].uploaded = true;
|
||||
|
||||
// Авто-парсинг: VM Python парсит ВСЁ (PDF + DOCX + DOC)
|
||||
@@ -280,15 +303,15 @@ fileInput.addEventListener('change', async function() {
|
||||
var elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
||||
state.files[rowIdx].parsed = true;
|
||||
state.files[rowIdx].parseInfo = pr;
|
||||
state.files[rowIdx].status = '<span class="status-ok">✓ ' + pr.element_count + ' эл. (' + elapsed + 'с)</span>';
|
||||
state.files[rowIdx].status = { kind: 'parsed', count: pr.element_count, elapsed: elapsed };
|
||||
} else if (pr && pr.status === 'error') {
|
||||
state.files[rowIdx].status = '<span class="status-err">✗ ' + (pr.error || 'ошибка парсинга') + '</span>';
|
||||
state.files[rowIdx].status = { kind: 'error', text: pr.error || 'ошибка парсинга' };
|
||||
state.files[rowIdx].parseInfo = pr;
|
||||
} else {
|
||||
state.files[rowIdx].status = '<span class="status-err">✗ Неизвестная ошибка</span>';
|
||||
state.files[rowIdx].status = { kind: 'error', text: 'Неизвестная ошибка' };
|
||||
}
|
||||
} catch(err) {
|
||||
state.files[rowIdx].status = '<span class="status-err">✗ ' + err.message + '</span>';
|
||||
state.files[rowIdx].status = { kind: 'error', text: err.message };
|
||||
}
|
||||
render(state);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user