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

278 lines
10 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 %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<div class="page-header">
<h1>Income — {{ month_name }} {{ year }}</h1>
<div class="month-nav">
<a href="{{ url_for('income_view', year=prev_year, month=prev_month) }}" class="btn btn-ghost"> Prev</a>
<a href="{{ url_for('income_view', year=next_year, month=next_month) }}" class="btn btn-ghost">Next </a>
</div>
</div>
<!-- YTD Summary Cards -->
<div class="card-grid" style="margin-bottom:1.5rem;">
<div class="card">
<div class="card-label">{{ month_name }} Total</div>
<div class="card-value">${{ month_total | money }}</div>
</div>
<div class="card">
<div class="card-label">{{ year }} YTD Total</div>
<div class="card-value">${{ ytd_total | money }}</div>
</div>
<div class="card">
<div class="card-label">Monthly Average</div>
<div class="card-value">
{% set months_with_income = ytd_by_month | length %}
${{ (ytd_total / months_with_income if months_with_income else 0) | money }}
</div>
</div>
</div>
<!-- Tax estimate banner -->
<div style="background:#fff8e6;border:1px solid #e8d49a;border-radius:var(--radius);padding:1rem 1.25rem;margin-bottom:1.5rem;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;">
<div>
<div style="font-weight:600;margin-bottom:0.2rem;">🧾 {{ year }} Tax Estimate — based on ${{ ytd_total | money }} YTD income</div>
<div style="font-size:0.82rem;color:var(--text-light);">
~${{ tax_estimate.total | money }} owed &nbsp;·&nbsp;
${{ tax_set_aside | money }} set aside &nbsp;·&nbsp;
{% if tax_still_needed > 0 %}
<span style="color:#c07070;font-weight:600;">${{ tax_still_needed | money }} still needed</span>
{% else %}
<span style="color:#5A9E68;font-weight:600;">fully covered ✓</span>
{% endif %}
</div>
</div>
<a href="{{ url_for('taxes_view') }}" class="btn btn-ghost" style="font-size:0.82rem;">View Tax Details →</a>
</div>
<!-- This Month by Stream -->
<div class="budget-section">
<div class="budget-section-header" style="border-left-color: #A0C5A8;">
<h3>{{ month_name }} {{ year }} — by Stream</h3>
</div>
<table class="budget-table">
<thead>
<tr><th>Stream</th><th>Owner</th><th>This Month</th><th>YTD</th><th></th></tr>
</thead>
<tbody>
{% for s in income_streams %}
<tr>
<td><strong>{{ s.name }}</strong></td>
<td><span class="owner-tag" style="background:{{ owner_colors.get(s.owner,'#ddd') }};font-size:0.72rem;">{{ s.owner }}</span></td>
<td class="amount">${{ by_stream.get(s.name, 0) | money }}</td>
<td class="amount muted">${{ ytd_by_stream.get(s.name, 0) | money }}</td>
<td>
<button class="btn btn-ghost" style="font-size:0.75rem;padding:0.2rem 0.5rem;"
onclick="openLogModal('{{ s.name }}')">+ Log</button>
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="2">Total</td>
<td class="amount">${{ month_total | money }}</td>
<td class="amount muted">${{ ytd_total | money }}</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
<!-- YTD by Month -->
{% if ytd_by_month %}
<div class="budget-section" style="margin-top:1.5rem;">
<div class="budget-section-header" style="border-left-color: #C5BBA0;">
<h3>{{ year }} — Month by Month</h3>
</div>
<div style="padding:1rem 0.5rem;">
<canvas id="income-monthly-chart" height="80"></canvas>
</div>
<table class="budget-table" style="margin-top:0.5rem;">
<thead><tr><th>Month</th><th>Total Income</th></tr></thead>
<tbody>
{% for mk in sorted_months %}
{% set amt = ytd_by_month[mk] %}
<tr>
<td>{{ mk }}</td>
<td class="amount">${{ amt | money }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="total-row">
<td>YTD Total</td>
<td class="amount">${{ ytd_total | money }}</td>
</tr>
</tfoot>
</table>
</div>
{% endif %}
<!-- Log Entries for This Month -->
{% if entries %}
<div class="budget-section" style="margin-top:1.5rem;">
<div class="budget-section-header" style="border-left-color: #B0CDD6;">
<h3>{{ month_name }} Entries</h3>
</div>
<table class="budget-table">
<thead><tr><th>Stream</th><th>Amount</th><th>Date Logged</th><th>Note</th><th></th></tr></thead>
<tbody>
{% for e in entries | sort(attribute='date', reverse=True) %}
<tr>
<td>{{ e.stream }}</td>
<td class="amount">${{ e.amount | money }}</td>
<td class="muted">{{ e.date }}</td>
<td class="muted">{{ e.note or '—' }}</td>
<td>
<form method="POST" action="/income/delete/{{ e.id }}" style="display:inline;">
<input type="hidden" name="year" value="{{ year }}">
<input type="hidden" name="month" value="{{ month }}">
<button type="submit" class="btn btn-ghost" style="font-size:0.72rem;padding:0.15rem 0.4rem;color:#c07070;"
onclick="return confirm('Delete this entry?')"></button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Log Income Modal -->
<div id="income-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.3);z-index:100;align-items:center;justify-content:center;">
<div style="background:#fff;border-radius:12px;padding:1.5rem;min-width:320px;max-width:420px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,0.18);">
<h3 style="margin:0 0 1rem;">Log Income</h3>
<form method="POST" action="/income/log">
<input type="hidden" name="month_str" value="{{ '%04d-%02d' | format(year, month) }}">
<div style="margin-bottom:0.75rem;">
<label style="display:block;font-size:0.85rem;margin-bottom:0.25rem;">Stream</label>
<select name="stream" id="modal-stream" class="form-input" style="width:100%;">
{% for s in income_streams %}
<option value="{{ s.name }}">{{ s.name }} ({{ s.owner }})</option>
{% endfor %}
</select>
</div>
<div style="margin-bottom:0.75rem;">
<label style="display:block;font-size:0.85rem;margin-bottom:0.25rem;">Amount</label>
<input type="number" name="amount" step="0.01" min="0" class="form-input budget-input" style="width:100%;" placeholder="0.00" required>
</div>
<div style="margin-bottom:1rem;">
<label style="display:block;font-size:0.85rem;margin-bottom:0.25rem;">Note (optional)</label>
<input type="text" name="note" class="form-input budget-input" style="width:100%;" placeholder="e.g. paycheck, client payment…">
</div>
<div style="display:flex;gap:0.75rem;justify-content:flex-end;">
<button type="button" class="btn btn-ghost" onclick="closeModal()">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
<script>
function openLogModal(stream) {
const modal = document.getElementById('income-modal');
modal.style.display = 'flex';
if (stream) {
document.getElementById('modal-stream').value = stream;
}
}
function closeModal() {
document.getElementById('income-modal').style.display = 'none';
}
document.getElementById('income-modal').addEventListener('click', function(e) {
if (e.target === this) closeModal();
});
</script>
<div style="margin-top:1.5rem;">
<button class="btn btn-primary" onclick="openLogModal('')">+ Log Income</button>
</div>
<!-- Bulk Backfill -->
<div class="budget-section" style="margin-top:2rem;">
<div class="budget-section-header" style="border-left-color:#C5BBA0;">
<h3>Backfill Past Months</h3>
</div>
<p class="muted" style="padding:0.5rem 0 1rem;">Enter totals for any month you haven't logged yet. Leave blank to skip. Existing entries won't be duplicated.</p>
<form method="POST" action="/income/backfill">
<input type="hidden" name="year" value="{{ year }}">
<div style="overflow-x:auto;">
<table class="budget-table">
<thead>
<tr>
<th>Month</th>
{% for s in income_streams %}
<th style="min-width:110px;">
{{ s.name }}<br>
<span class="owner-tag" style="background:{{ owner_colors.get(s.owner,'#ddd') }};font-size:0.68rem;">{{ s.owner }}</span>
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for m in range(1, 13) %}
{% set mk = '%04d-%02d' | format(year, m) %}
{% set mn = month_names[m] %}
{% set is_current = (m == month) %}
<tr{% if is_current %} style="background:#f5f2ee;"{% endif %}>
<td>
<strong>{{ mn }}</strong>
{% if is_current %}<span class="muted" style="font-size:0.75rem;"> (current)</span>{% endif %}
{% if ytd_by_month.get(mk) %}
<span style="font-size:0.72rem;color:#7A9E78;margin-left:0.25rem;">✓ ${{ "%.0f"|format(ytd_by_month[mk]) }}</span>
{% endif %}
</td>
{% for s in income_streams %}
<td>
<input type="number" step="0.01" min="0"
name="amt_{{ m }}_{{ s.name | replace(' ','_') }}"
class="budget-input" style="width:100px;"
placeholder="0.00">
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="margin-top:1rem;">
<button type="submit" class="btn btn-primary">Save All</button>
</div>
</form>
</div>
<script>
{% if ytd_by_month %}
const incomeLabels = {{ sorted_months | tojson }};
const incomeValues = [{% for mk in sorted_months %}{{ ytd_by_month[mk] }},{% endfor %}];
new Chart(document.getElementById('income-monthly-chart'), {
type: 'bar',
data: {
labels: incomeLabels,
datasets: [{
label: 'Income',
data: incomeValues,
backgroundColor: '#7AB87A',
borderRadius: 6,
borderSkipped: false,
}]
},
options: {
plugins: {
legend: { display: false },
tooltip: { callbacks: { label: ctx => ' $' + ctx.parsed.y.toFixed(2) } }
},
scales: {
x: { grid: { display: false } },
y: { grid: { color: '#f0ece6' }, ticks: { callback: v => '$' + v.toLocaleString() } }
},
animation: { duration: 400 },
}
});
{% endif %}
</script>
{% endblock %}