diff --git a/web/src/padelnomics/planner/routes.py b/web/src/padelnomics/planner/routes.py index b21b06a..ac64207 100644 --- a/web/src/padelnomics/planner/routes.py +++ b/web/src/padelnomics/planner/routes.py @@ -19,7 +19,7 @@ from ..core import ( waitlist_gate, ) from ..i18n import get_translations -from .calculator import COUNTRY_CURRENCY, CURRENCY_DEFAULT, DEFAULTS, calc, validate_state +from .calculator import COUNTRY_CURRENCY, CURRENCY_DEFAULT, calc, validate_state bp = Blueprint( "planner", @@ -322,7 +322,15 @@ async def index(): default = None if g.user: scenario_count = await count_scenarios(g.user["id"]) - default = await get_default_scenario(g.user["id"]) + # Load specific scenario if ?scenario= is set, else default + scenario_id = request.args.get("scenario", type=int) + if scenario_id: + default = await fetch_one( + "SELECT * FROM scenarios WHERE id = ? AND user_id = ? AND deleted_at IS NULL", + (scenario_id, g.user["id"]), + ) + if not default: + default = await get_default_scenario(g.user["id"]) initial_state = json.loads(default["state_json"]) if default else {} s = validate_state(initial_state) lang = g.get("lang", "en") @@ -339,8 +347,9 @@ async def index(): lang=lang, active_tab="capex", country_presets=COUNTRY_PRESETS, - defaults=DEFAULTS, currency_sym=cur["sym"], + step=1, + total_steps=TOTAL_WIZARD_STEPS, ) @@ -353,7 +362,7 @@ async def calculate(): d = calc(s, lang=lang) augment_d(d, s, lang) active_tab = form.get("activeTab", "capex") - if active_tab not in {"capex", "operating", "cashflow", "returns", "metrics"}: + if active_tab not in {"capex", "operating", "cashflow", "returns"}: active_tab = "capex" cur = COUNTRY_CURRENCY.get(s["country"], CURRENCY_DEFAULT) g.currency_sym = cur["sym"] @@ -368,6 +377,21 @@ async def calculate(): ) +TOTAL_WIZARD_STEPS = 4 + + +@bp.route("/wizard-nav") +async def wizard_nav(): + """HTMX endpoint: render wizard Back/Next/Calculate buttons for a given step.""" + step = request.args.get("step", 1, type=int) + step = max(1, min(step, TOTAL_WIZARD_STEPS)) + lang = g.get("lang", "en") + t = get_translations(lang) + return await render_template( + "partials/wizard_nav.html", step=step, total_steps=TOTAL_WIZARD_STEPS, t=t, + ) + + @bp.route("/scenarios", methods=["GET"]) @login_required async def scenario_list(): @@ -379,24 +403,23 @@ async def scenario_list(): @login_required @csrf_protect async def save_scenario(): - data = await request.get_json() - name = data.get("name", "Untitled Scenario") - state_json = data.get("state_json", "{}") - location = data.get("location", "") - scenario_id = data.get("id") + form = await request.form + name = form.get("scenario_name", "Untitled Scenario") + # Build state_json from form data (same form as the planner) + state_json = json.dumps(form_to_state(form)) + location = form.get("location", "") + scenario_id = form.get("scenario_id") now = datetime.utcnow().isoformat() is_first_save = not scenario_id and (await count_scenarios(g.user["id"])) == 0 if scenario_id: - # Update existing await execute( "UPDATE scenarios SET name = ?, state_json = ?, location = ?, updated_at = ? WHERE id = ? AND user_id = ? AND deleted_at IS NULL", - (name, state_json, location, now, scenario_id, g.user["id"]), + (name, state_json, location, now, int(scenario_id), g.user["id"]), ) else: - # Create new scenario_id = await execute( "INSERT INTO scenarios (user_id, name, state_json, location, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", (g.user["id"], name, state_json, location, now, now), @@ -416,8 +439,9 @@ async def save_scenario(): except Exception as e: print(f"[NURTURE] Failed to add {g.user['email']} to audience: {e}") - count = await count_scenarios(g.user["id"]) - return jsonify({"ok": True, "id": scenario_id, "count": count}) + lang = g.get("lang", "en") + t = get_translations(lang) + return f'
✓ {t.get("scenario_saved", "Saved")}
' @bp.route("/scenarios/", methods=["GET"]) diff --git a/web/src/padelnomics/planner/templates/partials/calculate_response.html b/web/src/padelnomics/planner/templates/partials/calculate_response.html index 0d4742e..a68f022 100644 --- a/web/src/padelnomics/planner/templates/partials/calculate_response.html +++ b/web/src/padelnomics/planner/templates/partials/calculate_response.html @@ -6,3 +6,5 @@
{% include "partials/court_summary.html" %}
{% include "partials/wizard_preview.html" %}
+ +
{{ d.capex | fmt_k }} {{ t.planner_cta_estimated|default('estimated') }}
diff --git a/web/src/padelnomics/planner/templates/partials/scenario_list.html b/web/src/padelnomics/planner/templates/partials/scenario_list.html index fe9aa61..be3c0c5 100644 --- a/web/src/padelnomics/planner/templates/partials/scenario_list.html +++ b/web/src/padelnomics/planner/templates/partials/scenario_list.html @@ -10,7 +10,7 @@
{{ s.name }}
{% if s.is_default %}{{ t.scenario_badge_default }}{% endif %} - + {{ t.scenario_btn_load }} +{% else %} +
+{% endif %} +{% if step >= total_steps %} + +{% else %} + +{% endif %} diff --git a/web/src/padelnomics/planner/templates/planner.html b/web/src/padelnomics/planner/templates/planner.html index 08c3e5c..4e24800 100644 --- a/web/src/padelnomics/planner/templates/planner.html +++ b/web/src/padelnomics/planner/templates/planner.html @@ -50,14 +50,30 @@ hx-swap="innerHTML"> {{ t.btn_my_scenarios }} ({{ scenario_count }}) - + +
+ {% endif %}