WORKSPACE.PM
ProjektEmpfohlen 4 × 9ProjektleitungProjektteam

Verantwortungslast im Strukturplan

Bündelt offene und überfällige Aufgaben nach der verantwortlichen Person über alle Strukturelemente hinweg.

Zum Code
OFFENE AUFGABEN JE PERSON371510115ohne Verantwortlichen6offenüberfällig

Nachbau im Stil dieser Website. Die echte Kachel übernimmt Schriften und Farbschema Ihres Mandanten.

Wofür das gut ist

„Bei wem stapelt es sich" lässt sich aus einer Aufgabenliste nur mühsam herauslesen. Diese Kachel dreht die Sicht um: sie aggregiert über alle Strukturelemente hinweg je Verantwortlichem und zeigt offene und überfällige Aufgaben in einem Balken. Elemente ohne zugeordneten Verantwortlichen bekommen bewusst eine eigene Zeile - das ist keine Person, sondern eine Planungslücke, und sie soll sichtbar sein.

Was Sie in der Kachel sehen

  • Je Zeile eine verantwortliche Person mit der Summe ihrer Aufgaben.
  • Der Balken teilt sich in offene und überfällige Aufgaben.
  • Die letzte, gestrichelte Zeile sammelt alle Strukturelemente ohne Verantwortlichen.

Der vollständige Code

Eigenständig und ohne externe Bibliothek. In den Editor einfügen, speichern, fertig.

TypeScript108 Zeilen
Rohdatei
// Verantwortungslast im Strukturplan
// Quelle: widget.query("project.containers")
// Aggregation je Verantwortlichem; Elemente ohne Zuordnung werden
// bewusst als eigene Zeile ausgewiesen (Planungslücke).

const isDark: boolean = widget.data.theme === "dark";
const C = {
  text: isDark ? "#f1f5f9" : "#1e293b",
  sub: "#94a3b8",
  card: isDark ? "#1e293b" : "#f8fafc",
  line: isDark ? "#334155" : "#e2e8f0",
  green: "#22c55e",
  amber: "#f59e0b",
  red: "#ef4444",
  blue: "#3b82f6",
};

function esc(s: string): string {
  return String(s).replace(/[&<>"]/g, (c: string) =>
    c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;");
}

function note(text: string): string {
  return `<div style="display:flex;align-items:center;justify-content:center;height:100%;min-height:60px;padding:12px;font-size:12px;color:${C.sub};text-align:center">${text}</div>`;
}

function queryError(code: string): string {
  return note(
    code === "forbidden" ? "Keine Berechtigung für den Strukturplan."
      : code === "unsupported_in_context" ? "Nur auf Projektebene verfügbar."
      : "Daten konnten nicht geladen werden.",
  );
}

function initials(name: string): string {
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
}

widget.el.innerHTML = note("Werte Verantwortlichkeiten aus …");

(async () => {
  const res = await widget.query("project.containers", { limit: 300 });
  if (!res.ok) {
    widget.el.innerHTML = queryError(res.error.code);
    widget.done();
    return;
  }

  const UNASSIGNED = "Ohne Verantwortlichen";
  const acc = new Map<string, { open: number; overdue: number; elements: number }>();

  res.data.items.forEach((c) => {
    if (c.type !== "container") return;
    const key: string = c.responsibleName && c.responsibleName.trim() !== ""
      ? c.responsibleName
      : UNASSIGNED;
    const cur = acc.get(key) || { open: 0, overdue: 0, elements: 0 };
    cur.open += c.openTaskCount;
    cur.overdue += c.overdueTaskCount;
    cur.elements += 1;
    acc.set(key, cur);
  });

  const rows = Array.from(acc.entries())
    .map((e) => ({ name: e[0], open: e[1].open, overdue: e[1].overdue, elements: e[1].elements }))
    .filter((r) => r.open > 0 || r.overdue > 0 || r.name === UNASSIGNED)
    .sort((a, b) => (b.overdue - a.overdue) || (b.open - a.open));

  if (rows.length === 0) {
    widget.el.innerHTML = note("Keine offenen Aufgaben im Strukturplan.");
    widget.done();
    return;
  }

  const max: number = Math.max.apply(null, rows.map((r) => r.open)) || 1;

  const list: string = rows.slice(0, 10).map((r) => {
    const isNobody: boolean = r.name === UNASSIGNED;
    const w: number = Math.max(2, (r.open / max) * 100);
    const overduePart: number = r.open > 0 ? Math.min((r.overdue / r.open) * 100, 100) : 0;
    return `<div style="display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid ${C.line}">
      <span style="width:24px;height:24px;flex-shrink:0;border-radius:50%;background:${isNobody ? C.line : C.blue + "22"};color:${isNobody ? C.sub : C.blue};display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700">${isNobody ? "?" : esc(initials(r.name))}</span>
      <div style="flex:1;min-width:0">
        <div style="display:flex;justify-content:space-between;gap:8px;font-size:11px;margin-bottom:3px">
          <span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:${isNobody ? C.sub : C.text};${isNobody ? "font-style:italic" : ""}">${esc(r.name)}</span>
          <span style="color:${C.sub};white-space:nowrap">${r.elements} Elem. · ${r.open} offen${r.overdue > 0 ? ` · <span style="color:${C.red};font-weight:600">${r.overdue} überfällig</span>` : ""}</span>
        </div>
        <div style="height:5px;background:${C.line};border-radius:3px;overflow:hidden;position:relative">
          <div style="height:100%;width:${w.toFixed(0)}%;background:${C.blue};opacity:.6"></div>
          <div style="position:absolute;left:0;top:0;height:100%;width:${(w * overduePart / 100).toFixed(0)}%;background:${C.red}"></div>
        </div>
      </div>
    </div>`;
  }).join("");

  const nobody = acc.get(UNASSIGNED);

  widget.el.innerHTML = `
    <div style="padding:8px 12px;color:${C.text}">
      <div style="font-size:10px;color:${C.sub};margin-bottom:6px">
        Offene Aufgaben je Verantwortlichem — roter Anteil ist überfällig.
        ${nobody ? ` ${nobody.elements} Strukturelemente ohne Zuordnung.` : ""}
      </div>
      ${list}
      ${rows.length > 10 ? `<div style="font-size:10px;color:${C.sub};margin-top:5px">${rows.length - 10} weitere Personen nicht dargestellt.</div>` : ""}
    </div>`;
  widget.done();
})();

Anpassen

KonstanteVorbelegungBedeutung
UNASSIGNED"Ohne Verantwortlichen"Beschriftung der Zeile für Strukturelemente ohne Verantwortlichen.
  1. 1Widget anlegen
  2. 2Code einfügen
  3. 3Größe setzen und speichern

Bauen Sie Ihre eigene Kachel

Testen Sie WORKSPACE.PM dreißig Tage lang mit allen Funktionen. Die Vorlagen dieser Seite funktionieren im Testmandanten genauso wie in der Produktivumgebung.