Update UI

This commit is contained in:
2026-04-18 22:24:17 +10:00
parent e202304043
commit a5e9fe9cbb
3 changed files with 71 additions and 112 deletions
+10 -1
View File
@@ -121,8 +121,15 @@ def get_weekly_overview() -> dict:
week_change_pct = round((week_gain / last_week_total) * 100, 2)
# Per-portfolio breakdown
_palette = [
{'badge': 'bg-indigo-100 text-indigo-800', 'row': 'bg-indigo-50', 'border': 'border-indigo-200'},
{'badge': 'bg-emerald-100 text-emerald-800', 'row': 'bg-emerald-50', 'border': 'border-emerald-200'},
{'badge': 'bg-amber-100 text-amber-800', 'row': 'bg-amber-50', 'border': 'border-amber-200'},
{'badge': 'bg-rose-100 text-rose-800', 'row': 'bg-rose-50', 'border': 'border-rose-200'},
{'badge': 'bg-sky-100 text-sky-800', 'row': 'bg-sky-50', 'border': 'border-sky-200'},
]
portfolio_rows = []
for portfolio in Portfolio.objects.all():
for idx, portfolio in enumerate(Portfolio.objects.all()):
def _snap(date):
if not date:
return None
@@ -145,6 +152,8 @@ def get_weekly_overview() -> dict:
'last_week_value': last_val,
'change': change,
'change_pct': change_pct,
'position_count': portfolio.stocks.filter(quantity__gt=0).count(),
'colors': _palette[idx % len(_palette)],
})
return {
+11 -1
View File
@@ -19,10 +19,20 @@ def dashboard(request):
fy_start = now.year if now.month >= 7 else now.year - 1
fy_label = f"FY {str(fy_start)[2:]}-{str(fy_start + 1)[2:]}"
all_holdings = get_all_holdings()
# Merge snapshot data (value, change, change_pct) into each holdings group
rows_by_id = {row['portfolio'].id: row for row in overview.get('portfolio_rows', [])}
for group in all_holdings:
row = rows_by_id.get(group['portfolio'].id, {})
group['this_week_value'] = row.get('this_week_value')
group['change'] = row.get('change')
group['change_pct'] = row.get('change_pct')
group['position_count'] = row.get('position_count', len(group['holdings']))
return render(request, 'invest/dashboard.html', {
'overview': overview,
'fy_label': fy_label,
'all_holdings': get_all_holdings(),
'all_holdings': all_holdings,
'chart_data_json': get_performance_chart_data() or 'null',
})
+50 -110
View File
@@ -84,132 +84,72 @@
</div>
{% endif %}
<!-- ── Portfolio value table ─────────────────────────────────── -->
{% if overview.portfolio_rows %}
<div class="bg-white rounded-lg shadow-sm overflow-hidden mb-4">
<div class="px-6 py-4 border-b border-stone-100">
<p class="text-xs font-semibold tracking-widest text-stone-500 uppercase">Portfolio Breakdown</p>
</div>
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold tracking-widest text-stone-400 uppercase border-b border-stone-100">
<th class="px-6 py-3 text-left">Portfolio</th>
<th class="px-6 py-3 text-right">This Week</th>
<th class="px-6 py-3 text-right">Last Week</th>
<th class="px-6 py-3 text-right">Change</th>
<th class="px-6 py-3 text-right">Change %</th>
</tr>
</thead>
<tbody class="divide-y divide-stone-50">
{% for row in overview.portfolio_rows %}
<tr class="hover:bg-stone-50">
<td class="px-6 py-4 font-semibold text-stone-900">{{ row.portfolio.name }}</td>
<td class="px-6 py-4 text-right text-stone-700">
{% if row.this_week_value is not None %}${{ row.this_week_value|floatformat:0 }}{% else %}<span class="text-stone-300"></span>{% endif %}
</td>
<td class="px-6 py-4 text-right text-stone-400">
{% if row.last_week_value is not None %}${{ row.last_week_value|floatformat:0 }}{% else %}<span class="text-stone-300"></span>{% endif %}
</td>
<td class="px-6 py-4 text-right font-medium
{% if row.change is not None %}{% if row.change >= 0 %}text-green-700{% else %}text-red-600{% endif %}{% else %}text-stone-300{% endif %}">
{% if row.change is not None %}{% if row.change >= 0 %}+{% endif %}${{ row.change|floatformat:0 }}{% else %}—{% endif %}
</td>
<td class="px-6 py-4 text-right font-medium
{% if row.change_pct is not None %}{% if row.change_pct >= 0 %}text-green-700{% else %}text-red-600{% endif %}{% else %}text-stone-300{% endif %}">
{% if row.change_pct is not None %}{% if row.change_pct >= 0 %}+{% endif %}{{ row.change_pct|floatformat:2 }}%{% else %}—{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
{% if overview.this_week_total is not None %}
<tfoot>
<tr class="border-t-2 border-stone-200 bg-stone-50">
<td class="px-6 py-3 font-semibold text-stone-700 text-xs uppercase tracking-wide">Total</td>
<td class="px-6 py-3 text-right font-bold text-stone-900">${{ overview.this_week_total|floatformat:0 }}</td>
<td class="px-6 py-3 text-right text-stone-400">
{% if overview.last_week_total is not None %}${{ overview.last_week_total|floatformat:0 }}{% else %}—{% endif %}
</td>
<td class="px-6 py-3 text-right font-bold
{% if overview.week_gain is not None %}{% if overview.week_gain >= 0 %}text-green-700{% else %}text-red-600{% endif %}{% endif %}">
{% if overview.week_gain is not None %}{% if overview.week_gain >= 0 %}+{% endif %}${{ overview.week_gain|floatformat:0 }}{% else %}—{% endif %}
</td>
<td class="px-6 py-3 text-right font-bold
{% if overview.week_change_pct is not None %}{% if overview.week_change_pct >= 0 %}text-green-700{% else %}text-red-600{% endif %}{% endif %}">
{% if overview.week_change_pct is not None %}{% if overview.week_change_pct >= 0 %}+{% endif %}{{ overview.week_change_pct|floatformat:2 }}%{% else %}—{% endif %}
</td>
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% else %}
<div class="bg-white rounded-lg shadow-sm p-12 text-center">
<p class="text-stone-400 text-lg">No portfolios yet.</p>
<p class="text-stone-300 text-sm mt-1">Snapshots are captured every Saturday at 08:00.</p>
</div>
{% endif %}
<!-- ── Hint ──────────────────────────────────────────────────── -->
<p class="text-xs text-stone-400 text-center mt-2">
<p class="text-xs text-stone-400 text-center mt-2 mb-6">
Snapshots captured every Saturday 08:00 · Values in portfolio's quote currency
</p>
<!-- ── Unified live holdings ─────────────────────────────────── -->
<!-- ── Live holdings (per-portfolio cards) ───────────────────── -->
{% if all_holdings %}
<div class="mt-6 mb-2">
<p class="text-xs font-semibold tracking-widest text-stone-500 uppercase">Live Holdings</p>
</div>
<!-- Legend badges -->
<div class="flex flex-wrap gap-2 mb-3">
<div class="space-y-4">
{% for group in all_holdings %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ group.colors.badge }}">
{{ group.portfolio.name }}
&nbsp;·&nbsp;${{ group.total_value|floatformat:0 }}
</span>
{% endfor %}
</div>
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold tracking-widest text-stone-400 uppercase border-b border-stone-100">
<th class="px-6 py-3 text-left">Portfolio</th>
<th class="px-6 py-3 text-left">Stock</th>
<th class="px-6 py-3 text-right">Qty</th>
<th class="px-6 py-3 text-right">Price</th>
<th class="px-6 py-3 text-right">Value</th>
</tr>
</thead>
<tbody>
{% for group in all_holdings %}
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<!-- Card header -->
<div class="px-6 py-4 flex items-center justify-between border-b border-stone-100">
<div>
<p class="font-semibold text-stone-900 text-sm">
{{ group.portfolio.name }}
</p>
<p class="text-xs text-stone-400 mt-0.5">
{{ group.position_count }} position{{ group.position_count|pluralize }}
{% if overview.this_week_date %}· Last updated {{ overview.this_week_date|date:"j M Y" }}{% endif %}
</p>
</div>
<div class="text-right">
<p class="font-bold text-stone-900 text-sm">
{% if group.this_week_value is not None %}${{ group.this_week_value|floatformat:0 }}{% else %}<span class="text-stone-300"></span>{% endif %}
</p>
{% if group.change is not None %}
<p class="text-xs font-medium mt-0.5 {% if group.change >= 0 %}text-green-700{% else %}text-red-600{% endif %}">
{% if group.change >= 0 %}+{% endif %}${{ group.change|floatformat:0 }}
({% if group.change_pct >= 0 %}+{% endif %}{{ group.change_pct|floatformat:1 }}%)
</p>
{% else %}
<p class="text-xs text-stone-300 mt-0.5">No prior snapshot</p>
{% endif %}
</div>
</div>
<!-- Holdings table -->
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold tracking-widest text-stone-400 uppercase border-b border-stone-100">
<th class="px-6 py-3 text-left">Ticker</th>
<th class="px-6 py-3 text-right">Qty</th>
<th class="px-6 py-3 text-right">Price</th>
<th class="px-6 py-3 text-right">Mkt Value</th>
</tr>
</thead>
<tbody>
{% for stock in group.holdings %}
<tr class="border-b border-stone-50 hover:bg-stone-50 {{ group.colors.row }}">
{% if forloop.first %}
<td class="px-6 py-3 font-semibold" rowspan="{{ group.holdings|length }}">
<td class="px-6 py-3 font-medium text-stone-900">
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {{ group.colors.badge }}">
{{ group.portfolio.name }}
{{ stock.stock_code }}
</span>
</td>
{% endif %}
<td class="px-6 py-3 font-medium text-stone-900">{{ stock.stock_code }}</td>
<td class="px-6 py-3 text-right text-stone-500">{{ stock.quantity|floatformat:2 }}</td>
<td class="px-6 py-3 text-right text-stone-500">{{ stock.quantity|floatformat:0 }}</td>
<td class="px-6 py-3 text-right text-stone-500">
{% if stock.current_price %}${{ stock.current_price|floatformat:2 }}{% else %}<span class="text-stone-300"></span>{% endif %}
</td>
<td class="px-6 py-3 text-right font-semibold text-stone-800">${{ stock.current_value|floatformat:0 }}</td>
<td class="px-6 py-3 text-right font-semibold text-stone-800">
{% if stock.current_value %}${{ stock.current_value|floatformat:0 }}{% else %}<span class="text-stone-300"></span>{% endif %}
</td>
</tr>
{% endfor %}
<!-- Sub-total row -->
<tr class="border-b-2 {{ group.colors.border }}">
<td class="px-6 py-2 text-xs text-right text-stone-400 uppercase tracking-wide font-semibold" colspan="4">
{{ group.portfolio.name }} total
</td>
<td class="px-6 py-2 text-right font-bold text-stone-700">${{ group.total_value|floatformat:0 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</tbody>
</table>
</div>
{% endfor %}
</div>
{% endif %}