5 Commits

Author SHA1 Message Date
19186097d5 fix(ssr): stop content-card forcing always-on scrollbars; add tmp/ scratch dir
content-card used `overflow-scroll`, which renders scrollbar tracks even
when the content fits — visible as superfluous bars around the admin chart
cards. Switch to `overflow-auto` so scrollbars only appear when content
genuinely overflows (e.g. wide data tables still scroll).

Also add a gitignored ./tmp/ scratch directory (tracked via .gitkeep) and
document in AGENTS.md that temp files belong there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 09:16:16 -07:00
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
d52159637e fixes 2026-06-02 07:15:42 -07:00
3648597031 update 2026-06-02 07:14:37 -07:00
901d9eb508 date-choosing 2026-06-02 07:13:29 -07:00
9 changed files with 287 additions and 238 deletions

3
.gitignore vendored
View File

@@ -51,3 +51,6 @@ sysco-poller/**/*.csv
.tmp/** .tmp/**
playwright-report/** playwright-report/**
test-results/** test-results/**
# Scratch dir for temp files (screenshots, logs, etc.); keep the dir, ignore contents
/tmp/*
!/tmp/.gitkeep

View File

@@ -1,5 +1,9 @@
# Integreat Development Guide # Integreat Development Guide
## Temporary Files
Write any temporary files (screenshots, scratch logs, generated artifacts, etc.) to the `./tmp/` directory at the repo root. Its contents are gitignored (only `.gitkeep` is tracked), so nothing there will be accidentally committed. Do not scatter temp files elsewhere in the repo or in the system `/tmp`.
## Build & Run Commands ## Build & Run Commands
### Build ### Build

File diff suppressed because one or more lines are too long

View File

@@ -333,7 +333,8 @@
(iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary (dc/db conn) {:db/id 17592314241429}) (iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary (dc/db conn) {:db/id 17592314241429})
(mark-all-dirty 5) (mark-all-dirty 14)
(delete-all) (delete-all)
(sales-summaries-v2) (sales-summaries-v2)

View File

@@ -10,8 +10,7 @@
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
[clj-time.coerce :as coerce] [clj-time.coerce :as coerce]
[clj-time.core :as time] [clj-time.core :as time]
[datomic.api :as dc] [datomic.api :as dc]))
[hiccup2.core :as hiccup]))
(defn hourly-changes [] (defn hourly-changes []
(let [tx-instant-attr (:db/id (dc/pull (dc/db conn) '[:db/id] :db/txInstant)) (let [tx-instant-attr (:db/id (dc/pull (dc/db conn) '[:db/id] :db/txInstant))
@@ -56,34 +55,68 @@
[:div [:div
[:h1.text-2xl.mb-3.font-bold "Growth in clients"] [:h1.text-2xl.mb-3.font-bold "Growth in clients"]
[:div [:div
[:div {:class "w-full h-64" [:div.w-full.h-64
:id "client-chart" [:canvas.w-full.h-full {:x-data (hx/json {:chart nil
:data-chart (hx/json {:labels ["2 years ago" "1 year ago" "today"], :labels ["2 years ago" "1 year ago" "today"]
:series [(for [n [2 1 0] :data (for [n [2 1 0]
:let [start (time/plus (time/now) (time/years (- n)))]] :let [start (time/plus (time/now) (time/years (- n)))]]
(->> (dc/q '[:find (count ?c) (->> (dc/q '[:find (count ?c)
:in $ :in $
:where [?c :client/code]] :where [?c :client/code]]
(dc/as-of (dc/db conn) (coerce/to-date start))) (dc/as-of (dc/db conn) (coerce/to-date start)))
first first
first))]})}] first))})
[:script {:lang "javascript"} :x-init "new Chart($el, {
(hiccup/raw type: 'bar',
"new Chartist.Bar('#client-chart', JSON.parse(document.getElementById('client-chart').getAttribute('data-chart')))")]]]]) 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"} (com/content-card {:class "w-1/2"}
[:div {:class "flex flex-col px-4 py-3 space-y-3"} [:div {:class "flex flex-col px-4 py-3 space-y-3"}
[:div [:div
[:h1.text-2xl.mb-3.font-bold "Changes by hour"] [:h1.text-2xl.mb-3.font-bold "Changes by hour"]
[:div [:div
[:div {:class "w-full h-64" [:div.w-full.h-64
:id "changes" [:canvas.w-full.h-full {:x-data (hx/json {:chart nil
:data-chart (hx/json {:labels (for [n (range -24 0)] :labels (for [n (range -24 0)]
(format "%d" n)), (format "%d" n))
:series [(hourly-changes)]})}] :data (hourly-changes)})
[:script {:lang "javascript"} :x-init "new Chart($el, {
(hiccup/raw type: 'line',
"new Chartist.Line('#changes', JSON.parse(document.getElementById('changes').getAttribute('data-chart')))")]]]])]) data: {
labels: labels,
datasets: [{
label: 'Changes',
data: data,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});"}]]]]])])
"Admin")) "Admin"))
(def key->handler (def key->handler

View File

@@ -14,5 +14,5 @@
[:section (merge params {:class (hh/add-class " py-3 sm:py-5" (:class params))}) [:section (merge params {:class (hh/add-class " py-3 sm:py-5" (:class params))})
[:div {:class (:max-w params "max-w-screen-2xl")} [:div {:class (:max-w params "max-w-screen-2xl")}
(into (into
[:div {:class "relative overflow-scroll shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white"}] [:div {:class "relative overflow-auto shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white"}]
children)]]) children)]])

View File

@@ -11,10 +11,10 @@
[auto-ap.routes.transactions :as transaction-routes] [auto-ap.routes.transactions :as transaction-routes]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com] [auto-ap.ssr.components :as com]
[auto-ap.ssr.components.date-range :as dr]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]] [auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.grid-page-helper :as helper] [auto-ap.ssr.grid-page-helper :as helper]
[auto-ap.ssr.hx :as hx] [auto-ap.ssr.hx :as hx]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg] [auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils [auto-ap.ssr.utils
:refer [clj-date-schema entity-id html-response ref->enum-schema :refer [clj-date-schema entity-id html-response ref->enum-schema
@@ -31,7 +31,7 @@
(defn exact-match-id* [request] (defn exact-match-id* [request]
(if (nat-int? (:exact-match-id (:query-params request))) (if (nat-int? (:exact-match-id (:query-params request)))
[:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag"} [:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag" :class "filter-trigger"}
(com/hidden {:name "exact-match-id" (com/hidden {:name "exact-match-id"
"x-model" "exact_match"}) "x-model" "exact_match"})
(com/pill {:color :primary} (com/pill {:color :primary}
@@ -46,7 +46,8 @@
[:div {:hx-trigger "clientSelected from:body" [:div {:hx-trigger "clientSelected from:body"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/bank-account-filter) :hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/bank-account-filter)
:hx-target "this" :hx-target "this"
:hx-swap "outerHTML"} :hx-swap "outerHTML"
:class "filter-trigger"}
(when (:client request) (when (:client request)
(let [bank-account-belongs-to-client? (get (set (map :db/id (:client/bank-accounts (:client request)))) (let [bank-account-belongs-to-client? (get (set (map :db/id (:client/bank-accounts (:client request))))
(:db/id (:bank-account (:query-params request))))] (:db/id (:bank-account (:query-params request))))]
@@ -67,7 +68,7 @@
(html-response (bank-account-filter* request))) (html-response (bank-account-filter* request)))
(defn filters [request] (defn filters [request]
[:form#ledger-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form#ledger-filters {"hx-trigger" "datesApplied, change delay:500ms from:.filter-trigger, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes "hx-get" (bidi/path-for ssr-routes/only-routes
::route/table) ::route/table)
"hx-target" "#entity-table" "hx-target" "#entity-table"
@@ -82,7 +83,8 @@
:url (bidi/path-for ssr-routes/only-routes :vendor-search) :url (bidi/path-for ssr-routes/only-routes :vendor-search)
:value (:vendor (:query-params request)) :value (:vendor (:query-params request))
:value-fn :db/id :value-fn :db/id
:content-fn :vendor/name})) :content-fn :vendor/name
:class "filter-trigger"}))
(com/field {:label "Account"} (com/field {:label "Account"}
(com/typeahead {:name "account" (com/typeahead {:name "account"
:id "account" :id "account"
@@ -90,11 +92,15 @@
:value (:account (:query-params request)) :value (:account (:query-params request))
:value-fn :db/id :value-fn :db/id
:content-fn #(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read (:db/id %)) :content-fn #(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read (:db/id %))
(:db/id (:client request))))})) (:db/id (:client request))))
:class "filter-trigger"}))
(bank-account-filter* request) (bank-account-filter* request)
(date-range-field* request) (dr/date-range-field {:value {:start (:start-date (:query-params request))
:end (:end-date (:query-params request))}
:id "date-range"
:apply-button? true})
(com/field {:label "Invoice #"} (com/field {:label "Invoice #"}
(com/text-input {:name "invoice-number" (com/text-input {:name "invoice-number"
:id "invoice-number" :id "invoice-number"
@@ -144,6 +150,7 @@
(com/hidden {:name "only-unbalanced" (com/hidden {:name "only-unbalanced"
":value" "onlyUnbalanced ? 'on' : ''"}) ":value" "onlyUnbalanced ? 'on' : ''"})
(com/checkbox {:value (:only-unbalanced (:query-params request)) (com/checkbox {:value (:only-unbalanced (:query-params request))
:class "filter-trigger"
:x-model "onlyUnbalanced"} :x-model "onlyUnbalanced"}
"Show unbalanced")] "Show unbalanced")]
(exact-match-id* request)]]) (exact-match-id* request)]])
@@ -468,7 +475,10 @@
:fetch-page fetch-page :fetch-page fetch-page
:oob-render :oob-render
(fn [request] (fn [request]
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true) [(assoc-in (dr/date-range-field {:value {:start (:start-date (:query-params request))
:end (:end-date (:query-params request))}
:id "date-range"
:apply-button? true}) [1 :hx-swap-oob] true)
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)]) (assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)])
:query-schema query-schema :query-schema query-schema
:action-buttons (fn [request] :action-buttons (fn [request]

View File

@@ -23,8 +23,6 @@
[:title (str "Integreat | " page-name)] [:title (str "Integreat | " page-name)]
[:link {:href "/css/font.min.css", :rel "stylesheet"}] [:link {:href "/css/font.min.css", :rel "stylesheet"}]
[:link {:rel "icon" :type "image/png" :href "/favicon.png"}] [:link {:rel "icon" :type "image/png" :href "/favicon.png"}]
[:link {:rel "stylesheet" :href "//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css"}]
[:script {:src "//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"}]
[:link {:rel "stylesheet", :href "/output.css"}] [:link {:rel "stylesheet", :href "/output.css"}]
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}] [:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
[:script {:src "https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" :defer true}] [:script {:src "https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" :defer true}]

0
tmp/.gitkeep Normal file
View File