feat(UX-001): add full-width two-pane dashboard layout
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user