From 9e78275c83e339f133d0cc7071131e4d44f4f16b Mon Sep 17 00:00:00 2001 From: Per Magnus Petersen Date: Sat, 14 Feb 2026 13:42:52 +0100 Subject: [PATCH] Add Today/Tomorrow tabs for price display HTMX-powered tabs switch between today's and tomorrow's prices without full page reload. Today tab is active by default. Co-Authored-By: Claude Opus 4.6 --- src/clj/pmagnus/elprice/web/routes/ui.clj | 54 ++++++++++++++++++----- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/clj/pmagnus/elprice/web/routes/ui.clj b/src/clj/pmagnus/elprice/web/routes/ui.clj index d1eb156..45dcbd5 100644 --- a/src/clj/pmagnus/elprice/web/routes/ui.clj +++ b/src/clj/pmagnus/elprice/web/routes/ui.clj @@ -69,12 +69,35 @@ (hour-block hour quarters))] [:p {:class "mt-2 text-sm text-gray-400"} "Not available yet"])]) -(defn- price-tables [query-fn] - (let [today (prices/get-today-prices query-fn) - tomorrow (prices/get-tomorrow-prices query-fn)] - [:div {:id "price-tables"} - (price-table "Today — DK1" today) - (price-table "Tomorrow — DK1" tomorrow)])) +(def ^:private tab-base + "px-4 py-2 font-medium text-sm rounded-t-lg border-2 border-b-0 ") + +(def ^:private tab-active + (str tab-base "border-blue-400 bg-white text-gray-900")) + +(def ^:private tab-inactive + (str tab-base "border-gray-200 bg-gray-100 text-gray-500 hover:text-gray-700 cursor-pointer")) + +(defn- tabs [active-tab] + [:div {:class "flex gap-1 mt-4"} + [:button {:class (if (= active-tab :today) tab-active tab-inactive) + :hx-get "/prices/today" + :hx-target "#price-content" + :hx-swap "innerHTML"} + "Today"] + [:button {:class (if (= active-tab :tomorrow) tab-active tab-inactive) + :hx-get "/prices/tomorrow" + :hx-target "#price-content" + :hx-swap "innerHTML"} + "Tomorrow"]]) + +(defn- price-content [tab prices] + (let [title (if (= tab :today) "Today — DK1" "Tomorrow — DK1")] + [:div + (tabs tab) + [:div {:id "price-content" + :class "border-2 border-blue-400 rounded-b-lg rounded-tr-lg p-4 bg-white"} + (price-table title prices)]])) (defn home [query-fn request] (page {:lang "en"} @@ -85,23 +108,34 @@ [:button {:class "mt-4 rounded-lg bg-blue-600 px-4 py-2 text-white font-medium active:bg-blue-700 transition-colors" :hx-post "/prices/fetch" - :hx-target "#price-tables" - :hx-swap "outerHTML" + :hx-target "#price-panel" + :hx-swap "innerHTML" :hx-indicator "#fetch-spinner"} "Refresh Prices"] [:span {:id "fetch-spinner" :class "ml-2 htmx-indicator text-sm text-gray-400"} "Loading..."] - (price-tables query-fn))) + [:div {:id "price-panel"} + (price-content :today (prices/get-today-prices query-fn))])) + +(defn prices-today [query-fn request] + (fragment + (price-table "Today — DK1" (prices/get-today-prices query-fn)))) + +(defn prices-tomorrow [query-fn request] + (fragment + (price-table "Tomorrow — DK1" (prices/get-tomorrow-prices query-fn)))) (defn fetch-prices [query-fn request] (prices/fetch-and-save! query-fn) (fragment - (price-tables query-fn))) + (price-content :today (prices/get-today-prices query-fn)))) ;; Routes (defn ui-routes [{:keys [query-fn]}] [["/" {:get (partial home query-fn)}] + ["/prices/today" {:get (partial prices-today query-fn)}] + ["/prices/tomorrow" {:get (partial prices-tomorrow query-fn)}] ["/prices/fetch" {:post (partial fetch-prices query-fn)}]]) (def route-data