🌐️ pmme-ui: Refactor admin/sensors split into single core module
- Merged admin.cljs and sensors.cljs into core.cljs to consolidate UI logic - Added WiFi network management interface with active/inactive status indicators - Improved admin panel with async status checking and visual feedback - Updated shadow-cljs config to use port 3000 and simplified mobile build - Enhanced CSS with utility layer for badge-error class
This commit is contained in:
parent
3143f3b37f
commit
f850e86b2f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/*Claude-PMME*
|
@ -5,7 +5,7 @@
|
||||
:dependencies
|
||||
[[no.cjohansen/replicant "2025.03.27"]]
|
||||
|
||||
:dev-http {3001 "public"}
|
||||
:dev-http {3000 "public"}
|
||||
|
||||
:builds
|
||||
{:dev {:target :browser
|
||||
@ -23,8 +23,4 @@
|
||||
:output-dir "../pmme-mobile/src/js"
|
||||
:asset-path "/js"
|
||||
:modules {:main {:init-fn pmme-ui.shell/main}}
|
||||
:compiler-options {:optimizations :advanced}
|
||||
:build-hooks [(shadow.resource/copy-resources
|
||||
{:from "public"
|
||||
:to "../pmme-mobile/src"
|
||||
:exclude ["**/*.js"]})]}}}
|
||||
:compiler-options {:optimizations :advanced}}}}
|
||||
|
@ -1,3 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@source "./**/*.{clj,cljs,cljc}";
|
||||
@plugin "daisyui";
|
||||
|
||||
@layer utilities {
|
||||
.badge-error { /* This forces Tailwind to include it */ }
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
(ns pmme-ui.admin
|
||||
(:require [replicant.dom :as r]))
|
||||
|
||||
(defn render-admin-panel [state]
|
||||
[:div.min-h-screen.bg-gradient-to-br.from-slate-50.to-slate-100.flex.items-center.justify-center.p-8
|
||||
[:div.card.bg-white.shadow-2xl.border-0.max-w-2xl.w-full
|
||||
[:div.card-body.p-8
|
||||
[:h2.text-2xl.font-bold.text-slate-800.mb-6.text-center "ESP32 Admin Panel"]
|
||||
|
||||
[:div.space-y-4
|
||||
[:div.form-control
|
||||
[:label.label.w-32.items-center [:span.label-text "WiFi SSID"]]
|
||||
[:input.input.input-bordered {:type "text" :placeholder "Enter WiFi network name"}]]
|
||||
|
||||
[:div.form-control
|
||||
[:label.label.w-32 [:span.label-text "WiFi Password"]]
|
||||
[:input.input.input-bordered {:type "password" :placeholder "Enter WiFi password"}]]
|
||||
|
||||
[:div.form-control
|
||||
[:label.label.w-32 [:span.label-text "Device Name"]]
|
||||
[:input.input.input-bordered {:type "text" :placeholder "Enter device name"}]]
|
||||
|
||||
[:div.flex.gap-2.mt-6
|
||||
[:button.btn.btn-primary.flex-1 "Provision Device"]
|
||||
[:button.btn.btn-outline.flex-1 "Reset Device"]]]]]])
|
@ -1,7 +1,84 @@
|
||||
(ns pmme-ui.core
|
||||
(:require [replicant.dom :as r]
|
||||
[pmme-ui.sensors :as sensors]
|
||||
[pmme-ui.admin :as admin]))
|
||||
(:require [replicant.dom :as r]))
|
||||
|
||||
(defn render-pm25-display [state]
|
||||
[:div.min-h-screen.bg-gradient-to-br.from-slate-50.to-slate-100.flex.items-center.justify-center.p-8
|
||||
[:div.card.bg-white.shadow-2xl.border-0.max-w-2xl.w-full
|
||||
[:div.card-body.text-center.p-12
|
||||
[:div.mb-6
|
||||
[:h1.text-2xl.font-light.text-slate-600.mb-2 "PME"]
|
||||
[:div.text-sm.text-slate-400.uppercase.tracking-wide.font-medium "PM 2.5 Level"]]
|
||||
|
||||
[:div.text-center.mb-8
|
||||
[:div.relative.inline-block
|
||||
[:span.text-8xl.md:text-9xl.font-black.text-slate-800.leading-none.tracking-tight
|
||||
(:pm25 state "250")]
|
||||
[:span.absolute.top-0.-right-20.text-2xl.text-slate-400.font-light "μg/m³"]]]
|
||||
|
||||
[:div.mt-4
|
||||
(let [pm25-val (js/parseInt (:pm25 state "0"))
|
||||
status (cond
|
||||
(<= pm25-val 12) {:text "Good" :color "text-success" :bg "bg-success/10"}
|
||||
(<= pm25-val 35) {:text "Moderate" :color "text-warning" :bg "bg-warning/10"}
|
||||
:else {:text "Unhealthy" :color "text-error" :bg "bg-error/10"})]
|
||||
[:div.badge.badge-lg.px-6.py-3.text-base.font-semibold
|
||||
{:class [(:bg status) (:color status)]}
|
||||
(:text status)])]
|
||||
]]])
|
||||
|
||||
(defn render-wifi-table [wifi-networks]
|
||||
[:div.min-h-screen.bg-gradient-to-br.from-slate-50.to-slate-100.flex.items-center.justify-center.p-8
|
||||
[:div.card.bg-white.shadow-2xl.border-0.max-w-2xl.w-full
|
||||
[:div.card-body.p-8
|
||||
[:h2.text-2xl.font-bold.text-slate-800.mb-6.text-center "Saved WiFi Networks"]
|
||||
[:div.overflow-x-auto
|
||||
[:table.table.w-full
|
||||
[:thead
|
||||
[:tr
|
||||
[:th.w-full "SSID"]
|
||||
[:th "Actions"]]]
|
||||
[:tbody
|
||||
(for [wifi wifi-networks]
|
||||
[:tr {:key (:ssid wifi)}
|
||||
[:td.font-bold (:ssid wifi)]
|
||||
[:td
|
||||
[:div.flex.gap-2
|
||||
(if (:active wifi)
|
||||
[:button.btn.btn-success.btn-sm.w-32.text-black.font-normal "Active"]
|
||||
[:button.btn.btn-info.btn-sm.w-32.text-black.font-normal "Make Active"])
|
||||
[:button.btn.btn-error.btn-sm.flex-1.font-normal "Delete"]]]])]]]
|
||||
[:btn.btn.btn-accent.mt-2 {:on {:click ::add-wifi}} "+ Add Wifi"]]]])
|
||||
|
||||
(defn render-admin-panel [state]
|
||||
(render-wifi-table [{:ssid "The Brick" :active true} {:ssid "Home" :active false}])
|
||||
)
|
||||
|
||||
;; [:div.min-h-screen.bg-gradient-to-br.from-slate-50.to-slate-100.flex.items-center.justify-center.p-8
|
||||
;; [:div.card.bg-white.shadow-2xl.border-0.max-w-2xl.w-full
|
||||
;; [:div.card-body.p-8
|
||||
;; [:h2.text-2xl.font-bold.text-slate-800.mb-6.text-center "ESP32 Admin Panel"]
|
||||
|
||||
;; [:div.space-y-4
|
||||
;; [:div.form-control
|
||||
;; [:label.label.w-32.items-center [:span.label-text "WiFi SSID"]]
|
||||
;; [:input.input.input-bordered {:type "text" :placeholder "Enter WiFi network name"}]]
|
||||
|
||||
;; [:div.form-control
|
||||
;; [:label.label.w-32 [:span.label-text "WiFi Password"]]
|
||||
;; [:input.input.input-bordered {:type "password" :placeholder "Enter WiFi password"}]]
|
||||
|
||||
;; [:div.form-control
|
||||
;; [:label.label.w-32 [:span.label-text "Device Name"]]
|
||||
;; [:input.input.input-bordered {:type "text" :placeholder "Enter device name"}]]
|
||||
|
||||
;; [:div.flex.gap-2.mt-6
|
||||
;; [:button.btn.btn-primary.flex-1 "Provision Device"]
|
||||
;; [:button.btn.btn-outline.flex-1 "Reset Device"]]]]]]
|
||||
|
||||
(defn render-current-content [state]
|
||||
(case (:current-tab state)
|
||||
:admin (render-admin-panel state)
|
||||
:readings (render-pm25-display state)))
|
||||
|
||||
(defn render-tabs [state]
|
||||
(when (:show-tabs state)
|
||||
@ -14,17 +91,20 @@
|
||||
[:button.tab
|
||||
{:class (when (= (:current-tab state) :admin) "tab-active")
|
||||
:on {:click [::switch-tab :admin]}}
|
||||
"Admin Panel"]]]))
|
||||
|
||||
(defn render-current-content [state]
|
||||
(case (:current-tab state)
|
||||
:admin (admin/render-admin-panel state)
|
||||
:readings (sensors/render-pm25-display state)))
|
||||
"Admin Panel"
|
||||
(case (:admin-status state)
|
||||
::available
|
||||
[:div.ml-2 {:class ["inline-grid" "*:[grid-area:1/1]"]}
|
||||
[:div.status.status-success.animate-ping]
|
||||
[:div.status.status-success]]
|
||||
::unavailable
|
||||
[:div.status.status-error.ml-2]
|
||||
::unknown
|
||||
[:div.status.status-neutral.ml-2])]]]))
|
||||
|
||||
(defn render-ui [state]
|
||||
(r/render
|
||||
js/document.body
|
||||
[:div
|
||||
(render-tabs state)
|
||||
(render-current-content state)]))
|
||||
|
||||
[:div
|
||||
(render-tabs state)
|
||||
(render-current-content state)]))
|
||||
|
@ -1,27 +0,0 @@
|
||||
(ns pmme-ui.sensors
|
||||
(:require [replicant.dom :as r]))
|
||||
|
||||
(defn render-pm25-display [state]
|
||||
[:div.min-h-screen.bg-gradient-to-br.from-slate-50.to-slate-100.flex.items-center.justify-center.p-8
|
||||
[:div.card.bg-white.shadow-2xl.border-0.max-w-2xl.w-full
|
||||
[:div.card-body.text-center.p-12
|
||||
[:div.mb-6
|
||||
[:h1.text-2xl.font-light.text-slate-600.mb-2 "PME"]
|
||||
[:div.text-sm.text-slate-400.uppercase.tracking-wide.font-medium "PM 2.5 Level"]]
|
||||
|
||||
[:div.text-center.mb-8
|
||||
[:div.relative.inline-block
|
||||
[:span.text-8xl.md:text-9xl.font-black.text-slate-800.leading-none.tracking-tight
|
||||
(:pm25 state "250")]
|
||||
[:span.absolute.top-0.-right-20.text-2xl.text-slate-400.font-light "μg/m³"]]]
|
||||
|
||||
[:div.mt-4
|
||||
(let [pm25-val (js/parseInt (:pm25 state "0"))
|
||||
status (cond
|
||||
(<= pm25-val 12) {:text "Good" :color "text-success" :bg "bg-success/10"}
|
||||
(<= pm25-val 35) {:text "Moderate" :color "text-warning" :bg "bg-warning/10"}
|
||||
:else {:text "Unhealthy" :color "text-error" :bg "bg-error/10"})]
|
||||
[:div.badge.badge-lg.px-6.py-3.text-base.font-semibold
|
||||
{:class [(:bg status) (:color status)]}
|
||||
(:text status)])]
|
||||
]]])
|
@ -1,12 +1,21 @@
|
||||
(ns pmme-ui.shell
|
||||
(:require [replicant.dom :as r]
|
||||
[pmme-ui.core :as core]))
|
||||
[pmme-ui.core :as core]
|
||||
[cljs.core.async :as async :refer [go <!]]
|
||||
[cljs.core.async.interop :refer [<p!]]))
|
||||
|
||||
(defonce store (atom {:number 0
|
||||
:current-tab :readings}))
|
||||
:current-tab :readings
|
||||
:show-tabs true
|
||||
:admin-status ::core/unknown}))
|
||||
|
||||
(defn should-show-tabs? []
|
||||
true)
|
||||
(defn ping-admin-endpoint []
|
||||
(go
|
||||
(try
|
||||
(<p! (js/fetch "http://localhost:8080"))
|
||||
true
|
||||
(catch js/Error e
|
||||
false))))
|
||||
|
||||
(defn init [store]
|
||||
(add-watch store ::render (fn [_ _ _ new-state] (core/render-ui new-state)))
|
||||
@ -14,12 +23,18 @@
|
||||
(fn [_ event-data]
|
||||
(let [[action & args] event-data]
|
||||
(case action
|
||||
::core/test-me
|
||||
(swap! store update :number inc)
|
||||
|
||||
::core/switch-tab
|
||||
(swap! store assoc :current-tab (first args))))))
|
||||
(swap! store assoc :show-tabs (should-show-tabs?))
|
||||
(let [target-tab (first args)]
|
||||
(if (= target-tab :admin)
|
||||
(go
|
||||
;; (let [admin-available? (<! (ping-admin-endpoint))]
|
||||
(let [admin-available? true]
|
||||
(if admin-available?
|
||||
(do
|
||||
(swap! store assoc :current-tab :admin)
|
||||
(swap! store assoc :admin-status ::core/available))
|
||||
(swap! store assoc :admin-status ::core/unavailable))))
|
||||
(swap! store assoc :current-tab target-tab)))))))
|
||||
(swap! store assoc ::loaded-at (.getTime (js/Date.))))
|
||||
|
||||
(defn main []
|
||||
@ -28,4 +43,4 @@
|
||||
|
||||
(defn ^:dev/after-load reload []
|
||||
(init store)
|
||||
(println "Reloaded!"))
|
||||
(println "Reloaded"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user