a8d9f7b757
- Add chart_tab() component for chart type pills - Implement full top_bar() with logo, title, chart tabs, data freshness - Heritage Blue background with white text, 64px fixed height - Reactive data freshness indicator using rx.cond for loading states - Added Transitions import to styles
355 lines
10 KiB
Python
355 lines
10 KiB
Python
"""
|
|
HCD Analysis v2 - Redesigned Reflex Application.
|
|
|
|
Single-page dashboard with reactive filtering and real-time chart updates.
|
|
Design reference: DESIGN_SYSTEM.md
|
|
"""
|
|
|
|
import reflex as rx
|
|
|
|
from pathways_app.styles import (
|
|
Colors,
|
|
Typography,
|
|
Spacing,
|
|
Radii,
|
|
Shadows,
|
|
Transitions,
|
|
TOP_BAR_HEIGHT,
|
|
PAGE_MAX_WIDTH,
|
|
PAGE_PADDING,
|
|
card_style,
|
|
text_h1,
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# State
|
|
# =============================================================================
|
|
|
|
class AppState(rx.State):
|
|
"""
|
|
Application state for HCD Analysis v2.
|
|
|
|
This is a minimal placeholder state for the app skeleton.
|
|
Will be expanded in Phase 3 with full filter state and data management.
|
|
"""
|
|
|
|
# Placeholder state variables (expanded in Phase 3)
|
|
data_loaded: bool = False
|
|
total_records: int = 0
|
|
chart_loading: bool = False
|
|
error_message: str = ""
|
|
|
|
# Placeholder for current chart type (for top bar tabs)
|
|
current_chart: str = "icicle"
|
|
|
|
|
|
# =============================================================================
|
|
# Layout Components
|
|
# =============================================================================
|
|
|
|
def chart_tab(label: str, chart_type: str, is_active: bool = False) -> rx.Component:
|
|
"""
|
|
Individual chart type tab/pill for top bar navigation.
|
|
|
|
Active state: White background with Heritage Blue text
|
|
Inactive state: Transparent with white text, hover shows Vibrant Blue background
|
|
"""
|
|
return rx.box(
|
|
rx.text(
|
|
label,
|
|
font_size=Typography.BODY_SMALL_SIZE,
|
|
font_weight="500",
|
|
color=Colors.HERITAGE_BLUE if is_active else Colors.WHITE,
|
|
font_family=Typography.FONT_FAMILY,
|
|
),
|
|
background_color=Colors.WHITE if is_active else "transparent",
|
|
padding_x=Spacing.LG,
|
|
padding_y=Spacing.SM,
|
|
border_radius=Radii.FULL,
|
|
cursor="pointer",
|
|
transition=f"background-color {Transitions.COLOR}",
|
|
_hover={
|
|
"background_color": Colors.WHITE if is_active else "rgba(255,255,255,0.15)",
|
|
},
|
|
# Future: on_click handler to switch chart type
|
|
)
|
|
|
|
|
|
def top_bar() -> rx.Component:
|
|
"""
|
|
Top navigation bar component.
|
|
|
|
Contains: Logo + App Name | Chart Type Tabs | Data Freshness Indicator
|
|
Fixed height: 64px (from design system)
|
|
Heritage Blue background with white text.
|
|
"""
|
|
return rx.box(
|
|
rx.hstack(
|
|
# Left: Logo and App Title
|
|
rx.hstack(
|
|
rx.image(
|
|
src="/logo.png",
|
|
height="36px",
|
|
alt="NHS Logo",
|
|
),
|
|
rx.text(
|
|
"HCD Analysis",
|
|
font_size=Typography.H2_SIZE,
|
|
font_weight=Typography.H2_WEIGHT,
|
|
color=Colors.WHITE,
|
|
font_family=Typography.FONT_FAMILY,
|
|
letter_spacing="-0.01em",
|
|
),
|
|
align="center",
|
|
spacing="3",
|
|
),
|
|
# Center: Chart Type Tabs
|
|
rx.hstack(
|
|
chart_tab("Icicle", "icicle", is_active=True),
|
|
chart_tab("Sankey", "sankey", is_active=False),
|
|
chart_tab("Timeline", "timeline", is_active=False),
|
|
spacing="2",
|
|
align="center",
|
|
background_color="rgba(255,255,255,0.1)",
|
|
padding=Spacing.XS,
|
|
border_radius=Radii.FULL,
|
|
),
|
|
# Right: Data Freshness Indicator
|
|
rx.hstack(
|
|
rx.icon(
|
|
"database",
|
|
size=16,
|
|
color=Colors.SKY,
|
|
),
|
|
rx.vstack(
|
|
rx.text(
|
|
rx.cond(
|
|
AppState.data_loaded,
|
|
AppState.total_records.to_string() + " records",
|
|
"Loading data...",
|
|
),
|
|
font_size=Typography.CAPTION_SIZE,
|
|
font_weight="500",
|
|
color=Colors.WHITE,
|
|
font_family=Typography.FONT_FAMILY,
|
|
),
|
|
rx.text(
|
|
rx.cond(
|
|
AppState.data_loaded,
|
|
"Last refreshed: recently",
|
|
"Connecting...",
|
|
),
|
|
font_size="11px",
|
|
color=Colors.WHITE,
|
|
opacity="0.7",
|
|
font_family=Typography.FONT_FAMILY,
|
|
),
|
|
spacing="0",
|
|
align="end",
|
|
),
|
|
spacing="2",
|
|
align="center",
|
|
),
|
|
justify="between",
|
|
align="center",
|
|
width="100%",
|
|
max_width=PAGE_MAX_WIDTH,
|
|
margin_x="auto",
|
|
padding_x=Spacing.XL,
|
|
),
|
|
background_color=Colors.HERITAGE_BLUE,
|
|
height=TOP_BAR_HEIGHT,
|
|
width="100%",
|
|
display="flex",
|
|
align_items="center",
|
|
position="sticky",
|
|
top="0",
|
|
z_index="100",
|
|
box_shadow=Shadows.MD,
|
|
)
|
|
|
|
|
|
def filter_section() -> rx.Component:
|
|
"""
|
|
Filter section component.
|
|
|
|
Contains: Date range pickers, searchable multi-select dropdowns.
|
|
|
|
Will be fully implemented in Task 2.2.
|
|
"""
|
|
return rx.box(
|
|
rx.text(
|
|
"Filters",
|
|
**text_h1(),
|
|
margin_bottom=Spacing.MD,
|
|
),
|
|
rx.text(
|
|
"Filter controls will be implemented in Phase 2.",
|
|
font_size=Typography.BODY_SIZE,
|
|
font_weight=Typography.BODY_WEIGHT,
|
|
color=Colors.SLATE_500,
|
|
font_family=Typography.FONT_FAMILY,
|
|
),
|
|
**card_style(),
|
|
width="100%",
|
|
)
|
|
|
|
|
|
def kpi_row() -> rx.Component:
|
|
"""
|
|
KPI metrics row component.
|
|
|
|
Contains: Unique patients count, and space for additional metrics.
|
|
|
|
Will be fully implemented in Task 2.3.
|
|
"""
|
|
return rx.hstack(
|
|
rx.box(
|
|
rx.vstack(
|
|
rx.text(
|
|
"—",
|
|
font_family=Typography.FONT_MONO,
|
|
font_size="32px",
|
|
font_weight="600",
|
|
color=Colors.SLATE_900,
|
|
),
|
|
rx.text(
|
|
"Unique Patients",
|
|
font_size=Typography.CAPTION_SIZE,
|
|
font_weight=Typography.CAPTION_WEIGHT,
|
|
color=Colors.SLATE_500,
|
|
),
|
|
spacing="1",
|
|
align="center",
|
|
),
|
|
**card_style(),
|
|
min_width="200px",
|
|
text_align="center",
|
|
),
|
|
# Space for additional KPI cards
|
|
spacing="4",
|
|
width="100%",
|
|
)
|
|
|
|
|
|
def chart_section() -> rx.Component:
|
|
"""
|
|
Main chart section component.
|
|
|
|
Contains: Plotly icicle chart with loading and error states.
|
|
|
|
Will be fully implemented in Task 2.4 and Phase 4.
|
|
"""
|
|
return rx.box(
|
|
rx.vstack(
|
|
rx.text(
|
|
"Patient Pathway Chart",
|
|
**text_h1(),
|
|
),
|
|
rx.text(
|
|
"Chart will be displayed here once data is loaded.",
|
|
font_size=Typography.BODY_SIZE,
|
|
font_weight=Typography.BODY_WEIGHT,
|
|
color=Colors.SLATE_500,
|
|
font_family=Typography.FONT_FAMILY,
|
|
),
|
|
# Placeholder for chart area
|
|
rx.box(
|
|
rx.center(
|
|
rx.text(
|
|
"Chart Placeholder",
|
|
color=Colors.SLATE_500,
|
|
font_size=Typography.BODY_SIZE,
|
|
),
|
|
width="100%",
|
|
height="400px",
|
|
),
|
|
background_color=Colors.SLATE_100,
|
|
border_radius=Radii.MD,
|
|
width="100%",
|
|
),
|
|
spacing="4",
|
|
width="100%",
|
|
align="start",
|
|
),
|
|
**card_style(),
|
|
width="100%",
|
|
)
|
|
|
|
|
|
def main_content() -> rx.Component:
|
|
"""
|
|
Main content area below the top bar.
|
|
|
|
Layout: Filter Section → KPI Row → Chart Section
|
|
Max width constrained to PAGE_MAX_WIDTH, centered.
|
|
"""
|
|
return rx.box(
|
|
rx.vstack(
|
|
filter_section(),
|
|
kpi_row(),
|
|
chart_section(),
|
|
spacing="5",
|
|
width="100%",
|
|
align="stretch",
|
|
),
|
|
width="100%",
|
|
max_width=PAGE_MAX_WIDTH,
|
|
margin_x="auto",
|
|
padding=PAGE_PADDING,
|
|
padding_top=Spacing.XL,
|
|
)
|
|
|
|
|
|
def page_layout() -> rx.Component:
|
|
"""
|
|
Full page layout combining top bar and main content.
|
|
|
|
Structure:
|
|
- Sticky top bar (64px)
|
|
- Scrollable main content area
|
|
- White background
|
|
"""
|
|
return rx.box(
|
|
rx.vstack(
|
|
top_bar(),
|
|
main_content(),
|
|
spacing="0",
|
|
width="100%",
|
|
min_height="100vh",
|
|
),
|
|
background_color=Colors.WHITE,
|
|
font_family=Typography.FONT_FAMILY,
|
|
width="100%",
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# Page Definition
|
|
# =============================================================================
|
|
|
|
def index() -> rx.Component:
|
|
"""Main page for HCD Analysis v2."""
|
|
return page_layout()
|
|
|
|
|
|
# =============================================================================
|
|
# App Configuration
|
|
# =============================================================================
|
|
|
|
app = rx.App(
|
|
theme=rx.theme(
|
|
accent_color="blue",
|
|
gray_color="slate",
|
|
radius="medium",
|
|
),
|
|
stylesheets=[
|
|
# Google Fonts - Inter (primary) and JetBrains Mono (monospace)
|
|
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap",
|
|
],
|
|
)
|
|
|
|
# Register page
|
|
app.add_page(index, route="/", title="HCD Analysis | Patient Pathways")
|