Implement custom combobox for account selection
Only on balance assertion edit page currently
This commit is contained in:
parent
9206c21025
commit
3083117730
@ -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
|
||||
|
64
drcr/static/js/combobox.js
Normal file
64
drcr/static/js/combobox.js
Normal file
@ -0,0 +1,64 @@
|
||||
/* DrCr: Web-based double-entry bookkeeping framework
|
||||
Copyright (C) 2022–2024 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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 %}
|
||||
|
@ -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"""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user