Implement custom combobox for account selection

Only on balance assertion edit page currently
This commit is contained in:
RunasSudo 2024-04-06 01:31:31 +11:00
parent 9206c21025
commit 3083117730
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 94 additions and 5 deletions

View File

@ -19,7 +19,7 @@ from flask import abort, redirect, render_template, request, url_for
from .. import AMOUNT_DPS
from ..database import db
from ..models import Amount, Posting, Transaction, TrialBalancer
from ..webapp import all_transactions, app
from ..webapp import all_accounts, all_transactions, app
from .models import BalanceAssertion
from ..statements.models import StatementLineReconciliation
@ -146,7 +146,7 @@ def balance_assertions():
@app.route('/balance-assertions/new', methods=['GET', 'POST'])
def balance_assertions_new():
if request.method == 'GET':
return render_template('journal/balance_assertions_edit.html', assertion=None)
return render_template('journal/balance_assertions_edit.html', assertion=None, accounts=all_accounts())
quantity = round(float(request.form['amount']) * (10**AMOUNT_DPS))
if request.form['sign'] == 'cr':
@ -172,7 +172,7 @@ def balance_assertions_edit():
abort(404)
if request.method == 'GET':
return render_template('journal/balance_assertions_edit.html', assertion=assertion)
return render_template('journal/balance_assertions_edit.html', assertion=assertion, accounts=all_accounts())
if request.form.get('action', None) == 'delete':
# Delete balance assertion

View File

@ -0,0 +1,64 @@
/* DrCr: Web-based double-entry bookkeeping framework
Copyright (C) 20222024 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
function updateComboboxInputs(elCombobox, elInput) {
elCombobox.querySelector('ul').querySelectorAll('li').forEach((elLi) => {
const liText = elLi.querySelector('.combobox-text').innerText;
if (liText.toLowerCase().startsWith(elInput.value.toLowerCase())) {
elLi.classList.remove('hidden');
elLi.classList.add('block');
if (liText == elInput.value) {
elLi.dataset.selected = 'selected';
} else {
elLi.dataset.selected = '';
}
} else {
elLi.classList.remove('block');
elLi.classList.add('hidden');
elLi.dataset.selected = '';
}
});
}
// Init combobox
document.querySelectorAll('.combobox').forEach((elCombobox) => {
const elInput = elCombobox.querySelector('input');
updateComboboxInputs(elCombobox, elInput);
// Update combobox options on input
elInput.addEventListener('input', (evt) => {
updateComboboxInputs(elCombobox, elInput);
});
// Open dropdown on click button
elCombobox.querySelector('button').addEventListener('click', (evt) => {
elInput.focus();
});
});
// Update combobox value on select
document.querySelectorAll('.combobox').forEach((elCombobox) => {
const elInput = elCombobox.querySelector('input');
elCombobox.querySelector('ul').querySelectorAll('li').forEach((elLi) => {
elLi.addEventListener('mousedown', (evt) => {
const liText = elLi.querySelector('.combobox-text').innerText;
elInput.value = liText;
updateComboboxInputs(elCombobox, elInput);
});
});
});

View File

@ -35,8 +35,25 @@
<input type="text" class="bordered-field" name="description" id="description" value="{{ assertion.description if assertion else '' }}">
</div>
<label for="account" class="block text-gray-900 pr-4">Account</label>
<div>
<input type="text" class="bordered-field" name="account" id="account" value="{{ assertion.account if assertion else '' }}">
<div class="relative combobox">
<input type="text" class="bordered-field peer" name="account" id="account" value="{{ assertion.account if assertion else '' }}" autocomplete="off">
<button type="button" class="absolute inset-y-0 right-0 flex items-center px-2 focus:outline-none">
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
</svg>
</button>
<ul class="hidden peer-focus:block absolute z-10 mt-1 max-h-60 w-full overflow-auto bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{% for account in accounts %}
<li class="group relative cursor-default select-none py-1 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600">
<span class="combobox-text block truncate group-data-[selected=selected]:font-semibold">{{ account }}</span>
<span class="hidden group-data-[selected=selected]:flex absolute inset-y-0 right-0 items-center pr-4 text-indigo-600 group-hover:text-white">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
</svg>
</span>
</li>
{% endfor %}
</ul>
</div>
<label for="amount" class="block text-gray-900 pr-4">Balance</label>
<div class="relative shadow-sm">
@ -63,3 +80,7 @@
</form>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/combobox.js') }}"></script>
{% endblock %}

View File

@ -55,6 +55,10 @@ def all_transactions(start_date=None, end_date=None, join_postings=True):
return transactions
def all_accounts():
# TODO: Can this be cached?
return sorted(list(set(p.account for t in all_transactions() for p in t.postings)))
def eofy_date():
"""Get the datetime for the end of the financial year"""