Files
Moon-Household-Budget/templates/sinking.html
2026-06-06 19:12:43 -05:00

176 lines
7.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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 %}