diff --git a/src/pages/StatementLinesView.vue b/src/pages/StatementLinesView.vue index 98b531b..67c44ff 100644 --- a/src/pages/StatementLinesView.vue +++ b/src/pages/StatementLinesView.vue @@ -36,7 +36,7 @@ -
+
@@ -50,13 +50,23 @@ - +
Balance
Loading data…
+ + +
@@ -69,6 +79,7 @@ import { onUnmounted, ref, watch } from 'vue'; + import ComboBoxAccounts from '../components/ComboBoxAccounts.vue'; import { db } from '../db.ts'; import { renderComponent } from '../webutil.ts'; import { ppWithCommodity } from '../display.ts'; @@ -87,6 +98,10 @@ const showOnlyUnclassified = ref(false); const statementLines = ref([] as StatementLine[]); + + const classificationLineId = ref(0); + const classificationAccount = ref(''); + let clusterize: Clusterize | null = null; async function load() { @@ -127,48 +142,59 @@ statementLines.value = newStatementLines; } - // TODO: Could probably avoid polluting global scope by using clusterize clusterChanged callback - (window as any).showClassifyLinePanel = function(el: HTMLAnchorElement) { - const CheckIconHTML = renderComponent(CheckIcon, { 'class': 'w-5 h-5' }); - - const td = el.closest('td')!; - td.className = 'relative'; // CSS trickery so as to not expand the height of the tr - td.innerHTML = - `
- - -
`; - - td.querySelector('input')!.addEventListener('keydown', async function(event: KeyboardEvent) { - if (event.key === 'Enter') { - await onLineClassified(event); + function onClickTableElement(event: MouseEvent) { + // Use event delegation to avoid polluting global scope with the event listener + if (event.target && (event.target as Element).classList.contains('classify-link')) { + // ------------------------ + // Show classify line panel + + // Prevent selecting a different line when already classifying one line + if ((document.getElementById('statement-line-classifier-button')! as HTMLButtonElement).disabled) { + return; } - }) - td.querySelector('button')!.addEventListener('click', onLineClassified); - - td.querySelector('input')!.focus(); - - return false; - }; + + // Set global state + const td = (event.target as Element).closest('td')!; // Reconciliation cell + const tr = td.closest('tr')!; + classificationLineId.value = parseInt(tr.dataset.lineId!); + + // Show all other reconciliation cells + for (const el of document.querySelectorAll('#statement-line-list .charge-account > span')) { + el.classList.remove('invisible'); + } + + // Hide contents of the cell + const span = td.querySelector('span')!; // Span wrapper for reconciliation cell content + span.classList.add('invisible'); + + // Position the classify line panel in place (relative to #statement-line-list) + const outerDiv = document.getElementById('statement-line-list')!; + const divReconciler = document.getElementById('statement-line-classifier')!; + divReconciler.classList.remove('hidden'); + divReconciler.style.top = (td.getBoundingClientRect().y - outerDiv.getBoundingClientRect().y - 4) + 'px'; + divReconciler.style.left = (td.getBoundingClientRect().x - outerDiv.getBoundingClientRect().x) + 'px'; + + // Focus classify line panel + divReconciler.querySelector('input')!.focus(); + } + } async function onLineClassified(event: Event) { - // Callback when clicking OK or pressing enter to classify a statement line - if ((event.target as HTMLInputElement).disabled) { + // Callback when clicking OK to classify a statement line + if ((event.target! as any).disabled) { return; } - const td = (event.target as Element).closest('td')!; - const tr = td.closest('tr')!; - const lineId = parseInt(tr.dataset.lineId!); - const chargeAccount = (td.querySelector('input')! as HTMLInputElement).value; + const lineId = classificationLineId.value; + const chargeAccount = classificationAccount.value; if (!chargeAccount) { return; } // Disable further submissions - td.querySelector('input')!.disabled = true; - td.querySelector('button')!.disabled = true; + (document.querySelector('.statement-line-classifier-input')! as HTMLInputElement).disabled = true; + (document.getElementById('statement-line-classifier-button')! as HTMLButtonElement).disabled = true; const statementLine = statementLines.value.find((l) => l.id === lineId)!; @@ -184,8 +210,8 @@ if (!doesAccountExist) { // Prompt for confirmation if (!await confirm('Account "' + chargeAccount + '" does not exist. Continue to reconcile this transaction and create a new account?')) { - td.querySelector('input')!.disabled = false; - td.querySelector('button')!.disabled = false; + (document.querySelector('.statement-line-classifier-input')! as HTMLInputElement).disabled = false; + (document.getElementById('statement-line-classifier-button')! as HTMLButtonElement).disabled = false; return; } } @@ -225,6 +251,14 @@ dbTransaction.commit(); + // Reset statement line classifier state + classificationAccount.value = ''; + (document.querySelector('.statement-line-classifier-input')! as HTMLInputElement).disabled = false; + (document.getElementById('statement-line-classifier-button')! as HTMLButtonElement).disabled = false; + + // Hide the statement line classifier + document.getElementById('statement-line-classifier')!.classList.add('hidden'); + // Reload transactions and re-render the table await load(); } @@ -309,7 +343,7 @@ if (line.posting_accounts.length === 0) { // Unreconciled reconciliationCell = - `Unclassified`; + `Unclassified`; checkboxCell = ``; // Only show checkbox for unreconciled lines } else if (line.posting_accounts.length === 2) { // Simple reconciliation @@ -336,7 +370,7 @@ ${ line.source_account } ${ dayjs(line.dt).format('YYYY-MM-DD') } ${ line.description } - ${ reconciliationCell } + ${ reconciliationCell } ${ line.quantity >= 0 ? ppWithCommodity(line.quantity, line.commodity) : '' } ${ line.quantity < 0 ? ppWithCommodity(-line.quantity, line.commodity) : '' } ${ line.balance ?? '' } @@ -349,11 +383,14 @@ 'rows': rows, scrollElem: document.getElementById('statement-line-list')!, contentElem: document.querySelector('#statement-line-list tbody')!, - show_no_data_row: false, + show_no_data_row: false }); } else { clusterize.update(rows); } + + // Hide the statement line classifier + document.getElementById('statement-line-classifier')!.classList.add('hidden'); } watch(showOnlyUnclassified, renderTable);