Brett je Einheit, Stufe 3: Aufgaben sehen, wer sie sehen soll (#684)
- TaskBoard: das Brett einer Aufgabe wird nicht gespeichert, es folgt aus der Organisation - Einheit des Zustaendigen, sonst des Anlegers. Offene Aufgaben ziehen damit beim Umzug mit. Sichtbar: das eigene Brett, fuer eine Leitung alle darunter, und was man selbst angelegt hat (nach oben Gereichtes bleibt im Blick). - TaskItem.CreatedBy, in der Datei als created_by und in der DB. Ein Import aus einer Datei ohne das Feld behaelt, was die DB weiss. - Taskboard-Tool: task_list, task_find und die Dublettenpruefung sehen nur die eigenen Bretter - eine aehnliche Buchhaltungsaufgabe haelt keine Marketingaufgabe auf und bekommt keinen Kommentar von dort. task_update und task_comment antworten auf eine fremde Aufgabe wie auf eine unbekannte. - TaskboardService.ListAsync mit Sichtfilter: erst filtern, dann kappen. - WebUI: jede Aufgabe nennt ihr Brett, ein Filter waehlt eines aus. - Konzept (Abschnitt 3) und Einrichtungshandbuch nachgetragen. Ohne Einheiten aendert sich nichts - ein Brett fuer alle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
609298dd7a
commit
29d953a5eb
@@ -2372,3 +2372,8 @@ tr.org-ausreisser td {
|
||||
@media (max-width: 900px) {
|
||||
.org-raster { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* #684: the board filter above the task cards */
|
||||
.taskboard-bretter {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@@ -346,6 +346,9 @@
|
||||
"tasks.budgetMissedLast": "zuletzt {time}",
|
||||
"tasks.budgetCarriedOver": "mitgenommen, wieder fällig ab {time}",
|
||||
"tasks.budgetCatchUp": "ein Nachholauf ab {time}",
|
||||
"tasks.board": "Brett",
|
||||
"tasks.boardAll": "Alle Bretter",
|
||||
"tasks.boardTopLevel": "Oberste Ebene",
|
||||
|
||||
"services.title": "Dienste",
|
||||
"services.newService": "Neuer Dienst",
|
||||
|
||||
@@ -346,6 +346,9 @@
|
||||
"tasks.budgetMissedLast": "last at {time}",
|
||||
"tasks.budgetCarriedOver": "carried over, due again from {time}",
|
||||
"tasks.budgetCatchUp": "one catch-up run from {time}",
|
||||
"tasks.board": "Board",
|
||||
"tasks.boardAll": "All boards",
|
||||
"tasks.boardTopLevel": "Top level",
|
||||
|
||||
"services.title": "Services",
|
||||
"services.newService": "New Service",
|
||||
|
||||
@@ -58,6 +58,9 @@ const ABGESCHLOSSEN = ["Done", "Canceled", "Archived"];
|
||||
|
||||
let zeigeAbgeschlossene = false;
|
||||
|
||||
/** #684: The board shown — "" for all, "-" for the top level, otherwise a unit id. */
|
||||
let brettFilter = "";
|
||||
|
||||
export async function rendereAufgaben() {
|
||||
const container = document.getElementById("ansicht-inhalt");
|
||||
if (!container) return;
|
||||
@@ -114,9 +117,14 @@ async function ladeTaskboard() {
|
||||
|
||||
try {
|
||||
const alle = (await api("/tasks?limit=200")) || [];
|
||||
const tasks = alle.filter((t) => zeigeAbgeschlossene || !ABGESCHLOSSEN.includes(t.status));
|
||||
const offen = alle.filter((t) => zeigeAbgeschlossene || !ABGESCHLOSSEN.includes(t.status));
|
||||
const tasks = offen.filter((t) => !brettFilter || (brettFilter === "-" ? !t.board : t.board === brettFilter));
|
||||
container.replaceChildren();
|
||||
|
||||
// #684: With units, every task is on a unit's board — pick one to see only that.
|
||||
const bretter = brettAuswahl(alle);
|
||||
if (bretter) container.append(bretter);
|
||||
|
||||
if (tasks.length === 0) {
|
||||
container.append(
|
||||
erstelleKarte(t("tasks.empty"), [
|
||||
@@ -141,6 +149,30 @@ async function ladeTaskboard() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #684: The boards the tasks are on, as a filter. Built from the tasks themselves, so it
|
||||
* shows only boards that hold something; absent when no task is on a unit's board.
|
||||
*/
|
||||
function brettAuswahl(tasks) {
|
||||
const bretter = new Map();
|
||||
for (const task of tasks) if (task.board) bretter.set(task.board, task.boardPath || task.board);
|
||||
if (bretter.size === 0) return null;
|
||||
|
||||
const auswahl = erstelleElement("select", {
|
||||
"aria-label": t("tasks.board"),
|
||||
onchange: (e) => { brettFilter = e.target.value; ladeTaskboard(); }
|
||||
},
|
||||
erstelleElement("option", { value: "", text: t("tasks.boardAll") }),
|
||||
erstelleElement("option", { value: "-", text: t("tasks.boardTopLevel") }),
|
||||
...[...bretter.entries()]
|
||||
.sort((a, b) => a[1].localeCompare(b[1]))
|
||||
.map(([id, pfad]) => erstelleElement("option", { value: id, text: pfad })));
|
||||
auswahl.value = brettFilter;
|
||||
|
||||
return erstelleElement("label", { klasse: "gruppe-horizontal text-klein taskboard-bretter" },
|
||||
erstelleElement("span", { text: t("tasks.board") }), auswahl);
|
||||
}
|
||||
|
||||
function terminText(task) {
|
||||
if (!task.when) return t("tasks.scheduleNone");
|
||||
const art = task.when.kind;
|
||||
@@ -155,7 +187,10 @@ function erstelleAufgabenKarte(task) {
|
||||
"div",
|
||||
{ klasse: "task-status-zeile gruppe-horizontal" },
|
||||
erstellePlakette(holeStatusText(task.status), STATUS_ART[task.status] || "neutral"),
|
||||
erstelleElement("span", { klasse: "gedaempft text-klein", text: `${task.id} · ${task.assignee || "—"}` })
|
||||
erstelleElement("span", {
|
||||
klasse: "gedaempft text-klein",
|
||||
text: `${task.id} · ${task.assignee || "—"}${task.boardPath ? ` · ▤ ${task.boardPath}` : ""}`
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user