Two-level Reports nav: Campaign dropdown, then Period dropdown

Replaces the single flat "Reporting Month" dropdown with two linked
ones - Campaign (LinkedIn Branding Campaign / Pilot Partner / CTA
Campaign), then Period, populated based on the campaign picked.
Dash's dropdown has no native nested submenu, so this is the standard
"pick a year, then a month" cascade instead of faking nesting in one
control.

LinkedIn Branding Campaign -> July/August/September/End of Campaign
(unchanged, same data as before). Pilot Partner -> October (unchanged).
CTA Campaign -> its 3 proposed phases by date (Nov 3-26, Dec 1-Jan 7,
Jan 12-Feb 4), each showing a "not approved yet" placeholder rather
than crashing - compute_period_stats()'s date parsing doesn't
understand "cta-phase-1" as a period key, so it's guarded the same way
"campaign-end" was guarded before that report was built out. Download
button is guarded the same way.

Dropdown structure only - no CTA Campaign report content built yet,
that's still pending approval.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
beantownbabeandClaude Sonnet 5 committed 2026-10-26 11:51:31 +01:00
1 parent efcad4a6da
commit f54db563a3
1 file changed
+73 -3
+73 -3
View File
@@ -2179,16 +2179,54 @@ _REPORTS_PERIODS = [
("2026-07", "July 2026"),
("2026-08", "August 2026"),
("2026-09", "September 2026"),
("2026-10", "October 2026"),
]
_REPORTS_MONTH_OPTIONS = [{"label": label, "value": key} for key, label in _REPORTS_PERIODS]
_REPORTS_DEFAULT_MONTH = "2026-07"
# Two-level nav: pick a campaign, then a period within it - a flat dropdown stopped
# making sense once campaigns stopped being "the same 3 calendar months" (the CTA
# Campaign's periods are custom date ranges spanning two calendar months each, not
# July/August/September). Dash's dcc.Dropdown has no native nested/flyout submenu,
# so this is two linked dropdowns instead - the standard "pick a year, then a month"
# pattern - rather than faking nesting inside one control.
_REPORTS_CAMPAIGNS = {
"linkedin-branding": {
"label": "LinkedIn Branding Campaign",
"periods": _REPORTS_PERIODS + [("campaign-end", "End of Campaign")],
},
"pilot-partner": {
"label": "Pilot Partner",
"periods": [("2026-10", "October 2026")],
},
"cta-campaign": {
"label": "CTA Campaign",
# Placeholder periods only - dates from the proposed plan, not yet approved.
# Selecting one shows a "not built yet" placeholder (_render_reports_tab),
# same treatment "End of Campaign" itself got before it was built out.
"periods": [
("cta-phase-1", "Nov 3 - Nov 26 (Failure Analysis)"),
("cta-phase-2", "Dec 1 - Jan 7 (Standardization)"),
("cta-phase-3", "Jan 12 - Feb 4 (WaveGate)"),
],
},
}
_REPORTS_CAMPAIGN_OPTIONS = [{"label": v["label"], "value": k} for k, v in _REPORTS_CAMPAIGNS.items()]
_REPORTS_DEFAULT_CAMPAIGN = "linkedin-branding"
reports_tab = dbc.Container([
html.Div([
html.Div([
html.Label("Reporting Month", style={"fontSize": "0.75rem", "fontWeight": "600",
"marginBottom": "4px", "display": "block"}),
html.Label("Campaign", style={"fontSize": "0.75rem", "fontWeight": "600",
"marginBottom": "4px", "display": "block"}),
dcc.Dropdown(
id="reports-campaign-select",
options=_REPORTS_CAMPAIGN_OPTIONS, value=_REPORTS_DEFAULT_CAMPAIGN,
clearable=False, searchable=False, style={"width": "240px"},
),
], style={"marginRight": "0.75rem"}),
html.Div([
html.Label("Period", style={"fontSize": "0.75rem", "fontWeight": "600",
"marginBottom": "4px", "display": "block"}),
dcc.Dropdown(
id="reports-month-select",
options=_REPORTS_MONTH_OPTIONS, value=_REPORTS_DEFAULT_MONTH,
@@ -4449,6 +4487,22 @@ def _render_campaign_end_summary():
return title, html.Div(body, style={"fontSize": "0.9rem"})
@app.callback(
Output("reports-month-select", "options"),
Output("reports-month-select", "value"),
Input("reports-campaign-select", "value"),
)
def _sync_reports_period_options(campaign_value):
"""Picking a campaign repopulates the Period dropdown with just that campaign's
own periods (its own custom date ranges for CTA Campaign, calendar months for
the other two), defaulting to the first one - the standard "pick a year, then
a month" cascade, not a real nested submenu (Dash's dropdown has none)."""
periods = _REPORTS_CAMPAIGNS.get(campaign_value, {}).get("periods", [])
options = [{"label": label, "value": key} for key, label in periods]
default_value = periods[0][0] if periods else None
return options, default_value
@app.callback(
Output("reports-title", "children"),
Output("reports-content-area", "children"),
@@ -4468,6 +4522,20 @@ def _render_reports_tab(month_value):
if month_value == "campaign-end":
return _render_campaign_end_summary() + (store_value,)
if month_value.startswith("cta-phase"):
# CTA Campaign is proposed, not yet approved — placeholder only, same
# treatment "End of Campaign" itself got before it was built out.
period_label = next((lbl for v, lbl in _REPORTS_CAMPAIGNS["cta-campaign"]["periods"]
if v == month_value), month_value)
title = html.H3(f"CTA Campaign — {period_label}",
style={"color": "#1b3a5c", "marginBottom": "0", "fontWeight": "700",
"fontSize": "2rem", "whiteSpace": "nowrap"})
placeholder = dbc.Alert(
"The CTA Campaign hasn't been approved yet, so there's no report to show. "
"This period will build out the same way the other reports do once it's live.",
color="info")
return title, placeholder, no_update
stats = report.compute_period_stats(_build_report_ctx(), month_value)
notes = monthly_report_notes.NOTES.get(month_value)
fts_f, posts_f = stats["fts_f"], stats["posts_f"]
@@ -4702,6 +4770,8 @@ def _render_reports_tab(month_value):
prevent_initial_call=True,
)
def _download_reports_pdf(_n_clicks, month_value):
if month_value and month_value.startswith("cta-phase"):
return no_update # CTA Campaign not approved yet, nothing to download
ctx_data = _build_report_ctx()
ctx_data["monthly_notes"] = monthly_report_notes.NOTES.get(month_value)
docx_bytes = report.build_report_docx(ctx_data, month_value)