feat(notes): render and wire notes inputs on budget and month pages
Each entry row gains a secondary notes row with an inline-editable
text input. Budget entries post to a new /entries/{id}/notes
endpoint; month entries reuse the existing update route. Add forms
gain an optional "notes (optional)" input that spans the form row.
Notes render muted with a dashed underline on hover to signal
editability without cluttering the layout. Changing notes on a
month row does not flip the deviation state since the financial
values are unchanged.
Refs #13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
034a8d65f5
commit
0f2f549d85
5 changed files with 87 additions and 6 deletions
|
|
@ -107,13 +107,14 @@ def create_entry(
|
|||
request: Request,
|
||||
name: str = Form(...),
|
||||
amount: str = Form(...),
|
||||
notes: str | None = Form(None),
|
||||
db: Session = Depends(get_session),
|
||||
) -> HTMLResponse:
|
||||
clean_name = name.strip()
|
||||
if not clean_name:
|
||||
raise HTTPException(status_code=400, detail="name is required")
|
||||
parsed = _parse_amount(amount)
|
||||
service.add_entry(db, section, clean_name, parsed)
|
||||
service.add_entry(db, section, clean_name, parsed, notes=notes)
|
||||
response = _render_section(request, db, section)
|
||||
extras: list[HTMLResponse] = [
|
||||
_render_zero(request, db),
|
||||
|
|
@ -143,6 +144,19 @@ def remove_entry(
|
|||
return _append_oob(response, *extras)
|
||||
|
||||
|
||||
@router.post("/entries/{entry_id}/notes", response_class=HTMLResponse)
|
||||
def update_entry_notes(
|
||||
entry_id: int,
|
||||
request: Request,
|
||||
notes: str | None = Form(None),
|
||||
db: Session = Depends(get_session),
|
||||
) -> HTMLResponse:
|
||||
updated = service.set_entry_notes(db, entry_id, notes)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="entry not found")
|
||||
return _render_section(request, db, updated.section)
|
||||
|
||||
|
||||
@router.post("/debt-target", response_class=HTMLResponse)
|
||||
def update_debt_target(
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ def add_month_entry(
|
|||
request: Request,
|
||||
name: str = Form(...),
|
||||
planned: str = Form(...),
|
||||
notes: str | None = Form(None),
|
||||
db: Session = Depends(get_session),
|
||||
) -> HTMLResponse:
|
||||
month = _require_month(db, year_month)
|
||||
|
|
@ -171,7 +172,7 @@ def add_month_entry(
|
|||
if not clean_name:
|
||||
raise HTTPException(status_code=400, detail="name is required")
|
||||
month_service.add_month_entry(
|
||||
db, month, section, clean_name, _parse_amount(planned)
|
||||
db, month, section, clean_name, _parse_amount(planned), notes=notes
|
||||
)
|
||||
db.refresh(month)
|
||||
return _append_oob(
|
||||
|
|
@ -210,6 +211,7 @@ def update_month_entry(
|
|||
name: str | None = Form(None),
|
||||
planned: str | None = Form(None),
|
||||
applied: str | None = Form(None),
|
||||
notes: str | None = Form(None),
|
||||
db: Session = Depends(get_session),
|
||||
) -> HTMLResponse:
|
||||
month = _require_month(db, year_month)
|
||||
|
|
@ -220,14 +222,14 @@ def update_month_entry(
|
|||
raise HTTPException(status_code=400, detail="name must not be empty")
|
||||
parsed_planned = _parse_amount(planned) if planned is not None else None
|
||||
parsed_applied = _parse_amount(applied) if applied is not None else None
|
||||
updated = month_service.update_month_entry(
|
||||
db,
|
||||
month,
|
||||
entry_id,
|
||||
kwargs: dict = dict(
|
||||
name=clean_name,
|
||||
planned=parsed_planned,
|
||||
applied=parsed_applied,
|
||||
)
|
||||
if notes is not None:
|
||||
kwargs["notes"] = notes
|
||||
updated = month_service.update_month_entry(db, month, entry_id, **kwargs)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="entry not found")
|
||||
db.refresh(month)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,37 @@ tr.add-row td {
|
|||
|
||||
.muted { color: var(--muted); font-style: italic; }
|
||||
|
||||
tr.entry-notes-row td {
|
||||
padding: 0 0.5rem 0.35rem;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.notes-input {
|
||||
width: 100%;
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px dashed transparent;
|
||||
}
|
||||
|
||||
.notes-input:hover,
|
||||
.notes-input:focus {
|
||||
border-bottom-color: var(--rule);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.notes-input:not(:placeholder-shown) {
|
||||
color: var(--ink);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.add-form .add-notes {
|
||||
grid-column: 1 / -1;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
button.delete {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,22 @@
|
|||
>×</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="entry-notes-row">
|
||||
<td colspan="4">
|
||||
<input
|
||||
class="notes-input"
|
||||
type="text"
|
||||
name="notes"
|
||||
value="{{ row.entry.notes or '' }}"
|
||||
placeholder="notes..."
|
||||
hx-post="/month/{{ month.year_month }}/entries/{{ row.entry.id }}"
|
||||
hx-trigger="change"
|
||||
hx-target="#section-{{ section.section.value }}"
|
||||
hx-swap="outerHTML"
|
||||
aria-label="Notes for {{ row.entry.name }}"
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="empty"><td colspan="4">No entries.</td></tr>
|
||||
{% endfor %}
|
||||
|
|
@ -83,6 +99,7 @@
|
|||
<input type="text" name="name" placeholder="Name" required>
|
||||
<input type="number" name="planned" step="0.01" min="0" placeholder="Planned" required>
|
||||
<button type="submit">Add</button>
|
||||
<input class="notes-input add-notes" type="text" name="notes" placeholder="notes (optional)">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,22 @@
|
|||
>×</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="entry-notes-row">
|
||||
<td colspan="3">
|
||||
<input
|
||||
class="notes-input"
|
||||
type="text"
|
||||
name="notes"
|
||||
value="{{ entry.notes or '' }}"
|
||||
placeholder="notes..."
|
||||
hx-post="/entries/{{ entry.id }}/notes"
|
||||
hx-trigger="change"
|
||||
hx-target="#section-{{ section.section.value }}"
|
||||
hx-swap="outerHTML"
|
||||
aria-label="Notes for {{ entry.name }}"
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="empty"><td colspan="3">No entries yet.</td></tr>
|
||||
{% endfor %}
|
||||
|
|
@ -37,6 +53,7 @@
|
|||
<input type="text" name="name" placeholder="Name" required>
|
||||
<input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required>
|
||||
<button type="submit">Add</button>
|
||||
<input class="notes-input add-notes" type="text" name="notes" placeholder="notes (optional)">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
Loading…
Reference in a new issue