from dash import html, dcc
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import figures as figs
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def empty_fig(title: str):
fig = go.Figure()
fig.update_layout(template="plotly_white", margin=dict(l=20, r=20, t=20, b=20))
fig.update_xaxes(visible=False); fig.update_yaxes(visible=False)
fig.add_annotation(text=title, showarrow=False, font=dict(size=14, color="#888"))
return fig
def kpi_card(label, value, id_value=None):
return dbc.Card(
dbc.CardBody([
html.Div(value, id=id_value, style={"fontSize": "28px", "fontWeight": 700, "color": "#c23b5a"}),
html.Div(label, style={"color": "#555"})
]),
className="shadow-sm border-0",
style={"borderRadius": "14px"}
)
def make_card(item, figure_lookup, width_basis=12):
# Calculate percentage width based on the grid system
width_pct = (item['width'] / width_basis) * 100
actual_figure = figure_lookup.get(item['id'], empty_fig(item['title']))
return html.Div(
style={"width": f"{width_pct}%", "padding": "10px", "boxSizing": "border-box"},
children=[
dbc.Card([
dbc.CardHeader(html.Div(item['title'], style={"fontWeight": 600})),
dbc.CardBody(
dcc.Graph(
figure=actual_figure,
config={"displayModeBar": False},
# Graph fills the CardBody
style={"height": "100%", "width": "100%"}
),
# FIX: Flex-grow to fill remaining space, hide overflow
style={"padding": "10px", "flex": "1", "minHeight": "0", "overflow": "hidden"}
)
],
className="shadow-sm",
# FIX: Flex column ensures Header stacks with Body correctly
style={"borderRadius": "14px", "height": item.get('height', '350px'), "display": "flex", "flexDirection": "column"})
]
)
def build_horizontal_layout(config, figure_lookup):
return html.Div(
style={"display": "flex", "flexWrap": "wrap", "margin": "-10px"},
children=[make_card(item, figure_lookup, width_basis=12) for item in config]
)
# =============================================================================
# MAIN LAYOUT GENERATOR
# =============================================================================
def create_layout(df, kpis_data):
# 1. Generate Figures
figure_lookup = {
"submitters_chart": figs.create_bar_submitters(df),
"ym_chart": figs.create_bar_timeline(df),
"timeline_pie": figs.create_pie_chart(df, figs.COL_TIMELINE),
"status_pie": figs.create_pie_chart(df, figs.COL_STATUS),
"location_chart": figs.create_bar_location(df)
}
# 2. Define Configuration (Horizontal Strategy)
config_horizontal = [
# Row 1
{"id": "submitters_chart", "width": 4, "title": "Total Projects By Submitters", "height": "380px"},
{"id": "ym_chart", "width": 8, "title": "Total Project By YYYY-MM", "height": "380px"},
# Row 2
{"id": "timeline_pie", "width": 4, "title": "Total Projects By Timeline", "height": "360px"},
{"id": "status_pie", "width": 4, "title": "Total Projects By Idea Status","height": "360px"},
{"id": "location_chart", "width": 4, "title": "Total Projects By Location", "height": "360px"}
]
# 3. Define KPI List
kpis_list = [
{"label": "TOTAL PROJECTS", "value": kpis_data.get("projects", "0"), "id": "kpi-proj"},
{"label": "TOTAL SUBMITTERS", "value": kpis_data.get("submitters", "0"), "id": "kpi-sub"},
{"label": "TOTAL OWNERS", "value": kpis_data.get("owners", "0"), "id": "kpi-own"},
{"label": "TOTAL APPROVERS", "value": kpis_data.get("approvers", "0"), "id": "kpi-app"},
]
# 4. Assemble Blocks
# Header
header_section = dbc.Row(
align="center",
className="gy-2 mb-3",
children=[
dbc.Col([
html.H3("PLANISWARE PROJECT OVERVIEW", style={"margin": 0, "fontWeight": 700}),
html.Div("Overview of Campaigns, Status, and Project Initiatives", style={"color": "#666"})
], md=10),
dbc.Col(dbc.Button("Refresh Data", id="btn-refresh", color="primary", className="w-100"), md=2),
],
)
# KPI Grid
kpi_section = html.Div(
children=[kpi_card(k['label'], k['value'], k['id']) for k in kpis_list],
style= {
"display": "grid",
"gridTemplateColumns": "repeat(4, 1fr)",
"gap": "20px",
"marginBottom": "20px",
"justifyContent": "center"
}
)
# Charts
chart_layout = build_horizontal_layout(config_horizontal, figure_lookup)
# Final Container
return dbc.Container(
fluid=True,
style={"backgroundColor": "#f5f6fa", "minHeight": "100vh", "padding": "18px"},
children=[
header_section,
html.Hr(),
kpi_section,
chart_layout
]
)