feat(UX-001): add full-width two-pane dashboard layout

This commit is contained in:
Bu5hm4nn
2026-03-25 23:19:09 +01:00
parent 960e1e9215
commit a60c5fb1f2
10 changed files with 473 additions and 212 deletions

View File

@@ -5,7 +5,7 @@ from nicegui import ui
from app.models.portfolio import PortfolioConfig
from app.models.workspace import get_workspace_repository
from app.pages.common import dashboard_page
from app.pages.common import dashboard_page, split_page_panes
from app.services.alerts import AlertService, build_portfolio_alert_context
from app.services.settings_status import save_status_text
@@ -84,7 +84,12 @@ def settings_page(workspace_id: str) -> None:
"settings",
workspace_id=workspace_id,
):
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
left_pane, right_pane = split_page_panes(
left_testid="settings-left-pane",
right_testid="settings-right-pane",
)
with left_pane:
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
@@ -154,28 +159,6 @@ def settings_page(workspace_id: str) -> None:
ui.label("Margin call at:").classes("ml-4 font-medium")
margin_price_display = ui.label()
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
ui.label("Data Sources").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
primary_source = ui.select(
["yfinance", "ibkr", "alpaca"],
value=config.primary_source,
label="Primary source",
).classes("w-full")
fallback_source = ui.select(
["fallback", "yfinance", "manual"],
value=config.fallback_source,
label="Fallback source",
).classes("w-full")
refresh_interval = ui.number(
"Refresh interval (seconds)",
value=config.refresh_interval,
min=1,
step=1,
).classes("w-full")
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
@@ -206,6 +189,28 @@ def settings_page(workspace_id: str) -> None:
"text-sm text-slate-500 dark:text-slate-400"
)
with right_pane:
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
ui.label("Data Sources").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
primary_source = ui.select(
["yfinance", "ibkr", "alpaca"],
value=config.primary_source,
label="Primary source",
).classes("w-full")
fallback_source = ui.select(
["fallback", "yfinance", "manual"],
value=config.fallback_source,
label="Fallback source",
).classes("w-full")
refresh_interval = ui.number(
"Refresh interval (seconds)",
value=config.refresh_interval,
min=1,
step=1,
).classes("w-full")
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
@@ -216,7 +221,6 @@ def settings_page(workspace_id: str) -> None:
alert_message = ui.label().classes("text-sm text-slate-600 dark:text-slate-300")
alert_history_column = ui.column().classes("w-full gap-2")
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
@@ -224,8 +228,19 @@ def settings_page(workspace_id: str) -> None:
ui.select(["json", "csv", "yaml"], value="json", label="Export format").classes("w-full")
ui.switch("Include scenario history", value=True)
ui.switch("Include option selections", value=True)
ui.button("Import settings", icon="upload").props("outline color=primary")
ui.button("Export settings", icon="download").props("outline color=primary")
with ui.row().classes("w-full gap-3 max-sm:flex-col"):
ui.button("Import settings", icon="upload").props("outline color=primary")
ui.button("Export settings", icon="download").props("outline color=primary")
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
ui.label("Save Workspace Settings").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
status = ui.label(
f"Current: start=${config.gold_value:,.0f}, entry=${config.entry_price:,.2f}/oz, "
f"weight={config.gold_ounces:,.2f} oz, current LTV={config.current_ltv:.1%}"
).classes("text-sm text-slate-500 dark:text-slate-400")
ui.button("Save settings", on_click=lambda: save_settings()).props("color=primary")
def apply_entry_basis_mode() -> None:
mode = str(entry_basis_mode.value or "value_price")
@@ -357,10 +372,3 @@ def settings_page(workspace_id: str) -> None:
ui.notify(f"Validation error: {e}", color="negative")
except Exception as e:
ui.notify(f"Failed to save: {e}", color="negative")
with ui.row().classes("mt-6 w-full items-center justify-between gap-4"):
status = ui.label(
f"Current: start=${config.gold_value:,.0f}, entry=${config.entry_price:,.2f}/oz, "
f"weight={config.gold_ounces:,.2f} oz, current LTV={config.current_ltv:.1%}"
).classes("text-sm text-slate-500 dark:text-slate-400")
ui.button("Save settings", on_click=save_settings).props("color=primary")