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, {