'
)
def _calendar_entries_html(entries: list) -> str:
"""Render calendar booking entries in cart."""
if not entries:
return ""
items = []
for e in entries:
name = getattr(e, "name", None) or getattr(e, "calendar_name", "")
start = e.start_at if hasattr(e, "start_at") else ""
end = getattr(e, "end_at", None)
cost = getattr(e, "cost", 0) or 0
end_html = f" \u2013 {end}" if end else ""
items.append(
f'
'
f'
{name}
'
f'
{start}{end_html}
'
f'
\u00a3{cost:.2f}
'
)
return (
'
'
'
Calendar bookings
'
f'
{"".join(items)}
'
)
def _ticket_groups_html(ticket_groups: list, ctx: dict) -> str:
"""Render ticket groups in cart."""
if not ticket_groups:
return ""
from shared.browser.app.csrf import generate_csrf_token
from quart import url_for
csrf = generate_csrf_token()
qty_url = url_for("cart_global.update_ticket_quantity")
parts = ['
',
'
Event tickets
',
'
']
for tg in ticket_groups:
name = tg.entry_name if hasattr(tg, "entry_name") else tg.get("entry_name", "")
tt_name = tg.ticket_type_name if hasattr(tg, "ticket_type_name") else tg.get("ticket_type_name", "")
price = tg.price if hasattr(tg, "price") else tg.get("price", 0)
quantity = tg.quantity if hasattr(tg, "quantity") else tg.get("quantity", 0)
line_total = tg.line_total if hasattr(tg, "line_total") else tg.get("line_total", 0)
entry_id = tg.entry_id if hasattr(tg, "entry_id") else tg.get("entry_id", "")
tt_id = tg.ticket_type_id if hasattr(tg, "ticket_type_id") else tg.get("ticket_type_id", "")
start_at = tg.entry_start_at if hasattr(tg, "entry_start_at") else tg.get("entry_start_at")
end_at = tg.entry_end_at if hasattr(tg, "entry_end_at") else tg.get("entry_end_at")
date_str = start_at.strftime("%-d %b %Y, %H:%M") if start_at else ""
if end_at:
date_str += f" \u2013 {end_at.strftime('%-d %b %Y, %H:%M')}"
tt_name_html = f'
{tt_name}
' if tt_name else ""
tt_hidden = f'' if tt_id else ""
parts.append(
f''
f'
'
f'
'
f'
{name}
{tt_name_html}'
f'
{date_str}
'
f'
\u00a3{price or 0:.2f}
'
f'
'
f'
'
f'Quantity'
f''
f'{quantity}'
f'
'
f'
'
f'
Line total: \u00a3{line_total:.2f}
'
)
parts.append('
')
return "".join(parts)
def _cart_summary_html(ctx: dict, cart: list, cal_entries: list, tickets: list,
total_fn: Any, cal_total_fn: Any, ticket_total_fn: Any) -> str:
"""Render the order summary sidebar."""
from shared.browser.app.csrf import generate_csrf_token
from quart import g, url_for, request
from shared.infrastructure.urls import login_url
csrf = generate_csrf_token()
product_qty = sum(ci.quantity for ci in cart) if cart else 0
ticket_qty = len(tickets) if tickets else 0
item_count = product_qty + ticket_qty
product_total = total_fn(cart) or 0
cal_total = cal_total_fn(cal_entries) or 0
tk_total = ticket_total_fn(tickets) or 0
grand = float(product_total) + float(cal_total) + float(tk_total)
symbol = "\u00a3"
if cart and hasattr(cart[0], "product") and getattr(cart[0].product, "regular_price_currency", None):
cur = cart[0].product.regular_price_currency
symbol = "\u00a3" if cur == "GBP" else cur
user = getattr(g, "user", None)
page_post = ctx.get("page_post")
if user:
if page_post:
action = url_for("page_cart.page_checkout")
else:
action = url_for("cart_global.checkout")
from shared.utils import route_prefix
action = route_prefix() + action
checkout_html = (
f''
)
else:
href = login_url(request.url)
checkout_html = (
f'
'
)
# ---------------------------------------------------------------------------
# Orders list (same pattern as orders service)
# ---------------------------------------------------------------------------
def _order_row_html(order: Any, detail_url: str) -> str:
"""Render a single order as desktop table row + mobile card."""
status = order.status or "pending"
sl = status.lower()
pill = (
"border-emerald-300 bg-emerald-50 text-emerald-700" if sl == "paid"
else "border-rose-300 bg-rose-50 text-rose-700" if sl in ("failed", "cancelled")
else "border-stone-300 bg-stone-50 text-stone-700"
)
created = order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else "\u2014"
total = f"{order.currency or 'GBP'} {order.total_amount or 0:.2f}"
return (
f'
'
''
)
# ---------------------------------------------------------------------------
# Single order detail
# ---------------------------------------------------------------------------
def _order_items_html(order: Any) -> str:
"""Render order items list."""
if not order or not order.items:
return ""
items = []
for item in order.items:
prod_url = market_product_url(item.product_slug)
img = (
f''
if item.product_image else
'
'
)
def _order_summary_html(order: Any) -> str:
"""Order summary card."""
return sexp(
'(~order-summary-card :order-id oid :created-at ca :description d :status s :currency c :total-amount ta)',
oid=order.id,
ca=order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else None,
d=order.description, s=order.status, c=order.currency,
ta=f"{order.total_amount:.2f}" if order.total_amount else None,
)
def _order_calendar_items_html(calendar_entries: list | None) -> str:
"""Render calendar bookings for an order."""
if not calendar_entries:
return ""
items = []
for e in calendar_entries:
st = e.state or ""
pill = (
"bg-emerald-100 text-emerald-800" if st == "confirmed"
else "bg-amber-100 text-amber-800" if st == "provisional"
else "bg-blue-100 text-blue-800" if st == "ordered"
else "bg-stone-100 text-stone-700"
)
ds = e.start_at.strftime("%-d %b %Y, %H:%M") if e.start_at else ""
if e.end_at:
ds += f" \u2013 {e.end_at.strftime('%-d %b %Y, %H:%M')}"
items.append(
f'
'
f'
{e.name}'
f''
f'{st.capitalize()}
'
f'
{ds}
'
f'
\u00a3{e.cost or 0:.2f}
'
)
return (
''
'
Calendar bookings in this order
'
f'
{"".join(items)}
'
)
def _order_main_html(order: Any, calendar_entries: list | None) -> str:
"""Main panel for single order detail."""
summary = _order_summary_html(order)
return f'