304 lines
11 KiB
HTML
304 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ _('bank.review_title') }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<div class="page-header">
|
|
<h1><i class="fas fa-check-circle"></i> {{ _('bank.review_title') }}</h1>
|
|
<p class="text-muted">{{ _('bank.review_subtitle') }}</p>
|
|
</div>
|
|
|
|
<!-- Summary Card -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-md-3">
|
|
<h3 class="text-primary">{{ total_found }}</h3>
|
|
<p class="text-muted">{{ _('bank.transactions_found') }}</p>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<h3 class="text-info">{{ bank_format }}</h3>
|
|
<p class="text-muted">{{ _('bank.detected_format') }}</p>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<h3 class="text-success" id="selectedCount">0</h3>
|
|
<p class="text-muted">{{ _('bank.selected') }}</p>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<h3 class="text-warning" id="unmappedCount">{{ total_found }}</h3>
|
|
<p class="text-muted">{{ _('bank.unmapped') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parse Errors (if any) -->
|
|
{% if parse_errors %}
|
|
<div class="alert alert-warning">
|
|
<strong><i class="fas fa-exclamation-triangle"></i> {{ _('bank.parse_warnings') }}:</strong>
|
|
<ul class="mb-0">
|
|
{% for error in parse_errors %}
|
|
<li>{{ error }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Bank Format Warning -->
|
|
{% if bank_format in ['generic', 'unknown', 'Generic'] %}
|
|
<div class="alert alert-warning">
|
|
<strong><i class="fas fa-exclamation-triangle"></i> {{ _('bank.format_not_recognized') }}</strong><br>
|
|
{{ _('bank.format_not_recognized_hint') }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Import Form -->
|
|
<form method="POST" action="{{ url_for('main.bank_import_confirm') }}" id="importForm">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<!-- Bulk Actions -->
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-6">
|
|
<div class="form-check">
|
|
<input type="checkbox"
|
|
class="form-check-input"
|
|
id="selectAll"
|
|
onchange="toggleAll(this)">
|
|
<label class="form-check-label" for="selectAll">
|
|
{{ _('bank.select_all') }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 text-right">
|
|
<div class="btn-group" role="group">
|
|
<button type="button"
|
|
class="btn btn-sm btn-secondary"
|
|
onclick="selectExpenses()">
|
|
<i class="fas fa-arrow-down"></i> {{ _('bank.select_expenses') }}
|
|
</button>
|
|
<button type="button"
|
|
class="btn btn-sm btn-secondary"
|
|
onclick="selectIncome()">
|
|
<i class="fas fa-arrow-up"></i> {{ _('bank.select_income') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transactions Table -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>{{ _('bank.transactions_to_import') }}</h3>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead class="thead-dark">
|
|
<tr>
|
|
<th width="5%">
|
|
<input type="checkbox" id="selectAllTable" onchange="toggleAll(this)">
|
|
</th>
|
|
<th width="10%">{{ _('bank.date') }}</th>
|
|
<th width="35%">{{ _('bank.description') }}</th>
|
|
<th width="15%">{{ _('bank.amount') }}</th>
|
|
<th width="10%">{{ _('bank.type') }}</th>
|
|
<th width="25%">{{ _('bank.category') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for trans in transactions %}
|
|
<tr class="transaction-row {% if trans.amount < 0 %}expense-row{% else %}income-row{% endif %}">
|
|
<td>
|
|
<input type="checkbox"
|
|
name="selected_transactions"
|
|
value="{{ loop.index0 }}"
|
|
class="transaction-checkbox"
|
|
onchange="updateCounts()">
|
|
</td>
|
|
<td>{{ trans.date.strftime('%Y-%m-%d') }}</td>
|
|
<td>
|
|
<small>{{ trans.description }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="{% if trans.amount < 0 %}text-danger{% else %}text-success{% endif %}">
|
|
{% if trans.amount < 0 %}-{% else %}+{% endif %}
|
|
{{ "%.2f"|format(trans.amount|abs) }} {{ _('expense.currency') }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if trans.amount < 0 %}
|
|
<span class="badge badge-danger">{{ _('bank.expense') }}</span>
|
|
{% else %}
|
|
<span class="badge badge-success">{{ _('bank.income') }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<select name="category_{{ loop.index0 }}"
|
|
class="form-control form-control-sm category-select"
|
|
onchange="updateCounts()">
|
|
<option value="">{{ _('bank.select_category') }}</option>
|
|
{% for category in categories %}
|
|
<option value="{{ category.id }}">{{ category.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="mt-4">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<a href="{{ url_for('main.bank_import') }}" class="btn btn-secondary btn-block">
|
|
<i class="fas fa-arrow-left"></i> {{ _('bank.back') }}
|
|
</a>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<button type="submit" class="btn btn-primary btn-block" id="submitBtn" disabled>
|
|
<i class="fas fa-check"></i> {{ _('bank.import_selected') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<style>
|
|
.transaction-row {
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.transaction-row:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.category-select {
|
|
min-width: 150px;
|
|
}
|
|
|
|
.expense-row {
|
|
border-left: 3px solid #dc3545;
|
|
}
|
|
|
|
.income-row {
|
|
border-left: 3px solid #28a745;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.table {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.btn-group {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
width: 100%;
|
|
margin-bottom: 5px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Toggle all checkboxes
|
|
function toggleAll(checkbox) {
|
|
const checkboxes = document.querySelectorAll('.transaction-checkbox');
|
|
checkboxes.forEach(cb => {
|
|
cb.checked = checkbox.checked;
|
|
});
|
|
updateCounts();
|
|
}
|
|
|
|
// Select expenses only
|
|
function selectExpenses() {
|
|
const checkboxes = document.querySelectorAll('.expense-row .transaction-checkbox');
|
|
checkboxes.forEach(cb => {
|
|
cb.checked = true;
|
|
});
|
|
updateCounts();
|
|
}
|
|
|
|
// Select income only
|
|
function selectIncome() {
|
|
const checkboxes = document.querySelectorAll('.income-row .transaction-checkbox');
|
|
checkboxes.forEach(cb => {
|
|
cb.checked = true;
|
|
});
|
|
updateCounts();
|
|
}
|
|
|
|
// Update counts and enable/disable submit button
|
|
function updateCounts() {
|
|
const checkboxes = document.querySelectorAll('.transaction-checkbox:checked');
|
|
const selectedCount = checkboxes.length;
|
|
|
|
// Count how many selected transactions have a category
|
|
let mappedCount = 0;
|
|
checkboxes.forEach(cb => {
|
|
const row = cb.closest('tr');
|
|
const categorySelect = row.querySelector('.category-select');
|
|
if (categorySelect && categorySelect.value) {
|
|
mappedCount++;
|
|
}
|
|
});
|
|
|
|
const unmappedCount = selectedCount - mappedCount;
|
|
|
|
// Update UI
|
|
document.getElementById('selectedCount').textContent = selectedCount;
|
|
document.getElementById('unmappedCount').textContent = unmappedCount;
|
|
|
|
// Enable submit button only if at least one transaction is selected with a category
|
|
document.getElementById('submitBtn').disabled = (mappedCount === 0);
|
|
}
|
|
|
|
// Auto-select checkbox when category is selected
|
|
document.querySelectorAll('.category-select').forEach(select => {
|
|
select.addEventListener('change', function() {
|
|
if (this.value) {
|
|
const row = this.closest('tr');
|
|
const checkbox = row.querySelector('.transaction-checkbox');
|
|
checkbox.checked = true;
|
|
}
|
|
updateCounts();
|
|
});
|
|
});
|
|
|
|
// Confirm before submit
|
|
document.getElementById('importForm').addEventListener('submit', function(e) {
|
|
const selectedCount = document.querySelectorAll('.transaction-checkbox:checked').length;
|
|
if (selectedCount === 0) {
|
|
e.preventDefault();
|
|
alert('{{ _('bank.no_transactions_selected') }}');
|
|
return false;
|
|
}
|
|
|
|
const unmappedCount = parseInt(document.getElementById('unmappedCount').textContent);
|
|
if (unmappedCount > 0) {
|
|
const msg = '{{ _('bank.confirm_unmapped') }}'.replace('{count}', unmappedCount);
|
|
if (!confirm(msg)) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Show loading state
|
|
document.getElementById('submitBtn').disabled = true;
|
|
document.getElementById('submitBtn').innerHTML = '<i class="fas fa-spinner fa-spin"></i> {{ _('bank.importing') }}...';
|
|
});
|
|
|
|
// Initial update
|
|
updateCounts();
|
|
</script>
|
|
{% endblock %}
|