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 .. import AMOUNT_DPS
|
||||||
from ..database import db
|
from ..database import db
|
||||||
from ..models import Amount, Posting, Transaction, TrialBalancer
|
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 .models import BalanceAssertion
|
||||||
from ..statements.models import StatementLineReconciliation
|
from ..statements.models import StatementLineReconciliation
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ def balance_assertions():
|
|||||||
@app.route('/balance-assertions/new', methods=['GET', 'POST'])
|
@app.route('/balance-assertions/new', methods=['GET', 'POST'])
|
||||||
def balance_assertions_new():
|
def balance_assertions_new():
|
||||||
if request.method == 'GET':
|
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))
|
quantity = round(float(request.form['amount']) * (10**AMOUNT_DPS))
|
||||||
if request.form['sign'] == 'cr':
|
if request.form['sign'] == 'cr':
|
||||||
@ -172,7 +172,7 @@ def balance_assertions_edit():
|
|||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
if request.method == 'GET':
|
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':
|
if request.form.get('action', None) == 'delete':
|
||||||
# Delete balance assertion
|
# 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 '' }}">
|
<input type="text" class="bordered-field" name="description" id="description" value="{{ assertion.description if assertion else '' }}">
|
||||||
</div>
|
</div>
|
||||||
<label for="account" class="block text-gray-900 pr-4">Account</label>
|
<label for="account" class="block text-gray-900 pr-4">Account</label>
|
||||||
<div>
|
<div class="relative combobox">
|
||||||
<input type="text" class="bordered-field" name="account" id="account" value="{{ assertion.account if assertion else '' }}">
|
<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>
|
</div>
|
||||||
<label for="amount" class="block text-gray-900 pr-4">Balance</label>
|
<label for="amount" class="block text-gray-900 pr-4">Balance</label>
|
||||||
<div class="relative shadow-sm">
|
<div class="relative shadow-sm">
|
||||||
@ -63,3 +80,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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
|
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():
|
def eofy_date():
|
||||||
"""Get the datetime for the end of the financial year"""
|
"""Get the datetime for the end of the financial year"""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user