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:
1 parent
efcad4a6da
commit
f54db563a3
1 file changed
+73
-3
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user