feat(ledger): expandable entry rows with transactions table and add form
Each month entry becomes a <details> block. The summary is the same
dense row (name, planned, applied, delete) plus a leading caret and
an applied cell that shows the transaction count ("$412.33 · 7 txns")
when postings exist. Expansion adds no horizontal space.
Expanded body holds: the entry's notes input, a transactions table
with date / description / payee / amount / delete per posting, and
an inline add-transaction form (date, description, payee, amount,
submit). Every field is HTMX-wired so editing any cell triggers the
section partial re-render with fresh derived totals.
Closed month: name / planned / notes / posting fields all collapse
to read-only spans, delete buttons and add forms are omitted. The
existing editable flag controls the branching.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
52bc52ec7f
commit
cca05fe9fc
2 changed files with 469 additions and 79 deletions
|
|
@ -675,6 +675,295 @@ tr.entry:hover + tr.entry-notes-row:has(input:placeholder-shown) {
|
||||||
padding: 0.15rem 0.4rem;
|
padding: 0.15rem 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============== MONTH ENTRIES — details-based layout =============== */
|
||||||
|
|
||||||
|
.month-entries {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-block {
|
||||||
|
border-bottom: 1px dotted var(--rule);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-block > summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.9rem minmax(0, 1fr) 5.5rem 5.5rem 1.2rem;
|
||||||
|
gap: 0.6rem;
|
||||||
|
align-items: baseline;
|
||||||
|
padding: 0.32rem 0.25rem 0.36rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.entry-block > summary::-webkit-details-marker { display: none; }
|
||||||
|
.entry-block > summary:hover { background: var(--paper-stripe); }
|
||||||
|
|
||||||
|
/* Progress bar on the summary row */
|
||||||
|
.entry-block > summary::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; bottom: -1px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--sage-soft);
|
||||||
|
width: min(100%, calc(var(--ratio, 1) * 100%));
|
||||||
|
transition: width 0.25s ease;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.entry-block > summary.state-edited::after { background: var(--accent); opacity: 0.85; }
|
||||||
|
.entry-block > summary.state-new_in_month::after { background: var(--indigo); opacity: 0.55; }
|
||||||
|
|
||||||
|
/* Caret: rotates on [open] */
|
||||||
|
.entry-block .caret {
|
||||||
|
display: inline-block;
|
||||||
|
width: 0.7rem;
|
||||||
|
position: relative;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
.entry-block .caret::before {
|
||||||
|
content: "▸";
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--muted);
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.entry-block[open] > summary .caret { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
/* Entry row cells */
|
||||||
|
.entry-block .entry-name {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.entry-block .entry-name input,
|
||||||
|
.entry-block .entry-amount input {
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.12s;
|
||||||
|
}
|
||||||
|
.entry-block .entry-name input:hover,
|
||||||
|
.entry-block .entry-amount input:hover { border-bottom-color: var(--rule); }
|
||||||
|
.entry-block .entry-name input:focus,
|
||||||
|
.entry-block .entry-amount input:focus { border-bottom-color: var(--ink); }
|
||||||
|
|
||||||
|
.entry-block .entry-amount {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-align: right;
|
||||||
|
font-feature-settings: "lnum" 1, "tnum" 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.entry-block .entry-amount input {
|
||||||
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.entry-block .entry-amount.planned input { color: var(--muted); font-weight: 400; }
|
||||||
|
|
||||||
|
.entry-block .applied-cell {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
.entry-block .applied-cell .value {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.entry-block .applied-cell .count {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-size: 0.66rem;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: lowercase;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-block .entry-actions button.delete {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--rule);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: color 0.12s ease, opacity 0.12s ease;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
.entry-block > summary:hover .entry-actions button.delete { opacity: 1; }
|
||||||
|
.entry-block .entry-actions button.delete:hover { color: var(--accent); }
|
||||||
|
|
||||||
|
/* Expanded body */
|
||||||
|
.entry-block .entry-body {
|
||||||
|
padding: 0.5rem 1.6rem 0.75rem;
|
||||||
|
background: var(--paper-soft);
|
||||||
|
border-top: 1px solid var(--rule-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-block .entry-notes {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.entry-block .entry-notes input {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--ink);
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px dashed var(--rule);
|
||||||
|
padding: 0.15rem 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.entry-block .entry-notes input:focus { border-bottom-color: var(--ink); }
|
||||||
|
.entry-block .entry-notes.readonly {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transactions table */
|
||||||
|
table.transactions {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
table.transactions thead th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.66rem;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 0.2rem 0.3rem 0.25rem;
|
||||||
|
border-bottom: 1px solid var(--rule);
|
||||||
|
}
|
||||||
|
table.transactions th.col-amount,
|
||||||
|
table.transactions td.col-amount { text-align: right; width: 5.5rem; }
|
||||||
|
table.transactions th.col-date,
|
||||||
|
table.transactions td.col-date { width: 7.5rem; }
|
||||||
|
table.transactions th.col-actions,
|
||||||
|
table.transactions td.col-actions { width: 1.2rem; }
|
||||||
|
|
||||||
|
table.transactions td {
|
||||||
|
padding: 0.2rem 0.3rem;
|
||||||
|
border-bottom: 1px dotted var(--rule-soft);
|
||||||
|
}
|
||||||
|
table.transactions tr.posting:hover { background: var(--paper); }
|
||||||
|
table.transactions input {
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.12s;
|
||||||
|
}
|
||||||
|
table.transactions input:hover { border-bottom-color: var(--rule); }
|
||||||
|
table.transactions input:focus { border-bottom-color: var(--ink); }
|
||||||
|
table.transactions input[type="number"] {
|
||||||
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
table.transactions input[type="date"] {
|
||||||
|
font-family: var(--sans);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
table.transactions tr.empty td {
|
||||||
|
color: var(--muted);
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.transactions td .readonly {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.transactions button.delete {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--rule);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: color 0.12s ease, opacity 0.12s ease;
|
||||||
|
}
|
||||||
|
table.transactions tr:hover button.delete { opacity: 1; }
|
||||||
|
table.transactions button.delete:hover { color: var(--accent); }
|
||||||
|
|
||||||
|
/* Add-posting form */
|
||||||
|
form.add-posting-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 7.5rem minmax(0, 1fr) minmax(0, 1fr) 5.5rem auto;
|
||||||
|
gap: 0.4rem;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.35rem 0.3rem 0.2rem;
|
||||||
|
border-top: 1px dashed var(--rule);
|
||||||
|
}
|
||||||
|
form.add-posting-form input {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
background: var(--paper);
|
||||||
|
color: var(--ink);
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.12s;
|
||||||
|
}
|
||||||
|
form.add-posting-form input[type="number"] { text-align: right; font-variant-numeric: tabular-nums; }
|
||||||
|
form.add-posting-form input:focus { border-color: var(--ink); }
|
||||||
|
form.add-posting-form button[type="submit"] {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border: 1px solid var(--ink);
|
||||||
|
background: var(--paper-soft);
|
||||||
|
color: var(--ink);
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s ease, color 0.12s ease;
|
||||||
|
}
|
||||||
|
form.add-posting-form button[type="submit"]:hover { background: var(--ink); color: var(--paper); }
|
||||||
|
|
||||||
|
.empty-row {
|
||||||
|
padding: 0.5rem 0.5rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.entry-block > summary {
|
||||||
|
grid-template-columns: 0.9rem minmax(0, 1fr) 4.6rem 4.6rem 1rem;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.entry-block .entry-body { padding: 0.5rem 0.75rem 0.6rem; }
|
||||||
|
form.add-posting-form {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
form.add-posting-form input[type="date"],
|
||||||
|
form.add-posting-form input[type="number"],
|
||||||
|
form.add-posting-form button[type="submit"] { grid-column: 1 / -1; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Disabled inputs (closed month) */
|
/* Disabled inputs (closed month) */
|
||||||
input[disabled],
|
input[disabled],
|
||||||
select[disabled],
|
select[disabled],
|
||||||
|
|
|
||||||
|
|
@ -2,68 +2,61 @@
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>{{ section.label }}</h2>
|
<h2>{{ section.label }}</h2>
|
||||||
<span class="total" data-testid="total-{{ section.section.value }}">
|
<span class="total" data-testid="total-{{ section.section.value }}">
|
||||||
<span class="applied">${{ '%.2f' | format(section.total_applied) }}</span>
|
<span class="applied">${{ '{:,.2f}'.format(section.total_applied) }}</span>
|
||||||
<span class="divider">/</span>
|
<span class="divider">/</span>
|
||||||
<span class="planned">${{ '%.2f' | format(section.total_planned) }}</span>
|
<span class="planned">${{ '{:,.2f}'.format(section.total_planned) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<table class="entries month-entries">
|
<div class="entries month-entries">
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="col-name">Name</th>
|
|
||||||
<th class="col-planned">Planned</th>
|
|
||||||
<th class="col-applied">Applied</th>
|
|
||||||
<th class="col-actions"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for row in section.rows %}
|
{% for row in section.rows %}
|
||||||
<tr class="entry state-{{ row.state.value }}" data-entry-id="{{ row.entry.id }}">
|
{% set applied = row.entry.applied %}
|
||||||
<td class="entry-name">
|
<details class="entry-block" data-entry-id="{{ row.entry.id }}">
|
||||||
|
<summary class="entry-row state-{{ row.state.value }}" style="--ratio: {{ (applied / row.entry.planned)|round(4) if row.entry.planned > 0 else 0 }}">
|
||||||
|
<span class="caret" aria-hidden="true"></span>
|
||||||
|
<span class="entry-name">
|
||||||
|
{% if editable %}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
value="{{ row.entry.name }}"
|
value="{{ row.entry.name }}"
|
||||||
{% if editable %}
|
|
||||||
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-target="#section-{{ section.section.value }}"
|
hx-target="#section-{{ section.section.value }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
{% else %}disabled{% endif %}
|
aria-label="Name"
|
||||||
>
|
>
|
||||||
|
{% else %}
|
||||||
|
<span class="readonly">{{ row.entry.name }}</span>
|
||||||
|
{% endif %}
|
||||||
{% if row.state.value == 'edited' %}
|
{% if row.state.value == 'edited' %}
|
||||||
<span class="tag tag-edited">modified</span>
|
<span class="tag tag-edited">modified</span>
|
||||||
{% elif row.state.value == 'new_in_month' %}
|
{% elif row.state.value == 'new_in_month' %}
|
||||||
<span class="tag tag-new">new this month</span>
|
<span class="tag tag-new">new this month</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</span>
|
||||||
<td class="entry-amount">
|
<span class="entry-amount planned">
|
||||||
|
{% if editable %}
|
||||||
<input
|
<input
|
||||||
type="number" step="0.01" min="0"
|
type="number" step="0.01" min="0"
|
||||||
name="planned"
|
name="planned"
|
||||||
value="{{ '%.2f' | format(row.entry.planned) }}"
|
value="{{ '%.2f' | format(row.entry.planned) }}"
|
||||||
{% if editable %}
|
|
||||||
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-target="#section-{{ section.section.value }}"
|
hx-target="#section-{{ section.section.value }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
{% else %}disabled{% endif %}
|
aria-label="Planned"
|
||||||
>
|
>
|
||||||
</td>
|
{% else %}
|
||||||
<td class="entry-amount">
|
<span class="readonly">${{ '{:,.2f}'.format(row.entry.planned) }}</span>
|
||||||
<input
|
{% endif %}
|
||||||
type="number" step="0.01" min="0"
|
</span>
|
||||||
name="applied"
|
<span class="entry-amount applied-cell" aria-label="Applied">
|
||||||
value="{{ '%.2f' | format(row.entry.applied) }}"
|
<span class="value">${{ '{:,.2f}'.format(applied) }}</span>
|
||||||
{% if editable %}
|
{% if row.entry.postings|length > 0 %}
|
||||||
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
<span class="count">· {{ row.entry.postings|length }} txn{% if row.entry.postings|length != 1 %}s{% endif %}</span>
|
||||||
hx-trigger="change"
|
{% endif %}
|
||||||
hx-target="#section-{{ section.section.value }}"
|
</span>
|
||||||
hx-swap="outerHTML"
|
<span class="entry-actions">
|
||||||
{% else %}disabled{% endif %}
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td class="entry-actions">
|
|
||||||
{% if editable %}
|
{% if editable %}
|
||||||
<button
|
<button
|
||||||
class="delete"
|
class="delete"
|
||||||
|
|
@ -74,32 +67,142 @@
|
||||||
aria-label="Delete {{ row.entry.name }}"
|
aria-label="Delete {{ row.entry.name }}"
|
||||||
>×</button>
|
>×</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</span>
|
||||||
</tr>
|
</summary>
|
||||||
<tr class="entry-notes-row">
|
<div class="entry-body">
|
||||||
<td colspan="4">
|
{% if editable %}
|
||||||
|
<div class="entry-notes">
|
||||||
<input
|
<input
|
||||||
class="notes-input"
|
class="notes-input"
|
||||||
type="text"
|
type="text"
|
||||||
name="notes"
|
name="notes"
|
||||||
value="{{ row.entry.notes or '' }}"
|
value="{{ row.entry.notes or '' }}"
|
||||||
placeholder="notes..."
|
placeholder="notes for this entry..."
|
||||||
{% if editable %}
|
|
||||||
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
hx-target="#section-{{ section.section.value }}"
|
hx-target="#section-{{ section.section.value }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
aria-label="Notes for {{ row.entry.name }}"
|
aria-label="Notes"
|
||||||
{% else %}disabled{% endif %}
|
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
{% elif row.entry.notes %}
|
||||||
|
<div class="entry-notes readonly">{{ row.entry.notes }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<table class="transactions">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-date">Date</th>
|
||||||
|
<th class="col-desc">Description</th>
|
||||||
|
<th class="col-payee">Payee</th>
|
||||||
|
<th class="col-amount">Amount</th>
|
||||||
|
<th class="col-actions"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for posting in row.entry.postings %}
|
||||||
|
<tr class="posting" data-posting-id="{{ posting.id }}">
|
||||||
|
<td class="col-date">
|
||||||
|
{% if editable %}
|
||||||
|
<input
|
||||||
|
type="date" name="occurred_on"
|
||||||
|
value="{{ posting.occurred_on.isoformat() }}"
|
||||||
|
hx-post="/month/{{ month.year_month }}/postings/{{ posting.id }}"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-target="#section-{{ section.section.value }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
aria-label="Date"
|
||||||
|
>
|
||||||
|
{% else %}
|
||||||
|
<span class="readonly">{{ posting.occurred_on.isoformat() }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="col-desc">
|
||||||
|
{% if editable %}
|
||||||
|
<input
|
||||||
|
type="text" name="description"
|
||||||
|
value="{{ posting.description or '' }}"
|
||||||
|
placeholder="description"
|
||||||
|
hx-post="/month/{{ month.year_month }}/postings/{{ posting.id }}"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-target="#section-{{ section.section.value }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
aria-label="Description"
|
||||||
|
>
|
||||||
|
{% else %}
|
||||||
|
<span class="readonly">{{ posting.description or '' }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="col-payee">
|
||||||
|
{% if editable %}
|
||||||
|
<input
|
||||||
|
type="text" name="payee"
|
||||||
|
value="{{ posting.payee or '' }}"
|
||||||
|
placeholder="payee"
|
||||||
|
hx-post="/month/{{ month.year_month }}/postings/{{ posting.id }}"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-target="#section-{{ section.section.value }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
aria-label="Payee"
|
||||||
|
>
|
||||||
|
{% else %}
|
||||||
|
<span class="readonly">{{ posting.payee or '' }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="col-amount">
|
||||||
|
{% if editable %}
|
||||||
|
<input
|
||||||
|
type="number" step="0.01" name="amount"
|
||||||
|
value="{{ '%.2f' | format(posting.amount) }}"
|
||||||
|
hx-post="/month/{{ month.year_month }}/postings/{{ posting.id }}"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-target="#section-{{ section.section.value }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
aria-label="Amount"
|
||||||
|
>
|
||||||
|
{% else %}
|
||||||
|
<span class="readonly">${{ '{:,.2f}'.format(posting.amount) }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="col-actions">
|
||||||
|
{% if editable %}
|
||||||
|
<button
|
||||||
|
class="delete"
|
||||||
|
type="button"
|
||||||
|
hx-delete="/month/{{ month.year_month }}/postings/{{ posting.id }}"
|
||||||
|
hx-target="#section-{{ section.section.value }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
aria-label="Delete transaction"
|
||||||
|
>×</button>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr class="empty"><td colspan="4">No entries.</td></tr>
|
<tr class="empty"><td colspan="5">No transactions yet.</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if editable %}
|
||||||
|
<form
|
||||||
|
class="add-posting-form"
|
||||||
|
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}/postings"
|
||||||
|
hx-target="#section-{{ section.section.value }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-on::after-request="if(event.detail.successful) this.reset()"
|
||||||
|
>
|
||||||
|
<input type="date" name="occurred_on" required value="{{ month.year_month }}-01" aria-label="Date">
|
||||||
|
<input type="text" name="description" placeholder="description" aria-label="Description">
|
||||||
|
<input type="text" name="payee" placeholder="payee" aria-label="Payee">
|
||||||
|
<input type="number" step="0.01" name="amount" placeholder="0.00" required aria-label="Amount">
|
||||||
|
<button type="submit">Add transaction</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{% else %}
|
||||||
|
<div class="empty-row">No entries.</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if editable %}
|
{% if editable %}
|
||||||
<tr class="add-row">
|
<div class="add-row">
|
||||||
<td colspan="4">
|
|
||||||
<form
|
<form
|
||||||
class="add-form month-add-form"
|
class="add-form month-add-form"
|
||||||
hx-post="/month/{{ month.year_month }}/sections/{{ section.section.value }}/entries"
|
hx-post="/month/{{ month.year_month }}/sections/{{ section.section.value }}/entries"
|
||||||
|
|
@ -112,9 +215,7 @@
|
||||||
<button type="submit">Add</button>
|
<button type="submit">Add</button>
|
||||||
<input class="notes-input add-notes" type="text" name="notes" placeholder="notes (optional)">
|
<input class="notes-input add-notes" type="text" name="notes" placeholder="notes (optional)">
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue