06/05 Optimize app

This commit is contained in:
2026-06-05 17:50:46 -04:00
parent 025f3f8823
commit e4007348f8
6 changed files with 497 additions and 32 deletions
+41
View File
@@ -120,6 +120,13 @@
{{ form.notes(class="form-control", rows=2, placeholder="Optional notes", id="field_notes") }}
</div>
<!-- Duplicate warning -->
<div id="dupe-banner" style="display:none;background:#fef9c3;border:1px solid #fcd34d;border-radius:8px;padding:8px 12px;margin-bottom:12px;font-size:12px;color:#854d0e;align-items:center;gap-8px;">
<i class="bi bi-exclamation-triangle-fill me-2" style="color:#d97706;"></i>
<span id="dupe-text"></span>
<button type="button" onclick="document.getElementById('dupe-banner').style.display='none';" style="background:none;border:none;font-size:14px;color:#854d0e;cursor:pointer;padding:0;margin-left:auto;">×</button>
</div>
<div class="d-flex gap-2">
<button type="submit" id="submitBtn"
class="btn {% if txn_type=='income' %}btn-success{% else %}btn-danger{% endif %}">
@@ -426,5 +433,39 @@ function setTxnType(type) {
sel.value = prevVal;
}
}
// ── Duplicate detection ───────────────────────────────────────────────────────
{% if not txn %}
const DUPE_EXCLUDE_ID = '';
{% else %}
const DUPE_EXCLUDE_ID = '{{ txn.id }}';
{% endif %}
let _dupeTimer = null;
function checkDuplicate() {
const amt = document.getElementById('field_amount').value;
const dt = document.getElementById('field_date').value;
const typ = document.querySelector('[name=transaction_type]').value;
const banner = document.getElementById('dupe-banner');
if (!amt || !dt || parseFloat(amt) <= 0) { banner.style.display = 'none'; return; }
clearTimeout(_dupeTimer);
_dupeTimer = setTimeout(() => {
const url = `/transactions/api/check-duplicate?date=${dt}&amount=${amt}&type=${typ}${DUPE_EXCLUDE_ID ? '&exclude_id=' + DUPE_EXCLUDE_ID : ''}`;
fetch(url).then(r => r.json()).then(data => {
if (data.duplicates && data.duplicates.length > 0) {
const list = data.duplicates.map(d => `"${d.description}" on ${d.date}${d.account ? ' (' + d.account + ')' : ''}`).join('; ');
document.getElementById('dupe-text').textContent = `Possible duplicate: ${list}`;
banner.style.display = 'flex';
} else {
banner.style.display = 'none';
}
}).catch(() => {});
}, 600);
}
['field_amount', 'field_date'].forEach(id => {
const el = document.getElementById(id);
if (el) el.addEventListener('change', checkDuplicate);
});
</script>
{% endblock %}