Files
2026-06-06 19:12:43 -05:00

229 lines
8.9 KiB
HTML
Raw Permalink 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>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="131" 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 id="row-{{ b.id }}">
<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 style="white-space:nowrap;">
<button class="edit-link" onclick="openEdit('{{ b.id }}')" style="background:none;border:none;cursor:pointer;color:#8B7BAB;margin-right:0.5rem;">Edit</button>
<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>
<tr id="edit-{{ b.id }}" style="display:none;background:var(--bg-card,#faf8f5);">
<td colspan="8" style="padding:1rem;">
<form method="POST" action="{{ url_for('bill_edit', bill_id=b.id) }}" class="bill-form">
<div class="bill-form-grid">
<div class="form-group">
<label>Bill Name</label>
<input type="text" name="name" value="{{ b.name }}" required>
</div>
<div class="form-group">
<label>Amount</label>
<input type="number" name="amount" step="0.01" min="0" value="{{ b.amount or '' }}" placeholder="0.00">
</div>
<div class="form-group">
<label>Due Day of Month</label>
<input type="number" name="due_day" min="1" max="31" value="{{ b.due_day }}" required>
</div>
<div class="form-group">
<label>Category</label>
<select name="category" id="edit-cat-{{ b.id }}" onchange="updateEditSubcats('{{ b.id }}')">
<option value="">— select —</option>
{% for cat in categories %}
<option value="{{ cat }}" {% if b.category == cat %}selected{% endif %}>{{ cat }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>Subcategory <span class="optional">(optional)</span></label>
<select name="subcategory" id="edit-subcat-{{ b.id }}">
<option value="">— none —</option>
{% for s in categories.get(b.category, []) %}
<option value="{{ s }}" {% if b.subcategory == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>Notes <span class="optional">(optional)</span></label>
<input type="text" name="notes" value="{{ b.notes or '' }}">
</div>
</div>
<div style="display:flex;gap:0.5rem;margin-top:0.5rem;">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-ghost" onclick="closeEdit('{{ b.id }}')">Cancel</button>
</div>
</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 openEdit(id) {
document.querySelectorAll('[id^="edit-"]').forEach(r => r.style.display = 'none');
document.getElementById('edit-' + id).style.display = '';
document.getElementById('row-' + id).classList.add('editing');
}
function closeEdit(id) {
document.getElementById('edit-' + id).style.display = 'none';
document.getElementById('row-' + id).classList.remove('editing');
}
function updateEditSubcats(id) {
const cat = document.getElementById('edit-cat-' + id).value;
const sel = document.getElementById('edit-subcat-' + id);
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 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 %}