122 lines
5.5 KiB
HTML
122 lines
5.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Import Transactions{% endblock %}
|
|
{% block page_title %}Import CSV{% endblock %}
|
|
|
|
{% block topbar_actions %}
|
|
<a href="{{ url_for('settings.index') }}" class="btn btn-sm btn-outline-secondary me-1" style="font-size:12px;"><i class="bi bi-arrow-left me-1"></i>Settings</a>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row g-3">
|
|
<div class="col-12 col-lg-6">
|
|
<div class="pcard">
|
|
<div class="pcard-title mb-3">Upload CSV File</div>
|
|
|
|
<!-- Format guide -->
|
|
<div class="mb-4 p-3" style="background:#f8fafc;border-radius:8px;font-size:12px;">
|
|
<div class="fw-semibold mb-2" style="font-size:13px;">Required CSV format:</div>
|
|
<code style="font-size:11px;background:#0f172a;color:#10b981;padding:8px 12px;border-radius:6px;display:block;line-height:1.8;">
|
|
date,type,description,category,account,amount,notes<br>
|
|
2025-01-15,expense,Groceries,Food & Dining,Checking,85.50,Weekly shop<br>
|
|
2025-01-16,income,Salary,Salary,Checking,3000.00,
|
|
</code>
|
|
<div class="mt-2 text-muted">
|
|
<strong>Required:</strong> date, type (income/expense), description, amount<br>
|
|
<strong>Optional:</strong> category, account, notes<br>
|
|
<strong>Date formats:</strong> YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" enctype="multipart/form-data" novalidate>
|
|
{{ form.hidden_tag() }}
|
|
|
|
<div class="mb-3">
|
|
{{ form.csv_file.label(class="form-label fw-medium", style="font-size:13px;") }}
|
|
{{ form.csv_file(class="form-control" + (" is-invalid" if form.csv_file.errors else "")) }}
|
|
{% for e in form.csv_file.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.default_account_id.label(class="form-label fw-medium", style="font-size:13px;") }}
|
|
{{ form.default_account_id(class="form-select") }}
|
|
<small class="text-muted" style="font-size:11px;">Used when account column is missing or unrecognised.</small>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<div class="form-check">
|
|
{{ form.skip_duplicates(class="form-check-input") }}
|
|
{{ form.skip_duplicates.label(class="form-check-label", style="font-size:13px;") }}
|
|
</div>
|
|
</div>
|
|
|
|
{{ form.submit(class="btn btn-primary") }}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% if parse_errors %}
|
|
<div class="col-12 col-lg-6">
|
|
<div class="pcard" style="border-color:#fca5a5;">
|
|
<div class="pcard-title mb-2" style="color:#ef4444;">Parse Errors</div>
|
|
{% for err in parse_errors %}
|
|
<div style="font-size:12px;color:#ef4444;padding:4px 0;border-bottom:1px solid #fee2e2;">{{ err }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if preview %}
|
|
<div class="col-12">
|
|
<div class="pcard p-0">
|
|
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-bottom:1px solid var(--border);">
|
|
<span class="pcard-title mb-0">Preview — {{ preview | length }} rows ready to import</span>
|
|
<form method="POST" action="{{ url_for('settings.import_confirm') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-sm btn-success" style="font-size:12px;">
|
|
<i class="bi bi-check-lg me-1"></i>Confirm Import
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<table class="pfm-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="padding-left:20px;">Date</th>
|
|
<th>Type</th>
|
|
<th>Description</th>
|
|
<th>Category</th>
|
|
<th>Account</th>
|
|
<th class="text-end" style="padding-right:20px;">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in preview[:20] %}
|
|
<tr>
|
|
<td style="padding-left:20px;font-size:12px;color:var(--muted);">{{ row.date.strftime('%b %d, %Y') }}</td>
|
|
<td><span class="badge {% if row.transaction_type == 'income' %}badge-income{% else %}badge-expense{% endif %}" style="font-size:11px;">{{ row.transaction_type | title }}</span></td>
|
|
<td style="font-size:13px;">{{ row.description }}</td>
|
|
<td style="font-size:12px;color:var(--muted);">
|
|
{{ row.category_name or '—' }}
|
|
{% if row.category_name and not row.category_id %}
|
|
<span title="Category not found — will be uncategorized" style="color:#f59e0b;">⚠</span>
|
|
{% endif %}
|
|
</td>
|
|
<td style="font-size:12px;color:var(--muted);">
|
|
{{ row.account_name or '—' }}
|
|
{% if not row.account_id %}
|
|
<span title="Account not found — will be unlinked" style="color:#f59e0b;">⚠</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-end mono {% if row.transaction_type == 'income' %}text-income{% else %}text-expense{% endif %}" style="font-size:13px;padding-right:20px;">{{ row.amount | currency }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% if preview | length > 20 %}
|
|
<tr><td colspan="6" class="text-center py-2" style="font-size:12px;color:var(--muted);">... and {{ (preview | length) - 20 }} more rows</td></tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|