The admin page was the only consumer of Chartist while the dashboard and expense report already use Chart.js. Convert the admin "Growth in clients" (bar) and "Changes by hour" (line) charts to Chart.js using the same Alpine x-data/x-init canvas pattern as the dashboard, and drop the global Chartist CSS/JS includes from the base page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
6.2 KiB
Clojure
125 lines
6.2 KiB
Clojure
(ns auto-ap.ssr.admin
|
|
(:require
|
|
[auto-ap.datomic :refer [conn]]
|
|
[auto-ap.routes.utils
|
|
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
|
[auto-ap.ssr-routes :as ssr-routes]
|
|
[auto-ap.ssr.components :as com]
|
|
[auto-ap.ssr.hx :as hx]
|
|
[auto-ap.ssr.ui :refer [base-page]]
|
|
[bidi.bidi :as bidi]
|
|
[clj-time.coerce :as coerce]
|
|
[clj-time.core :as time]
|
|
[datomic.api :as dc]))
|
|
|
|
(defn hourly-changes []
|
|
(let [tx-instant-attr (:db/id (dc/pull (dc/db conn) '[:db/id] :db/txInstant))
|
|
tx-lookup (->>
|
|
(dc/tx-range
|
|
(dc/log conn)
|
|
(coerce/to-date (time/plus (time/now) (time/hours -24)))
|
|
(coerce/to-date (time/now)))
|
|
(map (fn extract-tx-instant [tx]
|
|
(let [tx-id (->> (:data tx)
|
|
(map (fn [d]
|
|
(:tx d)))
|
|
first)
|
|
tx-instant (->> tx
|
|
:data
|
|
(filter (fn [d]
|
|
(and (= (:e d) tx-id)
|
|
(= tx-instant-attr (:a d)))))
|
|
(map :v)
|
|
first)]
|
|
|
|
tx-instant)))
|
|
(group-by (fn hours-ago [d]
|
|
(time/in-hours (time/interval (coerce/to-date-time d) (time/now))))))]
|
|
|
|
(for [h (range 24)]
|
|
(count (tx-lookup h [])))))
|
|
|
|
(defn page [request]
|
|
(base-page
|
|
request
|
|
(com/page {:nav com/admin-aside-nav
|
|
:client-selection (:client-selection request)
|
|
:clients (:clients request)
|
|
:client (:client request)
|
|
:identity (:identity request)}
|
|
(com/breadcrumbs {} [:a {:href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page)}
|
|
"Admin"])
|
|
[:div.flex.space-x-4
|
|
(com/content-card {:class "w-1/4"}
|
|
[:div {:class "flex flex-col px-4 py-3 space-y-3"}
|
|
[:div
|
|
[:h1.text-2xl.mb-3.font-bold "Growth in clients"]
|
|
[:div
|
|
[:div.w-full.h-64
|
|
[:canvas.w-full.h-full {:x-data (hx/json {:chart nil
|
|
:labels ["2 years ago" "1 year ago" "today"]
|
|
:data (for [n [2 1 0]
|
|
:let [start (time/plus (time/now) (time/years (- n)))]]
|
|
(->> (dc/q '[:find (count ?c)
|
|
:in $
|
|
:where [?c :client/code]]
|
|
(dc/as-of (dc/db conn) (coerce/to-date start)))
|
|
first
|
|
first))})
|
|
:x-init "new Chart($el, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Clients',
|
|
data: data,
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});"}]]]]])
|
|
|
|
(com/content-card {:class "w-1/2"}
|
|
[:div {:class "flex flex-col px-4 py-3 space-y-3"}
|
|
[:div
|
|
[:h1.text-2xl.mb-3.font-bold "Changes by hour"]
|
|
[:div
|
|
[:div.w-full.h-64
|
|
[:canvas.w-full.h-full {:x-data (hx/json {:chart nil
|
|
:labels (for [n (range -24 0)]
|
|
(format "%d" n))
|
|
:data (hourly-changes)})
|
|
:x-init "new Chart($el, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Changes',
|
|
data: data,
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});"}]]]]])])
|
|
"Admin"))
|
|
|
|
(def key->handler
|
|
{:auto-ap.routes.admin/page (wrap-client-redirect-unauthenticated (wrap-admin page))})
|
|
|