Component names now reflect filesystem location using / as path separator and : as namespace separator for shared components: ~sx-header → ~layouts/header ~layout-app-body → ~shared:layout/app-body ~blog-admin-dashboard → ~admin/dashboard 209 files, 4,941 replacements across all services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
555 lines
26 KiB
Plaintext
555 lines
26 KiB
Plaintext
;; Events form components — entry edit, slot add/edit, entry add,
|
|
;; ticket type add/edit, add buttons, post search results.
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Slot picker option (shared by entry-edit and entry-add)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/slot-option (&key value data-start data-end data-flexible data-cost selected label)
|
|
(option :value value :data-start data-start :data-end data-end
|
|
:data-flexible data-flexible :data-cost data-cost
|
|
:selected selected
|
|
label))
|
|
|
|
(defcomp ~forms/slot-picker (&key id options)
|
|
(select :id id :name "slot_id" :class "w-full border p-2 rounded"
|
|
:data-slot-picker "" :required "required"
|
|
options))
|
|
|
|
(defcomp ~forms/no-slots ()
|
|
(div :class "text-sm text-stone-500" "No slots defined for this day."))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Entry edit form (_types/entry/_edit.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/entry-edit-form (&key entry-id list-container put-url cancel-url csrf
|
|
name-val slot-picker
|
|
start-val end-val cost-display
|
|
ticket-price-val ticket-count-val
|
|
action-btn cancel-btn)
|
|
(section :id (str "entry-" entry-id) :class list-container
|
|
(div :id (str "entry-errors-" entry-id) :class "mt-2 text-sm text-red-600")
|
|
(form :class "space-y-3 mt-4" :sx-put put-url
|
|
:sx-target (str "#entry-" entry-id) :sx-swap "outerHTML"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
|
|
;; Name
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "entry-name-" entry-id) "Name")
|
|
(input :id (str "entry-name-" entry-id) :name "name"
|
|
:class "w-full border p-2 rounded" :placeholder "Name"
|
|
:value name-val))
|
|
|
|
;; Slot picker
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "entry-slot-" entry-id) "Slot")
|
|
slot-picker)
|
|
|
|
;; Time inputs (flexible slots)
|
|
(div :data-time-fields "" :class "hidden space-y-3"
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "entry-start-" entry-id) "From")
|
|
(input :id (str "entry-start-" entry-id) :name "start_at" :type "time"
|
|
:class "w-full border p-2 rounded" :value start-val
|
|
:data-entry-start ""))
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "entry-end-" entry-id) "To")
|
|
(input :id (str "entry-end-" entry-id) :name "end_at" :type "time"
|
|
:class "w-full border p-2 rounded" :value end-val
|
|
:data-entry-end ""))
|
|
(p :class "text-xs text-stone-500" :data-slot-boundary ""))
|
|
|
|
;; Fixed time summary
|
|
(div :data-fixed-summary "" :class "hidden text-sm text-stone-600")
|
|
|
|
;; Cost display
|
|
(div :data-cost-row "" :class "hidden text-sm font-medium text-stone-700"
|
|
"Estimated Cost: "
|
|
(span :data-cost-display "" :class "text-green-600" cost-display))
|
|
|
|
;; Ticket Configuration
|
|
(div :class "border-t pt-3 mt-3"
|
|
(h4 :class "text-sm font-semibold text-stone-700 mb-3" "Ticket Configuration")
|
|
(div :class "grid grid-cols-2 gap-3"
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "entry-ticket-price-" entry-id)
|
|
"Ticket Price (£)")
|
|
(input :id (str "entry-ticket-price-" entry-id) :name "ticket_price"
|
|
:type "number" :step "0.01" :min "0"
|
|
:class "w-full border p-2 rounded"
|
|
:placeholder "Leave empty for no tickets"
|
|
:value ticket-price-val)
|
|
(p :class "text-xs text-stone-500 mt-1" "Leave empty if no tickets needed"))
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "entry-ticket-count-" entry-id) "Total Tickets")
|
|
(input :id (str "entry-ticket-count-" entry-id) :name "ticket_count"
|
|
:type "number" :min "0"
|
|
:class "w-full border p-2 rounded"
|
|
:placeholder "Leave empty for unlimited"
|
|
:value ticket-count-val)
|
|
(p :class "text-xs text-stone-500 mt-1" "Leave empty for unlimited"))))
|
|
|
|
;; Buttons
|
|
(div :class "flex justify-end gap-2 pt-2"
|
|
(button :type "button" :class cancel-btn
|
|
:sx-get cancel-url
|
|
:sx-target (str "#entry-" entry-id) :sx-swap "outerHTML"
|
|
"Cancel")
|
|
(button :type "submit" :class action-btn
|
|
:data-confirm "true" :data-confirm-title "Save entry?"
|
|
:data-confirm-text "Are you sure you want to save this entry?"
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, save it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
(i :class "fa fa-save") " Save entry")))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Post search results (_types/entry/_post_search_results.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/post-search-item (&key post-url entry-id csrf post-id
|
|
img title)
|
|
(form :sx-post post-url :sx-target (str "#entry-posts-" entry-id) :sx-swap "innerHTML"
|
|
:class "p-2 hover:bg-stone-50 cursor-pointer rounded text-sm border-b"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
(input :type "hidden" :name "post_id" :value post-id)
|
|
(button :type "submit" :class "w-full text-left flex items-center gap-2"
|
|
:data-confirm "" :data-confirm-title "Add post?"
|
|
:data-confirm-text (str "Add " title " to this entry?")
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, add it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
img (span title))))
|
|
|
|
(defcomp ~forms/post-search-sentinel (&key page next-url)
|
|
(div :id (str "post-search-sentinel-" page)
|
|
:sx-get next-url
|
|
:sx-trigger "intersect once delay:250ms, sentinel:retry"
|
|
:sx-swap "outerHTML"
|
|
:_ "
|
|
init
|
|
if not me.dataset.retryMs then set me.dataset.retryMs to 1000 end
|
|
|
|
on sentinel:retry
|
|
remove .hidden from .js-loading in me
|
|
add .hidden to .js-neterr in me
|
|
set me.style.pointerEvents to 'none'
|
|
set me.style.opacity to '0'
|
|
trigger htmx:consume on me
|
|
call htmx.trigger(me, 'intersect')
|
|
end
|
|
|
|
def backoff()
|
|
add .hidden to .js-loading in me
|
|
remove .hidden from .js-neterr in me
|
|
set myMs to Number(me.dataset.retryMs)
|
|
if myMs < 10000 then set me.dataset.retryMs to myMs * 2 end
|
|
js setTimeout(() => htmx.trigger(me, 'sentinel:retry'), myMs)
|
|
end
|
|
|
|
on htmx:beforeRequest
|
|
set me.style.pointerEvents to 'none'
|
|
set me.style.opacity to '0'
|
|
end
|
|
|
|
on htmx:afterSwap
|
|
set me.dataset.retryMs to 1000
|
|
end
|
|
|
|
on htmx:sendError call backoff()
|
|
on htmx:responseError call backoff()
|
|
on htmx:timeout call backoff()
|
|
"
|
|
:role "status" :aria-live "polite" :aria-hidden "true" :class "py-2"
|
|
(div :class "text-xs text-center text-stone-400 js-loading" "Loading more...")
|
|
(div :class "text-xs text-center text-stone-400 js-neterr hidden" "Connection error. Retrying...")))
|
|
|
|
(defcomp ~forms/post-search-end ()
|
|
(div :class "py-2 text-xs text-center text-stone-400" "End of results"))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Slot edit form (_types/slot/_edit.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/day-checkbox (&key name label checked)
|
|
(label :class "flex items-center gap-1 px-2 py-1 rounded-full bg-slate-100"
|
|
(input :type "checkbox" :name name :value "1" :data-day name :checked checked)
|
|
(span label)))
|
|
|
|
(defcomp ~forms/day-all-checkbox (&key checked)
|
|
(label :class "flex items-center gap-1 px-2 py-1 rounded-full bg-slate-200"
|
|
(input :type "checkbox" :data-day-all "" :checked checked)
|
|
(span "All")))
|
|
|
|
(defcomp ~forms/slot-edit-form (&key slot-id list-container put-url cancel-url csrf
|
|
name-val cost-val start-val end-val desc-val
|
|
days flexible-checked
|
|
action-btn cancel-btn)
|
|
(section :id (str "slot-" slot-id) :class list-container
|
|
(div :id "slot-errors" :class "mt-2 text-sm text-red-600")
|
|
(form :class "space-y-3 mt-4" :sx-put put-url
|
|
:sx-target (str "#slot-" slot-id) :sx-swap "outerHTML"
|
|
:sx-on:afterRequest "if (event.detail.successful) this.reset()"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
|
|
;; Name
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "slot-name-" slot-id) "Name")
|
|
(input :id (str "slot-name-" slot-id) :name "name" :placeholder "Name"
|
|
:class "w-full border p-2 rounded" :value name-val))
|
|
|
|
;; Cost
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "slot-cost-" slot-id) "Cost")
|
|
(input :id (str "slot-cost-" slot-id) :name "cost" :placeholder "Cost e.g. 12.50"
|
|
:class "w-full border p-2 rounded" :value cost-val))
|
|
|
|
;; Start time
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "slot-start-" slot-id) "Start time")
|
|
(input :id (str "slot-start-" slot-id) :name "time_start" :placeholder "Start HH:MM"
|
|
:class "w-full border p-2 rounded" :value start-val))
|
|
|
|
;; End time
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "slot-end-" slot-id) "End time")
|
|
(input :id (str "slot-end-" slot-id) :name "time_end" :placeholder "End HH:MM"
|
|
:class "w-full border p-2 rounded" :value end-val))
|
|
|
|
;; Description
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "slot-desc-" slot-id) "Description")
|
|
(textarea :id (str "slot-desc-" slot-id) :name "description" :rows "2"
|
|
:placeholder "Description" :class "w-full border p-2 rounded"
|
|
desc-val))
|
|
|
|
;; Days
|
|
(div
|
|
(span :class "block text-sm font-medium text-stone-700 mb-1" "Days")
|
|
(div :class "flex flex-wrap gap-3 items-center text-sm" :data-days-group ""
|
|
days))
|
|
|
|
;; Flexible
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "slot-flexible-" slot-id) "Flexible booking")
|
|
(label :class "inline-flex items-center gap-2 text-xs"
|
|
(input :id (str "slot-flexible-" slot-id) :type "checkbox" :name "flexible"
|
|
:value "1" :checked flexible-checked)
|
|
(span "Allow bookings at any time within this band")))
|
|
|
|
;; Buttons
|
|
(div :class "flex justify-end gap-2 pt-2"
|
|
(button :type "button" :class cancel-btn
|
|
:sx-get cancel-url
|
|
:sx-target (str "#slot-" slot-id) :sx-swap "outerHTML"
|
|
"Cancel")
|
|
(button :type "submit" :class action-btn
|
|
:data-confirm "true" :data-confirm-title "Save slot?"
|
|
:data-confirm-text "Are you sure you want to save this slot?"
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, save it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
(i :class "fa fa-save") " Save slot")))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Slot add form (_types/slots/_add.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/slot-add-form (&key post-url csrf days action-btn cancel-btn cancel-url)
|
|
(form :sx-post post-url :sx-target "#slots-table" :sx-select "#slots-table"
|
|
:sx-disinherit "sx-select" :sx-swap "outerHTML"
|
|
:sx-headers csrf :class "space-y-3"
|
|
(div :class "grid grid-cols-1 md:grid-cols-4 gap-3"
|
|
(div :class "md:col-span-2"
|
|
(label :class "block text-xs font-semibold mb-1" "Name")
|
|
(input :type "text" :name "name" :class "w-full border rounded px-2 py-1 text-sm" :required "required"))
|
|
(div :class "md:col-span-2"
|
|
(label :class "block text-xs font-semibold mb-1" "Description")
|
|
(input :type "text" :name "description" :class "w-full border rounded px-2 py-1 text-sm"))
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Days")
|
|
(div :class "flex flex-wrap gap-1 text-xs" :data-days-group ""
|
|
days))
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Time start")
|
|
(input :type "time" :name "time_start" :class "w-full border rounded px-2 py-1 text-sm" :required "required"))
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Time end")
|
|
(input :type "time" :name "time_end" :class "w-full border rounded px-2 py-1 text-sm" :required "required"))
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Cost")
|
|
(input :type "text" :name "cost" :class "w-full border rounded px-2 py-1 text-sm" :placeholder "e.g. 5.00"))
|
|
(div :class "md:col-span-2"
|
|
(label :class "block text-xs font-semibold mb-1" "Flexible booking")
|
|
(label :class "inline-flex items-center gap-2 text-xs"
|
|
(input :type "checkbox" :name "flexible" :value "1")
|
|
(span "Allow bookings at any time within this band"))))
|
|
(div :class "flex justify-end gap-2 pt-2"
|
|
(button :type "button" :class cancel-btn
|
|
:sx-get cancel-url :sx-target "#slot-add-container" :sx-swap "innerHTML"
|
|
"Cancel")
|
|
(button :type "submit" :class action-btn
|
|
:data-confirm "true" :data-confirm-title "Add slot?"
|
|
:data-confirm-text "Are you sure you want to add this slot?"
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, add it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
(i :class "fa fa-save") " Save slot"))))
|
|
|
|
(defcomp ~forms/slot-add-button (&key pre-action add-url)
|
|
(button :type "button" :class pre-action
|
|
:sx-get add-url :sx-target "#slot-add-container" :sx-swap "innerHTML"
|
|
"+ Add slot"))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Composition defcomps — receive data, compose form trees
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
;; Day checkboxes from data — replaces Python loop
|
|
(defcomp ~forms/day-checkboxes-from-data (&key days-data all-checked)
|
|
(<>
|
|
(~forms/day-all-checkbox :checked (when all-checked "checked"))
|
|
(map (lambda (d)
|
|
(~forms/day-checkbox
|
|
:name (get d "name")
|
|
:label (get d "label")
|
|
:checked (when (get d "checked") "checked")))
|
|
(or days-data (list)))))
|
|
|
|
;; Slot options from data — replaces _slot_options_html Python loop
|
|
(defcomp ~forms/slot-options-from-data (&key slots)
|
|
(<> (map (lambda (s)
|
|
(~forms/slot-option
|
|
:value (get s "value")
|
|
:data-start (get s "data-start")
|
|
:data-end (get s "data-end")
|
|
:data-flexible (get s "data-flexible")
|
|
:data-cost (get s "data-cost")
|
|
:selected (get s "selected")
|
|
:label (get s "label")))
|
|
(or slots (list)))))
|
|
|
|
;; Slot picker from data — wraps picker + options
|
|
(defcomp ~forms/slot-picker-from-data (&key id slots)
|
|
(if (empty? (or slots (list)))
|
|
(~forms/no-slots)
|
|
(~forms/slot-picker
|
|
:id id
|
|
:options (~forms/slot-options-from-data :slots slots))))
|
|
|
|
;; Slot edit form from data
|
|
(defcomp ~forms/slot-edit-form-from-data (&key slot-id list-container put-url cancel-url csrf
|
|
name-val cost-val start-val end-val desc-val
|
|
days-data all-checked flexible-checked
|
|
action-btn cancel-btn)
|
|
(~forms/slot-edit-form
|
|
:slot-id slot-id :list-container list-container
|
|
:put-url put-url :cancel-url cancel-url :csrf csrf
|
|
:name-val name-val :cost-val cost-val :start-val start-val
|
|
:end-val end-val :desc-val desc-val
|
|
:days (~forms/day-checkboxes-from-data :days-data days-data :all-checked all-checked)
|
|
:flexible-checked flexible-checked
|
|
:action-btn action-btn :cancel-btn cancel-btn))
|
|
|
|
;; Slot add form from data
|
|
(defcomp ~forms/slot-add-form-from-data (&key post-url csrf days-data action-btn cancel-btn cancel-url)
|
|
(~forms/slot-add-form
|
|
:post-url post-url :csrf csrf
|
|
:days (~forms/day-checkboxes-from-data :days-data days-data)
|
|
:action-btn action-btn :cancel-btn cancel-btn :cancel-url cancel-url))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Entry add form (_types/day/_add.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/entry-add-form (&key post-url csrf slot-picker
|
|
action-btn cancel-btn cancel-url)
|
|
(<>
|
|
(div :id "entry-errors" :class "mt-2 text-sm text-red-600")
|
|
(form :class "mt-4 grid grid-cols-1 md:grid-cols-4 gap-2"
|
|
:sx-post post-url :sx-target "#day-entries"
|
|
:sx-on:afterRequest "if (event.detail.successful) this.reset()"
|
|
:sx-swap "innerHTML"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
|
|
;; Entry name
|
|
(input :name "name" :type "text" :required "required"
|
|
:class "border rounded px-3 py-2" :placeholder "Entry name")
|
|
|
|
;; Slot picker
|
|
slot-picker
|
|
|
|
;; Time entry + cost display
|
|
(div :class "md:col-span-2 flex flex-col gap-2"
|
|
;; Time inputs (flexible)
|
|
(div :data-time-fields "" :class "hidden"
|
|
(div :class "mb-2"
|
|
(label :class "block text-xs font-medium text-stone-700 mb-1" "From")
|
|
(input :name "start_time" :type "time" :class "border rounded px-3 py-2 w-full"
|
|
:data-entry-start ""))
|
|
(div :class "mb-2"
|
|
(label :class "block text-xs font-medium text-stone-700 mb-1" "To")
|
|
(input :name "end_time" :type "time" :class "border rounded px-3 py-2 w-full"
|
|
:data-entry-end ""))
|
|
(p :class "text-xs text-stone-500" :data-slot-boundary ""))
|
|
;; Cost display
|
|
(div :data-cost-row "" :class "hidden text-sm font-medium text-stone-700"
|
|
"Estimated Cost: "
|
|
(span :data-cost-display "" :class "text-green-600" "£0.00"))
|
|
;; Fixed summary
|
|
(div :data-fixed-summary "" :class "hidden text-sm text-stone-600"))
|
|
|
|
;; Ticket Configuration
|
|
(div :class "md:col-span-4 border-t pt-3 mt-2"
|
|
(h4 :class "text-sm font-semibold text-stone-700 mb-3" "Ticket Configuration (Optional)")
|
|
(div :class "grid grid-cols-2 gap-3"
|
|
(div
|
|
(label :class "block text-xs font-medium text-stone-700 mb-1"
|
|
"Ticket Price (£)")
|
|
(input :name "ticket_price" :type "number" :step "0.01" :min "0"
|
|
:class "w-full border rounded px-3 py-2 text-sm"
|
|
:placeholder "Leave empty for no tickets"))
|
|
(div
|
|
(label :class "block text-xs font-medium text-stone-700 mb-1" "Total Tickets")
|
|
(input :name "ticket_count" :type "number" :min "0"
|
|
:class "w-full border rounded px-3 py-2 text-sm"
|
|
:placeholder "Leave empty for unlimited"))))
|
|
|
|
;; Buttons
|
|
(div :class "flex justify-end gap-2 pt-2 md:col-span-4"
|
|
(button :type "button" :class cancel-btn
|
|
:sx-get cancel-url :sx-target "#entry-add-container" :sx-swap "innerHTML"
|
|
"Cancel")
|
|
(button :type "submit" :class action-btn
|
|
:data-confirm "true" :data-confirm-title "Add entry?"
|
|
:data-confirm-text "Are you sure you want to add this entry?"
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, add it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
(i :class "fa fa-save") " Save entry")))))
|
|
|
|
(defcomp ~forms/entry-add-button (&key pre-action add-url)
|
|
(button :type "button" :class pre-action
|
|
:sx-get add-url :sx-target "#entry-add-container" :sx-swap "innerHTML"
|
|
"+ Add entry"))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Ticket type edit form (_types/ticket_type/_edit.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/ticket-type-edit-form (&key ticket-id list-container put-url cancel-url csrf
|
|
name-val cost-val count-val
|
|
action-btn cancel-btn)
|
|
(section :id (str "ticket-" ticket-id) :class list-container
|
|
(div :id "ticket-errors" :class "mt-2 text-sm text-red-600")
|
|
(form :class "space-y-3 mt-4" :sx-put put-url
|
|
:sx-target (str "#ticket-" ticket-id) :sx-swap "outerHTML"
|
|
:sx-on:afterRequest "if (event.detail.successful) this.reset()"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
|
|
;; Name
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "ticket-name-" ticket-id) "Name")
|
|
(input :id (str "ticket-name-" ticket-id) :name "name"
|
|
:placeholder "e.g. Adult, Child, Student"
|
|
:class "w-full border p-2 rounded" :value name-val :required "required"))
|
|
|
|
;; Cost
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "ticket-cost-" ticket-id) "Cost (£)")
|
|
(input :id (str "ticket-cost-" ticket-id) :name "cost" :type "number"
|
|
:step "0.01" :min "0" :placeholder "e.g. 5.00"
|
|
:class "w-full border p-2 rounded" :value cost-val :required "required"))
|
|
|
|
;; Count
|
|
(div
|
|
(label :class "block text-sm font-medium text-stone-700 mb-1"
|
|
:for (str "ticket-count-" ticket-id) "Count")
|
|
(input :id (str "ticket-count-" ticket-id) :name "count" :type "number"
|
|
:min "0" :placeholder "e.g. 50"
|
|
:class "w-full border p-2 rounded" :value count-val :required "required"))
|
|
|
|
;; Buttons
|
|
(div :class "flex justify-end gap-2 pt-2"
|
|
(button :type "button" :class cancel-btn
|
|
:sx-get cancel-url
|
|
:sx-target (str "#ticket-" ticket-id) :sx-swap "outerHTML"
|
|
"Cancel")
|
|
(button :type "submit" :class action-btn
|
|
:data-confirm "true" :data-confirm-title "Save ticket type?"
|
|
:data-confirm-text "Are you sure you want to save this ticket type?"
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, save it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
(i :class "fa fa-save") " Save ticket type")))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Ticket type add form (_types/ticket_types/_add.html)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/ticket-type-add-form (&key post-url csrf action-btn cancel-btn cancel-url)
|
|
(form :sx-post post-url :sx-target "#tickets-table" :sx-select "#tickets-table"
|
|
:sx-disinherit "sx-select" :sx-swap "outerHTML"
|
|
:sx-headers csrf :class "space-y-3"
|
|
(div :class "grid grid-cols-1 md:grid-cols-3 gap-3"
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Name")
|
|
(input :type "text" :name "name" :class "w-full border rounded px-2 py-1 text-sm"
|
|
:placeholder "e.g. Adult, Child, Student" :required "required"))
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Cost (£)")
|
|
(input :type "number" :name "cost" :step "0.01" :min "0"
|
|
:class "w-full border rounded px-2 py-1 text-sm"
|
|
:placeholder "e.g. 5.00" :required "required"))
|
|
(div
|
|
(label :class "block text-xs font-semibold mb-1" "Count")
|
|
(input :type "number" :name "count" :min "0"
|
|
:class "w-full border rounded px-2 py-1 text-sm"
|
|
:placeholder "e.g. 50" :required "required")))
|
|
(div :class "flex justify-end gap-2 pt-2"
|
|
(button :type "button" :class cancel-btn
|
|
:sx-get cancel-url :sx-target "#ticket-add-container" :sx-swap "innerHTML"
|
|
"Cancel")
|
|
(button :type "submit" :class action-btn
|
|
:data-confirm "true" :data-confirm-title "Add ticket type?"
|
|
:data-confirm-text "Are you sure you want to add this ticket type?"
|
|
:data-confirm-icon "question"
|
|
:data-confirm-confirm-text "Yes, add it"
|
|
:data-confirm-cancel-text "Cancel"
|
|
(i :class "fa fa-save") " Save ticket type"))))
|
|
|
|
(defcomp ~forms/ticket-type-add-button (&key action-btn add-url)
|
|
(button :class action-btn
|
|
:sx-get add-url :sx-target "#ticket-add-container" :sx-swap "innerHTML"
|
|
(i :class "fa fa-plus") " Add ticket type"))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Entry admin nav — placeholder
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~forms/admin-placeholder-nav ()
|
|
(div :class "relative nav-group"
|
|
(span :class "block px-3 py-2 text-stone-400 text-sm italic" "Admin options"))) |