Add inline edit panel for claims

This commit is contained in:
Victor Andersson
2025-11-09 21:49:44 +01:00
parent 0d68c75fef
commit caf3df24cf
8 changed files with 359 additions and 108 deletions

View File

@@ -26,7 +26,7 @@ Nyckel-URLer (språkprefixed):
- **Auto-prefill:** Inloggade användare får namn, e-post och senaste kontonummer förifyllt.
- **Valuta & projekt:** Varje rad har dold valutaväljare (SEK default) och projektreferens. Projekt listas från Django admin > Projekt.
- **Kvitton:** Filuppladdningar sparas med slumpat UUID-baserat namn under `receipts/` för säkerhet och unika namn.
- **Dashboard:** KPI-kort med totalsiffror, senaste aktivitet, statusfördelning och samma inline-flöde för beslut/utbetalningar (inkl. möjlighet att korrigera projekt vid beslut).
- **Dashboard:** KPI-kort med totalsiffror, senaste aktivitet, statusfördelning och samma inline-flöde för beslut/utbetalningar. Attestanter kan öppna en redigeringspanel för att justera namn, belopp, valuta, kontonummer och projekt innan beslut.
- **Betalspårning:** När intern betalning är på får godkända claims en "Betala"-knapp. När ett claim markeras som betalt låses status/kommentar tills reset görs.
- **Mina utlägg:** Inloggade ser sina egna claims i samma Tailwind-layout med kvitto-länk och logg.
- **Användarhantering:** Tailwind-sida där personal kan skapa konton, tilldela `claims.view_claim`/`claims.change_claim`, markera staff och ta bort användare.

View File

@@ -73,14 +73,15 @@ class ClaimDecisionForm(forms.Form):
label=_("Evenemang/Projekt"),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(is_active=True).order_by("name")
decision_note = forms.CharField(
required=False,
widget=forms.Textarea(attrs={"rows": 2, "placeholder": _("Kommentar")}),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(is_active=True).order_by("name")
def clean(self):
cleaned = super().clean()
action = cleaned.get("action")
@@ -90,6 +91,29 @@ class ClaimDecisionForm(forms.Form):
return cleaned
class ClaimEditForm(forms.ModelForm):
class Meta:
model = Claim
fields = [
"full_name",
"email",
"account_number",
"amount",
"currency",
"project",
"description",
]
labels = {
"full_name": _("Namn"),
"email": _("E-post"),
"account_number": _("Kontonummer"),
"amount": _("Belopp"),
"currency": _("Valuta"),
"project": _("Evenemang/Projekt"),
"description": _("Beskrivning"),
}
class UserManagementForm(forms.Form):
username = forms.CharField(max_length=150, label=_("Användarnamn"))
email = forms.EmailField(required=False, label=_("E-post"))

View File

@@ -123,6 +123,7 @@ class ClaimLog(models.Model):
STATUS_CHANGED = "status_changed", _("Status changed")
MARKED_PAID = "marked_paid", _("Marked as paid")
PROJECT_CHANGED = "project_changed", _("Project changed")
DETAILS_EDITED = "details_edited", _("Details edited")
claim = models.ForeignKey(Claim, related_name="logs", on_delete=models.CASCADE)
action = models.CharField(max_length=32, choices=Action.choices)

View File

@@ -118,25 +118,32 @@
</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>
<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.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 %}
{% if can_change %}
<button type="button"
data-open-edit="{{ claim.id }}"
class="rounded-full border border-gray-300 px-3 py-1 text-xs font-semibold text-gray-700 transition hover:bg-gray-100">
{% trans "Redigera" %}
</button>
{% 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 {% if payments_enabled and claim.is_paid %}md:grid-cols-1{% else %}md:grid-cols-[2fr,1fr]{% endif %}">
@@ -294,6 +301,78 @@
<p class="mt-2 text-sm">{% trans "Välj en annan status för att se fler poster." %}</p>
</div>
{% endif %}
{% if can_change %}
<div class="fixed inset-0 z-40 hidden items-center justify-center bg-black/60 p-4" data-edit-panel="{{ claim.id }}">
<div class="w-full max-w-2xl rounded-3xl bg-white p-6 text-left shadow-2xl">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-wide text-gray-500">{% trans "Redigera utlägg" %}</p>
<h3 class="text-xl font-semibold text-gray-900">{{ claim.full_name }}</h3>
</div>
<button type="button" data-close-edit class="rounded-full bg-gray-100 px-3 py-1 text-xs font-semibold text-gray-600 transition hover:bg-gray-200">
{% trans "Stäng" %}
</button>
</div>
<form method="post" class="mt-4 space-y-4">
{% csrf_token %}
<input type="hidden" name="action_type" value="edit">
<input type="hidden" name="edit_claim_id" value="{{ claim.id }}">
<div class="grid gap-4 md:grid-cols-2">
<label class="text-sm font-medium text-gray-700">
{% trans "Namn" %}
<input type="text" name="full_name" value="{{ claim.full_name }}" class="mt-1 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" required>
</label>
<label class="text-sm font-medium text-gray-700">
{% trans "E-post" %}
<input type="email" name="email" value="{{ claim.email }}" class="mt-1 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" required>
</label>
<label class="text-sm font-medium text-gray-700">
{% trans "Kontonummer" %}
<input type="text" name="account_number" value="{{ claim.account_number }}" class="mt-1 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" required>
</label>
<label class="text-sm font-medium text-gray-700">
{% trans "Belopp" %}
<input type="number" step="0.01" name="amount" value="{{ claim.amount }}" class="mt-1 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" required>
</label>
<label class="text-sm font-medium text-gray-700">
{% trans "Valuta" %}
<select name="currency" class="mt-1 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 currency_choices %}
<option value="{{ value }}"{% if claim.currency == value %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<label class="text-sm font-medium text-gray-700">
{% trans "Evenemang/Projekt" %}
<select name="project" class="mt-1 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">
<option value="">{% trans "Ingen" %}</option>
{% for project in project_options %}
<option value="{{ project.id }}"{% if claim.project and project.id == claim.project.id %} selected{% endif %}>{{ project }}</option>
{% endfor %}
</select>
</label>
</div>
<div>
<label class="text-sm font-medium text-gray-700" for="edit-description-{{ claim.id }}">{% trans "Beskrivning" %}</label>
<textarea id="edit-description-{{ claim.id }}" name="description" rows="4" class="mt-1 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.description }}</textarea>
</div>
<div class="flex items-center justify-end gap-3">
<button type="button" data-close-edit class="rounded-full border border-gray-300 px-4 py-2 text-sm font-semibold text-gray-600 transition hover:bg-gray-100">
{% trans "Avbryt" %}
</button>
<button type="submit" class="rounded-full bg-brand-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-brand-700">
{% trans "Spara ändringar" %}
</button>
</div>
</form>
<noscript>
<p class="mt-4 rounded-2xl bg-amber-50 px-4 py-3 text-xs text-amber-800">
{% trans "Aktivera JavaScript för att kunna redigera uppgifter direkt här." %}
</p>
</noscript>
</div>
</div>
{% endif %}
</div>
<aside class="space-y-6">
@@ -352,6 +431,37 @@ 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]");
const editButtons = Array.from(document.querySelectorAll("[data-open-edit]"));
const panels = Array.from(document.querySelectorAll("[data-edit-panel]"));
if (!filterButtons.length || !cards.length) {
// still set up edit panels if filters missing
}
if (editButtons.length) {
editButtons.forEach((button) => {
button.addEventListener("click", () => {
const target = document.querySelector(`[data-edit-panel="${button.dataset.openEdit}"]`);
if (target) {
target.classList.remove("hidden");
target.classList.add("flex");
}
});
});
}
panels.forEach((panel) => {
panel.addEventListener("click", (event) => {
if (event.target === panel) {
panel.classList.add("hidden");
panel.classList.remove("flex");
}
});
panel.querySelectorAll("[data-close-edit]").forEach((btn) => {
btn.addEventListener("click", () => {
panel.classList.add("hidden");
panel.classList.remove("flex");
});
});
});
if (!filterButtons.length || !cards.length) {
return;
}

View File

@@ -144,6 +144,33 @@ class DashboardViewTests(TestCase):
self.assertEqual(response.status_code, 200)
claim.refresh_from_db()
self.assertEqual(claim.project, project_new)
self.assertTrue(
claim.logs.filter(action=ClaimLog.Action.PROJECT_CHANGED, note__icontains="Project updated").exists()
self.assertTrue(claim.logs.filter(action=ClaimLog.Action.PROJECT_CHANGED).exists())
def test_attester_can_edit_details(self):
project = Project.objects.create(name="Event", is_active=True)
claim = self._create_claim(project=project, amount=100)
response = self.client.post(
reverse("claims:admin-list"),
{
"action_type": "edit",
"edit_claim_id": claim.id,
"full_name": "Changed Name",
"email": "changed@example.com",
"account_number": "789-000",
"amount": "555.55",
"currency": Claim.Currency.EUR,
"project": "",
"description": "Updated description",
},
follow=True,
)
self.assertEqual(response.status_code, 200)
claim.refresh_from_db()
self.assertEqual(claim.full_name, "Changed Name")
self.assertEqual(claim.email, "changed@example.com")
self.assertEqual(claim.currency, Claim.Currency.EUR)
self.assertIsNone(claim.project)
edit_log = claim.logs.filter(action=ClaimLog.Action.DETAILS_EDITED).first()
self.assertIsNotNone(edit_log)
self.assertIn("Namn", edit_log.note)

View File

@@ -17,6 +17,7 @@ from django.views.generic import ListView, TemplateView
from .forms import (
ClaimDecisionForm,
ClaimEditForm,
ClaimLineForm,
ClaimantForm,
DeleteUserForm,
@@ -162,6 +163,7 @@ class ClaimDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
context["payments_enabled"] = getattr(settings, "CLAIMS_ENABLE_INTERNAL_PAYMENTS", False)
context["summary"] = self._build_summary()
context["project_options"] = Project.objects.filter(is_active=True).order_by("name")
context["currency_choices"] = Claim.Currency.choices
context["has_any_claims"] = context["summary"]["total_claims"] > 0
context["has_filtered_claims"] = self._has_filtered_claims(context["status_filter"], context["summary"])
context["recent_claims"] = (
@@ -175,6 +177,8 @@ class ClaimDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
action_type = request.POST.get("action_type", "decision")
if action_type == "payment":
return self._handle_payment(request)
if action_type == "edit":
return self._handle_edit(request)
return self._handle_decision(request)
def _handle_decision(self, request):
@@ -256,6 +260,39 @@ class ClaimDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
messages.success(request, _("%(claim)s markerades som betald.") % {"claim": claim})
return redirect(request.get_full_path())
def _handle_edit(self, request):
if not request.user.has_perm("claims.change_claim"):
messages.error(request, _("Du har inte behörighet att uppdatera utlägg."))
return redirect(request.get_full_path())
claim = get_object_or_404(Claim, pk=request.POST.get("edit_claim_id"))
form = ClaimEditForm(request.POST, instance=claim)
if not form.is_valid():
for error in form.errors.get("__all__", []):
messages.error(request, error)
for field, field_errors in form.errors.items():
if field == "__all__":
continue
for error in field_errors:
messages.error(request, f"{form.fields[field].label}: {error}")
return redirect(request.get_full_path())
updated_claim = form.save()
changed_fields = []
for field in form.changed_data:
label = form.fields[field].label or field
changed_fields.append(str(label))
if changed_fields:
note = _("Fields updated: %(fields)s") % {"fields": ", ".join(changed_fields)}
claim.add_log(
action=ClaimLog.Action.DETAILS_EDITED,
performed_by=request.user,
note=note,
)
messages.success(request, _("Informationen uppdaterades."))
else:
messages.info(request, _("Inga förändringar att spara."))
return redirect(request.get_full_path())
def _build_summary(self):
now = timezone.now()
last_week = now - timedelta(days=7)

Binary file not shown.

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: claims-system 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-09 14:00+0100\n"
"POT-Creation-Date: 2025-11-09 21:45+0100\n"
"PO-Revision-Date: 2025-11-08 23:40+0100\n"
"Last-Translator: ChatGPT <noreply@example.com>\n"
"Language-Team: English\n"
@@ -12,34 +12,43 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: claims/forms.py:19
#: claims/forms.py:19 claims/forms.py:107
msgid "Namn"
msgstr "Name"
#: claims/forms.py:23 claims/forms.py:95
#: claims/templates/claims/dashboard.html:169
#: claims/forms.py:23 claims/forms.py:108 claims/forms.py:119
#: claims/templates/claims/dashboard.html:176
#: claims/templates/claims/dashboard.html:326
msgid "E-post"
msgstr "Email"
#: claims/forms.py:28 claims/templates/claims/dashboard.html:157
#: claims/forms.py:28 claims/forms.py:109
#: claims/templates/claims/dashboard.html:164
#: claims/templates/claims/dashboard.html:330
msgid "Kontonummer"
msgstr "Account number"
#: claims/forms.py:49 claims/templates/claims/dashboard.html:204
#: claims/forms.py:49 claims/forms.py:113
#: claims/templates/claims/dashboard.html:211
#: claims/templates/claims/dashboard.html:356
msgid "Beskrivning"
msgstr "Description"
#: claims/forms.py:50 claims/templates/claims/dashboard.html:153
#: claims/forms.py:50 claims/forms.py:110
#: claims/templates/claims/dashboard.html:160
#: claims/templates/claims/dashboard.html:334
#: claims/templates/claims/my_claims.html:23
msgid "Belopp"
msgstr "Amount"
#: claims/forms.py:51
#: claims/forms.py:51 claims/forms.py:111
#: claims/templates/claims/dashboard.html:338
msgid "Valuta"
msgstr "Currency"
#: claims/forms.py:52 claims/forms.py:73
#: claims/templates/claims/dashboard.html:264
#: claims/forms.py:52 claims/forms.py:73 claims/forms.py:112
#: claims/templates/claims/dashboard.html:271
#: claims/templates/claims/dashboard.html:346
msgid "Evenemang/Projekt"
msgstr "Project"
@@ -55,76 +64,76 @@ msgstr "Approve"
msgid "Neka"
msgstr "Reject"
#: claims/forms.py:81 claims/templates/claims/dashboard.html:126
#: claims/templates/claims/dashboard.html:261
#: claims/forms.py:78 claims/templates/claims/dashboard.html:126
#: claims/templates/claims/dashboard.html:268
msgid "Kommentar"
msgstr "Comment"
#: claims/forms.py:89
#: claims/forms.py:90
msgid "Kommentar krävs när du nekar ett utlägg."
msgstr "A comment is required when you reject an expense."
#: claims/forms.py:94
#: claims/forms.py:118
msgid "Användarnamn"
msgstr "Username"
#: claims/forms.py:96
#: claims/forms.py:120
msgid "Förnamn"
msgstr "First name"
#: claims/forms.py:97
#: claims/forms.py:121
msgid "Efternamn"
msgstr "Last name"
#: claims/forms.py:98
#: claims/forms.py:122
msgid "Lösenord"
msgstr "Password"
#: claims/forms.py:99
#: claims/forms.py:123
msgid "Bekräfta lösenord"
msgstr "Confirm password"
#: claims/forms.py:100
#: claims/forms.py:124
msgid "Administratör (staff)"
msgstr "Administrator (staff)"
#: claims/forms.py:101
#: claims/forms.py:125
msgid "Ge behörighet att se utlägg"
msgstr "Allow viewing claims"
#: claims/forms.py:102
#: claims/forms.py:126
msgid "Ge behörighet att besluta utlägg"
msgstr "Allow deciding claims"
#: claims/forms.py:107
#: claims/forms.py:131
msgid "Användarnamnet är upptaget."
msgstr "That username is already taken."
#: claims/forms.py:113
#: claims/forms.py:137
msgid "Lösenorden matchar inte."
msgstr "Passwords do not match."
#: claims/forms.py:131 claims/templates/claims/user_management.html:116
#: claims/forms.py:155 claims/templates/claims/user_management.html:116
msgid "Admin/staff"
msgstr "Admin/staff"
#: claims/forms.py:132 claims/templates/claims/user_management.html:120
#: claims/forms.py:156 claims/templates/claims/user_management.html:120
msgid "Får se utlägg"
msgstr "May view claims"
#: claims/forms.py:133 claims/templates/claims/user_management.html:124
#: claims/forms.py:157 claims/templates/claims/user_management.html:124
msgid "Får besluta utlägg"
msgstr "May decide claims"
#: claims/models.py:29 claims/templates/claims/dashboard.html:334
#: claims/models.py:29 claims/templates/claims/dashboard.html:413
msgid "Pending"
msgstr "Pending"
#: claims/models.py:30 claims/templates/claims/dashboard.html:338
#: claims/models.py:30 claims/templates/claims/dashboard.html:417
msgid "Approved"
msgstr "Approved"
#: claims/models.py:31 claims/templates/claims/dashboard.html:342
#: claims/models.py:31 claims/templates/claims/dashboard.html:421
msgid "Rejected"
msgstr "Rejected"
@@ -164,6 +173,10 @@ msgstr "Marked as paid"
msgid "Project changed"
msgstr "Project changed"
#: claims/models.py:126
msgid "Details edited"
msgstr ""
#: claims/templates/claims/base.html:8
msgid "Claims"
msgstr "Claims"
@@ -323,7 +336,7 @@ msgid "Alla"
msgstr "All"
#: claims/templates/claims/dashboard.html:106
#: claims/templates/claims/dashboard.html:165
#: claims/templates/claims/dashboard.html:172
msgid "Skapad"
msgstr "Created"
@@ -356,20 +369,24 @@ msgstr "by"
msgid "Ej markerad som betald"
msgstr "Not marked as paid"
#: claims/templates/claims/dashboard.html:145
#: claims/templates/claims/dashboard.html:142
msgid "Redigera"
msgstr "Edit"
#: claims/templates/claims/dashboard.html:152
msgid "Utbetalningsdetaljer"
msgstr "Payout details"
#: claims/templates/claims/dashboard.html:161
#: claims/templates/claims/dashboard.html:168
msgid "Referens (Claim ID)"
msgstr "Reference (Claim ID)"
#: claims/templates/claims/dashboard.html:173
#: claims/templates/claims/dashboard.html:180
#: claims/templates/claims/my_claims.html:24
msgid "Projekt"
msgstr "Project"
#: claims/templates/claims/dashboard.html:177
#: claims/templates/claims/dashboard.html:184
msgid ""
"Använd referensen och beloppet när du lägger upp betalningen hjälper att "
"undvika dubbletter."
@@ -377,7 +394,7 @@ msgstr ""
"Use the reference and amount when entering the payment it helps avoid "
"duplicates."
#: claims/templates/claims/dashboard.html:182
#: claims/templates/claims/dashboard.html:189
msgid ""
"Är du säker på att du har lagt upp betalningen? Markera endast som betald om "
"beloppet skickas till banken."
@@ -385,17 +402,15 @@ msgstr ""
"Are you sure the payment has been scheduled? Only mark as paid if the amount "
"has been sent to the bank."
#: claims/templates/claims/dashboard.html:187
#, fuzzy
#| msgid "Markerad som betald"
#: claims/templates/claims/dashboard.html:194
msgid "Markera som betald"
msgstr "Marked as paid"
msgstr "Mark as paid"
#: claims/templates/claims/dashboard.html:190
#: claims/templates/claims/dashboard.html:197
msgid "Dubbelkolla belopp och kontonummer i panelen innan du bekräftar."
msgstr "Double-check the amount and account number before confirming."
#: claims/templates/claims/dashboard.html:195
#: claims/templates/claims/dashboard.html:202
msgid ""
"Intern betalningshantering är av markera betalning i ekonomisystemet och "
"resetta status vid behov."
@@ -403,91 +418,115 @@ msgstr ""
"Internal payment handling is off register the payment in the finance "
"system and reset the status if needed."
#: claims/templates/claims/dashboard.html:212
#: claims/templates/claims/dashboard.html:219
msgid "Visa kvitto"
msgstr "View receipt"
#: claims/templates/claims/dashboard.html:215
#: claims/templates/claims/dashboard.html:222
msgid "Inget kvitto bifogat"
msgstr "No receipt attached"
#: claims/templates/claims/dashboard.html:217
#: claims/templates/claims/dashboard.html:224
msgid "Senast uppdaterad"
msgstr "Last updated"
#: claims/templates/claims/dashboard.html:222
#: claims/templates/claims/dashboard.html:229
msgid "Logg"
msgstr "Log"
#: claims/templates/claims/dashboard.html:229
#: claims/templates/claims/dashboard.html:236
#: claims/templates/claims/my_claims.html:62
msgid "Status"
msgstr "Status"
#: claims/templates/claims/dashboard.html:235
#: claims/templates/claims/dashboard.html:242
msgid "Av"
msgstr "By"
#: claims/templates/claims/dashboard.html:239
#: claims/templates/claims/dashboard.html:246
#: claims/templates/claims/my_claims.html:69
msgid "Ingen logg än."
msgstr "No log entries yet."
#: claims/templates/claims/dashboard.html:247
#: claims/templates/claims/dashboard.html:254
msgid ""
"Utlägget är markerat som betalt. Ändringar av beslut/kommentar är låsta."
msgstr "The claim is marked as paid. Decision/comments are locked."
#: claims/templates/claims/dashboard.html:254
#: claims/templates/claims/dashboard.html:261
msgid "Åtgärd"
msgstr "Action"
#: claims/templates/claims/dashboard.html:266
#: claims/templates/claims/dashboard.html:273
msgid "Behåll nuvarande"
msgstr "Keep current"
#: claims/templates/claims/dashboard.html:271
#: claims/templates/claims/dashboard.html:278
msgid "Justera projekt om underlaget skickats in mot fel evenemang."
msgstr "Adjust the project if the submission was sent against the wrong event."
#: claims/templates/claims/dashboard.html:275
#: claims/templates/claims/dashboard.html:282
msgid "Uppdatera beslut"
msgstr "Update decision"
#: claims/templates/claims/dashboard.html:286
#: claims/templates/claims/dashboard.html:293
#: claims/templates/claims/my_claims.html:78
msgid "Inga utlägg ännu"
msgstr "No claims yet"
#: claims/templates/claims/dashboard.html:287
#: claims/templates/claims/dashboard.html:294
msgid "När formuläret tas emot visas posterna automatiskt här."
msgstr "As soon as submissions arrive they will appear here."
#: claims/templates/claims/dashboard.html:293
#: claims/templates/claims/dashboard.html:300
msgid "Inga utlägg matchar filtret"
msgstr "No claims match the filter"
#: claims/templates/claims/dashboard.html:294
#: claims/templates/claims/dashboard.html:301
msgid "Välj en annan status för att se fler poster."
msgstr "Choose another status to see more entries."
#: claims/templates/claims/dashboard.html:302
#: claims/templates/claims/dashboard.html:309
msgid "Redigera utlägg"
msgstr "Edit claim"
#: claims/templates/claims/dashboard.html:313
msgid "Stäng"
msgstr "Close"
#: claims/templates/claims/dashboard.html:348
msgid "Ingen"
msgstr "None"
#: claims/templates/claims/dashboard.html:361
msgid "Avbryt"
msgstr "Cancel"
#: claims/templates/claims/dashboard.html:364
msgid "Spara ändringar"
msgstr "Save changes"
#: claims/templates/claims/dashboard.html:370
msgid "Aktivera JavaScript för att kunna redigera uppgifter direkt här."
msgstr "Enable JavaScript to edit the information directly here."
#: claims/templates/claims/dashboard.html:381
msgid "Senaste inskick"
msgstr "Latest submissions"
#: claims/templates/claims/dashboard.html:303
#: claims/templates/claims/dashboard.html:382
msgid "Aktivitet"
msgstr "Activity"
#: claims/templates/claims/dashboard.html:321
#: claims/templates/claims/dashboard.html:400
msgid "Inga aktiviteter än."
msgstr "No activity yet."
#: claims/templates/claims/dashboard.html:329
#: claims/templates/claims/dashboard.html:408
msgid "Statusfördelning"
msgstr "Status breakdown"
#: claims/templates/claims/dashboard.html:330
#: claims/templates/claims/dashboard.html:409
msgid "Snabbstatistik"
msgstr "Quick stats"
@@ -831,102 +870,115 @@ msgstr ""
msgid "Filens innehåll matchar inte förväntat format."
msgstr ""
#: claims/views.py:126
#: claims/views.py:127
#, python-brace-format
msgid "{} utlägg skickade in."
msgstr ""
#: claims/views.py:129
#: claims/views.py:130
msgid "Inga utlägg kunde sparas. Fyll i minst en rad."
msgstr ""
#: claims/views.py:131
#: claims/views.py:132
msgid "Kunde inte spara utläggen. Kontrollera formuläret."
msgstr ""
#: claims/views.py:182 claims/views.py:237
#: claims/views.py:186 claims/views.py:241 claims/views.py:265
#, fuzzy
#| msgid "Ge behörighet att besluta utlägg"
msgid "Du har inte behörighet att uppdatera utlägg."
msgstr "Allow deciding claims"
#: claims/views.py:196
#: claims/views.py:200
#, fuzzy
#| msgid ""
#| "Utlägget är markerat som betalt. Ändringar av beslut/kommentar är låsta."
msgid "Utlägget är redan markerat som betalt och kan inte ändras."
msgstr "The claim is marked as paid. Decision/comments are locked."
#: claims/views.py:208
#: claims/views.py:212
#, python-format
msgid "%(claim)s markerades som godkänd."
msgstr "%(claim)s was marked as approved."
#: claims/views.py:211
#: claims/views.py:215
#, python-format
msgid "%(claim)s markerades som nekad."
msgstr "%(claim)s was marked as rejected."
#: claims/views.py:228
#: claims/views.py:232
msgid "Project updated during decision."
msgstr "Project updated during decision."
#: claims/views.py:234
#: claims/views.py:238
msgid "Betalningshantering är inte aktiverad."
msgstr "Payment handling is not enabled."
#: claims/views.py:242
#: claims/views.py:246
msgid "Endast godkända utlägg kan markeras som betalda."
msgstr "Only approved claims can be marked as paid."
#: claims/views.py:245
#: claims/views.py:249
msgid "Detta utlägg är redan markerat som betalt."
msgstr "This claim is already marked as paid."
#: claims/views.py:256
#: claims/views.py:260
#, python-format
msgid "%(claim)s markerades som betald."
msgstr "%(claim)s was marked as paid."
#: claims/views.py:322
msgid "Du saknar behörighet för åtgärden."
msgstr ""
#: claims/views.py:287
#, python-format
msgid "Fields updated: %(fields)s"
msgstr "Fields updated: %(fields)s"
#: claims/views.py:369
#: claims/views.py:293
msgid "Informationen uppdaterades."
msgstr "Information updated."
#: claims/views.py:295
msgid "Inga förändringar att spara."
msgstr "No changes to save."
#: claims/views.py:361
msgid "Du saknar behörighet för åtgärden."
msgstr "You do not have permission to perform this action."
#: claims/views.py:408
#, python-format
msgid "Användaren %(user)s skapades."
msgstr ""
#: claims/views.py:380
#: claims/views.py:419
msgid "Du kan inte ta bort din egen staff-status."
msgstr ""
#: claims/views.py:386
#: claims/views.py:425
#, python-format
msgid "Behörigheter uppdaterades för %(user)s."
msgstr ""
#: claims/views.py:388
#: claims/views.py:427
#, fuzzy
#| msgid "Justera behörigheter"
msgid "Kunde inte uppdatera behörigheter."
msgstr "Adjust permissions"
#: claims/views.py:398
#: claims/views.py:437
msgid "Du kan inte ta bort ditt eget konto."
msgstr ""
#: claims/views.py:400
#: claims/views.py:439
msgid "Du kan inte ta bort en superuser via detta gränssnitt."
msgstr ""
#: claims/views.py:403
#: claims/views.py:442
#, fuzzy
#| msgid "Användare"
msgid "Användaren togs bort."
msgstr "Users"
#: claims/views.py:406
#: claims/views.py:445
msgid "Okänd åtgärd."
msgstr ""