diff --git a/dashboard/app.py b/dashboard/app.py index 5f4212f..f4c6f79 100644 --- a/dashboard/app.py +++ b/dashboard/app.py @@ -2,7 +2,7 @@ import sys sys.path.append('/workspace') -from dash import Dash, html, dcc, page_container +from dash import Dash, html, dcc, page_container, Output, Input, State, callback from flask import Flask import dash_bootstrap_components as dbc import dash_mantine_components as dmc @@ -29,11 +29,13 @@ app = Dash( app.layout = dmc.MantineProvider([ Header(), html.Div([ - html.Div(id="sidebar", className="sidebar"), + html.Div(id="sidebar"), # KEINE className="sidebar" hier! html.Div(page_container, className="page-content"), dcc.Store(id="sidebar-state", data={"collapsed": False}), ], style={"display": "flex"}), ]) + + if __name__ == "__main__": app.run(host="0.0.0.0", port=8050, debug=(ENV=="development")) diff --git a/dashboard/assets/custom.css b/dashboard/assets/custom.css index 8032785..80c5981 100644 --- a/dashboard/assets/custom.css +++ b/dashboard/assets/custom.css @@ -1,6 +1,14 @@ /* ========================== Allgemeines Layout ========================== */ +:root { + --mb-z-index: 2000 !important; + --mantine-z-index-popover: 2100 !important; + --mantine-z-index-overlay: 2999 !important; + --mantine-z-index-dropdown: 2100 !important; + --mantine-z-index-max: 9999 !important; + --mantine-z-index-modal: 3000 !important; +} body { margin: 0; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; @@ -15,6 +23,7 @@ body { min-width: 0; /* verhindert Überlauf bei zu breitem Inhalt */ transition: margin-left 0.3s ease; min-height: calc(100vh - 60px); /* Mindesthöhe minus Header-Höhe */ + margin-left: 220px; /* <--- Ergänzen */ } /* Wenn Sidebar collapsed ist, reduziere margin-left */ @@ -73,7 +82,7 @@ body { top: 60px; /* Den gleichen Wert wie Header-Höhe verwenden */ left: 0; z-index: 1000; - position: relative; /* oder fixed, je nach Layout */ + position: fixed; /* <--- Ändere das von relative zu fixed */ overflow-x: hidden; overflow-y: auto; } @@ -164,10 +173,10 @@ body { /* ========================== Responsive (bei Bedarf) ========================== */ -@media (max-width: 768px) { +/* @media (max-width: 768px) { body { padding-top: 60px; /* Header-Platz auch auf mobilen Geräten */ - } + /* } .sidebar { position: fixed; @@ -186,34 +195,36 @@ body { .sidebar.collapsed ~ .page-content { margin-left: 0; } -} - -/* :root { - --mb-z-index: 2000 !important; - --mantine-z-index-popover: 2100 !important; - --mantine-z-index-overlay: 2100 !important; - --mantine-z-index-dropdown: 2100 !important; - --mantine-z-index-max: 9999 !important; -} - -.mantine-Modal-root, -.mantine-Modal-modal, -.mantine-Modal-overlay { - z-index: 3000 !important; -} - -.mantine-Modal-overlay { - z-index: 2000 !important; -} +} */ .mantine-Modal-modal { - z-index: 2100 !important; + z-index: var(--mantine-z-index-modal, 3000) !important; } -.mantine-Popover-dropdown, -.mantine-DatePicker-dropdown, -.mantine-Select-dropdown { - z-index: 2200 !important; -} */ +.mantine-Modal-inner, +.mantine-Modal-content { + z-index: 4000 !important; +} +/* Sidebar collapsed: Icon-Farbe normal */ +.sidebar.collapsed .sidebar-item-collapsed svg { + color: #7c5617; /* Icon-Linie/Text */ + fill: #e4d5c1; /* Icon-Fläche */ + width: 24px; + height: 24px; + margin: 0 auto; + display: block; + transition: color 0.2s, fill 0.2s; +} +/* Sidebar collapsed: Hintergrund und Icon invertieren bei Hover/Active */ +.sidebar.collapsed .nav-link:hover, +.sidebar.collapsed .nav-link.active { + background-color: #7c5617 !important; +} + +.sidebar.collapsed .nav-link:hover svg, +.sidebar.collapsed .nav-link.active svg { + color: #e4d5c1; /* Icon-Linie/Text invertiert */ + fill: #7c5617; /* Icon-Fläche invertiert */ +} diff --git a/dashboard/callbacks/ui_callbacks.py b/dashboard/callbacks/ui_callbacks.py index ddac799..9b18d97 100644 --- a/dashboard/callbacks/ui_callbacks.py +++ b/dashboard/callbacks/ui_callbacks.py @@ -3,29 +3,24 @@ from dash import Input, Output, State, callback from components.sidebar import Sidebar -# 1) Toggle-Callback: Umschalten von collapsed = False ↔ True +@callback( + Output("sidebar", "children"), + Output("sidebar", "className"), + Input("sidebar-state", "data"), +) +def render_sidebar(data): + collapsed = data.get("collapsed", False) + return Sidebar(collapsed=collapsed), f"sidebar{' collapsed' if collapsed else ''}" + @callback( Output("sidebar-state", "data"), Input("btn-toggle-sidebar", "n_clicks"), State("sidebar-state", "data"), - prevent_initial_call=True + prevent_initial_call=True, ) -def toggle_sidebar(n_clicks, state): - # Wenn der Button geklickt wurde, invertiere den collapsed-Wert - collapsed = state.get("collapsed", False) - return {"collapsed": not collapsed} - - -# 2) Render-Callback: Zeichnet die Sidebar neu und setzt die CSS-Klasse -@callback( - [Output("sidebar", "children"), Output("sidebar", "className")], - Input("sidebar-state", "data") -) -def render_sidebar(state): - collapsed = state.get("collapsed", False) - sidebar_class = "sidebar collapsed" if collapsed else "sidebar" - - # Sidebar() gibt jetzt nur den Inhalt zurück - sidebar_content = Sidebar(collapsed=collapsed) - - return sidebar_content, sidebar_class \ No newline at end of file +def toggle_sidebar(n, data): + if n is None: + # Kein Klick, nichts ändern! + return data + collapsed = not data.get("collapsed", False) + return {"collapsed": collapsed} \ No newline at end of file diff --git a/dashboard/components/sidebar.py b/dashboard/components/sidebar.py index dce89ea..6b073a0 100644 --- a/dashboard/components/sidebar.py +++ b/dashboard/components/sidebar.py @@ -3,9 +3,8 @@ from dash import html import dash_bootstrap_components as dbc from dash_iconify import DashIconify -from typing import List, Any -def Sidebar(collapsed: bool = False) -> List[Any]: +def Sidebar(collapsed: bool = False): """ Gibt nur den Inhalt der Sidebar zurück (ohne das äußere div mit id="sidebar"). Das äußere div wird bereits in app.py definiert. @@ -14,50 +13,37 @@ def Sidebar(collapsed: bool = False) -> List[Any]: nav_items = [ {"label": "Übersicht", "href": "/overview", "icon": "mdi:view-dashboard"}, {"label": "Termine", "href": "/appointments","icon": "mdi:calendar"}, - {"label": "Bildschirme", "href": "/clients", "icon": "mdi:monitor"}, + {"label": "Bildschirme", "href": "/clients", "icon": "mdi:monitor"}, {"label": "Einstellungen","href": "/settings", "icon": "mdi:cog"}, {"label": "Benutzer", "href": "/users", "icon": "mdi:account"}, ] - nav_links = [] - - for item in nav_items: - # Die ID muss in den Callbacks exakt so referenziert werden: - link_id = {"type": "nav-item", "index": item["label"]} - - # ✅ NavLink erstellen - nav_link = dbc.NavLink( - [ + if collapsed: + nav_links = [ + dbc.NavLink( DashIconify(icon=item["icon"], width=24), - html.Span(item["label"], className="ms-2 sidebar-label"), - ], - href=item["href"], - active="exact", - className="sidebar-item", - id=link_id, - ) - - # ✅ Conditional List Construction - keine append() nötig - if collapsed: - tooltip = dbc.Tooltip( - item["label"], - target=link_id, - placement="right", - id=f"tooltip-{item['label']}", + href=item["href"], + active="exact", + className="sidebar-item-collapsed", + id={"type": "nav-item", "index": item["label"]}, ) - link_components = [nav_link, tooltip] - else: - link_components = [nav_link] - - # ✅ Alle Komponenten in einem div-Container - nav_links.append( - html.Div( - children=link_components, - className="nav-item-container" + for item in nav_items + ] + else: + nav_links = [ + dbc.NavLink( + [ + DashIconify(icon=item["icon"], width=24), + html.Span(item["label"], className="ms-2 sidebar-label"), + ], + href=item["href"], + active="exact", + className="sidebar-item", + id={"type": "nav-item", "index": item["label"]}, ) - ) + for item in nav_items + ] - # Gib nur den Inhalt zurück, nicht das äußere div return [ html.Div( className="sidebar-toggle", @@ -67,5 +53,20 @@ def Sidebar(collapsed: bool = False) -> List[Any]: className="toggle-button", ) ), - dbc.Nav(nav_links, vertical=True, pills=True, className="sidebar-nav") + dbc.Collapse( + dbc.Nav( + nav_links, + vertical=True, + pills=True, + className="sidebar-nav", + ), + is_open=not collapsed, + className="sidebar-nav", + ) if not collapsed else + dbc.Nav( + nav_links, + vertical=True, + pills=True, + className="sidebar-nav-collapsed", + ), ] \ No newline at end of file