diff --git a/Client/Components/widgets/Treetable_V2.ts b/Client/Components/widgets/Treetable_V2.ts index f0a04e6..523662d 100644 --- a/Client/Components/widgets/Treetable_V2.ts +++ b/Client/Components/widgets/Treetable_V2.ts @@ -39,6 +39,20 @@ export interface iTreetable { * -Zeile; die columnmapper-Inhalte brauchen dann kein eigenes * Selektions-Styling mehr. */ isSelected?: (node: iTreenode) => boolean + /** Optional: Spaltenüberschriften (ein Titel je columnmapper-Spalte, + * 1:1 von links nach rechts). Wenn gesetzt, rendert das Widget eine + * Kopfzeile über den Datenzeilen. Hinweis zur ersten Spalte: Die + * Einrückung/der Expand-Pfeil der Baumdarstellung liegt im selben + * td wie die ERSTE columnmapper-Zelle — der erste Titel steht + * daher direkt über der ersten Datenspalte. Die Kopfzeile bleibt + * beim Scrollen sticky sichtbar. */ + header?: string[] + /** Optional: Ein-/Ausklappen-Buttons (unfold_more/unfold_less) in der + * Topbar anzeigen. Default true. Für flache Listen ohne Hierarchie + * auf false setzen — die Topbar entfällt dann ganz, sofern auch + * keine Children (z.B. TopbarButtons) übergeben und kein filter + * gesetzt ist. */ + showExpandButtons?: boolean } export const Treetable: m.ClosureComponent = (vnode) => { @@ -58,26 +72,63 @@ export const Treetable: m.ClosureComponent = (vnode) => { const topHasSiblingsWithChildren = vnode.attrs.treenodes.some( (tn: iTreenode) => tn.hasChildren ) + // Topbar (Expand-Buttons, Children, Suchfeld) nur rendern, wenn + // tatsächlich Inhalt vorhanden ist — bei flachen Listen ohne + // Expand-Buttons, Children und filter bleibt sie komplett weg. + const showExpand = vnode.attrs.showExpandButtons !== false + // vnode.children kann ein Array oder ein Primitiv sein + // (Mithril-Typ ChildArrayOrPrimitive). + const hatChildren = Array.isArray(vnode.children) + ? vnode.children.length > 0 + : vnode.children != null + const hatTopbar = showExpand || hatChildren || vnode.attrs.filter != undefined return m("div.w3-border-theme", {style: {height: '99%', overflow: 'hidden', 'border-style': 'solid', 'border-width': '1px'}}, - m("div.w3-bar.w3-theme", - m(TopbarButton, { - onclick: () => { - expandtoggle = true; changeTreenodes(vnode.attrs.treenodes, expandtoggle) - }}, - m("i.material-icons", "unfold_more") - ), - m(TopbarButton, { - onclick: () => { - expandtoggle = false; changeTreenodes(vnode.attrs.treenodes, expandtoggle) - }}, - m("i.material-icons", "unfold_less") - ), - vnode.children, - (vnode.attrs.filter != undefined) ? m(SearchField, {setSearchvalue: setSearchvalue} as iSearchfield) : null - ), + hatTopbar + ? m("div.w3-bar.w3-theme", + showExpand + ? [ + m(TopbarButton, { + onclick: () => { + expandtoggle = true; changeTreenodes(vnode.attrs.treenodes, expandtoggle) + }}, + m("i.material-icons", "unfold_more") + ), + m(TopbarButton, { + onclick: () => { + expandtoggle = false; changeTreenodes(vnode.attrs.treenodes, expandtoggle) + }}, + m("i.material-icons", "unfold_less") + ) + ] + : null, + vnode.children, + (vnode.attrs.filter != undefined) ? m(SearchField, {setSearchvalue: setSearchvalue} as iSearchfield) : null + ) + : null, m("div", {style: {height: '100%'}}, - m("div", {style: {height: 'calc(100% - 32px', overflow: 'auto', width: '100%'}}, + // Ohne Topbar nutzt der scrollbare Bereich die volle Höhe. + m("div", {style: {height: hatTopbar ? 'calc(100% - 32px' : '100%', overflow: 'auto', width: '100%'}}, m("table.w3-table", {style: {'overflow-y': 'auto'}}, + // Optionale Spaltenüberschriften: Ein Titel je + // columnmapper-Spalte, 1:1 von links nach rechts + // (die Einrückung teilt sich das td mit der + // ersten Datenspalte, keine Führungs-Spalte). + // Die Kopfzeile bleibt beim Scrollen sticky. + vnode.attrs.header + ? m("thead", + m("tr.w3-theme", + vnode.attrs.header.map((titel: string) => + m("th", { + style: { + position: 'sticky', + top: '0', + 'z-index': '1', + 'text-align': 'left', + 'white-space': 'nowrap' + }, + title: titel + }, titel)))) + : null, vnode.attrs.treenodes.map((tn:iTreenode, z) => { if (vnode.attrs.filter == undefined || (vnode.attrs.filter != undefined && vnode.attrs.filter(tn, searchvalue) == true)) { return m(Treerow, {