From 1f8e6c2d5ca7214e40fdfa3c4d428b9916a9c633 Mon Sep 17 00:00:00 2001 From: Christian Heller <c.heller@plomlompom.de> Date: Sun, 26 Jan 2025 22:39:19 +0100 Subject: [PATCH] To structured editing view, add addition/deletion/movement of lines. --- ledger.py | 28 +++++++-- templates/_base.tmpl | 3 + templates/edit_structured.tmpl | 106 ++++++++++++++++++++++++++------- 3 files changed, 111 insertions(+), 26 deletions(-) diff --git a/ledger.py b/ledger.py index 364ca1f..a3bc7af 100755 --- a/ledger.py +++ b/ledger.py @@ -79,6 +79,14 @@ class DatLine: self.code = halves[0] self.booking_line: Optional[BookingLine] = None + @property + def as_dict(self) -> dict: + """Return as JSON-ready dict.""" + assert isinstance(self.booking_line, (IntroLine, TransferLine)) + return {'comment': self.comment, 'code': self.code, + 'is_intro': self.is_intro, 'error': self.error, + 'booking_line': self.booking_line.as_dict} + @property def is_intro(self) -> bool: """Return if intro line of a Booking.""" @@ -144,20 +152,24 @@ class IntroLine(BookingLine): else: self.date = toks[0] + @property + def as_dict(self) -> dict: + """Return as JSON-ready dict.""" + return {'date': self.date, 'target': self.target} + class TransferLine(BookingLine): """Non-first Booking line, expected to carry value movement.""" def __init__(self, booking: 'Booking', code: str) -> None: super().__init__(booking) + self.currency = '' if not code[0].isspace(): self.errors += ['transfer line not indented'] toks = code.lstrip().split() self.account = toks[0] self.amount: Optional[Decimal] = None - if 1 == len(toks): - self.currency = '' - elif 3 <= len(toks): + if 3 <= len(toks): self.currency = toks[2] try: self.amount = Decimal(toks[1]) @@ -175,6 +187,12 @@ class TransferLine(BookingLine): return f'{self.amount:.1f}â¦' if exp < -2 else f'{self.amount:.2f}' return '' + @property + def as_dict(self) -> dict: + """Return as JSON-ready dict.""" + return {'account': self.account, 'currency': self.currency, + 'amount': str(self.amount)} + class Booking: """Represents lines of individual booking.""" @@ -273,15 +291,17 @@ class Handler(PlomHttpHandler): ctx = {'tainted': self.server.tainted} if self.pagename == 'booking' or self.pagename.startswith('edit_'): ctx['id'] = int(self.path_toks[2]) - ctx['dat_lines'] = self.server.bookings[ctx['id']].dat_lines if self.pagename == 'balance': valid, balance_roots = self.server.balance_roots( int(self.params.first('cutoff') or '0')) self.send_rendered(Path('balance.tmpl'), ctx | {'roots': balance_roots, 'valid': valid}) elif self.pagename in {'booking', 'edit_structured'}: + ctx['dat_lines'] = [dl.as_dict for dl + in self.server.bookings[ctx['id']].dat_lines] self.send_rendered(Path('edit_structured.tmpl'), ctx) elif self.pagename == 'edit_raw': + ctx['dat_lines'] = self.server.bookings[ctx['id']].dat_lines self.send_rendered(Path('edit_raw.tmpl'), ctx) elif self.pagename == 'ledger_raw': self.send_rendered(Path('ledger_raw.tmpl'), diff --git a/templates/_base.tmpl b/templates/_base.tmpl index d6881d2..c90f1ab 100644 --- a/templates/_base.tmpl +++ b/templates/_base.tmpl @@ -3,6 +3,9 @@ <html> <head> <meta charset="UTF-8"> +<script> +{% block script %}{% endblock %} +</script> <style> body { background-color: white; font-family: sans-serif; } tr:nth-child(odd) { background-color: #dcdcdc; } diff --git a/templates/edit_structured.tmpl b/templates/edit_structured.tmpl index 89919e5..af0c634 100644 --- a/templates/edit_structured.tmpl +++ b/templates/edit_structured.tmpl @@ -6,32 +6,94 @@ {{ macros.css_errors() }} {% endblock %} + +{% block script %} +var dat_lines = {{dat_lines|tojson|safe}}; + +function update_form() { + const table = document.getElementById("dat_lines"); + table.innerHTML = ""; + function add_button(td, text, disable, f) { + const btn = document.createElement("button"); + td.appendChild(btn); + btn.textContent = text; + btn.type = "button"; // otherwise will act as form submit + btn.disabled = disable; + btn.onclick = function() {f(); update_form();}; + } + function add_td(tr, colspan=1) { + const td = document.createElement("td"); + tr.appendChild(td); + td.colSpan = colspan; + return td; + } + for (let i = 0; i < dat_lines.length; i++) { + const dat_line = dat_lines[i]; + const tr = document.createElement("tr"); + table.appendChild(tr); + function add_input(name, value, colspan=1) { + const td = add_td(tr, colspan); + const input = document.createElement("input"); + td.appendChild(input); + input.name = `line_${i}_${name}` + input.value = value.trim(); + if (dat_line.error) { + td.classList.add("invalid"); + } + } + if (dat_line.is_intro) { + add_input('date', dat_line.booking_line.date) + add_input('target', dat_line.booking_line.target, 2) + } else if (!dat_line.error) { + add_input('account', dat_line.booking_line.account); + add_input('amount', dat_line.booking_line.amount == 'None' ? '' : dat_line.booking_line.amount); + add_input('currency', dat_line.booking_line.currency); + } else { + add_input('error', dat_line.code, 3) + } + add_input('comment', dat_line.comment); + const td_btns = add_td(tr); + add_button(td_btns, 'delete', false, function() { + dat_lines.splice(i, 1); + }); + add_button(td_btns, 'move up', i > 1 ? false : true, function() { + const prev_line = dat_lines[i-1]; + dat_lines.splice(i-1, 1); + dat_lines.splice(i, 0, prev_line); + }); + add_button(td_btns, 'move down', i+1 < dat_lines.length ? false : true, function() { + const next_line = dat_lines[i]; + dat_lines.splice(i, 1); + dat_lines.splice(i+1, 0, next_line); + }); + if (dat_line.error) { + const tr = document.createElement("tr"); + table.appendChild(tr); + const td = add_td(tr, 3); + tr.appendChild(document.createElement("td")); + td.textContent = dat_line.error; + td.classList.add("invalid"); + tr.classList.add("warning"); + } + } + const tr = document.createElement("tr"); + table.appendChild(tr); + const td = add_td(tr, 5); + add_button(td, 'add line', false, function() { + new_line = {error: '', comment: '', booking_line: {account: '', amount: '', currency: ''}}; + dat_lines.push(new_line); + }); +} + +window.onload = update_form; +{% endblock %} + + {% block content %} <a href="/edit_raw/{{id}}">edit raw</a> <hr /> <form action="/edit_structured/{{id}}" method="POST"> -<table> -{% for dat_line in dat_lines %} - <tr> - {% if dat_line.is_intro %} - <td{% if dat_line.error %} class="invalid"{% endif %}><input name="line_{{loop.index0}}_date" value="{{dat_line.booking_line.date}}" /></td> - <td{% if dat_line.error %} class="invalid"{% endif %} colspan=2><input name="line_{{loop.index0}}_target" value="{{dat_line.booking_line.target}}" /></td> - {% elif not dat_line.error %} - <td><input name="line_{{loop.index0}}_account" value="{{dat_line.booking_line.account}}" /></td> - <td><input name="line_{{loop.index0}}_amount" value="{{dat_line.booking_line.amount or ''}}" /></td> - <td><input name="line_{{loop.index0}}_currency" value="{{dat_line.booking_line.currency or ''}}" /></td> - {% else %} - <td class="invalid" colspan=3><input name="line_{{loop.index0}}_error" value="{{dat_line.code|trim}}" /></td> - {% endif %} - <td><input name="line_{{loop.index0}}_comment" value="{{dat_line.comment|trim}}" /></td> - </tr> - {% if dat_line.error %} - <tr class="warning"> - <td class="invalid" colspan=3>{{dat_line.error}}</td> - <td></td> - </tr> - {% endif %} -{% endfor %} +<table id="dat_lines"> </table> <input type="submit" value="update" /> </form> -- 2.30.2