Piggybank — family budget
The family budget in four pages: expenses, income, balance with a chart and savings goals.
data space «casa-en» — the suite’s glue: on your device, multi-user with one invite (link or QR), end-to-end encrypted.
▶ Open the suite (5 micro apps)
Integrates with: Hearth — same data space.
What it records
- What you bought
- Category...
- Amount (€)
- Salary, refund, gift...
- Amount (€)
- Goal (holiday, emergency fund...)
- Target (€)
- Already set aside (€)
Composed of 5 micro apps
One job each, over the same data: every micro app reads and writes the shared data space “casa-en” (with your consent, asked on first open). Everything stays on your device — and with a single invite the whole suite goes multi-user, end-to-end encrypted.
Shared micro apps are the bridge between solutions: the same data also powers Hearth.
How it works
This is a Reactive app: a plain text document that your browser turns into a working app — forms, live data, tables, statistics and even charts, with nothing to install or configure.
- Use it right away. Hit “Use this app”: it opens right in your browser, ready to go, with data saved only on your device.
- Your data stays yours. Everything is stored locally on your device, kept separate for each app: no account, no server, nothing to set up.
- Make it your own. The text below is the entire app: copy it, tweak fields, views and words, and your changes become interactive instantly.
- Share it as a file. An app is a simple file: save it, export it or share it with a link; and with encrypted collaboration several people edit the same data in real time.
The app’s source
# 🐷 Piggybank
The family budget in four pages: expenses, income, balance with a chart and savings goals.
:::::page{title="Expenses" icon="💸"}
## Log what goes out
::::form{path="expenses" id="fsp"}
::input{form="fsp" field="date" type="date" legend="When"}
::input{form="fsp" field="item" placeholder="What you bought" required="true"}
:::select{form="fsp" field="category" placeholder="Category..."}
- Home
- Food
- Transport
- Health
- Leisure
- Other
:::
::input{form="fsp" field="amount" type="number" placeholder="Amount (€)" required="true"}
::add-form{form="fsp" path="expenses" label="Add expense"}
::::
:::table{path="expenses" headers="When,Item,Category,Amount" deletable="true" editform="fsp"}
{date} | {item} | {category} | € {amount}
:::
**Total spending: € :sum[usc]{path="expenses" field="amount"}**
:::::
:::::page{title="Income" icon="💰"}
## Log what comes in
::::form{path="income" id="fen"}
::input{form="fen" field="date" type="date" legend="When"}
::input{form="fen" field="item" placeholder="Salary, refund, gift..." required="true"}
::input{form="fen" field="amount" type="number" placeholder="Amount (€)" required="true"}
::add-form{form="fen" path="income" label="Add income"}
::::
:::list{path="income" deletable="true"}
{date} — **{item}**: € {amount}
:::
**Total income: € :sum[ent]{path="income" field="amount"}**
:::::
:::::page{title="Balance" icon="📊"}
## How we are doing
Income: **€ :sum[be]{path="income" field="amount"}** ·
Spending: **€ :sum[bu]{path="expenses" field="amount"}**
:::python{data="expenses,income" packages="matplotlib"}
```
import matplotlib.pyplot as plt
expenses = data.get("expenses", [])
income = data.get("income", [])
tot_out = sum(float(r.get("amount", 0) or 0) for r in expenses)
tot_in = sum(float(r.get("amount", 0) or 0) for r in income)
print(f"Balance: € {tot_in - tot_out:.2f}")
if expenses:
percat = {}
for r in expenses:
c = r.get("category") or "Other"
percat[c] = percat.get(c, 0) + float(r.get("amount", 0) or 0)
fig, ax = plt.subplots(figsize=(6, 3.2))
ax.bar(list(percat.keys()), list(percat.values()))
ax.set_ylabel("€")
ax.set_title("Spending by category")
plt.xticks(rotation=20)
plt.tight_layout()
else:
print("Add a few expenses to see the chart by category.")
```
:::
:::::
:::::page{title="Goals" icon="🎯"}
## Savings milestones
::::form{path="goals" id="fob"}
::input{form="fob" field="name" placeholder="Goal (holiday, emergency fund...)" required="true"}
::input{form="fob" field="target" type="number" placeholder="Target (€)" required="true"}
::input{form="fob" field="saved" type="number" placeholder="Already set aside (€)"}
::add-form{form="fob" path="goals" label="Add goal"}
::::
:::list{path="goals" deletable="true" editform="fob"}
**{name}** — € {saved} of € {target}
:::
Set aside in total: **€ :sum[obm]{path="goals" field="saved"}** of
**€ :sum[obt]{path="goals" field="target"}** in targets.
:::::