Files
2026-06-06 14:22:57 -05:00

235 lines
13 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="page-header">
<h1>Edit Transaction</h1>
</div>
<div class="review-card">
<div class="review-meta">
<span class="review-date">{{ transaction.date }}</span>
<span class="review-account">{{ transaction.account }}</span>
</div>
<div class="review-description">{{ transaction.description }}</div>
<div class="review-amount">${{ transaction.amount | money }}</div>
<form method="POST" class="categorize-form">
<div class="form-group">
<label>Category</label>
<div class="category-grid">
{% for cat, subcats in categories.items() %}
<label class="cat-option" style="--cat-color: {{ colors.get(cat, '#ddd') }};">
<input type="radio" name="category" value="{{ cat }}"
{% if transaction.category == cat %}checked{% endif %}
onchange="updateSubcats(this.value)">
<span>{{ cat }}</span>
</label>
{% endfor %}
</div>
</div>
<div class="form-group" id="subcat-group">
<label>Subcategory <span class="optional">(optional)</span></label>
<select name="subcategory" id="subcat-select">
<option value="">— none —</option>
</select>
</div>
<div class="form-group">
<label>Notes <span class="optional">(optional)</span></label>
<input type="text" name="notes" value="{{ transaction.notes }}" placeholder="e.g. Costco run, birthday gift...">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="button" class="btn btn-ghost" onclick="toggleSplit()">Split transaction</button>
<a href="{{ url_for('transactions_view') }}" class="btn btn-ghost">Cancel</a>
</div>
</form>
<!-- Split transaction panel -->
<div id="split-panel" style="display:none;margin-top:1.5rem;">
<div class="budget-section">
<div class="budget-section-header" style="border-left-color:#D4A96A;">
<h3>Split Transaction — ${{ transaction.amount | money }} total</h3>
</div>
<p class="muted" style="margin-bottom:1rem;font-size:0.85rem;">Divide this charge across multiple categories. Amounts must add up to ${{ transaction.amount | money }}.</p>
<form method="POST" action="{{ url_for('split_transaction') }}">
<input type="hidden" name="id" value="{{ transaction.id }}">
<input type="hidden" name="redirect" value="transactions">
<table class="budget-table">
<thead><tr><th>Category</th><th>Subcategory</th><th>Amount</th><th>Note</th><th></th></tr></thead>
<tbody id="split-rows">
{% if transaction.splits %}
{% for s in transaction.splits %}
<tr class="split-row">
<td>
<select name="split_cat_{{ loop.index0 }}" class="budget-input split-cat" onchange="updateSplitSubcat(this, {{ loop.index0 }})" style="width:130px;">
<option value="">— pick —</option>
{% for cat in categories %}<option value="{{ cat }}" {% if s.category == cat %}selected{% endif %}>{{ cat }}</option>{% endfor %}
</select>
</td>
<td><select name="split_subcat_{{ loop.index0 }}" class="budget-input split-subcat" style="width:130px;"><option value="">— none —</option></select></td>
<td><input type="number" name="split_amt_{{ loop.index0 }}" step="0.01" min="0" class="budget-input split-amt" style="width:90px;" value="{{ s.amount }}" oninput="updateRemaining()"></td>
<td><input type="text" name="split_note_{{ loop.index0 }}" class="budget-input" style="width:110px;" value="{{ s.note or '' }}"></td>
<td><button type="button" onclick="this.closest('tr').remove();updateRemaining();" style="background:none;border:none;color:#c07070;cursor:pointer;font-size:1rem;"></button></td>
</tr>
{% endfor %}
{% else %}
<tr class="split-row">
<td><select name="split_cat_0" class="budget-input split-cat" onchange="updateSplitSubcat(this,0)" style="width:130px;"><option value="">— pick —</option>{% for cat in categories %}<option value="{{ cat }}">{{ cat }}</option>{% endfor %}</select></td>
<td><select name="split_subcat_0" class="budget-input split-subcat" style="width:130px;"><option value="">— none —</option></select></td>
<td><input type="number" name="split_amt_0" step="0.01" min="0" class="budget-input split-amt" style="width:90px;" placeholder="0.00" oninput="updateRemaining()"></td>
<td><input type="text" name="split_note_0" class="budget-input" style="width:110px;" placeholder="optional"></td>
<td></td>
</tr>
<tr class="split-row">
<td><select name="split_cat_1" class="budget-input split-cat" onchange="updateSplitSubcat(this,1)" style="width:130px;"><option value="">— pick —</option>{% for cat in categories %}<option value="{{ cat }}">{{ cat }}</option>{% endfor %}</select></td>
<td><select name="split_subcat_1" class="budget-input split-subcat" style="width:130px;"><option value="">— none —</option></select></td>
<td><input type="number" name="split_amt_1" step="0.01" min="0" class="budget-input split-amt" style="width:90px;" placeholder="0.00" oninput="updateRemaining()"></td>
<td><input type="text" name="split_note_1" class="budget-input" style="width:110px;" placeholder="optional"></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
<div style="margin:0.75rem 0;font-size:0.85rem;">
Remaining: <strong id="split-remaining">${{ transaction.amount | money }}</strong>
<button type="button" class="btn btn-ghost" style="margin-left:1rem;font-size:0.78rem;" onclick="addSplitRow()">+ Add row</button>
</div>
<div style="display:flex;gap:0.75rem;">
<button type="submit" class="btn btn-primary">Save Split</button>
<button type="button" class="btn btn-ghost" onclick="toggleSplit()">Cancel</button>
</div>
</form>
</div>
</div>
<div style="margin-top:1.25rem;padding-top:1.25rem;border-top:1px solid var(--border);display:flex;gap:0.5rem;flex-wrap:wrap;">
<form method="POST" action="{{ url_for('ignore_transaction') }}">
<input type="hidden" name="id" value="{{ transaction.id }}">
<input type="hidden" name="redirect" value="transactions">
<button type="submit" class="btn btn-ghost" style="color:var(--text-light);font-size:0.82rem;">✓ Already tracked — ignore</button>
</form>
<button type="button" class="btn btn-ghost" style="background:#e8f5e9;color:#3a7d44;border:1px solid #a5d6a7;font-size:0.82rem;" onclick="document.getElementById('sinking-picker').style.display='block';this.style.display='none';">🏦 Sinking Fund Transfer</button>
<div id="sinking-picker" style="display:none;background:#e8f5e9;border:1px solid #a5d6a7;border-radius:var(--radius);padding:0.75rem 1rem;width:100%;margin-top:0.25rem;">
<form method="POST" action="{{ url_for('categorize_sinking_transfer') }}">
<input type="hidden" name="id" value="{{ transaction.id }}">
<input type="hidden" name="redirect" value="transactions">
<label style="font-size:0.82rem;font-weight:600;display:block;margin-bottom:0.4rem;">Which sinking fund?</label>
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
<select name="sinking_fund_name" class="budget-input" style="min-width:180px;" required>
<option value="">— select fund —</option>
{% for f in sinking_funds %}
<option value="{{ f.name }}">{{ f.name }} (${{ (f.balance or 0) | money }})</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary" style="font-size:0.82rem;">Save</button>
<button type="button" class="btn btn-ghost" style="font-size:0.82rem;" onclick="document.getElementById('sinking-picker').style.display='none';this.closest('div').previousElementSibling.style.display='inline-block';">Cancel</button>
</div>
</form>
</div>
<form method="POST" action="{{ url_for('ignore_transaction') }}">
<input type="hidden" name="id" value="{{ transaction.id }}">
<input type="hidden" name="label" value="Investment Transfer">
<input type="hidden" name="redirect" value="transactions">
<button type="submit" class="btn btn-ghost" style="background:#e3f2fd;color:#1565c0;border:1px solid #90caf9;font-size:0.82rem;">📈 Investment Transfer</button>
</form>
<form method="POST" action="{{ url_for('ignore_transaction') }}">
<input type="hidden" name="id" value="{{ transaction.id }}">
<input type="hidden" name="label" value="Savings Transfer">
<input type="hidden" name="redirect" value="transactions">
<button type="submit" class="btn btn-ghost" style="background:#f3e5f5;color:#6a1b9a;border:1px solid #ce93d8;font-size:0.82rem;">💰 Savings Transfer</button>
</form>
</div>
</div>
<script>
const subcats = {{ categories | tojson }};
const current = {{ transaction | tojson }};
function updateSubcats(cat) {
const select = document.getElementById('subcat-select');
const group = document.getElementById('subcat-group');
const list = subcats[cat] || [];
select.innerHTML = '<option value="">— none —</option>';
list.forEach(s => {
const opt = document.createElement('option');
opt.value = s;
opt.textContent = s;
if (current.subcategory === s) opt.selected = true;
select.appendChild(opt);
});
group.style.display = list.length ? 'block' : 'none';
}
const checked = document.querySelector('input[name="category"]:checked');
if (checked) updateSubcats(checked.value);
document.querySelectorAll('input[name="category"]').forEach(r => {
r.addEventListener('change', () => updateSubcats(r.value));
});
// Split transaction
const totalAmount = {{ transaction.amount }};
const allSubcats = {{ categories | tojson }};
let splitRowCount = {{ transaction.splits | length if transaction.splits else 2 }};
function toggleSplit() {
const panel = document.getElementById('split-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
if (panel.style.display === 'block') updateRemaining();
}
function updateSplitSubcat(selectEl, idx) {
const cat = selectEl.value;
const subcatSel = document.querySelector(`select[name="split_subcat_${idx}"]`);
const list = allSubcats[cat] || [];
const current = subcatSel ? subcatSel.dataset.current || '' : '';
subcatSel.innerHTML = '<option value="">— none —</option>';
list.forEach(s => {
const opt = document.createElement('option');
opt.value = s; opt.textContent = s;
if (s === current) opt.selected = true;
subcatSel.appendChild(opt);
});
}
function updateRemaining() {
const inputs = document.querySelectorAll('.split-amt');
let used = 0;
inputs.forEach(el => used += parseFloat(el.value) || 0);
const remaining = Math.round((totalAmount - used) * 100) / 100;
const el = document.getElementById('split-remaining');
el.textContent = '$' + remaining.toFixed(2);
el.style.color = Math.abs(remaining) < 0.01 ? '#5A9E68' : (remaining < 0 ? '#c07070' : 'inherit');
}
function addSplitRow() {
const idx = splitRowCount++;
const tbody = document.getElementById('split-rows');
const row = document.createElement('tr');
row.className = 'split-row';
row.innerHTML = `
<td><select name="split_cat_${idx}" class="budget-input split-cat" onchange="updateSplitSubcat(this,${idx})" style="width:130px;">
<option value="">— pick —</option>
${Object.keys(allSubcats).map(c => `<option value="${c}">${c}</option>`).join('')}
</select></td>
<td><select name="split_subcat_${idx}" class="budget-input split-subcat" style="width:130px;"><option value="">— none —</option></select></td>
<td><input type="number" name="split_amt_${idx}" step="0.01" min="0" class="budget-input split-amt" style="width:90px;" placeholder="0.00" oninput="updateRemaining()"></td>
<td><input type="text" name="split_note_${idx}" class="budget-input" style="width:110px;" placeholder="optional"></td>
<td><button type="button" onclick="this.closest('tr').remove();updateRemaining();" style="background:none;border:none;color:#c07070;cursor:pointer;font-size:1rem;">✕</button></td>
`;
tbody.appendChild(row);
}
// Pre-populate subcats for existing splits
document.querySelectorAll('.split-cat').forEach((sel, idx) => {
if (sel.value) updateSplitSubcat(sel, idx);
});
// Open split panel automatically if transaction already has splits
{% if transaction.splits %}
toggleSplit();
{% endif %}
</script>
{% endblock %}