// Gruppen bilden einen gerichteten azyklischen Graphen, keinen Baum: // „Grillwurst" hängt unter „Wurst" UND unter „Grillgut". Deshalb hier // Mengen-Helfer statt der Baum-Helfer aus categoryTree.js – ein Baum-Walk // würde eine über zwei Wege erreichbare Gruppe doppelt liefern. // // Alle Funktionen schützen sich mit einem gesehen-Set gegen Ringe. Die API // lässt keinen zu, eine eingespielte Sicherung könnte aber einen mitbringen. const byId = (groups) => new Map((groups || []).map((g) => [g.id, g])); /** IDs aller Untergruppen (transitiv, ohne die Gruppe selbst). */ export function nachfahrenIds(groups, id) { const map = byId(groups); const gesehen = new Set(); const offen = [id]; while (offen.length) { const cur = offen.pop(); for (const kind of map.get(cur)?.child_ids || []) { if (!gesehen.has(kind)) { gesehen.add(kind); offen.push(kind); } } } gesehen.delete(id); return gesehen; } /** IDs aller Obergruppen (transitiv, ohne die Gruppe selbst). */ export function vorfahrenIds(groups, id) { const map = byId(groups); const gesehen = new Set(); const offen = [...(map.get(id)?.parent_ids || [])]; while (offen.length) { const cur = offen.pop(); if (gesehen.has(cur)) continue; gesehen.add(cur); offen.push(...(map.get(cur)?.parent_ids || [])); } gesehen.delete(id); return gesehen; } /** * Alle Wege zu einer Gruppe, z.B. [["Wurst","Grillwurst"], ["Grillgut","Grillwurst"]]. * Anders als bei Kategorien gibt es nicht DEN einen Pfad – deshalb eine Liste. */ export function pfade(groups, id) { const map = byId(groups); const bauen = (cur, gesehen) => { const g = map.get(cur); if (!g) return [[]]; const eltern = (g.parent_ids || []).filter((p) => !gesehen.has(p)); if (eltern.length === 0) return [[g.name]]; const raus = []; for (const p of eltern) { for (const oben of bauen(p, new Set([...gesehen, cur]))) { raus.push([...oben, g.name]); } } return raus; }; return bauen(id, new Set()); } /** Die Wege als ein Text: „Wurst → Grillwurst · Grillgut → Grillwurst". */ export function pfadText(groups, id) { return pfade(groups, id) .map((teile) => teile.join(" → ")) .join(" · "); } /** Namen der Obergruppen, für Chips und Spaltentexte. */ export function elternNamen(groups, group) { const map = byId(groups); return (group?.parent_ids || []).map((id) => map.get(id)?.name).filter(Boolean); } /** * Gruppen für ein Auswahlfeld: als Baum ausgerollt und eingerückt. * * Eine Gruppe mit mehreren Obergruppen erscheint unter JEDER – genau das ist * der Sinn mehrerer Obergruppen. Der Wert ist ohnehin dieselbe ID, egal welche * Zeile man trifft. * * Eingerückt wird mit GESCHÜTZTEN Leerzeichen: normale fasst der Browser in * einem