Render edit overlay per claim
This commit is contained in:
@@ -85,6 +85,82 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if can_change %}
|
||||||
|
<div class="fixed inset-0 z-40 hidden items-center justify-center bg-slate-900/80 p-4"
|
||||||
|
data-edit-panel="{{ claim.id }}"
|
||||||
|
data-edit-backdrop="{{ claim.id }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true">
|
||||||
|
<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"
|
||||||
|
class="rounded-full bg-gray-100 px-3 py-1 text-xs font-semibold text-gray-600 transition hover:bg-gray-200"
|
||||||
|
onclick="claimsCloseEdit('{{ claim.id }}'); return false;">
|
||||||
|
{% 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"
|
||||||
|
class="rounded-full border border-gray-300 px-4 py-2 text-sm font-semibold text-gray-600 transition hover:bg-gray-100"
|
||||||
|
onclick="claimsCloseEdit('{{ claim.id }}'); return false;">
|
||||||
|
{% 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="space-y-6" data-claim-list>
|
<div class="space-y-6" data-claim-list>
|
||||||
@@ -137,7 +213,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_change %}
|
{% if can_change %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-open-edit="{{ claim.id }}"
|
onclick="claimsOpenEdit('{{ claim.id }}'); return false;"
|
||||||
class="rounded-full border border-gray-300 px-3 py-1 text-xs font-semibold text-gray-700 transition hover:bg-gray-100">
|
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" %}
|
{% trans "Redigera" %}
|
||||||
</button>
|
</button>
|
||||||
@@ -301,82 +377,6 @@
|
|||||||
<p class="mt-2 text-sm">{% trans "Välj en annan status för att se fler poster." %}</p>
|
<p class="mt-2 text-sm">{% trans "Välj en annan status för att se fler poster." %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_change %}
|
|
||||||
<div class="fixed inset-0 z-40 hidden items-center justify-center bg-slate-900/80 p-4"
|
|
||||||
data-edit-panel="{{ claim.id }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true">
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<aside class="space-y-6">
|
<aside class="space-y-6">
|
||||||
@@ -431,134 +431,118 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
(function () {
|
||||||
const filterButtons = Array.from(document.querySelectorAll("[data-filter-button]"));
|
function openPanel(id) {
|
||||||
const cards = Array.from(document.querySelectorAll("[data-claim-card]"));
|
|
||||||
const emptyState = document.querySelector("[data-claim-empty]");
|
|
||||||
const panels = Array.from(document.querySelectorAll("[data-edit-panel]"));
|
|
||||||
|
|
||||||
const normalizeTarget = (target) => {
|
|
||||||
let node = target;
|
|
||||||
while (node && node.nodeType !== 1) {
|
|
||||||
node = node.parentElement;
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openPanel = (id) => {
|
|
||||||
const panel = document.querySelector(`[data-edit-panel="${id}"]`);
|
const panel = document.querySelector(`[data-edit-panel="${id}"]`);
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
panel.classList.remove("hidden");
|
panel.classList.remove("hidden");
|
||||||
panel.classList.add("flex");
|
panel.classList.add("flex");
|
||||||
panel.setAttribute("aria-hidden", "false");
|
panel.setAttribute("aria-hidden", "false");
|
||||||
};
|
}
|
||||||
|
|
||||||
const closePanel = (panel) => {
|
function closePanelElement(panel) {
|
||||||
panel.classList.add("hidden");
|
panel.classList.add("hidden");
|
||||||
panel.classList.remove("flex");
|
panel.classList.remove("flex");
|
||||||
panel.setAttribute("aria-hidden", "true");
|
panel.setAttribute("aria-hidden", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.claimsOpenEdit = function (id) {
|
||||||
|
openPanel(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.claimsCloseEdit = function (id) {
|
||||||
|
const panel = document.querySelector(`[data-edit-panel="${id}"]`);
|
||||||
|
if (panel) {
|
||||||
|
closePanelElement(panel);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("click", (event) => {
|
document.addEventListener("click", (event) => {
|
||||||
const normalizedTarget = normalizeTarget(event.target);
|
const backdrop = event.target.closest("[data-edit-backdrop]");
|
||||||
if (!normalizedTarget) {
|
if (backdrop && event.target === backdrop) {
|
||||||
return;
|
closePanelElement(backdrop);
|
||||||
}
|
}
|
||||||
const openTrigger = normalizedTarget.closest("[data-open-edit]");
|
|
||||||
if (openTrigger) {
|
|
||||||
event.preventDefault();
|
|
||||||
openPanel(openTrigger.dataset.openEdit);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeTrigger = normalizedTarget.closest("[data-close-edit]");
|
|
||||||
if (closeTrigger) {
|
|
||||||
const panel = closeTrigger.closest("[data-edit-panel]");
|
|
||||||
if (panel) {
|
|
||||||
closePanel(panel);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
panels.forEach((panel) => {
|
|
||||||
if (event.target === panel) {
|
|
||||||
closePanel(panel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
panels.forEach((panel) => {
|
document.querySelectorAll("[data-edit-panel]").forEach((panel) => {
|
||||||
if (!panel.classList.contains("hidden")) {
|
if (!panel.classList.contains("hidden")) {
|
||||||
closePanel(panel);
|
closePanelElement(panel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!filterButtons.length || !cards.length) {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
return;
|
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 activeClasses = ["bg-brand-600", "text-white", "hover:bg-brand-700"];
|
if (!filterButtons.length || !cards.length) {
|
||||||
const inactiveClasses = ["bg-slate-100", "text-gray-700", "hover:bg-slate-200"];
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const setButtonState = (activeValue) => {
|
|
||||||
filterButtons.forEach((btn) => {
|
filterButtons.forEach((btn) => {
|
||||||
const value = btn.dataset.filterValue || "all";
|
btn.addEventListener("click", (event) => {
|
||||||
const isActive = value === activeValue;
|
event.preventDefault();
|
||||||
btn.setAttribute("aria-pressed", String(isActive));
|
applyFilter(btn.dataset.filterValue || "all");
|
||||||
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) {
|
const initialFilter = new URLSearchParams(window.location.search).get("status") || "all";
|
||||||
emptyState.classList.toggle("hidden", visibleCount > 0);
|
applyFilter(initialFilter);
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user