Budget: - Modelle + Migration 0003: budget_template_lines, planned_income_lines, budget_periods, budget_period_lines. - services/budget: Perioden-Grenzen (einstellbarer Starttag, Label nach Endmonat), Standard-Vorlage vs. Monats-Override (Snapshot-on-Customize), Soll/Ist inkl. budgetierter Sparen-Transfers, geplante Einnahmen (Soll/Ist). - routers/budgets: Vorlage, geplante Einnahmen (CRUD), Monats-Soll/Ist, Monats-Override, Zurücksetzen. pytest: Perioden, Soll/Ist, Override/Reset, Einnahmen. - Frontend: Budget-Seite (Monat + Standard-Budget), Fortschrittsbalken grün/rot, Monats-Navigation, editierbare Soll-Beträge; Sparen-Option im Transfer-Formular. Deployment-Fix (Nutzer-Anforderung: http://IP:Port muss funktionieren): - Dienst bindet an APP_BIND_HOST=0.0.0.0 (direkt im LAN erreichbar). - APP_COOKIE_SECURE=false als Standard (sonst kein Login über reines HTTP); für reinen HTTPS-Betrieb umstellbar. Doku + install.sh entsprechend angepasst. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
881 B
React
33 lines
881 B
React
import { useEffect, useState } from "react";
|
|
import { centsToInput, parseAmountToCents } from "../format";
|
|
|
|
// Kleines, editierbares Betragsfeld (Cent). Speichert bei Verlassen/Enter,
|
|
// aber nur bei Änderung.
|
|
export default function EditableAmount({ cents, onSave, placeholder = "0,00" }) {
|
|
const [val, setVal] = useState(centsToInput(cents));
|
|
useEffect(() => { setVal(centsToInput(cents)); }, [cents]);
|
|
|
|
function commit() {
|
|
const c = parseAmountToCents(val) ?? 0;
|
|
if (c !== cents) onSave(c);
|
|
}
|
|
|
|
return (
|
|
<input
|
|
className="amount-input"
|
|
inputMode="decimal"
|
|
value={val}
|
|
placeholder={placeholder}
|
|
onChange={(e) => setVal(e.target.value)}
|
|
onBlur={commit}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
commit();
|
|
e.target.blur();
|
|
}
|
|
}}
|
|
/>
|
|
);
|
|
}
|