Files
impakt/src/impakt/web/layout.py
2026-04-10 16:54:40 -04:00

158 lines
5.2 KiB
Python

"""Top-level Dash layout builder.
Assembles all component modules into the complete page layout.
Two tabs:
- **Data**: Channel grid (left, resizable) + Plot area + Cursor values grid
- **Analysis**: Injury criteria + Protocol scoring + Export
"""
from __future__ import annotations
from typing import Any
import dash_bootstrap_components as dbc
from dash import dcc, html
from impakt.web.components.channel_grid import build_channel_grid
from impakt.web.components.channel_values import build_channel_values_panel
from impakt.web.components.criteria import build_criteria_panel
from impakt.web.components.header import (
build_header,
build_open_test_modal,
build_overlay_modal,
build_test_info_panel,
)
from impakt.web.components.plot_grid import build_plot_grid
from impakt.web.components.corridors import build_corridor_panel
from impakt.web.components.math_builder import build_math_panel
from impakt.web.components.report import build_report_panel
from impakt.web.components.templates import build_template_panel
from impakt.web.components.transforms import build_transform_panel
from impakt.web.state import AppState
def _build_data_tab(app_state: AppState) -> html.Div:
"""Build the Data tab: channels + transforms | plots + cursor grid."""
return html.Div(
[
# === Left panel: Channels + Transforms ===
html.Div(
[
build_channel_grid(app_state),
html.Div(style={"height": "8px"}),
build_transform_panel(),
],
id="left-panel",
style={
"width": "320px",
"minWidth": "200px",
"maxWidth": "600px",
"overflowY": "auto",
"flexShrink": "0",
"padding": "0 8px",
},
),
# === Splitter handle ===
html.Div(
id="splitter-handle",
style={
"width": "6px",
"cursor": "col-resize",
"backgroundColor": "#e9ecef",
"flexShrink": "0",
"position": "relative",
"zIndex": "10",
},
),
# === Right side: Plot area + Channel values table (full width) ===
html.Div(
[
build_plot_grid("1x1"),
build_channel_values_panel(),
],
style={
"flex": "1",
"minWidth": "0",
"overflowY": "auto",
"overflowX": "hidden",
"padding": "0 8px",
},
),
],
style={
"display": "flex",
"flexDirection": "row",
"height": "calc(100vh - 160px)",
"overflow": "hidden",
},
)
def _build_analysis_tab(app_state: AppState) -> html.Div:
"""Build the Analysis tab: criteria, corridors, math, templates, export."""
return html.Div(
[
dbc.Row(
[
dbc.Col([build_criteria_panel()], width=4),
dbc.Col([build_math_panel(app_state)], width=4),
dbc.Col([build_template_panel(app_state)], width=4),
],
className="mb-2",
),
dbc.Row(
[
dbc.Col([build_corridor_panel()], width=6),
dbc.Col([build_report_panel()], width=6),
],
),
],
style={"padding": "8px"},
)
def build_layout(app_state: AppState, template_names: list[str] | None = None) -> html.Div:
"""Build the complete page layout from components."""
return html.Div(
[
# --- Header ---
build_header(app_state, template_names),
# --- Modals ---
build_open_test_modal(),
build_overlay_modal(),
# --- Test info bar ---
html.Div(
build_test_info_panel(app_state),
className="px-3",
),
# --- Tabs ---
dbc.Tabs(
[
dbc.Tab(
_build_data_tab(app_state),
label="Data",
tab_id="tab-data",
tab_style={"fontSize": "13px"},
active_label_style={"fontWeight": "bold"},
),
dbc.Tab(
_build_analysis_tab(app_state),
label="Analysis",
tab_id="tab-analysis",
tab_style={"fontSize": "13px"},
active_label_style={"fontWeight": "bold"},
),
],
id="main-tabs",
active_tab="tab-data",
className="px-3 pt-1",
),
# --- Hidden stores ---
dcc.Store(id="selected-channels-store", data=[]),
dcc.Store(id="session-store", data={}),
dcc.Store(id="page-refresh-trigger", data={}),
dcc.Store(id="splitter-state", data={"width": 320}),
]
)