diff --git a/resources/css/input.css b/resources/css/input.css index f1d8c73..1009c3e 100644 --- a/resources/css/input.css +++ b/resources/css/input.css @@ -1 +1,2 @@ @import "tailwindcss"; +@source "../../src/**/*.clj"; diff --git a/src/clj/pmagnus/elprice/web/routes/ui.clj b/src/clj/pmagnus/elprice/web/routes/ui.clj index a3dfedc..b0c0b18 100644 --- a/src/clj/pmagnus/elprice/web/routes/ui.clj +++ b/src/clj/pmagnus/elprice/web/routes/ui.clj @@ -14,13 +14,15 @@ (def ^:private hour-fmt (DateTimeFormatter/ofPattern "HH:00")) +(defn- ->ldt [time-dk] + (cond + (instance? LocalDateTime time-dk) time-dk + (instance? java.sql.Timestamp time-dk) + (.toLocalDateTime ^java.sql.Timestamp time-dk) + :else (LocalDateTime/parse (str time-dk)))) + (defn- format-hour [time-dk] - (let [ldt (cond - (instance? LocalDateTime time-dk) time-dk - (instance? java.sql.Timestamp time-dk) - (.toLocalDateTime ^java.sql.Timestamp time-dk) - :else (LocalDateTime/parse (str time-dk)))] - (.format ldt hour-fmt))) + (.format (->ldt time-dk) hour-fmt)) (defn- dkk-mwh->ore-kwh "Convert DKK/MWh to øre/kWh (divide by 10)." @@ -28,24 +30,43 @@ (when price-dkk (/ (double price-dkk) 10.0))) +(defn- format-ore [price-dkk] + (if-let [ore (dkk-mwh->ore-kwh price-dkk)] + (format "%.1f" ore) + "-")) + +(defn- group-by-hour [prices] + (->> prices + (group-by #(.getHour (->ldt (:time_dk %)))) + (sort-by key))) + +(defn- avg-ore [quarters] + (let [vals (keep :price_dkk quarters)] + (when (seq vals) + (/ (reduce + 0.0 (map double vals)) (count vals) 10.0)))) + +(defn- hour-block [hour quarters] + (let [sorted (sort-by #(.getMinute (->ldt (:time_dk %))) quarters) + avg (avg-ore quarters)] + [:details {:class "border-2 border-blue-400 rounded-lg mb-2" :open true} + [:summary {:class "flex justify-between py-2 px-3 cursor-pointer hover:bg-gray-50 + font-medium text-gray-900"} + [:span (format "%02d:00" hour)] + [:span {:class "font-mono"} + (if avg (format "%.1f" avg) "-")]] + [:div {:class "border-t-2 border-blue-400 px-3 pb-2"} + (for [q sorted] + [:div {:class "flex justify-between py-1 ml-4 text-gray-500"} + [:span (format "%02d:%02d" hour (.getMinute (->ldt (:time_dk q))))] + [:span {:class "font-mono"} (format-ore (:price_dkk q))]])]])) + (defn- price-table [title prices] [:div {:class "mt-6"} - [:h2 {:class "text-lg font-semibold text-gray-900"} title] + [:h2 {:class "text-lg font-semibold text-gray-900 mb-2"} title] (if (seq prices) - [:table {:class "mt-2 w-full text-sm"} - [:thead - [:tr {:class "border-b border-gray-200 text-left text-gray-500"} - [:th {:class "py-2 pr-4"} "Time"] - [:th {:class "py-2 text-right"} "øre/kWh"]]] - [:tbody - (for [p prices] - [:tr {:class "border-b border-gray-100"} - [:td {:class "py-2 pr-4 text-gray-700"} - (format-hour (:time_dk p))] - [:td {:class "py-2 text-right font-mono text-gray-900"} - (if-let [ore (dkk-mwh->ore-kwh (:price_dkk p))] - (format "%.1f" ore) - "-")]])]] + [:div + (for [[hour quarters] (group-by-hour prices)] + (hour-block hour quarters))] [:p {:class "mt-2 text-sm text-gray-400"} "Not available yet"])]) (defn- price-tables [query-fn] @@ -60,7 +81,7 @@ [:h1 {:class "text-2xl font-bold text-gray-900"} "Electricity Prices"] [:p {:class "mt-1 text-sm text-gray-500"} - "Day-ahead spot prices for DK1 in øre/kWh"] + "Day-ahead spot prices for DK1 (øre/kWh)"] [: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"