// 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 === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """); } function note(text: string): string { return `
${text}
`; } 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(); 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 `
${isNobody ? "?" : esc(initials(r.name))}
${esc(r.name)} ${r.elements} Elem. · ${r.open} offen${r.overdue > 0 ? ` · ${r.overdue} überfällig` : ""}
`; }).join(""); const nobody = acc.get(UNASSIGNED); widget.el.innerHTML = `
Offene Aufgaben je Verantwortlichem — roter Anteil ist überfällig. ${nobody ? ` ${nobody.elements} Strukturelemente ohne Zuordnung.` : ""}
${list} ${rows.length > 10 ? `
${rows.length - 10} weitere Personen nicht dargestellt.
` : ""}
`; widget.done(); })();