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

@@ -11,6 +11,7 @@ from app.pages.common import (
dashboard_page,
demo_spot_price,
portfolio_snapshot,
split_page_panes,
strategy_catalog,
strategy_metrics,
)
@@ -111,6 +112,7 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
spot_label = (
f"Current spot reference: ${portfolio['spot_price']:,.2f}/oz (converted collateral spot via {quote_source})"
)
updated_label = f"Quote timestamp: {quote_updated_at}" if quote_updated_at else "Quote timestamp: unavailable"
with dashboard_page(
"Hedge Analysis",
@@ -118,7 +120,12 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
"hedge",
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="hedge-left-pane",
right_testid="hedge-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"
):
@@ -127,6 +134,7 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
slider_value = ui.label("Scenario move: +0%").classes("text-sm text-slate-500 dark:text-slate-400")
slider = ui.slider(min=-25, max=25, value=0, step=5).classes("w-full")
ui.label(spot_label).classes("text-sm text-slate-500 dark:text-slate-400")
ui.label(updated_label).classes("text-xs text-slate-500 dark:text-slate-400")
if workspace_id:
ui.label(f"Workspace route: /{workspace_id}/hedge").classes(
"text-xs text-slate-500 dark:text-slate-400"
@@ -140,15 +148,18 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
)
charts_row = ui.row().classes("w-full gap-6 max-lg:flex-col")
with charts_row:
initial_metrics = strategy_metrics(selected["strategy"], selected["scenario_pct"], portfolio=portfolio)
cost_chart = ui.echart(_cost_benefit_options(initial_metrics)).classes(
"h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
)
waterfall_chart = ui.echart(_waterfall_options(initial_metrics)).classes(
"h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
with right_pane:
scenario_results = ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
)
with ui.row().classes("w-full gap-6 max-xl:flex-col"):
initial_metrics = strategy_metrics(selected["strategy"], selected["scenario_pct"], portfolio=portfolio)
cost_chart = ui.echart(_cost_benefit_options(initial_metrics)).classes(
"h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
)
waterfall_chart = ui.echart(_waterfall_options(initial_metrics)).classes(
"h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
)
def render_summary() -> None:
metrics = strategy_metrics(selected["strategy"], selected["scenario_pct"], portfolio=portfolio)
@@ -156,7 +167,8 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
summary.clear()
with summary:
ui.label("Scenario Summary").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
with ui.grid(columns=3).classes("w-full gap-4 max-lg:grid-cols-2 max-sm:grid-cols-1"):
ui.label(strategy["description"]).classes("text-sm text-slate-600 dark:text-slate-300")
with ui.grid(columns=1).classes("w-full gap-4 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2"):
cards = [
("Start value", f"${portfolio['gold_value']:,.0f}"),
("Start price", f"${portfolio['spot_price']:,.2f}/oz"),
@@ -164,10 +176,6 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
("Loan amount", f"${portfolio['loan_amount']:,.0f}"),
("Margin call LTV", f"{portfolio['margin_call_ltv']:.1%}"),
("Monthly hedge budget", f"${portfolio['hedge_budget']:,.0f}"),
("Scenario spot", f"${metrics['scenario_price']:,.2f}"),
("Hedge cost", f"${strategy['estimated_cost']:,.2f}/oz"),
("Unhedged equity", f"${metrics['unhedged_equity']:,.0f}"),
("Hedged equity", f"${metrics['hedged_equity']:,.0f}"),
]
for label, value in cards:
with ui.card().classes(
@@ -175,7 +183,25 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
):
ui.label(label).classes("text-sm text-slate-500 dark:text-slate-400")
ui.label(value).classes("text-2xl font-bold text-slate-900 dark:text-slate-100")
ui.label(strategy["description"]).classes("text-sm text-slate-600 dark:text-slate-300")
scenario_results.clear()
with scenario_results:
ui.label("Scenario Results").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
with ui.grid(columns=2).classes("w-full gap-4 max-md:grid-cols-1"):
result_cards = [
("Scenario spot", f"${metrics['scenario_price']:,.2f}"),
("Hedge cost", f"${strategy['estimated_cost']:,.2f}/oz"),
("Unhedged equity", f"${metrics['unhedged_equity']:,.0f}"),
("Hedged equity", f"${metrics['hedged_equity']:,.0f}"),
("Net hedge benefit", f"${metrics['hedged_equity'] - metrics['unhedged_equity']:,.0f}"),
("Scenario move", f"{selected['scenario_pct']:+d}%"),
]
for label, value in result_cards:
with ui.card().classes(
"rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-none dark:border-slate-800 dark:bg-slate-950"
):
ui.label(label).classes("text-sm text-slate-500 dark:text-slate-400")
ui.label(value).classes("text-2xl font-bold text-slate-900 dark:text-slate-100")
cost_chart.options.clear()
cost_chart.options.update(_cost_benefit_options(metrics))