344 lines
17 KiB
HTML
344 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ account.name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="section-panel">
|
|
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-end gap-3">
|
|
<div>
|
|
<h1 class="h2 mb-2">{{ account.name }}</h1>
|
|
<p class="text-secondary mb-0">{{ account.provider|default:"manual" }}{% if account.number %} • {{ account.number }}{% endif %}</p>
|
|
</div>
|
|
<div class="text-lg-end">
|
|
<div class="metric-label">Current Balance</div>
|
|
<div class="metric-value">${{ account.balance }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-12 col-lg-8">
|
|
<section class="section-panel h-100">
|
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
<div>
|
|
<h2 class="h5 mb-1">Account Value Over Time</h2>
|
|
<p class="text-secondary mb-0">Historical balance snapshots recorded each time this account is synced.</p>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-end gap-3 flex-wrap mb-3">
|
|
{% if latest_account_point %}
|
|
<div class="text-end">
|
|
<div class="metric-label">Latest Snapshot</div>
|
|
<div class="fw-semibold fs-5">${{ latest_account_point.display_value }}</div>
|
|
</div>
|
|
{% endif %}
|
|
<form method="get" class="range-form">
|
|
<label for="account-range" class="metric-label mb-0">Range</label>
|
|
<select id="account-range" name="range" class="form-select form-select-sm range-select" onchange="this.form.submit()">
|
|
{% for value, label in date_range_options %}
|
|
<option value="{{ value }}"{% if selected_range == value %} selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</form>
|
|
</div>
|
|
|
|
{% if account_timeline %}
|
|
<div class="chart-frame">
|
|
<div class="chart-axis chart-axis-top">
|
|
<span>${{ account_chart.max_label }}</span>
|
|
<span>Change shown on hover</span>
|
|
</div>
|
|
<svg class="chart-svg" viewBox="0 0 100 100" preserveAspectRatio="none" role="img" aria-label="Account value over time">
|
|
<line class="chart-baseline" x1="0" y1="100" x2="100" y2="100"></line>
|
|
<path class="chart-line" d="{{ account_chart.path }}"></path>
|
|
{% for point in account_chart.points %}
|
|
<circle class="chart-point" cx="{{ point.x }}" cy="{{ point.y }}" r="2.2">
|
|
<title>{{ point.label }} | ${{ point.display_value }} | {{ point.change_display }}</title>
|
|
</circle>
|
|
{% endfor %}
|
|
</svg>
|
|
<div class="chart-axis chart-axis-bottom">
|
|
<span>{{ account_chart.start_label }}</span>
|
|
<span>${{ account_chart.min_label }}</span>
|
|
<span>{{ account_chart.end_label }}</span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-secondary mb-0">No account history yet. Run another sync to capture snapshots.</p>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
<div class="col-12 col-lg-4">
|
|
<section class="section-panel h-100">
|
|
<h2 class="h5 mb-3">Snapshot Timeline</h2>
|
|
{% if account_timeline %}
|
|
<div class="list-group list-group-flush timeline-list">
|
|
{% for point in account_timeline %}
|
|
<div class="list-group-item">
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">{{ point.label }}</span>
|
|
<strong>${{ point.display_value }}</strong>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-secondary mb-0">No snapshot points available yet.</p>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<section class="section-panel">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h2 class="h5 mb-0">Account Activity</h2>
|
|
<span class="text-secondary small">{{ holdings|length }} holding{{ holdings|length|pluralize }} • {{ transactions|length }} transaction{{ transactions|length|pluralize }}</span>
|
|
</div>
|
|
<ul class="nav nav-tabs mb-3" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<a
|
|
class="nav-link{% if active_tab == 'holdings' %} active{% endif %}"
|
|
href="?range={{ selected_range }}&tab=holdings"
|
|
role="tab"
|
|
aria-selected="{% if active_tab == 'holdings' %}true{% else %}false{% endif %}"
|
|
>
|
|
Holdings
|
|
</a>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<a
|
|
class="nav-link{% if active_tab == 'transactions' %} active{% endif %}"
|
|
href="?range={{ selected_range }}&tab=transactions"
|
|
role="tab"
|
|
aria-selected="{% if active_tab == 'transactions' %}true{% else %}false{% endif %}"
|
|
>
|
|
Transactions
|
|
</a>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<a
|
|
class="nav-link{% if active_tab == 'income' %} active{% endif %}"
|
|
href="?range={{ selected_range }}&tab=income"
|
|
role="tab"
|
|
aria-selected="{% if active_tab == 'income' %}true{% else %}false{% endif %}"
|
|
>
|
|
Income
|
|
</a>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<a
|
|
class="nav-link{% if active_tab == 'realized_gain' %} active{% endif %}"
|
|
href="?range={{ selected_range }}&tab=realized_gain"
|
|
role="tab"
|
|
aria-selected="{% if active_tab == 'realized_gain' %}true{% else %}false{% endif %}"
|
|
>
|
|
Realized Gain/Loss
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content">
|
|
<div class="tab-pane fade{% if active_tab == 'holdings' %} show active{% endif %}">
|
|
<div class="list-group">
|
|
<div class="list-group-item">
|
|
<div class="row align-items-center">
|
|
<div class="col-3"><strong>Ticker</strong></div>
|
|
<div class="col-3"><strong>Value</strong></div>
|
|
<div class="col-3"><strong>Amount</strong></div>
|
|
<div class="col-3"><strong>Cost Basis</strong></div>
|
|
</div>
|
|
</div>
|
|
{% for holding in holdings %}
|
|
<div class="list-group-item">
|
|
<div class="row align-items-center">
|
|
<div class="col-3">
|
|
<strong>{{ holding.asset.ticker }}</strong>
|
|
</div>
|
|
<div class="col-3">
|
|
${{ holding.holding_value|floatformat:2 }}
|
|
<div class="small text-secondary">${{ holding.holding_gain|floatformat:2 }}</div>
|
|
</div>
|
|
<div class="col-3">
|
|
{{ holding.ammount|floatformat:4 }}
|
|
</div>
|
|
<div class="col-3">
|
|
${{ holding.cost_basis|floatformat:2 }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="list-group-item">
|
|
<p class="text-secondary mb-0">No holdings available for this account.</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade{% if active_tab == 'transactions' %} show active{% endif %}">
|
|
<div class="list-group">
|
|
<div class="list-group-item">
|
|
<div class="row align-items-center">
|
|
<div class="col-2"><strong>Date</strong></div>
|
|
<div class="col-2"><strong>Type</strong></div>
|
|
<div class="col-3"><strong>Name</strong></div>
|
|
<div class="col-2"><strong>Asset</strong></div>
|
|
<div class="col-3 text-end"><strong>Value</strong></div>
|
|
</div>
|
|
</div>
|
|
{% for transaction in transactions %}
|
|
<div class="list-group-item">
|
|
<div class="row align-items-center">
|
|
<div class="col-2">
|
|
<span class="small text-secondary">{{ transaction.date|date:"Y-m-d" }}</span>
|
|
</div>
|
|
<div class="col-2">
|
|
<strong>{{ transaction.transaction_type|title }}</strong>
|
|
</div>
|
|
<div class="col-3">
|
|
{{ transaction.name }}
|
|
</div>
|
|
<div class="col-2">
|
|
{% if transaction.asset %}
|
|
{{ transaction.asset.ticker }}
|
|
{% else %}
|
|
<span class="text-secondary">Cash</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-3 text-end">
|
|
<strong>${{ transaction.value|floatformat:2 }}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="list-group-item">
|
|
<p class="text-secondary mb-0">No transactions available for this account.</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade{% if active_tab == 'income' %} show active{% endif %}">
|
|
{% if income_chart.bars %}
|
|
<div class="chart-frame mb-3">
|
|
<div class="chart-axis chart-axis-top">
|
|
<span>${{ income_chart.max_label }}</span>
|
|
<span>Monthly income from dividends and other income transactions</span>
|
|
</div>
|
|
<svg class="chart-svg" viewBox="0 0 100 100" preserveAspectRatio="none" role="img" aria-label="Monthly income bar chart">
|
|
<line class="chart-baseline" x1="0" y1="100" x2="100" y2="100"></line>
|
|
{% for bar in income_chart.bars %}
|
|
<rect
|
|
class="chart-bar"
|
|
x="{{ bar.x }}"
|
|
y="{{ bar.y }}"
|
|
width="{{ bar.width }}"
|
|
height="{{ bar.height }}"
|
|
rx="1.5"
|
|
>
|
|
<title>{{ bar.label }} | ${{ bar.display_value }}</title>
|
|
</rect>
|
|
{% endfor %}
|
|
</svg>
|
|
<div class="chart-axis chart-axis-bottom">
|
|
{% for bar in income_chart.bars %}
|
|
<span>{{ bar.short_label }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="list-group">
|
|
{% for month in income_chart.months %}
|
|
<div class="list-group-item">
|
|
<div class="d-flex justify-content-between align-items-center gap-3">
|
|
<span>{{ month.label }}</span>
|
|
<strong>${{ month.display_value }}</strong>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-secondary mb-0">No dividend or asset income transactions are available for this account in the selected range.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="tab-pane fade{% if active_tab == 'realized_gain' %} show active{% endif %}">
|
|
<div class="section-panel p-3 mb-3">
|
|
<div class="d-flex justify-content-between align-items-center gap-3 flex-wrap">
|
|
<div>
|
|
<div class="metric-label">{{ realized_gain_summary.label }}</div>
|
|
<div class="fw-semibold fs-4 {% if realized_gain_summary.is_gain %}text-success{% else %}text-danger{% endif %}">
|
|
${{ realized_gain_summary.total_display }}
|
|
</div>
|
|
<div class="text-secondary small">
|
|
{{ realized_gain_summary.sales_count }} sale{{ realized_gain_summary.sales_count|pluralize }} in {{ realized_gain_summary.selected_year }}
|
|
</div>
|
|
</div>
|
|
<form method="get" class="range-form">
|
|
<input type="hidden" name="range" value="{{ selected_range }}">
|
|
<input type="hidden" name="tab" value="realized_gain">
|
|
<label for="realized-gain-year" class="metric-label mb-0">Year</label>
|
|
<select id="realized-gain-year" name="realized_year" class="form-select form-select-sm range-select" onchange="this.form.submit()">
|
|
{% for year in realized_gain_years %}
|
|
<option value="{{ year }}"{% if selected_realized_gain_year == year %} selected{% endif %}>{{ year }}</option>
|
|
{% empty %}
|
|
<option value="{{ selected_realized_gain_year }}" selected>{{ selected_realized_gain_year }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="list-group">
|
|
<div class="list-group-item">
|
|
<div class="row align-items-center">
|
|
<div class="col-2"><strong>Date</strong></div>
|
|
<div class="col-3"><strong>Name</strong></div>
|
|
<div class="col-2"><strong>Asset</strong></div>
|
|
<div class="col-1 text-end"><strong>Qty</strong></div>
|
|
<div class="col-2 text-end"><strong>Proceeds</strong></div>
|
|
<div class="col-2 text-end"><strong>Gain/Loss</strong></div>
|
|
</div>
|
|
</div>
|
|
{% for sale in realized_gain_summary.sales %}
|
|
<div class="list-group-item">
|
|
<div class="row align-items-center">
|
|
<div class="col-2">
|
|
<span class="small text-secondary">{{ sale.date|date:"Y-m-d" }}</span>
|
|
</div>
|
|
<div class="col-3">{{ sale.name }}</div>
|
|
<div class="col-2">
|
|
{% if sale.asset %}
|
|
{{ sale.asset.ticker }}
|
|
{% else %}
|
|
<span class="text-secondary">Cash</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-1 text-end">
|
|
{% if sale.quantity %}
|
|
{{ sale.quantity|floatformat:4 }}
|
|
{% else %}
|
|
<span class="text-secondary">N/A</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-2 text-end">${{ sale.proceeds_display }}</div>
|
|
<div class="col-2 text-end">
|
|
{% if sale.realized_gain_display %}
|
|
<strong class="{% if sale.is_gain %}text-success{% else %}text-danger{% endif %}">${{ sale.realized_gain_display }}</strong>
|
|
{% else %}
|
|
<span class="text-secondary">N/A</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="list-group-item">
|
|
<p class="text-secondary mb-0">No realized sale transactions are available for this account.</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{% endblock %}
|