Initial commit — Moon Household Budget
This commit is contained in:
175
templates/sinking.html
Normal file
175
templates/sinking.html
Normal file
@@ -0,0 +1,175 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Sinking Funds</h1>
|
||||
</div>
|
||||
|
||||
<!-- Total savings banner -->
|
||||
<div class="sinking-total-bar">
|
||||
<div>
|
||||
<div class="sinking-total-label">Total Across All Funds</div>
|
||||
<div class="sinking-total-amount">${{ total | money }}</div>
|
||||
</div>
|
||||
<div class="sinking-total-note">Match this number to your savings account balance</div>
|
||||
</div>
|
||||
|
||||
<!-- Fund cards -->
|
||||
<div class="sinking-grid">
|
||||
{% for f in funds %}
|
||||
{% set color = fund_colors.get(f.name, '#C5BBA0') %}
|
||||
<div class="sinking-card" style="border-top: 4px solid {{ color }};">
|
||||
<div class="sinking-card-header">
|
||||
<div class="sinking-card-name">{{ f.name }}</div>
|
||||
<span class="sinking-type-badge {% if f.type == 'goal' %}badge-goal{% endif %}">
|
||||
{{ 'Goal' if f.type == 'goal' else 'Ongoing' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if f.notes %}
|
||||
<div class="sinking-notes">{{ f.notes }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="sinking-balance">${{ (f.balance or 0) | money }}</div>
|
||||
|
||||
{% if f.type == 'goal' and f.goal %}
|
||||
{% set pct = f.pct or 0 %}
|
||||
<div class="sinking-progress-wrap">
|
||||
<div class="sinking-progress-bar">
|
||||
<div class="sinking-progress-fill" style="width:{{ pct }}%;background:{{ color }};"></div>
|
||||
</div>
|
||||
<div class="sinking-progress-label">
|
||||
${{ (f.balance or 0) | money }} of ${{ f.goal | money }} ({{ pct }}%)
|
||||
</div>
|
||||
</div>
|
||||
{% if f.monthly_needed %}
|
||||
<div class="sinking-monthly-needed">
|
||||
Save <strong>${{ f.monthly_needed | money }}/mo</strong>
|
||||
to reach goal
|
||||
{% if f.months_left %} in {{ f.months_left }} months{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if f.monthly %}
|
||||
<div class="sinking-monthly-needed">
|
||||
Monthly contribution: <strong>${{ f.monthly | money }}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Contribute / Withdraw -->
|
||||
<div class="sinking-actions">
|
||||
<form method="POST" action="{{ url_for('sinking_contribute') }}" class="sinking-action-form">
|
||||
<input type="hidden" name="name" value="{{ f.name }}">
|
||||
<input type="number" name="amount" step="0.01" min="0" placeholder="Amount" class="sinking-amount-input">
|
||||
<button type="submit" class="btn btn-accept">+ Add</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('sinking_withdraw') }}" class="sinking-action-form">
|
||||
<input type="hidden" name="name" value="{{ f.name }}">
|
||||
<input type="number" name="amount" step="0.01" min="0" placeholder="Amount" class="sinking-amount-input">
|
||||
<button type="submit" class="btn btn-ghost">− Use</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Edit settings -->
|
||||
<details class="sinking-edit-details">
|
||||
<summary class="sinking-edit-toggle">Edit settings</summary>
|
||||
<div class="sinking-edit-form">
|
||||
<div class="form-group">
|
||||
<label>Correct Balance <span class="optional">(override if something's off)</span></label>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;">
|
||||
<input type="number" step="0.01" id="balance-override-{{ loop.index }}" placeholder="{{ '%.2f' | format(f.balance or 0) }}" style="flex:1;">
|
||||
<button type="button" class="btn btn-ghost" style="font-size:0.82rem;white-space:nowrap;"
|
||||
onclick="setBalance('{{ f.name }}', document.getElementById('balance-override-{{ loop.index }}').value)">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Type</label>
|
||||
<select onchange="updateFund('{{ f.name }}', 'type', this.value)">
|
||||
<option value="ongoing" {% if f.type != 'goal' %}selected{% endif %}>Ongoing</option>
|
||||
<option value="goal" {% if f.type == 'goal' %}selected{% endif %}>Goal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Monthly Contribution</label>
|
||||
<input type="number" step="0.01" value="{{ f.monthly or '' }}" placeholder="0.00"
|
||||
onchange="updateFund('{{ f.name }}', 'monthly', this.value)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Goal Amount <span class="optional">(if goal type)</span></label>
|
||||
<input type="number" step="0.01" value="{{ f.goal or '' }}" placeholder="0.00"
|
||||
onchange="updateFund('{{ f.name }}', 'goal', this.value)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Target Date <span class="optional">(if goal type)</span></label>
|
||||
<input type="date" value="{{ f.target_date or '' }}"
|
||||
onchange="updateFund('{{ f.name }}', 'target_date', this.value)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Notes</label>
|
||||
<input type="text" value="{{ f.notes or '' }}" placeholder="Optional note..."
|
||||
onchange="updateFund('{{ f.name }}', 'notes', this.value)">
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Add new fund card -->
|
||||
<div class="sinking-card sinking-add-card">
|
||||
<div class="sinking-card-name" style="margin-bottom:1rem;">+ New Fund</div>
|
||||
<form method="POST" action="{{ url_for('sinking_add') }}">
|
||||
<div class="form-group">
|
||||
<label>Fund Name</label>
|
||||
<input type="text" name="name" placeholder="e.g. New Car, Vacation" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Type</label>
|
||||
<select name="type">
|
||||
<option value="ongoing">Ongoing</option>
|
||||
<option value="goal">Goal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Starting Balance <span class="optional">(optional)</span></label>
|
||||
<input type="number" name="balance" step="0.01" min="0" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Monthly Contribution</label>
|
||||
<input type="number" name="monthly" step="0.01" min="0" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Goal Amount <span class="optional">(if goal)</span></label>
|
||||
<input type="number" name="goal" step="0.01" min="0" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Target Date <span class="optional">(if goal)</span></label>
|
||||
<input type="date" name="target_date">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Notes <span class="optional">(optional)</span></label>
|
||||
<input type="text" name="notes" placeholder="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add Fund</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateFund(name, field, value) {
|
||||
fetch('/sinking/update', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({name, field, value})
|
||||
});
|
||||
}
|
||||
|
||||
function setBalance(name, value) {
|
||||
if (value === '' || isNaN(parseFloat(value))) return;
|
||||
fetch('/sinking/update', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({name, field: 'balance', value})
|
||||
}).then(() => location.reload());
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user