Files
integreat/src/clj/auto_ap/ssr/admin.clj
Bryce 1f6395382d refactor(charts): unify on Chart.js, remove Chartist
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>
2026-06-02 07:55:47 -07:00

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))})