412 lines
29 KiB
HTML
412 lines
29 KiB
HTML
{% extends "claims/base.html" %}
|
||
{% load i18n %}
|
||
|
||
{% block title %}{% trans "Admin – Dashboard" %}{% endblock %}
|
||
|
||
{% block content %}
|
||
<section class="space-y-8 py-6">
|
||
<header class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold uppercase tracking-wide text-gray-500">{% trans "Översikt" %}</p>
|
||
<h1 class="text-3xl font-semibold text-gray-900">{% trans "Dashboard för utlägg" %}</h1>
|
||
<p class="mt-2 text-sm text-gray-600">{% trans "Få koll på inflödet, beslutsläget och utbetalningar – och hantera ärenden direkt." %}</p>
|
||
</div>
|
||
<div class="rounded-2xl border border-dashed border-gray-200 bg-white px-4 py-3 text-xs text-gray-600">
|
||
{% trans "Tips: använd filtren för att fokusera på specifika statusar eller projekt. Dashboarden uppdateras i realtid när data ändras." %}
|
||
</div>
|
||
</header>
|
||
|
||
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Totalt antal utlägg" %}</p>
|
||
<p class="mt-3 text-4xl font-semibold text-gray-900">{{ summary.total_claims }}</p>
|
||
<p class="mt-1 text-xs text-gray-500">{% trans "Alla statusar" %}</p>
|
||
</article>
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Senaste 7 dagarna" %}</p>
|
||
<p class="mt-3 text-4xl font-semibold text-gray-900">{{ summary.last_week_claims }}</p>
|
||
<p class="mt-1 text-xs text-gray-500">{% trans "Nya inskick sedan en vecka" %}</p>
|
||
</article>
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Pågående granskning" %}</p>
|
||
<p class="mt-3 text-4xl font-semibold text-amber-600">{{ summary.pending_count }}</p>
|
||
<p class="mt-1 text-xs text-gray-500">{% trans "Behöver beslut" %}</p>
|
||
</article>
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Redo för utbetalning" %}</p>
|
||
<p class="mt-3 text-4xl font-semibold text-emerald-600">{{ summary.ready_to_pay }}</p>
|
||
<p class="mt-1 text-xs text-gray-500">{% trans "Godkända men ej markerade som betalda" %}</p>
|
||
</article>
|
||
</div>
|
||
|
||
<div class="grid gap-4 md:grid-cols-3">
|
||
<article class="rounded-3xl bg-slate-900 px-5 py-6 text-white shadow-sm ring-1 ring-slate-800">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-300">{% trans "Belopp att besluta" %}</p>
|
||
<p class="mt-3 text-3xl font-semibold">{{ summary.pending_amount|floatformat:2 }}</p>
|
||
<p class="mt-1 text-xs text-slate-400">{% trans "Summa av väntande utlägg (alla valutor)" %}</p>
|
||
</article>
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Godkända belopp" %}</p>
|
||
<p class="mt-3 text-3xl font-semibold text-gray-900">{{ summary.approved_amount|floatformat:2 }}</p>
|
||
<p class="mt-1 text-xs text-gray-500">{% trans "Summa för alla godkända utlägg" %}</p>
|
||
</article>
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Utbetalda belopp" %}</p>
|
||
<p class="mt-3 text-3xl font-semibold text-gray-900">{{ summary.paid_amount|floatformat:2 }}</p>
|
||
<p class="mt-1 text-xs text-gray-500">{% trans "Markerade som betalda" %}</p>
|
||
</article>
|
||
</div>
|
||
|
||
<div class="grid gap-6 lg:grid-cols-[minmax(0,2fr),minmax(0,1fr)]">
|
||
<div class="space-y-6">
|
||
<section class="rounded-3xl bg-white px-5 py-5 shadow-sm ring-1 ring-gray-100">
|
||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold uppercase tracking-wide text-gray-500">{% trans "Filtrera" %}</p>
|
||
<h2 class="text-xl font-semibold text-gray-900">{% trans "Hantera utlägg" %}</h2>
|
||
<p class="mt-1 text-sm text-gray-600">{% trans "Välj status för att fokusera listan nedan." %}</p>
|
||
</div>
|
||
<div class="flex flex-wrap gap-2" data-filter-controls>
|
||
<a href="?status=all"
|
||
data-filter-button
|
||
data-filter-value="all"
|
||
aria-pressed="{% if status_filter == 'all' %}true{% else %}false{% endif %}"
|
||
class="rounded-full px-4 py-2 text-sm font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-600 focus-visible:ring-offset-2 {% if status_filter == 'all' %}bg-brand-600 text-white hover:bg-brand-700{% else %}bg-slate-100 text-gray-700 hover:bg-slate-200{% endif %}">
|
||
{% trans "Alla" %}
|
||
</a>
|
||
{% for value, label in status_choices %}
|
||
<a href="?status={{ value }}"
|
||
data-filter-button
|
||
data-filter-value="{{ value }}"
|
||
aria-pressed="{% if status_filter == value %}true{% else %}false{% endif %}"
|
||
class="rounded-full px-4 py-2 text-sm font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-600 focus-visible:ring-offset-2 {% if status_filter == value %}bg-brand-600 text-white hover:bg-brand-700{% else %}bg-slate-100 text-gray-700 hover:bg-slate-200{% endif %}">
|
||
{{ label }}
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="space-y-6" data-claim-list>
|
||
{% for claim in claims %}
|
||
<article class="rounded-3xl bg-white shadow-sm ring-1 ring-gray-100 {% if status_filter != 'all' and claim.status != status_filter %}hidden{% endif %}"
|
||
data-claim-card
|
||
data-status="{{ claim.status }}">
|
||
<div class="flex flex-col gap-4 border-b border-gray-100 px-6 py-5 lg:flex-row lg:justify-between">
|
||
<div class="space-y-3">
|
||
<div class="flex flex-wrap items-center gap-3 text-sm text-gray-500">
|
||
<span class="rounded-full bg-slate-100 px-3 py-1 font-semibold text-gray-700">
|
||
{{ claim.amount }} {{ claim.currency }}
|
||
</span>
|
||
{% if claim.project %}
|
||
<span class="rounded-full bg-violet-50 px-3 py-1 font-semibold text-violet-700">
|
||
{{ claim.project }}
|
||
</span>
|
||
{% endif %}
|
||
<span class="text-xs text-gray-400">{% trans "Skapad" %} {{ claim.created_at|date:"Y-m-d H:i" }}</span>
|
||
</div>
|
||
<div class="space-y-1">
|
||
<p class="text-sm font-semibold uppercase tracking-wide text-gray-500">{% trans "Person" %}</p>
|
||
<h2 class="text-2xl font-semibold text-gray-900">{{ claim.full_name }}</h2>
|
||
<p class="text-sm text-gray-600">
|
||
{{ claim.email }} · {% trans "Konto" %}: <span class="font-mono text-gray-900">{{ claim.account_number }}</span><br>
|
||
{% if claim.submitted_by %}
|
||
<span class="text-xs uppercase tracking-wide text-green-600">{% trans "Inloggad användare" %}: {{ claim.submitted_by.get_username }}</span>
|
||
{% else %}
|
||
<span class="text-xs uppercase tracking-wide text-gray-500">{% trans "Inskickad av gäst" %}</span>
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="flex flex-col items-start gap-2 text-sm lg:items-end">
|
||
<span class="rounded-full px-4 py-2 text-sm font-semibold {% if claim.status == 'approved' %}bg-green-50 text-green-700 border border-green-200{% elif claim.status == 'rejected' %}bg-rose-50 text-rose-700 border border-rose-200{% else %}bg-amber-50 text-amber-800 border border-amber-200{% endif %}">
|
||
{{ claim.get_status_display }}
|
||
</span>
|
||
{% if claim.decision_note %}
|
||
<p class="text-xs text-gray-500">{% trans "Kommentar" %}: {{ claim.decision_note }}</p>
|
||
{% endif %}
|
||
{% if payments_enabled and claim.status == 'approved' %}
|
||
{% if claim.is_paid %}
|
||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-800">
|
||
{% trans "Betald" %} {{ claim.paid_at|date:"Y-m-d H:i" }}
|
||
{% if claim.paid_by %}{% trans "av" %} {{ claim.paid_by.get_username }}{% endif %}
|
||
</span>
|
||
{% else %}
|
||
<span class="text-xs text-gray-500">{% trans "Ej markerad som betald" %}</span>
|
||
{% endif %}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{% if claim.status == 'approved' %}
|
||
<div class="mx-6 mt-4 grid gap-4 rounded-3xl border border-green-100 bg-green-50 px-6 py-4 text-sm text-green-900 md:grid-cols-[2fr,1fr]">
|
||
<details class="space-y-3" {% if not claim.is_paid %}open{% endif %}>
|
||
<summary class="flex cursor-pointer items-center justify-between text-xs font-semibold uppercase tracking-wide text-green-600">
|
||
<span>{% trans "Utbetalningsdetaljer" %}</span>
|
||
<svg class="h-4 w-4 transition group-open:-rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||
</svg>
|
||
</summary>
|
||
<div class="rounded-2xl bg-white/80 p-4 md:h-full">
|
||
<dl class="grid h-full gap-2 text-sm text-green-900 md:grid-cols-2">
|
||
<div>
|
||
<dt class="text-xs uppercase tracking-wide text-green-700">{% trans "Belopp" %}</dt>
|
||
<dd class="text-lg font-semibold">{{ claim.amount }} {{ claim.currency }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-xs uppercase tracking-wide text-green-700">{% trans "Kontonummer" %}</dt>
|
||
<dd class="font-mono text-base">{{ claim.account_number }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-xs uppercase tracking-wide text-green-700">{% trans "Referens (Claim ID)" %}</dt>
|
||
<dd class="font-semibold">#{{ claim.id }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-xs uppercase tracking-wide text-green-700">{% trans "Skapad" %}</dt>
|
||
<dd>{{ claim.created_at|date:"Y-m-d H:i" }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-xs uppercase tracking-wide text-green-700">{% trans "E-post" %}</dt>
|
||
<dd>{{ claim.email }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-xs uppercase tracking-wide text-green-700">{% trans "Projekt" %}</dt>
|
||
<dd>{{ claim.project|default:"-" }}</dd>
|
||
</div>
|
||
</dl>
|
||
<p class="mt-3 text-[11px] text-green-700">{% trans "Använd referensen och beloppet när du lägger upp betalningen – hjälper att undvika dubbletter." %}</p>
|
||
</div>
|
||
</details>
|
||
<div class="flex flex-col items-start gap-3 md:items-end">
|
||
{% if payments_enabled %}
|
||
{% if not claim.is_paid %}
|
||
<form method="post" class="w-full max-w-xs" onsubmit="return confirm('{% trans "Är du säker på att du har lagt upp betalningen? Markera endast som betald om beloppet skickas till banken." %}');">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action_type" value="payment">
|
||
<input type="hidden" name="payment_claim_id" value="{{ claim.id }}">
|
||
<button type="submit" class="flex w-full items-center justify-center gap-2 rounded-2xl bg-emerald-600 px-4 py-3 text-xs font-semibold uppercase tracking-wide text-white transition hover:bg-emerald-700">
|
||
{% trans "Markera som betald" %}
|
||
</button>
|
||
</form>
|
||
<p class="text-[11px] text-green-700">{% trans "Dubbelkolla belopp och kontonummer i panelen innan du bekräftar." %}</p>
|
||
{% endif %}
|
||
{% else %}
|
||
<p class="rounded-2xl bg-white/70 px-4 py-3 text-xs text-green-800">
|
||
{% trans "Intern betalningshantering är av – markera betalning i ekonomisystemet och resetta status vid behov." %}
|
||
</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="grid gap-6 px-6 py-6 lg:grid-cols-3">
|
||
<div class="lg:col-span-2">
|
||
<p class="text-sm font-semibold text-gray-500">{% trans "Beskrivning" %}</p>
|
||
<p class="mt-2 whitespace-pre-wrap text-gray-800">{{ claim.description }}</p>
|
||
<div class="mt-4 flex flex-wrap items-center gap-4 text-sm text-gray-600">
|
||
{% if claim.receipt %}
|
||
<a class="inline-flex items-center gap-2 text-brand-600 hover:text-brand-700" href="{{ claim.receipt.url }}" target="_blank" rel="noopener">
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||
</svg>
|
||
{% trans "Visa kvitto" %}
|
||
</a>
|
||
{% else %}
|
||
<span class="text-xs text-gray-400">{% trans "Inget kvitto bifogat" %}</span>
|
||
{% endif %}
|
||
<span class="text-xs text-gray-400">{% trans "Senast uppdaterad" %}: {{ claim.updated_at|date:"Y-m-d H:i" }}</span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<details class="rounded-2xl bg-slate-50 p-4 text-sm text-gray-700">
|
||
<summary class="cursor-pointer select-none text-sm font-semibold text-gray-800">{% trans "Logg" %}</summary>
|
||
<ul class="mt-3 space-y-2 text-sm text-gray-600">
|
||
{% for log in claim.logs.all %}
|
||
<li class="rounded-lg bg-white px-3 py-2 shadow-sm">
|
||
<p class="font-semibold text-gray-900">{{ log.get_action_display }}</p>
|
||
<p class="text-xs text-gray-500">{{ log.created_at|date:"Y-m-d H:i" }}</p>
|
||
{% if log.from_status %}
|
||
<p class="text-xs text-gray-500">{% trans "Status" %}: {{ log.get_from_status_display }} → {{ log.get_to_status_display }}</p>
|
||
{% endif %}
|
||
{% if log.note %}
|
||
<p class="mt-1 text-xs text-gray-600">"{{ log.note }}"</p>
|
||
{% endif %}
|
||
{% if log.performed_by %}
|
||
<p class="text-xs text-gray-400">{% trans "Av" %} {{ log.performed_by.get_username }}</p>
|
||
{% endif %}
|
||
</li>
|
||
{% empty %}
|
||
<li class="text-xs text-gray-400">{% trans "Ingen logg än." %}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</details>
|
||
|
||
{% if can_change %}
|
||
{% if claim.is_paid %}
|
||
<p class="mt-4 rounded-lg bg-slate-100 px-3 py-2 text-xs text-slate-600">
|
||
{% trans "Utlägget är markerat som betalt. Ändringar av beslut/kommentar är låsta." %}
|
||
</p>
|
||
{% else %}
|
||
<form method="post" class="mt-4 space-y-3">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="claim_id" value="{{ claim.id }}">
|
||
|
||
<label class="block text-sm font-medium text-gray-700">{% trans "Åtgärd" %}</label>
|
||
<select name="action" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:border-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-600">
|
||
{% for value, label in decision_choices %}
|
||
<option value="{{ value }}">{{ label }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
|
||
<label class="block text-sm font-medium text-gray-700">{% trans "Kommentar" %}</label>
|
||
<textarea name="decision_note" rows="3" class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:border-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-600">{{ claim.decision_note }}</textarea>
|
||
|
||
<input type="hidden" name="action_type" value="decision">
|
||
<button type="submit" class="w-full rounded-full bg-brand-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-brand-700">
|
||
{% trans "Uppdatera beslut" %}
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</article>
|
||
{% empty %}
|
||
<div class="rounded-3xl border border-dashed border-gray-200 bg-white px-6 py-10 text-center text-gray-500">
|
||
<p class="text-lg font-semibold text-gray-900">{% trans "Inga utlägg ännu" %}</p>
|
||
<p class="mt-2 text-sm">{% trans "När formuläret tas emot visas posterna automatiskt här." %}</p>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% if has_any_claims %}
|
||
<div data-claim-empty class="{% if has_filtered_claims %}hidden{% endif %} rounded-3xl border border-dashed border-gray-200 bg-white px-6 py-10 text-center text-gray-500">
|
||
<p class="text-lg font-semibold text-gray-900">{% trans "Inga utlägg matchar filtret" %}</p>
|
||
<p class="mt-2 text-sm">{% trans "Välj en annan status för att se fler poster." %}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<aside class="space-y-6">
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<header>
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Senaste inskick" %}</p>
|
||
<h2 class="text-xl font-semibold text-gray-900">{% trans "Aktivitet" %}</h2>
|
||
</header>
|
||
<ul class="mt-4 space-y-3">
|
||
{% for recent in recent_claims %}
|
||
<li class="rounded-2xl border border-gray-100 bg-slate-50 px-4 py-3">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-900">{{ recent.full_name }}</p>
|
||
<p class="text-xs text-gray-500">{{ recent.created_at|date:"Y-m-d H:i" }}</p>
|
||
</div>
|
||
<span class="rounded-full px-3 py-1 text-xs font-semibold {% if recent.status == 'approved' %}bg-green-100 text-green-700{% elif recent.status == 'rejected' %}bg-rose-100 text-rose-700{% else %}bg-amber-100 text-amber-700{% endif %}">
|
||
{{ recent.get_status_display }}
|
||
</span>
|
||
</div>
|
||
<p class="mt-2 text-xs text-gray-600">{{ recent.description|default:"-" }}</p>
|
||
</li>
|
||
{% empty %}
|
||
<li class="rounded-2xl border border-dashed border-gray-200 px-4 py-6 text-center text-sm text-gray-500">
|
||
{% trans "Inga aktiviteter än." %}
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</article>
|
||
|
||
<article class="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100">
|
||
<header>
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Statusfördelning" %}</p>
|
||
<h2 class="text-xl font-semibold text-gray-900">{% trans "Snabbstatistik" %}</h2>
|
||
</header>
|
||
<dl class="mt-4 space-y-3 text-sm text-gray-700">
|
||
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
|
||
<dt class="font-semibold text-amber-700">{% trans "Pending" %}</dt>
|
||
<dd class="text-lg font-semibold text-amber-700">{{ summary.pending_count }}</dd>
|
||
</div>
|
||
<div class="flex items-center justify-between rounded-2xl bg-green-50 px-4 py-3">
|
||
<dt class="font-semibold text-green-700">{% trans "Approved" %}</dt>
|
||
<dd class="text-lg font-semibold text-green-700">{{ summary.approved_count }}</dd>
|
||
</div>
|
||
<div class="flex items-center justify-between rounded-2xl bg-rose-50 px-4 py-3">
|
||
<dt class="font-semibold text-rose-700">{% trans "Rejected" %}</dt>
|
||
<dd class="text-lg font-semibold text-rose-700">{{ summary.rejected_count }}</dd>
|
||
</div>
|
||
</dl>
|
||
</article>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
<script>
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
const filterButtons = Array.from(document.querySelectorAll("[data-filter-button]"));
|
||
const cards = Array.from(document.querySelectorAll("[data-claim-card]"));
|
||
const emptyState = document.querySelector("[data-claim-empty]");
|
||
if (!filterButtons.length || !cards.length) {
|
||
return;
|
||
}
|
||
|
||
const activeClasses = ["bg-brand-600", "text-white", "hover:bg-brand-700"];
|
||
const inactiveClasses = ["bg-slate-100", "text-gray-700", "hover:bg-slate-200"];
|
||
|
||
const setButtonState = (activeValue) => {
|
||
filterButtons.forEach((btn) => {
|
||
const value = btn.dataset.filterValue || "all";
|
||
const isActive = value === activeValue;
|
||
btn.setAttribute("aria-pressed", String(isActive));
|
||
const classList = btn.classList;
|
||
if (isActive) {
|
||
inactiveClasses.forEach((cls) => classList.remove(cls));
|
||
activeClasses.forEach((cls) => classList.add(cls));
|
||
} else {
|
||
activeClasses.forEach((cls) => classList.remove(cls));
|
||
inactiveClasses.forEach((cls) => classList.add(cls));
|
||
}
|
||
});
|
||
};
|
||
|
||
const applyFilter = (filterValue) => {
|
||
const value = filterValue || "all";
|
||
let visibleCount = 0;
|
||
|
||
cards.forEach((card) => {
|
||
const matches = value === "all" || card.dataset.status === value;
|
||
card.classList.toggle("hidden", !matches);
|
||
if (matches) {
|
||
visibleCount += 1;
|
||
}
|
||
});
|
||
|
||
if (emptyState) {
|
||
emptyState.classList.toggle("hidden", visibleCount > 0);
|
||
}
|
||
|
||
setButtonState(value);
|
||
|
||
try {
|
||
const url = new URL(window.location.href);
|
||
if (value === "all") {
|
||
url.searchParams.delete("status");
|
||
} else {
|
||
url.searchParams.set("status", value);
|
||
}
|
||
window.history.replaceState({}, "", url);
|
||
} catch (error) {
|
||
// ignore history errors
|
||
}
|
||
};
|
||
|
||
filterButtons.forEach((btn) => {
|
||
btn.addEventListener("click", (event) => {
|
||
event.preventDefault();
|
||
applyFilter(btn.dataset.filterValue || "all");
|
||
});
|
||
});
|
||
|
||
const initialFilter = new URLSearchParams(window.location.search).get("status") || "all";
|
||
applyFilter(initialFilter);
|
||
});
|
||
</script>
|
||
{% endblock %}
|