Convert last Python fragment handlers to SX defhandlers: 100% declarative fragment API

- Add dict recursion to _convert_result for service methods returning dict[K, list[DTO]]
- New container-cards.sx: parses post_ids/slugs, calls confirmed-entries-for-posts, emits card-widget markers
- New account-page.sx: dispatches on slug for tickets/bookings panels with status pills and empty states
- Fix blog _parse_card_fragments to handle SxExpr via str() cast
- Remove events Python fragment handlers and simplify app.py to plain auto_mount

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 19:42:19 +00:00
parent e30cb0a992
commit f551fc7453
7 changed files with 91 additions and 63 deletions

View File

@@ -0,0 +1,49 @@
;; Account-page fragment handler
;;
;; Renders tickets or bookings panel for the account dashboard.
;; slug=tickets → ticket list; slug=bookings → booking list.
(defhandler account-page (&key slug user_id)
(let ((uid (parse-int (or user_id "0"))))
(when (> uid 0)
(cond
(= slug "tickets")
(let ((tickets (service "calendar" "user-tickets" :user-id uid)))
(~events-frag-tickets-panel
:items (if (empty? tickets)
(~empty-state :message "No tickets yet."
:cls "text-sm text-stone-500")
(~events-frag-tickets-list
:items (<> (map (fn (t)
(~events-frag-ticket-item
:href (app-url "events"
(str "/tickets/" (get t "code") "/"))
:entry-name (get t "entry_name")
:date-str (format-date (get t "entry_start_at") "%d %b %Y, %H:%M")
:calendar-name (when (get t "calendar_name")
(span (str "\u00b7 " (get t "calendar_name"))))
:type-name (when (get t "ticket_type_name")
(span (str "\u00b7 " (get t "ticket_type_name"))))
:badge (~status-pill :status (or (get t "state") ""))))
tickets))))))
(= slug "bookings")
(let ((bookings (service "calendar" "user-bookings" :user-id uid)))
(~events-frag-bookings-panel
:items (if (empty? bookings)
(~empty-state :message "No bookings yet."
:cls "text-sm text-stone-500")
(~events-frag-bookings-list
:items (<> (map (fn (b)
(~events-frag-booking-item
:name (get b "name")
:date-str (str (format-date (get b "start_at") "%d %b %Y, %H:%M")
(if (get b "end_at")
(str " \u2013 " (format-date (get b "end_at") "%H:%M"))
""))
:calendar-name (when (get b "calendar_name")
(span (str "\u00b7 " (get b "calendar_name"))))
:cost-str (when (get b "cost")
(span (str "\u00b7 \u00a3" (get b "cost"))))
:badge (~status-pill :status (or (get b "state") ""))))
bookings))))))))))

View File

@@ -0,0 +1,38 @@
;; Container-cards fragment handler
;;
;; Returns HTML with <!-- card-widget:ID --> comment markers so the
;; blog consumer can split per-post fragments. Each post section
;; contains an events-frag-entries-widget with entry cards.
(defhandler container-cards (&key post_ids post_slugs)
(let ((ids (filter (fn (x) (> x 0))
(map parse-int
(filter (fn (s) (not (empty? s)))
(split (or post_ids "") ",")))))
(slugs (map trim
(split (or post_slugs "") ","))))
(when (not (empty? ids))
(let ((batch (service "calendar" "confirmed-entries-for-posts" :post-ids ids)))
(<> (map-indexed (fn (i pid)
(let ((entries (or (get batch pid) (list)))
(post-slug (or (nth slugs i) "")))
(<> (str "<!-- card-widget:" pid " -->")
(when (not (empty? entries))
(~events-frag-entries-widget
:cards (<> (map (fn (e)
(let ((time-str (str (format-date (get e "start_at") "%H:%M")
(if (get e "end_at")
(str " \u2013 " (format-date (get e "end_at") "%H:%M"))
""))))
(~events-frag-entry-card
:href (app-url "events"
(str "/" post-slug
"/" (get e "calendar_slug")
"/" (get e "start_at_year")
"/" (get e "start_at_month")
"/" (get e "start_at_day")
"/entries/" (get e "id") "/"))
:name (get e "name")
:date-str (format-date (get e "start_at") "%a, %b %d")
:time-str time-str))) entries))))
(str "<!-- /card-widget:" pid " -->")))) ids))))))