"""Layout registration + header builders.""" from __future__ import annotations from typing import Any from shared.sx.parser import SxExpr from shared.sx.helpers import ( sx_call, post_header_sx as _post_header_sx, post_admin_header_sx, oob_header_sx as _oob_header_sx, header_child_sx, ) from .utils import _set_prices, _price_str, _clear_deeper_oob # --------------------------------------------------------------------------- # Header helpers # --------------------------------------------------------------------------- def _market_header_sx(ctx: dict, *, oob: bool = False) -> str: """Build the market-level header row as sx call string.""" from quart import url_for market_title = ctx.get("market_title", "") top_slug = ctx.get("top_slug", "") sub_slug = ctx.get("sub_slug", "") hx_select_search = ctx.get("hx_select_search", "#main-panel") label_sx = sx_call( "market-shop-label", title=market_title, top_slug=top_slug or "", sub_div=sub_slug or None, ) link_href = url_for("defpage_market_home") # Build desktop nav from categories categories = ctx.get("categories", {}) qs = ctx.get("qs", "") nav_sx = _desktop_category_nav_sx(ctx, categories, qs, hx_select_search) return sx_call( "menu-row-sx", id="market-row", level=2, link_href=link_href, link_label_content=label_sx, nav=nav_sx or None, child_id="market-header-child", oob=oob, ) def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str, hx_select: str) -> str: """Build desktop category navigation links as sx.""" from quart import url_for from shared.utils import route_prefix prefix = route_prefix() category_label = ctx.get("category_label", "") select_colours = ctx.get("select_colours", "") rights = ctx.get("rights", {}) all_href = prefix + url_for("market.browse.browse_all") + qs all_active = (category_label == "All Products") link_parts = [sx_call( "market-category-link", href=all_href, hx_select=hx_select, active=all_active, select_colours=select_colours, label="All", )] for cat, data in categories.items(): cat_href = prefix + url_for("market.browse.browse_top", top_slug=data["slug"]) + qs cat_active = (cat == category_label) link_parts.append(sx_call( "market-category-link", href=cat_href, hx_select=hx_select, active=cat_active, select_colours=select_colours, label=cat, )) links_sx = "(<> " + " ".join(link_parts) + ")" admin_sx = "" if rights and rights.get("admin"): admin_href = prefix + url_for("defpage_market_admin") admin_sx = sx_call("market-admin-link", href=admin_href, hx_select=hx_select) return sx_call("market-desktop-category-nav", links=SxExpr(links_sx), admin=admin_sx or None) def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str: """Build the product-level header row as sx call string.""" from quart import url_for from shared.browser.app.csrf import generate_csrf_token slug = d.get("slug", "") title = d.get("title", "") hx_select_search = ctx.get("hx_select_search", "#main-panel") link_href = url_for("market.browse.product.product_detail", product_slug=slug) label_sx = sx_call("market-product-label", title=title) # Prices in nav area pr = _set_prices(d) cart = ctx.get("cart", []) prices_nav = _prices_header_sx(d, pr, cart, slug, ctx) rights = ctx.get("rights", {}) nav_parts = [prices_nav] if rights and rights.get("admin"): admin_href = url_for("market.browse.product.admin", product_slug=slug) nav_parts.append(sx_call("market-admin-link", href=admin_href, hx_select=hx_select_search)) nav_sx = "(<> " + " ".join(nav_parts) + ")" return sx_call( "menu-row-sx", id="product-row", level=3, link_href=link_href, link_label_content=label_sx, nav=SxExpr(nav_sx), child_id="product-header-child", oob=oob, ) def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict) -> str: """Build prices + add-to-cart for product header row as sx.""" from quart import url_for from shared.browser.app.csrf import generate_csrf_token csrf = generate_csrf_token() cart_action = url_for("market.browse.product.cart", product_slug=slug) cart_url_fn = ctx.get("cart_url") # Add-to-cart button quantity = sum(ci.quantity for ci in cart if ci.product.slug == slug) if cart else 0 add_sx = _cart_add_sx(slug, quantity, cart_action, csrf, cart_url_fn) parts = [add_sx] sp_val, rp_val = pr.get("sp_val"), pr.get("rp_val") if sp_val: parts.append(sx_call("market-header-price-special-label")) parts.append(sx_call("market-header-price-special", price=_price_str(sp_val, pr["sp_raw"], pr["sp_cur"]))) if rp_val: parts.append(sx_call("market-header-price-strike", price=_price_str(rp_val, pr["rp_raw"], pr["rp_cur"]))) elif rp_val: parts.append(sx_call("market-header-price-regular-label")) parts.append(sx_call("market-header-price-regular", price=_price_str(rp_val, pr["rp_raw"], pr["rp_cur"]))) # RRP rrp_raw = d.get("rrp_raw") rrp_val = d.get("rrp") case_size = d.get("case_size_count") or 1 if rrp_raw and rrp_val: rrp_str = f"{rrp_raw[0]}{rrp_val * case_size:.2f}" parts.append(sx_call("market-header-rrp", rrp=rrp_str)) inner_sx = "(<> " + " ".join(parts) + ")" return sx_call("market-prices-row", inner=SxExpr(inner_sx)) def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str, cart_url_fn: Any = None) -> str: """Build add-to-cart button or quantity controls as sx.""" if not quantity: return sx_call( "market-cart-add-empty", cart_id=f"cart-{slug}", action=action, csrf=csrf, ) cart_href = cart_url_fn("/") if callable(cart_url_fn) else "/" return sx_call( "market-cart-add-quantity", cart_id=f"cart-{slug}", action=action, csrf=csrf, minus_val=str(quantity - 1), plus_val=str(quantity + 1), quantity=str(quantity), cart_href=cart_href, ) # --------------------------------------------------------------------------- # Mobile nav panel # --------------------------------------------------------------------------- def _mobile_nav_panel_sx(ctx: dict) -> str: """Build mobile nav panel with category accordion as sx.""" from quart import url_for from shared.utils import route_prefix prefix = route_prefix() categories = ctx.get("categories", {}) qs = ctx.get("qs", "") category_label = ctx.get("category_label", "") top_slug = ctx.get("top_slug", "") sub_slug = ctx.get("sub_slug", "") hx_select = ctx.get("hx_select_search", "#main-panel") select_colours = ctx.get("select_colours", "") all_href = prefix + url_for("market.browse.browse_all") + qs all_active = (category_label == "All Products") item_parts = [sx_call( "market-mobile-all-link", href=all_href, hx_select=hx_select, active=all_active, select_colours=select_colours, )] for cat, data in categories.items(): cat_slug = data.get("slug", "") cat_active = (top_slug == cat_slug.lower() if top_slug else False) cat_href = prefix + url_for("market.browse.browse_top", top_slug=cat_slug) + qs bg_cls = " bg-stone-900 text-white hover:bg-stone-900" if cat_active else "" chevron_sx = sx_call("market-mobile-chevron") cat_count = data.get("count", 0) summary_sx = sx_call( "market-mobile-cat-summary", bg_cls=bg_cls, href=cat_href, hx_select=hx_select, select_colours=select_colours, cat_name=cat, count_label=f"{cat_count} products", count_str=str(cat_count), chevron=chevron_sx, ) subs = data.get("subs", []) subs_sx = "" if subs: sub_link_parts = [] for sub in subs: sub_href = prefix + url_for("market.browse.browse_sub", top_slug=cat_slug, sub_slug=sub["slug"]) + qs sub_active = (cat_active and sub_slug == sub.get("slug")) sub_label = sub.get("html_label") or sub.get("name", "") sub_count = sub.get("count", 0) sub_link_parts.append(sx_call( "market-mobile-sub-link", select_colours=select_colours, active=sub_active, href=sub_href, hx_select=hx_select, label=sub_label, count_label=f"{sub_count} products", count_str=str(sub_count), )) sub_links_sx = "(<> " + " ".join(sub_link_parts) + ")" subs_sx = sx_call("market-mobile-subs-panel", links=SxExpr(sub_links_sx)) else: view_href = prefix + url_for("market.browse.browse_top", top_slug=cat_slug) + qs subs_sx = sx_call("market-mobile-view-all", href=view_href, hx_select=hx_select) item_parts.append(sx_call( "market-mobile-cat-details", open=cat_active or None, summary=summary_sx, subs=subs_sx, )) items_sx = "(<> " + " ".join(item_parts) + ")" return sx_call("market-mobile-nav-wrapper", items=SxExpr(items_sx)) # --------------------------------------------------------------------------- # Product admin header # --------------------------------------------------------------------------- def _product_admin_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str: """Build product admin header row as sx.""" from quart import url_for slug = d.get("slug", "") link_href = url_for("market.browse.product.admin", product_slug=slug) return sx_call( "menu-row-sx", id="product-admin-row", level=4, link_href=link_href, link_label="admin!!", icon="fa fa-cog", child_id="product-admin-header-child", oob=oob, ) # --------------------------------------------------------------------------- # Market admin header # --------------------------------------------------------------------------- async def _market_admin_header_sx(ctx: dict, *, oob: bool = False, selected: str = "") -> str: """Build market admin header row -- delegates to shared helper.""" slug = (ctx.get("post") or {}).get("slug", "") return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected) # =========================================================================== # Layout registration # =========================================================================== def _register_market_layouts() -> None: from shared.sx.layouts import register_custom_layout register_custom_layout("market", _market_full, _market_oob, _market_mobile) register_custom_layout("market-admin", _market_admin_full, _market_admin_oob) async def _market_full(ctx: dict, **kw: Any) -> str: from shared.sx.helpers import render_to_sx_with_env return await render_to_sx_with_env("market-browse-layout-full", {}, post_header=await _post_header_sx(ctx), market_header=_market_header_sx(ctx)) async def _market_oob(ctx: dict, **kw: Any) -> str: oob_hdr = await _oob_header_sx("post-header-child", "market-header-child", _market_header_sx(ctx)) return sx_call("market-browse-layout-oob", oob_header=oob_hdr, post_header_oob=await _post_header_sx(ctx, oob=True), clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", "market-row", "market-header-child"))) def _market_mobile(ctx: dict, **kw: Any) -> str: return _mobile_nav_panel_sx(ctx) async def _market_admin_full(ctx: dict, **kw: Any) -> str: from shared.sx.helpers import render_to_sx_with_env selected = kw.get("selected", "") return await render_to_sx_with_env("market-admin-layout-full", {}, post_header=await _post_header_sx(ctx), market_header=_market_header_sx(ctx), admin_header=await _market_admin_header_sx(ctx, selected=selected)) async def _market_admin_oob(ctx: dict, **kw: Any) -> str: selected = kw.get("selected", "") return sx_call("market-admin-layout-oob", market_header_oob=_market_header_sx(ctx, oob=True), admin_oob_header=await _oob_header_sx("market-header-child", "market-admin-header-child", await _market_admin_header_sx(ctx, selected=selected)), clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", "market-row", "market-header-child", "market-admin-row", "market-admin-header-child")))