144 lines
8.2 KiB
HTML
144 lines
8.2 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Transactions{% endblock %}
|
||
{% block page_title %}Transactions{% endblock %}
|
||
|
||
{% block topbar_actions %}
|
||
<a href="{{ url_for('transactions.new', type='income') }}" class="btn btn-sm" style="background:#d1fae5;color:#065f46;font-size:12px;"><i class="bi bi-plus-lg me-1"></i>Income</a>
|
||
<a href="{{ url_for('transactions.new', type='expense') }}" class="btn btn-sm ms-1" style="background:#fee2e2;color:#991b1b;font-size:12px;"><i class="bi bi-plus-lg me-1"></i>Expense</a>
|
||
<a href="{{ url_for('transactions.transfer') }}" class="btn btn-sm btn-outline-secondary ms-1" style="font-size:12px;"><i class="bi bi-arrow-left-right me-1"></i>Transfer</a>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<!-- Tabs -->
|
||
<div class="d-flex gap-1 mb-3">
|
||
<a href="{{ url_for('transactions.index', tab='expense', q=search, account_id=account_id, category_id=category_id, date_from=date_from, date_to=date_to) }}"
|
||
class="btn btn-sm {% if tab=='expense' %}btn-danger{% else %}btn-outline-secondary{% endif %}" style="font-size:13px;">
|
||
<i class="bi bi-arrow-up-circle me-1"></i>Expenses
|
||
<span class="badge ms-1" style="font-size:10px;background:{% if tab=='expense' %}rgba(255,255,255,.25){% else %}#fee2e2;color:#991b1b{% endif %};">{{ expense_count }}</span>
|
||
</a>
|
||
<a href="{{ url_for('transactions.index', tab='income', q=search, account_id=account_id, category_id=category_id, date_from=date_from, date_to=date_to) }}"
|
||
class="btn btn-sm {% if tab=='income' %}btn-success{% else %}btn-outline-secondary{% endif %}" style="font-size:13px;">
|
||
<i class="bi bi-arrow-down-circle me-1"></i>Income
|
||
<span class="badge ms-1" style="font-size:10px;background:{% if tab=='income' %}rgba(255,255,255,.25){% else %}#d1fae5;color:#065f46{% endif %};">{{ income_count }}</span>
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Filters -->
|
||
<div class="pcard pcard-sm mb-3">
|
||
<form method="GET" action="{{ url_for('transactions.index') }}" class="row g-2 align-items-end">
|
||
<input type="hidden" name="tab" value="{{ tab }}">
|
||
<div class="col-12 col-md-3">
|
||
<input type="text" name="q" class="form-control form-control-sm" placeholder="Search description…" value="{{ search }}">
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<select name="category_id" class="form-select form-select-sm">
|
||
<option value="">All categories</option>
|
||
{% for cat in categories %}
|
||
<option value="{{ cat.id }}" {% if category_id == cat.id|string %}selected{% endif %}>{{ cat.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<select name="account_id" class="form-select form-select-sm">
|
||
<option value="">All accounts</option>
|
||
{% for acct in accounts %}
|
||
<option value="{{ acct.id }}" {% if account_id == acct.id|string %}selected{% endif %}>{{ acct.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<input type="date" name="date_from" class="form-control form-control-sm" value="{{ date_from }}" placeholder="From">
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<input type="date" name="date_to" class="form-control form-control-sm" value="{{ date_to }}" placeholder="To">
|
||
</div>
|
||
<div class="col-12 col-md-1 d-flex gap-1">
|
||
<button type="submit" class="btn btn-sm btn-primary flex-grow-1"><i class="bi bi-search"></i></button>
|
||
<a href="{{ url_for('transactions.index', tab=tab) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-x-lg"></i></a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
{% set has_filter = search or category_id or account_id or date_from or date_to %}
|
||
<div class="pcard p-0">
|
||
{% if transactions %}
|
||
<table class="pfm-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="padding-left:20px;">Date</th>
|
||
<th>Description</th>
|
||
<th>Category</th>
|
||
<th>Account</th>
|
||
<th class="text-end">Amount</th>
|
||
<th class="text-end" style="padding-right:20px;">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for txn in transactions %}
|
||
<tr>
|
||
<td style="padding-left:20px;font-size:12px;color:var(--muted);white-space:nowrap;">{{ txn.date.strftime('%b %d, %Y') }}</td>
|
||
<td>
|
||
<div style="font-size:13px;font-weight:500;">{{ txn.description }}</div>
|
||
{% if txn.notes %}<div style="font-size:11px;color:var(--muted);">{{ txn.notes | truncate(60) }}</div>{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if txn.category %}
|
||
<span style="font-size:12px;white-space:nowrap;">
|
||
<i class="bi {{ txn.category.icon }}" style="color:{{ txn.category.color }};"></i> {{ txn.category.name }}
|
||
</span>
|
||
{% else %}<span class="text-muted" style="font-size:12px;">—</span>{% endif %}
|
||
</td>
|
||
<td style="font-size:12px;color:var(--muted);">{{ txn.account.name if txn.account else '—' }}</td>
|
||
<td class="text-end mono {% if txn.transaction_type=='income' %}text-income{% else %}text-expense{% endif %}" style="font-size:13px;font-weight:600;white-space:nowrap;">
|
||
{% if txn.transaction_type=='income' %}+{% else %}-{% endif %}{{ txn.amount | currency }}
|
||
</td>
|
||
<td class="text-end" style="padding-right:20px;white-space:nowrap;">
|
||
<a href="{{ url_for('transactions.edit', id=txn.id) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;padding:2px 8px;">Edit</a>
|
||
<form method="POST" action="{{ url_for('transactions.delete', id=txn.id) }}" style="display:inline;" onsubmit="return confirm('Delete this transaction?')">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger ms-1" style="font-size:11px;padding:2px 8px;">Del</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination -->
|
||
{% if pagination.pages > 1 %}
|
||
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);">
|
||
<span>Showing {{ ((pagination.page-1)*30)+1 }}–{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}</span>
|
||
<div class="d-flex gap-1">
|
||
{% if pagination.has_prev %}
|
||
<a href="{{ url_for('transactions.index', tab=tab, page=pagination.prev_num, q=search, category_id=category_id, account_id=account_id, date_from=date_from, date_to=date_to) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a>
|
||
{% endif %}
|
||
{% if pagination.has_next %}
|
||
<a href="{{ url_for('transactions.index', tab=tab, page=pagination.next_num, q=search, category_id=category_id, account_id=account_id, date_from=date_from, date_to=date_to) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">Next →</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% else %}
|
||
<div class="text-center py-5 px-3">
|
||
{% if has_filter %}
|
||
<i class="bi bi-funnel text-muted" style="font-size:2.5rem;opacity:.4;"></i>
|
||
<p class="fw-semibold mt-3 mb-1">No matching transactions</p>
|
||
<p class="text-muted small mb-3">Try adjusting your filters or search term.</p>
|
||
<a href="{{ url_for('transactions.index', tab=tab) }}" class="btn btn-sm btn-outline-secondary">
|
||
<i class="bi bi-x-lg me-1"></i>Clear filters
|
||
</a>
|
||
{% else %}
|
||
<i class="bi bi-arrow-left-right text-muted" style="font-size:2.5rem;opacity:.4;"></i>
|
||
<p class="fw-semibold mt-3 mb-1">No {{ tab }} transactions yet</p>
|
||
<p class="text-muted small mb-3">Record your first {{ tab }} to start tracking.</p>
|
||
<a href="{{ url_for('transactions.new', type=tab) }}" class="btn btn-sm btn-primary">
|
||
<i class="bi bi-plus-lg me-1"></i>Add {{ tab | title }}
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|