feat: add shared task assignments and quick win sorting

This commit is contained in:
2026-04-15 13:18:50 +02:00
parent f8f3641811
commit 4233175067
18 changed files with 414 additions and 55 deletions

View File

@@ -473,6 +473,9 @@ p {
color: #1d4ed8;
}
.status-badge--due_today,
.status-badge--due_tomorrow,
.status-badge--due_day_after_tomorrow,
.status-badge--soon {
background: #fff3d6;
color: #b45309;
@@ -514,6 +517,16 @@ p {
color: var(--muted);
}
.task-assignee__avatars {
display: inline-flex;
align-items: center;
}
.task-assignee__avatars .avatar + .avatar {
margin-left: -10px;
border: 2px solid var(--surface-strong);
}
.avatar {
width: 34px;
height: 34px;
@@ -903,6 +916,9 @@ p {
border-left: 4px solid #2563eb;
}
.calendar-task--due_today,
.calendar-task--due_tomorrow,
.calendar-task--due_day_after_tomorrow,
.calendar-task--soon {
border-left: 4px solid #f59e0b;
}
@@ -1066,6 +1082,11 @@ p {
.quick-win-manage-card {
align-items: stretch;
cursor: move;
}
.quick-win-manage-card.is-dragging {
opacity: 0.72;
}
.quick-win-manage-form {
@@ -1073,6 +1094,14 @@ p {
gap: 12px;
}
.quick-win-manage-card__drag {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: 0.9rem;
}
.quick-win-manage-card__actions {
display: flex;
gap: 10px;
@@ -1427,6 +1456,9 @@ p {
color: #8db7ff;
}
.status-badge--due_today,
.status-badge--due_tomorrow,
.status-badge--due_day_after_tomorrow,
.status-badge--soon {
background: rgba(245, 158, 11, 0.18);
color: #ffd38a;

View File

@@ -3,6 +3,7 @@
const dialogForm = document.getElementById("completeDialogForm");
const dialogChoice = document.getElementById("completeDialogChoice");
const dialogText = document.getElementById("completeDialogText");
const dialogChoices = document.getElementById("completeDialogChoices");
const closeButton = document.getElementById("completeDialogClose");
const quickTaskDialog = document.getElementById("quickTaskDialog");
const quickTaskOpen = document.getElementById("quickTaskOpen");
@@ -13,23 +14,55 @@
const quickWinCustomFields = document.getElementById("quickWinCustomFields");
const quickWinTitle = document.getElementById("quick-title");
const quickWinEffort = document.getElementById("quick-effort");
const currentUserId = document.body.dataset.currentUserId;
const currentUserName = document.body.dataset.currentUserName;
const quickWinSortList = document.querySelector("[data-quick-win-sort-list]");
const quickWinSortToken = document.getElementById("quickWinSortToken");
let draggedQuickWin = null;
function buildCompletionOptions(button) {
const options = [];
const assignedPairs = [
[button.dataset.assignedPrimaryId, button.dataset.assignedPrimaryName],
[button.dataset.assignedSecondaryId, button.dataset.assignedSecondaryName],
];
assignedPairs.forEach(([id, label]) => {
if (id && label && !options.some((option) => option.value === id)) {
options.push({ value: id, label });
}
});
if (currentUserId && currentUserName && !options.some((option) => option.value === currentUserId)) {
options.push({ value: currentUserId, label: "Ich" });
}
return options;
}
document.querySelectorAll("[data-complete-action]").forEach((button) => {
button.addEventListener("click", () => {
if (!dialog || !dialogForm || !dialogChoice || !dialogText) {
if (!dialog || !dialogForm || !dialogChoice || !dialogText || !dialogChoices) {
return;
}
dialogForm.action = button.dataset.completeAction;
dialogText.textContent = `Die Aufgabe "${button.dataset.completeTitle}" war ${button.dataset.completeAssigned} zugewiesen. Wer hat sie erledigt?`;
dialog.showModal();
});
});
dialogChoices.innerHTML = "";
document.querySelectorAll("[data-complete-choice]").forEach((button) => {
button.addEventListener("click", () => {
dialogChoice.value = button.dataset.completeChoice || "me";
dialog.close();
dialogForm.submit();
buildCompletionOptions(button).forEach((option, index) => {
const choiceButton = document.createElement("button");
choiceButton.type = "button";
choiceButton.className = index === 0 ? "button button--secondary" : "button";
choiceButton.dataset.completeChoice = option.value;
choiceButton.textContent = option.label;
choiceButton.addEventListener("click", () => {
dialogChoice.value = option.value;
dialog.close();
dialogForm.submit();
});
dialogChoices.appendChild(choiceButton);
});
dialog.showModal();
});
});
@@ -87,6 +120,64 @@
});
}
async function persistQuickWinSort() {
if (!quickWinSortList || !quickWinSortToken) {
return;
}
const ids = [...quickWinSortList.querySelectorAll("[data-quick-win-sort-item]")]
.map((item) => item.dataset.quickWinSortItem)
.filter(Boolean);
await fetch("/settings/quick-wins/reorder", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": quickWinSortToken.value,
},
body: JSON.stringify({ ids }),
});
}
if (quickWinSortList) {
quickWinSortList.querySelectorAll("[data-quick-win-sort-item]").forEach((item) => {
item.addEventListener("dragstart", () => {
draggedQuickWin = item;
item.classList.add("is-dragging");
});
item.addEventListener("dragend", () => {
item.classList.remove("is-dragging");
draggedQuickWin = null;
});
item.addEventListener("dragover", (event) => {
event.preventDefault();
});
item.addEventListener("drop", async (event) => {
event.preventDefault();
if (!draggedQuickWin || draggedQuickWin === item) {
return;
}
const items = [...quickWinSortList.querySelectorAll("[data-quick-win-sort-item]")];
const draggedIndex = items.indexOf(draggedQuickWin);
const targetIndex = items.indexOf(item);
if (draggedIndex < targetIndex) {
item.after(draggedQuickWin);
} else {
item.before(draggedQuickWin);
}
try {
await persistQuickWinSort();
} catch (error) {
console.error("Quick-Win-Sortierung konnte nicht gespeichert werden", error);
}
});
});
}
const pushButton = document.getElementById("pushToggle");
const pushHint = document.getElementById("pushHint");
const vapidKey = document.body.dataset.pushKey;