Initial commit — Moon Household Budget
This commit is contained in:
159
templates/bills.html
Normal file
159
templates/bills.html
Normal file
@@ -0,0 +1,159 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Manage Bills</h1>
|
||||
<a href="{{ url_for('bill_calendar') }}" class="btn btn-ghost">View Calendar</a>
|
||||
</div>
|
||||
|
||||
<div class="card-wide">
|
||||
<h2>Add a Bill</h2>
|
||||
<form method="POST" action="{{ url_for('bill_add') }}" class="bill-form">
|
||||
<div class="bill-form-grid">
|
||||
<div class="form-group">
|
||||
<label>Bill Name</label>
|
||||
<input type="text" name="name" placeholder="e.g. Verizon, Affirm (home)" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Amount</label>
|
||||
<input type="number" name="amount" step="0.01" min="0" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Due Day of Month</label>
|
||||
<input type="number" name="due_day" min="1" max="31" placeholder="1–31" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Category</label>
|
||||
<select name="category" id="add-category" onchange="updateAddSubcats()">
|
||||
<option value="">— select —</option>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat }}">{{ cat }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Subcategory <span class="optional">(optional)</span></label>
|
||||
<select name="subcategory" id="add-subcategory">
|
||||
<option value="">— none —</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Notes <span class="optional">(optional)</span></label>
|
||||
<input type="text" name="notes" placeholder="e.g. T=Theresa, B=both">
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:0.5rem;padding-top:0.5rem;">
|
||||
<input type="checkbox" name="autopay" id="autopay-new">
|
||||
<label for="autopay-new" style="margin:0;font-weight:400;">On autopay (no reminder needed)</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add Bill</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if bills %}
|
||||
<section class="section">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.75rem;margin-bottom:0.75rem;">
|
||||
<h2>Your Bills <span class="muted">({{ bills | length }} total)</span></h2>
|
||||
<input type="text" id="bills-search" placeholder="Search bills…" class="budget-input" style="width:220px;" oninput="filterBills(this.value)">
|
||||
</div>
|
||||
<table class="txn-table txn-table-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Due Day</th>
|
||||
<th>Name</th>
|
||||
<th>Amount</th>
|
||||
<th>Category</th>
|
||||
<th>Autopay</th>
|
||||
<th>Remind Me</th>
|
||||
<th>Notes</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for b in bills | sort(attribute='due_day') %}
|
||||
<tr>
|
||||
<td class="amount">{{ b.due_day }}</td>
|
||||
<td>{{ b.name }}</td>
|
||||
<td class="amount">{% if b.amount %}${{ b.amount | money }}{% else %}<span class="muted">varies</span>{% endif %}</td>
|
||||
<td>
|
||||
{% if b.category %}
|
||||
<span class="tag" style="background: {{ colors.get(b.category, '#ddd') }};">
|
||||
{{ b.category }}{% if b.subcategory %} › {{ b.subcategory }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button class="toggle-btn {% if b.get('autopay') %}toggle-on{% endif %}"
|
||||
onclick="toggle('{{ b.id }}', 'autopay', this)">
|
||||
{{ '✓ Yes' if b.get('autopay') else 'No' }}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="toggle-btn {% if b.get('remind', not b.get('autopay')) %}toggle-on{% endif %}"
|
||||
onclick="toggle('{{ b.id }}', 'remind', this)">
|
||||
{{ '🔔 Yes' if b.get('remind', not b.get('autopay')) else 'Off' }}
|
||||
</button>
|
||||
</td>
|
||||
<td class="muted">{{ b.notes }}</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ url_for('bill_delete', bill_id=b.id) }}"
|
||||
onsubmit="return confirm('Delete {{ b.name }}?');" style="display:inline;">
|
||||
<button type="submit" class="edit-link" style="background:none;border:none;cursor:pointer;color:#C5A0A0;">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p>No bills yet. Add your first one above.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="reminder-bar">
|
||||
<span>Run reminders now to test your email & Mac notifications:</span>
|
||||
<button class="btn btn-ghost" onclick="runReminders(this)">Send Test Reminder</button>
|
||||
<span class="reminder-result" id="reminder-result"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const subcats = {{ categories | tojson }};
|
||||
|
||||
function updateAddSubcats() {
|
||||
const cat = document.getElementById('add-category').value;
|
||||
const sel = document.getElementById('add-subcategory');
|
||||
sel.innerHTML = '<option value="">— none —</option>';
|
||||
(subcats[cat] || []).forEach(s => {
|
||||
const o = document.createElement('option');
|
||||
o.value = s; o.textContent = s;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
}
|
||||
|
||||
function toggle(billId, field, btn) {
|
||||
fetch(`/bills/toggle/${billId}/${field}`, { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(() => location.reload());
|
||||
}
|
||||
|
||||
function filterBills(q) {
|
||||
q = q.toLowerCase();
|
||||
document.querySelectorAll('.txn-table tbody tr').forEach(row => {
|
||||
row.style.display = row.textContent.toLowerCase().includes(q) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function runReminders(btn) {
|
||||
btn.textContent = 'Sending…';
|
||||
btn.disabled = true;
|
||||
fetch('/reminders/run', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
document.getElementById('reminder-result').textContent = data.message;
|
||||
btn.textContent = 'Send Test Reminder';
|
||||
btn.disabled = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user