;; Events entry card components (all events / page summary) ;; --------------------------------------------------------------------------- ;; State badges — cond maps state string to class + label ;; --------------------------------------------------------------------------- (defcomp ~entries/entry-state-badge (&key state) (~shared:misc/badge :cls (cond ((= state "confirmed") "bg-emerald-100 text-emerald-800") ((= state "provisional") "bg-amber-100 text-amber-800") ((= state "ordered") "bg-sky-100 text-sky-800") ((= state "pending") "bg-stone-100 text-stone-700") ((= state "declined") "bg-red-100 text-red-800") (true "bg-stone-100 text-stone-700")) :label (cond ((= state "confirmed") "Confirmed") ((= state "provisional") "Provisional") ((= state "ordered") "Ordered") ((= state "pending") "Pending") ((= state "declined") "Declined") (true (or state "Unknown"))))) (defcomp ~entries/entry-state-badge-lg (&key state) (span :class (str "inline-flex items-center rounded-full px-3 py-1 text-sm font-medium " (cond ((= state "confirmed") "bg-emerald-100 text-emerald-800") ((= state "provisional") "bg-amber-100 text-amber-800") ((= state "ordered") "bg-sky-100 text-sky-800") ((= state "pending") "bg-stone-100 text-stone-700") ((= state "declined") "bg-red-100 text-red-800") (true "bg-stone-100 text-stone-700"))) (cond ((= state "confirmed") "Confirmed") ((= state "provisional") "Provisional") ((= state "ordered") "Ordered") ((= state "pending") "Pending") ((= state "declined") "Declined") (true (or state "Unknown"))))) (defcomp ~entries/ticket-state-badge (&key state) (~shared:misc/badge :cls (cond ((= state "confirmed") "bg-emerald-100 text-emerald-800") ((= state "checked_in") "bg-blue-100 text-blue-800") ((= state "reserved") "bg-amber-100 text-amber-800") ((= state "cancelled") "bg-red-100 text-red-800") (true "bg-stone-100 text-stone-700")) :label (cond ((= state "confirmed") "Confirmed") ((= state "checked_in") "Checked in") ((= state "reserved") "Reserved") ((= state "cancelled") "Cancelled") (true (or state "Unknown"))))) (defcomp ~entries/ticket-state-badge-lg (&key state) (span :class (str "inline-flex items-center rounded-full px-3 py-1 text-sm font-medium " (cond ((= state "confirmed") "bg-emerald-100 text-emerald-800") ((= state "checked_in") "bg-blue-100 text-blue-800") ((= state "reserved") "bg-amber-100 text-amber-800") ((= state "cancelled") "bg-red-100 text-red-800") (true "bg-stone-100 text-stone-700"))) (cond ((= state "confirmed") "Confirmed") ((= state "checked_in") "Checked in") ((= state "reserved") "Reserved") ((= state "cancelled") "Cancelled") (true (or state "Unknown"))))) ;; --------------------------------------------------------------------------- ;; Entry card components ;; --------------------------------------------------------------------------- (defcomp ~entries/entry-title-linked (&key href name) (a :href href :class "hover:text-emerald-700" (h2 :class "text-lg font-semibold text-stone-900" name))) (defcomp ~entries/entry-title-plain (&key name) (h2 :class "text-lg font-semibold text-stone-900" name)) (defcomp ~entries/entry-title-tile-linked (&key href name) (a :href href :class "hover:text-emerald-700" (h2 :class "text-base font-semibold text-stone-900 line-clamp-2" name))) (defcomp ~entries/entry-title-tile-plain (&key name) (h2 :class "text-base font-semibold text-stone-900 line-clamp-2" name)) (defcomp ~entries/entry-page-badge (&key href title) (a :href href :class "inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800 hover:bg-amber-200" title)) (defcomp ~entries/entry-cal-badge (&key name) (span :class "inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-sky-100 text-sky-700" name)) (defcomp ~entries/entry-time-linked (&key href date-str) (<> (a :href href :class "hover:text-stone-700" date-str) " · ")) (defcomp ~entries/entry-time-plain (&key date-str) (<> (span date-str) " · ")) (defcomp ~entries/entry-cost (&key cost) (div :class "mt-1 text-sm font-medium text-green-600" cost)) (defcomp ~entries/entry-card (&key title badges time-parts cost widget) (article :class "rounded-xl bg-white shadow-sm border border-stone-200 p-4" (div :class "flex flex-col sm:flex-row sm:items-start justify-between gap-3" (div :class "flex-1 min-w-0" title (div :class "flex flex-wrap items-center gap-1.5 mt-1" badges) (div :class "mt-1 text-sm text-stone-500" time-parts) cost) widget))) (defcomp ~entries/entry-card-tile (&key title badges time cost widget) (article :class "rounded-xl bg-white shadow-sm border border-stone-200 overflow-hidden" (div :class "p-3" title (div :class "flex flex-wrap items-center gap-1 mt-1" badges) (div :class "mt-1 text-xs text-stone-500" time) cost) widget)) (defcomp ~entries/entry-tile-widget-wrapper (&key widget) (div :class "border-t border-stone-100 px-3 py-2" widget)) (defcomp ~entries/entry-widget-wrapper (&key widget) (div :class "shrink-0" widget)) (defcomp ~entries/date-separator (&key date-str) (div :class "pt-2 pb-1" (h3 :class "text-sm font-semibold text-stone-500 uppercase tracking-wide" date-str))) (defcomp ~entries/grid (&key grid-cls cards) (div :class grid-cls cards)) (defcomp ~entries/main-panel-body (&key toggle body) (<> toggle body (div :class "pb-8"))) ;; --------------------------------------------------------------------------- ;; Composition defcomps — receive data, compose entry card trees ;; --------------------------------------------------------------------------- ;; Ticket widget from data — replaces _ticket_widget_html Python composition (defcomp ~entries/tw-widget-from-data (&key entry-id price qty ticket-url csrf) (~page/tw-widget :entry-id (str entry-id) :price price :inner (if (= (or qty 0) 0) (~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id) :csrf csrf :entry-id (str entry-id) :count-val "1" :btn (~page/tw-cart-plus)) (<> (~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id) :csrf csrf :entry-id (str entry-id) :count-val (str (- qty 1)) :btn (~page/tw-minus)) (~page/tw-cart-icon :qty (str qty)) (~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id) :csrf csrf :entry-id (str entry-id) :count-val (str (+ qty 1)) :btn (~page/tw-plus)))))) ;; Entry card (list view) from data (defcomp ~entries/entry-card-from-data (&key entry-href name day-href page-badge-href page-badge-title cal-name date-str start-time end-time is-page-scoped cost has-ticket ticket-data) (~entries/entry-card :title (if entry-href (~entries/entry-title-linked :href entry-href :name name) (~entries/entry-title-plain :name name)) :badges (<> (when page-badge-title (~entries/entry-page-badge :href page-badge-href :title page-badge-title)) (when cal-name (~entries/entry-cal-badge :name cal-name))) :time-parts (<> (when (and day-href (not is-page-scoped)) (~entries/entry-time-linked :href day-href :date-str date-str)) (when (and (not day-href) (not is-page-scoped) date-str) (~entries/entry-time-plain :date-str date-str)) start-time (when end-time (str " \u2013 " end-time))) :cost (when cost (~entries/entry-cost :cost cost)) :widget (when has-ticket (~entries/entry-widget-wrapper :widget (~entries/tw-widget-from-data :entry-id (get ticket-data "entry-id") :price (get ticket-data "price") :qty (get ticket-data "qty") :ticket-url (get ticket-data "ticket-url") :csrf (get ticket-data "csrf")))))) ;; Entry card (tile view) from data (defcomp ~entries/entry-card-tile-from-data (&key entry-href name day-href page-badge-href page-badge-title cal-name date-str time-str cost has-ticket ticket-data) (~entries/entry-card-tile :title (if entry-href (~entries/entry-title-tile-linked :href entry-href :name name) (~entries/entry-title-tile-plain :name name)) :badges (<> (when page-badge-title (~entries/entry-page-badge :href page-badge-href :title page-badge-title)) (when cal-name (~entries/entry-cal-badge :name cal-name))) :time time-str :cost (when cost (~entries/entry-cost :cost cost)) :widget (when has-ticket (~entries/entry-tile-widget-wrapper :widget (~entries/tw-widget-from-data :entry-id (get ticket-data "entry-id") :price (get ticket-data "price") :qty (get ticket-data "qty") :ticket-url (get ticket-data "ticket-url") :csrf (get ticket-data "csrf")))))) ;; Entry cards list (with date separators + sentinel) from data (defcomp ~entries/entry-cards-from-data (&key items view page has-more next-url) (<> (map (lambda (item) (if (get item "is-separator") (~entries/date-separator :date-str (get item "date-str")) (if (= view "tile") (~entries/entry-card-tile-from-data :entry-href (get item "entry-href") :name (get item "name") :day-href (get item "day-href") :page-badge-href (get item "page-badge-href") :page-badge-title (get item "page-badge-title") :cal-name (get item "cal-name") :date-str (get item "date-str") :time-str (get item "time-str") :cost (get item "cost") :has-ticket (get item "has-ticket") :ticket-data (get item "ticket-data")) (~entries/entry-card-from-data :entry-href (get item "entry-href") :name (get item "name") :day-href (get item "day-href") :page-badge-href (get item "page-badge-href") :page-badge-title (get item "page-badge-title") :cal-name (get item "cal-name") :date-str (get item "date-str") :start-time (get item "start-time") :end-time (get item "end-time") :is-page-scoped (get item "is-page-scoped") :cost (get item "cost") :has-ticket (get item "has-ticket") :ticket-data (get item "ticket-data"))))) (or items (list))) (when has-more (~shared:misc/sentinel-simple :id (str "sentinel-" page) :next-url next-url)))) ;; Events main panel (toggle + cards grid) from data (defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url) (~entries/main-panel-body :toggle toggle :body (if items (~entries/grid :grid-cls (if (= view "tile") "max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4" "max-w-full px-3 py-3 space-y-3") :cards (~entries/entry-cards-from-data :items items :view view :page page :has-more has-more :next-url next-url)) (~shared:misc/empty-state :icon "fa fa-calendar-xmark" :message "No upcoming events" :cls "px-3 py-12 text-center text-stone-400"))))