"""Toronto Housing Dashboard page.""" import dash import dash_mantine_components as dmc from dash import dcc, html from dash_iconify import DashIconify from portfolio_app.components import ( create_map_controls, create_metric_cards_row, create_time_slider, create_year_selector, ) dash.register_page(__name__, path="/toronto", name="Toronto Housing") # Metric options for the purchase market PURCHASE_METRIC_OPTIONS = [ {"label": "Average Price", "value": "avg_price"}, {"label": "Median Price", "value": "median_price"}, {"label": "Sales Volume", "value": "sales_count"}, {"label": "Days on Market", "value": "avg_dom"}, ] # Metric options for the rental market RENTAL_METRIC_OPTIONS = [ {"label": "Average Rent", "value": "avg_rent"}, {"label": "Vacancy Rate", "value": "vacancy_rate"}, {"label": "Rental Universe", "value": "rental_universe"}, ] # Sample metrics for KPI cards (will be populated by callbacks) SAMPLE_METRICS = [ { "title": "Avg. Price", "value": 1125000, "delta": 2.3, "prefix": "$", "format_spec": ",.0f", }, { "title": "Sales Volume", "value": 4850, "delta": -5.1, "format_spec": ",", }, { "title": "Avg. DOM", "value": 18, "delta": 3, "suffix": " days", "positive_is_good": False, }, { "title": "Avg. Rent", "value": 2450, "delta": 4.2, "prefix": "$", "format_spec": ",.0f", }, ] def create_header() -> dmc.Group: """Create the dashboard header with title and controls.""" return dmc.Group( [ dmc.Stack( [ dmc.Title("Toronto Housing Dashboard", order=1), dmc.Text( "Real estate market analysis for the Greater Toronto Area", c="dimmed", ), ], gap="xs", ), dmc.Group( [ dcc.Link( dmc.Button( "Methodology", leftSection=DashIconify( icon="tabler:info-circle", width=18 ), variant="subtle", color="gray", ), href="/toronto/methodology", ), create_year_selector( id_prefix="toronto", min_year=2020, default_year=2024, label="Year", ), ], gap="md", ), ], justify="space-between", align="flex-start", ) def create_kpi_section() -> dmc.Box: """Create the KPI metrics row.""" return dmc.Box( children=[ dmc.Title("Key Metrics", order=3, size="h4", mb="sm"), html.Div( id="toronto-kpi-cards", children=[ create_metric_cards_row(SAMPLE_METRICS, id_prefix="toronto-kpi") ], ), ], ) def create_purchase_map_section() -> dmc.Grid: """Create the purchase market choropleth section.""" return dmc.Grid( [ dmc.GridCol( create_map_controls( id_prefix="purchase-map", metric_options=PURCHASE_METRIC_OPTIONS, default_metric="avg_price", ), span={"base": 12, "md": 3}, ), dmc.GridCol( dmc.Paper( children=[ dcc.Graph( id="purchase-choropleth", config={"scrollZoom": True}, style={"height": "500px"}, ), ], p="xs", radius="sm", withBorder=True, ), span={"base": 12, "md": 9}, ), ], gutter="md", ) def create_rental_map_section() -> dmc.Grid: """Create the rental market choropleth section.""" return dmc.Grid( [ dmc.GridCol( create_map_controls( id_prefix="rental-map", metric_options=RENTAL_METRIC_OPTIONS, default_metric="avg_rent", ), span={"base": 12, "md": 3}, ), dmc.GridCol( dmc.Paper( children=[ dcc.Graph( id="rental-choropleth", config={"scrollZoom": True}, style={"height": "500px"}, ), ], p="xs", radius="sm", withBorder=True, ), span={"base": 12, "md": 9}, ), ], gutter="md", ) def create_time_series_section() -> dmc.Grid: """Create the time series charts section.""" return dmc.Grid( [ dmc.GridCol( dmc.Paper( children=[ dmc.Title("Price Trends", order=4, size="h5", mb="sm"), dcc.Graph( id="price-time-series", config={"displayModeBar": False}, style={"height": "350px"}, ), ], p="md", radius="sm", withBorder=True, ), span={"base": 12, "md": 6}, ), dmc.GridCol( dmc.Paper( children=[ dmc.Title("Sales Volume", order=4, size="h5", mb="sm"), dcc.Graph( id="volume-time-series", config={"displayModeBar": False}, style={"height": "350px"}, ), ], p="md", radius="sm", withBorder=True, ), span={"base": 12, "md": 6}, ), ], gutter="md", ) def create_market_comparison_section() -> dmc.Paper: """Create the market comparison chart section.""" return dmc.Paper( children=[ dmc.Group( [ dmc.Title("Market Indicators", order=4, size="h5"), create_time_slider( id_prefix="market-comparison", min_year=2020, label="", ), ], justify="space-between", align="center", mb="md", ), dcc.Graph( id="market-comparison-chart", config={"displayModeBar": False}, style={"height": "400px"}, ), ], p="md", radius="sm", withBorder=True, ) def create_data_notice() -> dmc.Alert: """Create a notice about data availability.""" return dmc.Alert( children=[ dmc.Text( "This dashboard uses TRREB and CMHC data. " "Geographic boundaries require QGIS digitization to enable choropleth maps. " "Sample data is shown below.", size="sm", ), ], title="Data Notice", color="blue", variant="light", ) # Register callbacks from portfolio_app.pages.toronto import callbacks # noqa: E402, F401 layout = dmc.Container( dmc.Stack( [ create_header(), create_data_notice(), create_kpi_section(), dmc.Divider(my="md", label="Purchase Market", labelPosition="center"), create_purchase_map_section(), dmc.Divider(my="md", label="Rental Market", labelPosition="center"), create_rental_map_section(), dmc.Divider(my="md", label="Trends", labelPosition="center"), create_time_series_section(), create_market_comparison_section(), dmc.Space(h=40), ], gap="lg", ), size="xl", py="xl", )