Notes field per entry #14

Merged
claude-code merged 4 commits from feat/13-notes-field into main 2026-04-17 12:54:25 -06:00
5 changed files with 87 additions and 6 deletions
Showing only changes of commit 0f2f549d85 - Show all commits

View file

@ -107,13 +107,14 @@ def create_entry(
request: Request, request: Request,
name: str = Form(...), name: str = Form(...),
amount: str = Form(...), amount: str = Form(...),
notes: str | None = Form(None),
db: Session = Depends(get_session), db: Session = Depends(get_session),
) -> HTMLResponse: ) -> HTMLResponse:
clean_name = name.strip() clean_name = name.strip()
if not clean_name: if not clean_name:
raise HTTPException(status_code=400, detail="name is required") raise HTTPException(status_code=400, detail="name is required")
parsed = _parse_amount(amount) 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) response = _render_section(request, db, section)
extras: list[HTMLResponse] = [ extras: list[HTMLResponse] = [
_render_zero(request, db), _render_zero(request, db),
@ -143,6 +144,19 @@ def remove_entry(
return _append_oob(response, *extras) 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) @router.post("/debt-target", response_class=HTMLResponse)
def update_debt_target( def update_debt_target(
request: Request, request: Request,

View file

@ -164,6 +164,7 @@ def add_month_entry(
request: Request, request: Request,
name: str = Form(...), name: str = Form(...),
planned: str = Form(...), planned: str = Form(...),
notes: str | None = Form(None),
db: Session = Depends(get_session), db: Session = Depends(get_session),
) -> HTMLResponse: ) -> HTMLResponse:
month = _require_month(db, year_month) month = _require_month(db, year_month)
@ -171,7 +172,7 @@ def add_month_entry(
if not clean_name: if not clean_name:
raise HTTPException(status_code=400, detail="name is required") raise HTTPException(status_code=400, detail="name is required")
month_service.add_month_entry( 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) db.refresh(month)
return _append_oob( return _append_oob(
@ -210,6 +211,7 @@ def update_month_entry(
name: str | None = Form(None), name: str | None = Form(None),
planned: str | None = Form(None), planned: str | None = Form(None),
applied: str | None = Form(None), applied: str | None = Form(None),
notes: str | None = Form(None),
db: Session = Depends(get_session), db: Session = Depends(get_session),
) -> HTMLResponse: ) -> HTMLResponse:
month = _require_month(db, year_month) 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") raise HTTPException(status_code=400, detail="name must not be empty")
parsed_planned = _parse_amount(planned) if planned is not None else None parsed_planned = _parse_amount(planned) if planned is not None else None
parsed_applied = _parse_amount(applied) if applied is not None else None parsed_applied = _parse_amount(applied) if applied is not None else None
updated = month_service.update_month_entry( kwargs: dict = dict(
db,
month,
entry_id,
name=clean_name, name=clean_name,
planned=parsed_planned, planned=parsed_planned,
applied=parsed_applied, 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: if updated is None:
raise HTTPException(status_code=404, detail="entry not found") raise HTTPException(status_code=404, detail="entry not found")
db.refresh(month) db.refresh(month)

View file

@ -111,6 +111,37 @@ tr.add-row td {
.muted { color: var(--muted); font-style: italic; } .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 { button.delete {
background: transparent; background: transparent;
border: none; border: none;

View file

@ -68,6 +68,22 @@
>&times;</button> >&times;</button>
</td> </td>
</tr> </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 %} {% else %}
<tr class="empty"><td colspan="4">No entries.</td></tr> <tr class="empty"><td colspan="4">No entries.</td></tr>
{% endfor %} {% endfor %}
@ -83,6 +99,7 @@
<input type="text" name="name" placeholder="Name" required> <input type="text" name="name" placeholder="Name" required>
<input type="number" name="planned" step="0.01" min="0" placeholder="Planned" required> <input type="number" name="planned" step="0.01" min="0" placeholder="Planned" required>
<button type="submit">Add</button> <button type="submit">Add</button>
<input class="notes-input add-notes" type="text" name="notes" placeholder="notes (optional)">
</form> </form>
</td> </td>
</tr> </tr>

View file

@ -22,6 +22,22 @@
>&times;</button> >&times;</button>
</td> </td>
</tr> </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 %} {% else %}
<tr class="empty"><td colspan="3">No entries yet.</td></tr> <tr class="empty"><td colspan="3">No entries yet.</td></tr>
{% endfor %} {% endfor %}
@ -37,6 +53,7 @@
<input type="text" name="name" placeholder="Name" required> <input type="text" name="name" placeholder="Name" required>
<input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required> <input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required>
<button type="submit">Add</button> <button type="submit">Add</button>
<input class="notes-input add-notes" type="text" name="notes" placeholder="notes (optional)">
</form> </form>
</td> </td>
</tr> </tr>