211 lines
14 KiB
HTML
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 %}
|