06/05 Optimize app

This commit is contained in:
2026-06-05 15:23:23 -04:00
parent db44d6057b
commit 9c9aa694c4
9 changed files with 781 additions and 19 deletions
+54 -18
View File
@@ -197,6 +197,17 @@
</div>
</div>
{# Import progress bar (shown only during chunked import) #}
<div id="import-progress-wrap" class="d-none mt-3">
<div class="d-flex justify-content-between align-items-center mb-1" style="font-size:12px;color:var(--muted);">
<span id="import-progress-label">Importing…</span>
<span id="import-progress-pct" class="mono fw-bold">0%</span>
</div>
<div class="progress" style="height:6px;border-radius:3px;">
<div id="import-progress-bar" class="progress-bar bg-success" role="progressbar" style="width:0%;transition:width .3s ease;"></div>
</div>
</div>
{# Parse errors (non-fatal) #}
<div id="parse-warnings" class="alert alert-warning d-none mb-3" style="font-size:12px;">
<strong>Warnings</strong> — these rows were skipped:<br>
@@ -580,31 +591,56 @@
setImportLoading(true);
$('import-error').classList.add('d-none');
fetch('{{ url_for("bank_import.confirm_import") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': CSRF,
},
body: JSON.stringify({ account_id: parseInt(accountId), skip_dupes: skipDupes, rows }),
})
.then(r => r.json())
.then(data => {
const CHUNK = 50;
const useChunks = rows.length > CHUNK;
let totalImported = 0, totalSkipped = 0;
function setProgress(done, total) {
const pct = Math.round(done / total * 100);
$('import-progress-bar').style.width = pct + '%';
$('import-progress-pct').textContent = pct + '%';
$('import-progress-label').textContent = `Importing… ${done} of ${total}`;
}
async function sendChunks() {
if (useChunks) {
$('import-progress-wrap').classList.remove('d-none');
setProgress(0, rows.length);
}
const chunks = [];
for (let i = 0; i < rows.length; i += CHUNK) chunks.push(rows.slice(i, i + CHUNK));
let sent = 0;
for (const chunk of chunks) {
const r = await fetch('{{ url_for("bank_import.confirm_import") }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': CSRF },
body: JSON.stringify({ account_id: parseInt(accountId), skip_dupes: skipDupes, rows: chunk }),
});
const data = await r.json();
if (data.error) throw new Error(data.error);
totalImported += data.imported;
totalSkipped += data.skipped;
sent += chunk.length;
if (useChunks) setProgress(sent, rows.length);
}
}
sendChunks()
.then(() => {
setImportLoading(false);
if (data.error) {
$('import-error').textContent = data.error;
$('import-error').classList.remove('d-none');
return;
}
$('import-progress-wrap').classList.add('d-none');
$('done-title').textContent =
data.imported + ' transaction' + (data.imported !== 1 ? 's' : '') + ' imported successfully';
totalImported + ' transaction' + (totalImported !== 1 ? 's' : '') + ' imported successfully';
$('done-subtitle').textContent =
data.skipped > 0 ? data.skipped + ' duplicate(s) skipped.' : 'All transactions were new.';
totalSkipped > 0 ? totalSkipped + ' duplicate(s) skipped.' : 'All transactions were new.';
showStep('step-done');
})
.catch(err => {
setImportLoading(false);
$('import-error').textContent = 'Request failed: ' + err;
$('import-progress-wrap').classList.add('d-none');
$('import-error').textContent = 'Import failed: ' + err;
$('import-error').classList.remove('d-none');
});
});