- Merge Sharing + Household → /people (tab switcher: sharing | household) - Merge Accounts + Categories → /settings (tab switcher: accounts | categories) - Add Analysis dropdown in nav: Reports, Projections, Tax, Net Worth, What If - Add Settings dropdown in nav: Accounts & Categories, Import CSV, Import Guide - Legacy GET /sharing, /household, /accounts, /categories redirect 301 to new URLs - Remove Import and Import Guide as standalone nav links - New People handler consolidates all people-related mutations (_action field) - New Settings handler renders both account and category lists in one page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
192 lines
8.4 KiB
HTML
192 lines
8.4 KiB
HTML
{{template "base" .}}
|
|
|
|
{{define "content"}}
|
|
{{$d := .}}
|
|
<style>
|
|
.tab-bar { display:flex; gap:4px; border-bottom:2px solid var(--border); margin-bottom:24px; }
|
|
.tab-bar a { padding:8px 18px; font-size:0.9rem; font-weight:600; color:var(--muted); text-decoration:none; border-bottom:2px solid transparent; margin-bottom:-2px; transition:all 0.15s; }
|
|
.tab-bar a.active { color:var(--accent); border-bottom-color:var(--accent); }
|
|
.tab-bar a:hover:not(.active) { color:var(--text); }
|
|
.s-card { background:var(--card); border:1px solid var(--border); border-radius:12px; padding:22px 24px; margin-bottom:16px; }
|
|
.s-card h3 { margin:0 0 14px; font-size:0.95rem; font-weight:700; }
|
|
.form-row { display:flex; gap:10px; flex-wrap:wrap; }
|
|
.form-row input, .form-row select { flex:1; min-width:120px; padding:8px 12px; border:1px solid var(--border); border-radius:8px; background:var(--bg); color:var(--text); font-size:0.875rem; }
|
|
.btn { padding:8px 18px; border:none; border-radius:8px; font-size:0.875rem; font-weight:600; cursor:pointer; }
|
|
.btn-primary { background:var(--accent); color:#fff; }
|
|
.btn-danger { background:transparent; border:1px solid #f4433660; color:#f44336; font-size:0.8rem; padding:4px 10px; border-radius:6px; }
|
|
.btn-danger:hover { background:#f4433615; }
|
|
table { width:100%; border-collapse:collapse; font-size:0.875rem; }
|
|
th { text-align:left; padding:8px 12px; color:var(--muted); font-weight:600; border-bottom:2px solid var(--border); }
|
|
td { padding:10px 12px; border-bottom:1px solid var(--border); vertical-align:middle; }
|
|
tr:last-child td { border-bottom:none; }
|
|
.color-dot { display:inline-block; width:12px; height:12px; border-radius:50%; margin-right:6px; vertical-align:middle; }
|
|
.type-badge { display:inline-block; padding:2px 8px; border-radius:20px; font-size:0.72rem; font-weight:600; background:var(--bg); border:1px solid var(--border); color:var(--muted); }
|
|
.empty-state { padding:32px; text-align:center; color:var(--muted); font-size:0.875rem; }
|
|
</style>
|
|
|
|
<h1 style="margin:0 0 20px;">Settings</h1>
|
|
|
|
<div class="tab-bar">
|
|
<a href="/settings?tab=accounts" class="{{if eq $d.Tab "accounts"}}active{{end}}">Accounts</a>
|
|
<a href="/settings?tab=categories" class="{{if eq $d.Tab "categories"}}active{{end}}">Categories</a>
|
|
</div>
|
|
|
|
{{if eq $d.Tab "accounts"}}
|
|
|
|
<div class="s-card">
|
|
<h3>Add account</h3>
|
|
<form method="post" action="/accounts" id="add-account-form">
|
|
<div class="form-row">
|
|
<input type="text" name="name" placeholder="Account name" required>
|
|
<select name="type">
|
|
<option value="checking">Checking</option>
|
|
<option value="savings">Savings</option>
|
|
<option value="credit">Credit card</option>
|
|
<option value="securities">Securities</option>
|
|
</select>
|
|
<button type="submit" class="btn btn-primary">Add</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="s-card">
|
|
{{if $d.Accounts}}
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Type</th><th></th></tr></thead>
|
|
<tbody>
|
|
{{range $d.Accounts}}
|
|
<tr>
|
|
<td style="font-weight:500;">{{.Name}}</td>
|
|
<td><span class="type-badge">{{.Type}}</span></td>
|
|
<td style="text-align:right;">
|
|
<button class="btn btn-danger" onclick="deleteAccount('{{.ID}}')">Delete</button>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
{{else}}
|
|
<div class="empty-state">No accounts yet — add one above.</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{else}}{{/* categories tab */}}
|
|
|
|
<div class="s-card">
|
|
<h3>Add category</h3>
|
|
<form method="post" action="/categories" id="add-cat-form">
|
|
<div class="form-row">
|
|
<input type="text" name="name" placeholder="Category name" required>
|
|
<input type="number" name="budget_cents" placeholder="Monthly budget (cents)" min="0">
|
|
<input type="color" name="color" value="#6366f1" style="flex:0; width:44px; padding:4px; border-radius:8px; cursor:pointer;">
|
|
<button type="submit" class="btn btn-primary">Add</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="s-card">
|
|
{{if $d.Categories}}
|
|
<table>
|
|
<thead><tr><th>Category</th><th style="text-align:right;">Monthly Budget</th><th></th></tr></thead>
|
|
<tbody>
|
|
{{range $d.Categories}}
|
|
<tr id="cat-row-{{.ID}}">
|
|
<td>
|
|
<span class="color-dot" style="background:{{if .Color}}{{.Color}}{{else}}#9e9e9e{{end}};"></span>
|
|
<span id="cat-name-{{.ID}}">{{.Name}}</span>
|
|
</td>
|
|
<td style="text-align:right;">
|
|
{{if .BudgetCents}}
|
|
<span id="cat-budget-{{.ID}}">€{{cents .BudgetCents}}</span>
|
|
{{else}}
|
|
<span id="cat-budget-{{.ID}}" style="color:var(--muted);">—</span>
|
|
{{end}}
|
|
<button onclick="editCat('{{.ID}}','{{.Name}}',{{.BudgetCents}},'{{.Color}}')"
|
|
style="margin-left:8px; background:none; border:none; cursor:pointer; color:var(--muted); font-size:0.85rem;">✎</button>
|
|
</td>
|
|
<td style="text-align:right;">
|
|
<button class="btn btn-danger" onclick="deleteCat('{{.ID}}')">Delete</button>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
{{else}}
|
|
<div class="empty-state">No categories yet — add one above.</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{end}}
|
|
|
|
<!-- Edit category modal -->
|
|
<div id="edit-cat-modal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); backdrop-filter:blur(4px); z-index:300; align-items:center; justify-content:center;">
|
|
<div class="s-card" style="width:380px; max-width:95vw; margin:0; box-shadow:var(--shadow-lg);">
|
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
|
|
<span style="font-weight:700;">Edit Category</span>
|
|
<button onclick="closeEditCat()" style="background:none; border:none; cursor:pointer; color:var(--muted); font-size:1.1rem;">✕</button>
|
|
</div>
|
|
<div style="display:flex; flex-direction:column; gap:10px;">
|
|
<input id="edit-cat-name" type="text" placeholder="Name" style="padding:8px 12px; border:1px solid var(--border); border-radius:8px; background:var(--bg); color:var(--text);">
|
|
<input id="edit-cat-budget" type="number" placeholder="Budget (cents)" min="0" style="padding:8px 12px; border:1px solid var(--border); border-radius:8px; background:var(--bg); color:var(--text);">
|
|
<input id="edit-cat-color" type="color" style="width:100%; height:38px; padding:4px; border:1px solid var(--border); border-radius:8px; cursor:pointer;">
|
|
</div>
|
|
<div style="display:flex; gap:8px; justify-content:flex-end; margin-top:16px;">
|
|
<button onclick="closeEditCat()" class="btn" style="background:var(--bg); border:1px solid var(--border); color:var(--text);">Cancel</button>
|
|
<button onclick="saveEditCat()" class="btn btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let editingCatID = null;
|
|
|
|
function deleteAccount(id) {
|
|
if (!confirm('Delete this account?')) return;
|
|
fetch('/accounts/' + id, { method: 'DELETE' })
|
|
.then(r => { if (r.ok) location.reload(); });
|
|
}
|
|
|
|
function deleteCat(id) {
|
|
if (!confirm('Delete this category?')) return;
|
|
fetch('/categories/' + id, { method: 'DELETE' })
|
|
.then(r => { if (r.ok) location.reload(); });
|
|
}
|
|
|
|
function editCat(id, name, budgetCents, color) {
|
|
editingCatID = id;
|
|
document.getElementById('edit-cat-name').value = name;
|
|
document.getElementById('edit-cat-budget').value = budgetCents || '';
|
|
document.getElementById('edit-cat-color').value = color || '#6366f1';
|
|
document.getElementById('edit-cat-modal').style.display = 'flex';
|
|
}
|
|
|
|
function closeEditCat() {
|
|
document.getElementById('edit-cat-modal').style.display = 'none';
|
|
editingCatID = null;
|
|
}
|
|
|
|
function saveEditCat() {
|
|
if (!editingCatID) return;
|
|
const body = new URLSearchParams({
|
|
name: document.getElementById('edit-cat-name').value,
|
|
budget_cents: document.getElementById('edit-cat-budget').value || '0',
|
|
color: document.getElementById('edit-cat-color').value,
|
|
});
|
|
fetch('/categories/' + editingCatID, { method: 'PUT', body })
|
|
.then(r => { if (r.ok) { closeEditCat(); location.reload(); } });
|
|
}
|
|
|
|
// After adding account/category, redirect back to same tab
|
|
document.getElementById('add-account-form')?.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
fetch(this.action, { method: 'POST', body: new FormData(this) })
|
|
.then(r => { if (r.ok || r.redirected) location.reload(); });
|
|
});
|
|
document.getElementById('add-cat-form')?.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
fetch(this.action, { method: 'POST', body: new FormData(this) })
|
|
.then(r => { if (r.ok || r.redirected) location.reload(); });
|
|
});
|
|
</script>
|
|
{{end}}
|