06/05 Optimize app: report upgrades
This commit is contained in:
@@ -134,12 +134,67 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Month-over-month comparison (monthly report only) -->
|
||||
{% if report_type == 'monthly' and mom_data %}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="pcard">
|
||||
<div class="pcard-title mb-3">Month-over-Month Comparison</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm" style="font-size:13px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:2px solid var(--border);">
|
||||
<th>Category</th>
|
||||
<th class="text-end">This Month</th>
|
||||
<th class="text-end">Last Month</th>
|
||||
<th class="text-end d-none d-md-table-cell">3-Mo Avg</th>
|
||||
<th class="text-end">Change</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in mom_data %}
|
||||
<tr>
|
||||
<td>
|
||||
<span style="display:inline-block;width:8px;height:8px;border-radius:2px;background:{{ row.color }};margin-right:6px;"></span>
|
||||
{{ row.icon }} {{ row.name }}
|
||||
</td>
|
||||
<td class="text-end mono">{{ row.this_month | currency }}</td>
|
||||
<td class="text-end mono text-muted">{{ row.last_month | currency }}</td>
|
||||
<td class="text-end mono text-muted d-none d-md-table-cell">{{ row.avg_3mo | currency }}</td>
|
||||
<td class="text-end">
|
||||
{% if row.change_pct is none %}
|
||||
<span class="text-muted">—</span>
|
||||
{% elif row.change_pct > 0 %}
|
||||
<span class="badge text-expense" style="background:#ef444415;font-size:11px;">+{{ row.change_pct | round(0) | int }}%</span>
|
||||
{% elif row.change_pct < 0 %}
|
||||
<span class="badge text-income" style="background:#10b98115;font-size:11px;">{{ row.change_pct | round(0) | int }}%</span>
|
||||
{% else %}
|
||||
<span class="text-muted" style="font-size:11px;">0%</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Net worth history -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="pcard">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="pcard-title mb-0">Net Worth History</span>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<span class="pcard-title mb-0">Net Worth History</span>
|
||||
{% if nw_history.projected_1yr is defined and nw_history.projected_1yr %}
|
||||
<span class="badge" style="background:#3b82f615;color:#3b82f6;font-size:11px;font-weight:500;">
|
||||
Proj. 1yr: {{ nw_history.projected_1yr | currency }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<form method="POST" action="{{ url_for('reports.manual_snapshot') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
@@ -257,13 +312,28 @@ const fmtCur = v => sym + Math.abs(v).toLocaleString(undefined, {minimumFraction
|
||||
{% if nw_history.count > 1 %}
|
||||
(function(){
|
||||
const ctx = document.getElementById('nwChart').getContext('2d');
|
||||
const histLabels = {{ nw_history.labels | tojson }};
|
||||
const histValues = {{ nw_history['values'] | tojson }};
|
||||
{% if nw_history.proj_labels is defined and nw_history.proj_labels %}
|
||||
const projLabels = {{ nw_history.proj_labels | tojson }};
|
||||
const projValues = {{ nw_history.proj_values | tojson }};
|
||||
// Stitch: last actual point is first projected point
|
||||
const allLabels = histLabels.concat(projLabels);
|
||||
const histPad = new Array(projLabels.length).fill(null);
|
||||
const projPad = new Array(histLabels.length - 1).fill(null);
|
||||
{% else %}
|
||||
const allLabels = histLabels;
|
||||
{% endif %}
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: {{ nw_history.labels | tojson }},
|
||||
labels: allLabels,
|
||||
datasets: [
|
||||
{ label:'Net Worth', data: {{ nw_history['values'] | tojson }}, borderColor:'#3b82f6', backgroundColor:'#3b82f611', borderWidth:2, pointRadius:3, tension:.3, fill:true },
|
||||
{ label:'Assets', data: {{ nw_history.assets | tojson }}, borderColor:'#10b981', borderWidth:1.5, pointRadius:2, tension:.3, fill:false, borderDash:[4,3] },
|
||||
{ label:'Net Worth', data: {% if nw_history.proj_labels is defined and nw_history.proj_labels %}histValues.concat(histPad){% else %}histValues{% endif %}, borderColor:'#3b82f6', backgroundColor:'#3b82f611', borderWidth:2, pointRadius:3, tension:.3, fill:true },
|
||||
{ label:'Assets', data: {% if nw_history.proj_labels is defined and nw_history.proj_labels %}{{ nw_history.assets | tojson }}.concat(histPad){% else %}{{ nw_history.assets | tojson }}{% endif %}, borderColor:'#10b981', borderWidth:1.5, pointRadius:2, tension:.3, fill:false, borderDash:[4,3] },
|
||||
{% if nw_history.proj_labels is defined and nw_history.proj_labels %}
|
||||
{ label:'Projected', data: projPad.concat([histValues[histValues.length-1]]).concat(projValues), borderColor:'#3b82f6', borderWidth:1.5, pointRadius:2, tension:.3, fill:false, borderDash:[6,4], backgroundColor:'transparent' },
|
||||
{% endif %}
|
||||
]
|
||||
},
|
||||
options: { responsive:true, maintainAspectRatio:false, plugins:{ legend:{ position:'bottom', labels:{ font:{ size:11 } } } }, scales:{ y:{ ticks:{ callback: v => fmtCur(v) }, grid:{ color:'#f1f5f9' } }, x:{ grid:{ display:false }, ticks:{ font:{ size:10 } } } } }
|
||||
|
||||
Reference in New Issue
Block a user