Files
Mateusz Gruszczyński 986ffb200a first commit
2026-03-13 15:17:32 +01:00

211 lines
14 KiB
HTML

{% extends 'base.html' %}
{% block content %}
<div class="hero-panel mb-4">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3">
<div>
<div class="app-section-title mb-2">
<span class="feature-icon"><i class="fa-solid fa-receipt"></i></span>
<div>
<h1 class="h3 mb-0">{{ t('expenses.list') }}</h1>
<div class="text-body-secondary">{{ selected_year }}-{{ '%02d'|format(selected_month) }}</div>
</div>
</div>
</div>
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-outline-secondary" href="{{ url_for('expenses.export_csv', **request.args) }}"><i class="fa-solid fa-file-csv me-2"></i>{{ t('expenses.export_csv') }}</a>
<a class="btn btn-outline-secondary" href="{{ url_for('expenses.export_pdf', **request.args) }}"><i class="fa-solid fa-file-pdf me-2"></i>{{ t('expenses.export_pdf') }}</a>
<a class="btn btn-primary" href="{{ url_for('expenses.create_expense') }}"><i class="fa-solid fa-plus me-2"></i>{{ t('nav.add_expense') }}</a>
</div>
</div>
<div class="month-switcher mb-3">
<a class="btn btn-outline-secondary" href="{{ url_for('expenses.list_expenses', year=selected_year if selected_month>1 else selected_year-1, month=selected_month-1 if selected_month>1 else 12, category_id=filters.category_id or None, payment_method=filters.payment_method or None, status=filters.status or None, q=filters.q or None, sort_by=filters.sort_by, sort_dir=filters.sort_dir, group_by=filters.group_by) }}"><i class="fa-solid fa-chevron-left me-2"></i>{{ t('common.previous') }}</a>
<form class="center-panel" method="get">
<i class="fa-regular fa-calendar"></i>
<input class="form-control" style="max-width:130px" type="number" name="year" value="{{ selected_year }}">
<input class="form-control" style="max-width:110px" type="number" name="month" value="{{ selected_month }}" min="1" max="12">
<input type="hidden" name="category_id" value="{{ filters.category_id or 0 }}">
<input type="hidden" name="payment_method" value="{{ filters.payment_method }}">
<input type="hidden" name="status" value="{{ filters.status }}">
<input type="hidden" name="q" value="{{ filters.q }}">
<input type="hidden" name="sort_by" value="{{ filters.sort_by }}">
<input type="hidden" name="sort_dir" value="{{ filters.sort_dir }}">
<input type="hidden" name="group_by" value="{{ filters.group_by }}">
<button class="btn btn-outline-primary"><i class="fa-solid fa-arrow-right me-2"></i>OK</button>
</form>
<a class="btn btn-outline-secondary" href="{{ url_for('expenses.list_expenses', year=selected_year if selected_month<12 else selected_year+1, month=selected_month+1 if selected_month<12 else 1, category_id=filters.category_id or None, payment_method=filters.payment_method or None, status=filters.status or None, q=filters.q or None, sort_by=filters.sort_by, sort_dir=filters.sort_dir, group_by=filters.group_by) }}">{{ t('common.next') }}<i class="fa-solid fa-chevron-right ms-2"></i></a>
</div>
<div class="quick-stats expense-list-stats mb-3">
<div class="metric-card">
<div class="metric-label">{{ t('expenses.filtered_total') }}</div>
<div class="metric-value">{{ '%.2f'|format(month_total) }}</div>
<div class="small text-body-secondary">{{ expenses|length }} {{ t('expenses.results') }}</div>
</div>
<div class="metric-card">
<div class="metric-label">{{ t('expenses.active_sort') }}</div>
<div class="metric-value fs-5">{{ dict(sort_options).get(filters.sort_by, t('expenses.date')) }}</div>
<div class="small text-body-secondary">{{ t('expenses.' ~ filters.sort_dir) if filters.sort_dir in ['asc', 'desc'] else filters.sort_dir }}</div>
</div>
<div class="metric-card">
<div class="metric-label">{{ t('expenses.grouping') }}</div>
<div class="metric-value fs-5">{{ t('expenses.group_' ~ filters.group_by) if filters.group_by in ['category','payment_method','status','none'] else filters.group_by }}</div>
<div class="small text-body-secondary">{{ grouped_expenses|length }} {{ t('expenses.sections') }}</div>
</div>
<div class="metric-card">
<div class="metric-label">{{ t('expenses.categories_count') }}</div>
<div class="metric-value">{{ categories|length }}</div>
<div class="small text-body-secondary">{{ t('expenses.month_view') }}</div>
</div>
</div>
<form method="get" class="expense-filters card card-body border-0 shadow-sm">
<div class="row g-3 align-items-end">
<input type="hidden" name="year" value="{{ selected_year }}">
<input type="hidden" name="month" value="{{ selected_month }}">
<div class="col-lg-4">
<label class="form-label small">{{ t('common.search') }}</label>
<div class="search-input-wrap">
<i class="fa-solid fa-magnifying-glass"></i>
<input class="form-control ps-5" name="q" value="{{ filters.q }}" placeholder="{{ t('expenses.search_placeholder') }}">
</div>
</div>
<div class="col-sm-6 col-lg-2">
<label class="form-label small">{{ t('expenses.category') }}</label>
<select class="form-select" name="category_id">
<option value="0">{{ t('common.all') }}</option>
{% for category in categories %}
<option value="{{ category.id }}" {% if filters.category_id==category.id %}selected{% endif %}>{{ category.localized_name(current_language) }}</option>
{% endfor %}
</select>
</div>
<div class="col-sm-6 col-lg-2">
<label class="form-label small">{{ t('expenses.payment_method') }}</label>
<select class="form-select" name="payment_method">
<option value="">{{ t('common.all') }}</option>
<option value="card" {% if filters.payment_method=='card' %}selected{% endif %}>{{ t('expenses.payment_card') }}</option>
<option value="cash" {% if filters.payment_method=='cash' %}selected{% endif %}>{{ t('expenses.payment_cash') }}</option>
<option value="transfer" {% if filters.payment_method=='transfer' %}selected{% endif %}>{{ t('expenses.payment_transfer') }}</option>
<option value="blik" {% if filters.payment_method=='blik' %}selected{% endif %}>{{ t('expenses.payment_blik') }}</option>
</select>
</div>
<div class="col-sm-6 col-lg-2">
<label class="form-label small">{{ t('expenses.status') }}</label>
<select class="form-select" name="status">
<option value="">{{ t('common.all') }}</option>
<option value="new" {% if filters.status=='new' %}selected{% endif %}>{{ t('expenses.status_new') }}</option>
<option value="needs_review" {% if filters.status=='needs_review' %}selected{% endif %}>{{ t('expenses.status_needs_review') }}</option>
<option value="confirmed" {% if filters.status=='confirmed' %}selected{% endif %}>{{ t('expenses.status_confirmed') }}</option>
</select>
</div>
<div class="col-sm-6 col-lg-2">
<label class="form-label small">{{ t('expenses.sort_by') }}</label>
<select class="form-select" name="sort_by">
{% for value, label in sort_options %}
<option value="{{ value }}" {% if filters.sort_by==value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="col-sm-6 col-lg-2">
<label class="form-label small">{{ t('expenses.sort_direction') }}</label>
<select class="form-select" name="sort_dir">
<option value="desc" {% if filters.sort_dir=='desc' %}selected{% endif %}>{{ t('expenses.desc') }}</option>
<option value="asc" {% if filters.sort_dir=='asc' %}selected{% endif %}>{{ t('expenses.asc') }}</option>
</select>
</div>
<div class="col-sm-6 col-lg-2">
<label class="form-label small">{{ t('expenses.group_by') }}</label>
<select class="form-select" name="group_by">
<option value="category" {% if filters.group_by=='category' %}selected{% endif %}>{{ t('expenses.group_category') }}</option>
<option value="payment_method" {% if filters.group_by=='payment_method' %}selected{% endif %}>{{ t('expenses.group_payment_method') }}</option>
<option value="status" {% if filters.group_by=='status' %}selected{% endif %}>{{ t('expenses.group_status') }}</option>
<option value="none" {% if filters.group_by=='none' %}selected{% endif %}>{{ t('expenses.group_none') }}</option>
</select>
</div>
<div class="col-lg-4 d-flex gap-2">
<button class="btn btn-primary flex-grow-1"><i class="fa-solid fa-filter me-2"></i>{{ t('common.filter') }}</button>
<a class="btn btn-outline-secondary" href="{{ url_for('expenses.list_expenses', year=selected_year, month=selected_month) }}"><i class="fa-solid fa-rotate-left me-2"></i>{{ t('common.reset') }}</a>
</div>
</div>
</form>
</div>
{% if budgets %}<div class="alert alert-info border-0 shadow-sm"><i class="fa-solid fa-bullseye me-2"></i>{{ t('budgets.title') }}: {% for budget in budgets %}{{ budget.category.localized_name(current_language) }} {{ budget.amount }}{% if not loop.last %}, {% endif %}{% endfor %}</div>{% endif %}
{% if expenses %}
<div class="expense-groups d-grid gap-3">
{% for group in grouped_expenses %}
<section class="card expense-group-card">
<div class="card-header expense-group-header">
<div>
<div class="h5 mb-1">{{ group['label'] }}</div>
<div class="small text-body-secondary">{{ group['items']|length }} {{ t('expenses.results') }}</div>
</div>
<div class="text-end">
<div class="small text-body-secondary">{{ t('expenses.filtered_total') }}</div>
<div class="h5 mb-0">{{ '%.2f'|format(group['total']) }}</div>
</div>
</div>
<div class="card-body p-0">
{% for expense in group['items'] %}
<article class="expense-list-item">
<div class="expense-list-main">
<div class="expense-list-thumb-wrap">
{% if expense.preview_filename %}
<img class="expense-row-thumb" src="{{ url_for('static', filename='previews/' ~ expense.preview_filename) }}" alt="preview">
{% else %}
<span class="soft-icon"><i class="fa-solid fa-receipt"></i></span>
{% endif %}
</div>
<div class="expense-list-copy">
<div class="d-flex flex-wrap align-items-center gap-2 mb-1">
<span class="expense-title">{{ expense.title }}</span>
<span class="badge rounded-pill soft-badge">{{ expense.category.localized_name(current_language) if expense.category else t('common.uncategorized') }}</span>
<span class="badge text-bg-light border">{{ expense.purchase_date }}</span>
</div>
<div class="expense-meta-row">
{% if expense.vendor %}<span><i class="fa-solid fa-store me-1"></i>{{ expense.vendor }}</span>{% endif %}
{% if expense.payment_method %}<span><i class="fa-regular fa-credit-card me-1"></i>{{ t('expenses.payment_' ~ expense.payment_method) if expense.payment_method in ['card','cash','transfer','blik'] else expense.payment_method }}</span>{% endif %}
{% if expense.tags %}<span><i class="fa-solid fa-tags me-1"></i>{{ expense.tags }}</span>{% endif %}
{% if expense.status %}<span><i class="fa-solid fa-shield-halved me-1"></i>{{ t('expenses.status_' ~ expense.status) if expense.status in ['new','needs_review','confirmed'] else expense.status }}</span>{% endif %}
</div>
{% if expense.description %}<div class="small text-body-secondary mt-2">{{ expense.description }}</div>{% endif %}
</div>
</div>
<div class="expense-list-side">
<div class="expense-amount">{{ expense.amount }} {{ expense.currency }}</div>
<div class="expense-actions">
{% if expense.all_previews %}
{% for preview_name in expense.all_previews[:3] %}
<button type="button" class="btn btn-sm btn-outline-secondary preview-trigger" data-bs-toggle="modal" data-bs-target="#previewModal" data-preview="{{ url_for('static', filename='previews/' ~ preview_name) }}"><i class="fa-solid fa-image"></i></button>
{% endfor %}
{% endif %}
<a class="btn btn-sm btn-outline-primary" href="{{ url_for('expenses.edit_expense', expense_id=expense.id) }}"><i class="fa-solid fa-pen-to-square me-1"></i>{{ t('expenses.edit') }}</a>
<form method="post" action="{{ url_for('expenses.delete_expense', expense_id=expense.id) }}" class="d-inline">{{ csrf_token() if csrf_token else '' }}<button class="btn btn-sm btn-outline-danger"><i class="fa-solid fa-trash"></i></button></form>
</div>
</div>
</article>
{% endfor %}
</div>
</section>
{% endfor %}
</div>
{% else %}
<div class="card"><div class="empty-state"><i class="fa-solid fa-wallet"></i><div>{{ t('expenses.empty') }}</div></div></div>
{% endif %}
<div class="modal fade" id="previewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content glass-card">
<div class="modal-header border-0">
<h2 class="h5 mb-0"><i class="fa-solid fa-image me-2"></i>{{ t('expenses.preview') }}</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<img id="previewModalImage" src="" alt="preview" class="img-fluid rounded-4 border">
</div>
</div>
</div>
</div>
{% endblock %}