Initial commit

This commit is contained in:
iulian 2025-12-26 00:52:56 +00:00
commit 983cee0320
322 changed files with 57174 additions and 0 deletions

View file

@ -0,0 +1,304 @@
{% 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 %}