7 Commits

Author SHA1 Message Date
3759258ebe fix(ssr): require Apply for all date-range filters
Most grid pages auto-submitted their date-range filter on every change
event, which fired mid-typing and re-rendered the date inputs, breaking
manual date entry. Invoices and ledgers already gated date submission
behind an explicit Apply button; this brings the other ten pages in line.

- date-range component: stop `change` from the date inputs bubbling to
  the form (@change.stop) and always render the Apply button, so typed or
  picked dates submit only via the Apply button's `datesApplied` event.
  The All/Week/Month/Year presets and all other filters are unaffected.
- payments, invoice import, transactions, import batches, sales
  summaries, expected deposits, cash drawer shifts, refunds, tenders,
  sales orders: add `datesApplied` to the form hx-trigger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:42:17 -07:00
55650c2dab Merge pull request 'refactor(charts): unify on Chart.js, remove Chartist' (#11) from integreat-unify-charts into staging
Reviewed-on: #11
2026-06-02 09:23:29 -07:00
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
20 changed files with 304 additions and 256 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

@@ -35,7 +35,7 @@
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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"

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

@@ -7,7 +7,7 @@
[clj-time.core :as t] [clj-time.core :as t]
[clj-time.periodic :as per])) [clj-time.periodic :as per]))
(defn date-range-field [{:keys [value id apply-button?]}] (defn date-range-field [{:keys [value id]}]
[:div {:id id} [:div {:id id}
(com/field {:label "Date Range"} (com/field {:label "Date Range"}
[:div.space-y-4 [:div.space-y-4
@@ -17,7 +17,7 @@
(com/button-group-button {:size :small :value "week" :hx-trigger "click"} "Week") (com/button-group-button {:size :small :value "week" :hx-trigger "click"} "Week")
(com/button-group-button {:size :small :value "month" :hx-trigger "click"} "Month") (com/button-group-button {:size :small :value "month" :hx-trigger "click"} "Month")
(com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year"))] (com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year"))]
[:div.flex.space-x-1.items-baseline.w-full.justify-start [:div.flex.space-x-1.items-baseline.w-full.justify-start {"@change.stop" ""}
(com/date-input {:name "start-date" (com/date-input {:name "start-date"
:value (some-> (:start value) :value (some-> (:start value)
(atime/unparse-local atime/normal-date)) (atime/unparse-local atime/normal-date))
@@ -31,9 +31,8 @@
:placeholder "Date" :placeholder "Date"
:size :small :size :small
:class "shrink date-filter-input"}) :class "shrink date-filter-input"})
(when apply-button? (but/button- {:color :secondary
(but/button- {:color :secondary :size :small
:size :small :type "button"
:type "button" "x-on:click" "$dispatch('datesApplied')"}
"x-on:click" "$dispatch('datesApplied')"} "Apply")]])])
"Apply"))]])])

View File

@@ -56,7 +56,7 @@
[:div {:id "exact-match-id-tag"}])) [:div {:id "exact-match-id-tag"}]))
(defn filters [request] (defn filters [request]
[:form#invoice-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form#invoice-filters {"hx-trigger" "datesApplied, change delay:500ms, 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/import-table) ::route/import-table)
"hx-target" "#entity-table" "hx-target" "#entity-table"

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,13 +46,14 @@
[: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))))]
(com/field {:label "Bank Account"} (com/field {:label "Bank Account"}
(com/radio-card {:size :small (com/radio-card {:size :small
:name "bank-account" :name "bank-account"
:value (or (when bank-account-belongs-to-client? :value (or (when bank-account-belongs-to-client?
(:db/id (:bank-account (:query-params request)))) (:db/id (:bank-account (:query-params request))))
"") "")
@@ -60,90 +61,96 @@
(into [{:value "" (into [{:value ""
:content "All"}] :content "All"}]
(for [ba (:client/bank-accounts (:client request))] (for [ba (:client/bank-accounts (:client request))]
{:value (:db/id ba) {:value (:db/id ba)
:content (:bank-account/name ba)}))}))))]) :content (:bank-account/name ba)}))}))))])
(defn bank-account-filter [request] (defn bank-account-filter [request]
(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"
"hx-indicator" "#entity-table"} "hx-indicator" "#entity-table"}
(com/hidden {:name "status" (com/hidden {:name "status"
:value (some-> (:status (:query-params request)) name)}) :value (some-> (:status (:query-params request)) name)})
[:fieldset.space-y-6 [:fieldset.space-y-6
(com/field {:label "Vendor"} (com/field {:label "Vendor"}
(com/typeahead {:name "vendor" (com/typeahead {:name "vendor"
:id "vendor" :id "vendor"
: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"
:url (bidi/path-for ssr-routes/only-routes :account-search) :url (bidi/path-for ssr-routes/only-routes :account-search)
: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"
:class "hot-filter" :class "hot-filter"
:value (:invoice-number (:query-params request)) :value (:invoice-number (:query-params request))
:placeholder "e.g., ABC-456" :placeholder "e.g., ABC-456"
:size :small})) :size :small}))
(com/field {:label "Account Code"} (com/field {:label "Account Code"}
[:div.flex.space-x-4.items-baseline [:div.flex.space-x-4.items-baseline
(com/int-input {:name "numeric-code-gte" (com/int-input {:name "numeric-code-gte"
:id "numeric-code-gte" :id "numeric-code-gte"
:hx-preserve "true" :hx-preserve "true"
:class "hot-filter w-20" :class "hot-filter w-20"
:value (:numeric-code-gte (:query-params request)) :value (:numeric-code-gte (:query-params request))
:placeholder "40000" :placeholder "40000"
:size :small}) :size :small})
[:div.align-baseline [:div.align-baseline
"to"] "to"]
(com/int-input {:name "numeric-code-lte" (com/int-input {:name "numeric-code-lte"
:hx-preserve "true" :hx-preserve "true"
:id "numeric-code-lte" :id "numeric-code-lte"
:class "hot-filter w-20" :class "hot-filter w-20"
:value (:numeric-code-lte (:query-params request)) :value (:numeric-code-lte (:query-params request))
:placeholder "50000" :placeholder "50000"
:size :small})]) :size :small})])
(com/field {:label "Amount"} (com/field {:label "Amount"}
[:div.flex.space-x-4.items-baseline [:div.flex.space-x-4.items-baseline
(com/money-input {:name "amount-gte" (com/money-input {:name "amount-gte"
:id "amount-gte" :id "amount-gte"
:hx-preserve "true" :hx-preserve "true"
:class "hot-filter w-20" :class "hot-filter w-20"
:value (:amount-gte (:query-params request)) :value (:amount-gte (:query-params request))
:placeholder "0.01" :placeholder "0.01"
:size :small}) :size :small})
[:div.align-baseline [:div.align-baseline
"to"] "to"]
(com/money-input {:name "amount-lte" (com/money-input {:name "amount-lte"
:hx-preserve "true" :hx-preserve "true"
:id "amount-lte" :id "amount-lte"
:class "hot-filter w-20" :class "hot-filter w-20"
:value (:amount-lte (:query-params request)) :value (:amount-lte (:query-params request))
:placeholder "9999.34" :placeholder "9999.34"
:size :small})]) :size :small})])
[:div.mt-4 {:x-data (hx/json {:onlyUnbalanced (:only-unbalanced (:query-params request))})} [:div.mt-4 {:x-data (hx/json {:onlyUnbalanced (:only-unbalanced (:query-params request))})}
(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)]])
@@ -184,12 +191,12 @@
args query-params args query-params
query query
(if (:exact-match-id args) (if (:exact-match-id args)
{:query {:find '[?e] {:query {:find '[?e]
:in '[$ ?e [?c ...]] :in '[$ ?e [?c ...]]
:where '[[?e :journal-entry/client ?c]]} :where '[[?e :journal-entry/client ?c]]}
:args [db :args [db
(:exact-match-id args) (:exact-match-id args)
valid-clients]} valid-clients]}
(cond-> {:query {:find [] (cond-> {:query {:find []
:in ['$ '[?clients ?start ?end]] :in ['$ '[?clients ?start ?end]]
:where '[[(iol-ion.query/scan-ledger $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]} :where '[[(iol-ion.query/scan-ledger $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]}
@@ -202,28 +209,28 @@
(merge-query {:query {:where ['(not [?e :journal-entry/original-entity])]}}) (merge-query {:query {:where ['(not [?e :journal-entry/original-entity])]}})
(seq (:external-id-like args)) (seq (:external-id-like args))
(merge-query {:query {:in ['?external-id-like] (merge-query {:query {:in ['?external-id-like]
:where ['[?e :journal-entry/external-id ?external-id] :where ['[?e :journal-entry/external-id ?external-id]
'[(.contains ^String ?external-id ?external-id-like)]]} '[(.contains ^String ?external-id ?external-id-like)]]}
:args [(:external-id-like args)]}) :args [(:external-id-like args)]})
(seq (:source args)) (seq (:source args))
(merge-query {:query {:in ['?source] (merge-query {:query {:in ['?source]
:where ['[?e :journal-entry/source ?source]]} :where ['[?e :journal-entry/source ?source]]}
:args [(:source args)]}) :args [(:source args)]})
(:external? route-params) (:external? route-params)
(merge-query {:query {:where ['[?e :journal-entry/external-id]]}}) (merge-query {:query {:where ['[?e :journal-entry/external-id]]}})
(:vendor args) (:vendor args)
(merge-query {:query {:in ['?vendor-id] (merge-query {:query {:in ['?vendor-id]
:where ['[?e :journal-entry/vendor ?vendor-id]]} :where ['[?e :journal-entry/vendor ?vendor-id]]}
:args [(:db/id (:vendor args))]}) :args [(:db/id (:vendor args))]})
(:invoice-number args) (:invoice-number args)
(merge-query {:query {:in ['?invoice-number] (merge-query {:query {:in ['?invoice-number]
:where ['[?e :journal-entry/original-entity ?oe] :where ['[?e :journal-entry/original-entity ?oe]
'[?oe :invoice/invoice-number ?invoice-number]]} '[?oe :invoice/invoice-number ?invoice-number]]}
:args [(:invoice-number args)]}) :args [(:invoice-number args)]})
(or (:numeric-code-lte args) (or (:numeric-code-lte args)
(:numeric-code-gte args) (:numeric-code-gte args)
@@ -235,77 +242,77 @@
(or (:numeric-code-gte args) (or (:numeric-code-gte args)
(:numeric-code-lte args)) (:numeric-code-lte args))
(merge-query {:query {:in '[?from-numeric-code ?to-numeric-code] (merge-query {:query {:in '[?from-numeric-code ?to-numeric-code]
:where ['[?li :journal-entry-line/account ?a] :where ['[?li :journal-entry-line/account ?a]
'(or-join [?a ?c] '(or-join [?a ?c]
[?a :account/numeric-code ?c] [?a :account/numeric-code ?c]
[?a :bank-account/numeric-code ?c]) [?a :bank-account/numeric-code ?c])
'[(>= ?c ?from-numeric-code)] '[(>= ?c ?from-numeric-code)]
'[(<= ?c ?to-numeric-code)]]} '[(<= ?c ?to-numeric-code)]]}
:args [(or (:numeric-code-gte args) 0) (or (:numeric-code-lte args) 99999)]}) :args [(or (:numeric-code-gte args) 0) (or (:numeric-code-lte args) 99999)]})
(seq (:numeric-code args)) (seq (:numeric-code args))
(merge-query {:query {:in '[[[?from-numeric-code ?to-numeric-code] ...]] (merge-query {:query {:in '[[[?from-numeric-code ?to-numeric-code] ...]]
:where ['[?li :journal-entry-line/account ?a] :where ['[?li :journal-entry-line/account ?a]
'(or-join [?a ?c] '(or-join [?a ?c]
[?a :account/numeric-code ?c] [?a :account/numeric-code ?c]
[?a :bank-account/numeric-code ?c]) [?a :bank-account/numeric-code ?c])
'[(>= ?c ?from-numeric-code)] '[(>= ?c ?from-numeric-code)]
'[(<= ?c ?to-numeric-code)]]} '[(<= ?c ?to-numeric-code)]]}
:args [(map (juxt :from :to) (:numeric-code args))]}) :args [(map (juxt :from :to) (:numeric-code args))]})
(seq (:account args)) (seq (:account args))
(merge-query {:query {:in ['?a3] (merge-query {:query {:in ['?a3]
:where ['[?li :journal-entry-line/account ?a3]]} :where ['[?li :journal-entry-line/account ?a3]]}
:args [(:db/id (:account args))]}) :args [(:db/id (:account args))]})
(:amount-gte args) (:amount-gte args)
(merge-query {:query {:in ['?amount-gte] (merge-query {:query {:in ['?amount-gte]
:where ['[?e :journal-entry/amount ?a] :where ['[?e :journal-entry/amount ?a]
'[(>= ?a ?amount-gte)]]} '[(>= ?a ?amount-gte)]]}
:args [(:amount-gte args)]}) :args [(:amount-gte args)]})
(:amount-lte args) (:amount-lte args)
(merge-query {:query {:in ['?amount-lte] (merge-query {:query {:in ['?amount-lte]
:where ['[?e :journal-entry/amount ?a] :where ['[?e :journal-entry/amount ?a]
'[(<= ?a ?amount-lte)]]} '[(<= ?a ?amount-lte)]]}
:args [(:amount-lte args)]}) :args [(:amount-lte args)]})
(:db/id (:bank-account args)) (:db/id (:bank-account args))
(merge-query {:query {:in ['?a] (merge-query {:query {:in ['?a]
:where ['[?li :journal-entry-line/account ?a]]} :where ['[?li :journal-entry-line/account ?a]]}
:args [(:db/id (:bank-account args))]}) :args [(:db/id (:bank-account args))]})
(:account-id args) (:account-id args)
(merge-query {:query {:in ['?a2] (merge-query {:query {:in ['?a2]
:where ['[?e :journal-entry/line-items ?li2] :where ['[?e :journal-entry/line-items ?li2]
'[?li2 :journal-entry-line/account ?a2]]} '[?li2 :journal-entry-line/account ?a2]]}
:args [(:account-id args)]}) :args [(:account-id args)]})
(not-empty (:location args)) (not-empty (:location args))
(merge-query {:query {:in ['?location] (merge-query {:query {:in ['?location]
:where ['[?li :journal-entry-line/location ?location]]} :where ['[?li :journal-entry-line/location ?location]]}
:args [(:location args)]}) :args [(:location args)]})
(not-empty (:locations args)) (not-empty (:locations args))
(merge-query {:query {:in ['[?location ...]] (merge-query {:query {:in ['[?location ...]]
:where ['[?li :journal-entry-line/location ?location]]} :where ['[?li :journal-entry-line/location ?location]]}
:args [(:locations args)]}) :args [(:locations args)]})
(:sort args) (add-sorter-fields {"client" ['[?e :journal-entry/client ?c] (:sort args) (add-sorter-fields {"client" ['[?e :journal-entry/client ?c]
'[?c :client/name ?sort-client]] '[?c :client/name ?sort-client]]
"date" ['[?e :journal-entry/date ?sort-date]] "date" ['[?e :journal-entry/date ?sort-date]]
"vendor" '[(or-join [?e ?sort-vendor] "vendor" '[(or-join [?e ?sort-vendor]
(and (and
[?e :journal-entry/vendor ?v] [?e :journal-entry/vendor ?v]
[?v :vendor/name ?sort-vendor]) [?v :vendor/name ?sort-vendor])
(and [(missing? $ ?e :journal-entry/vendor)] (and [(missing? $ ?e :journal-entry/vendor)]
[(ground "") ?sort-vendor]))] [(ground "") ?sort-vendor]))]
"amount" ['[?e :journal-entry/amount ?sort-amount]] "amount" ['[?e :journal-entry/amount ?sort-amount]]
"external-id" ['[?e :journal-entry/external-id ?sort-external-id]] "external-id" ['[?e :journal-entry/external-id ?sort-external-id]]
"source" '[(or-join [?e ?sort-source] "source" '[(or-join [?e ?sort-source]
[?e :journal-entry/source ?sort-source] [?e :journal-entry/source ?sort-source]
(and [(missing? $ ?e :journal-entry/source)] (and [(missing? $ ?e :journal-entry/source)]
[(ground "") ?sort-source]))]} [(ground "") ?sort-source]))]}
args) args)
true true
@@ -334,11 +341,11 @@
:journal-entry/external-id :journal-entry/external-id
:db/id :db/id
[:journal-entry/date :xform clj-time.coerce/from-date] [:journal-entry/date :xform clj-time.coerce/from-date]
{:journal-entry/vendor [:vendor/name :db/id] {:journal-entry/vendor [:vendor/name :db/id]
:journal-entry/original-entity [:invoice/invoice-number :journal-entry/original-entity [:invoice/invoice-number
:invoice/source-url :invoice/source-url
:transaction/description-original :db/id] :transaction/description-original :db/id]
:journal-entry/client [:client/name :client/code :db/id] :journal-entry/client [:client/name :client/code :db/id]
:journal-entry/line-items [:journal-entry-line/debit :journal-entry/line-items [:journal-entry-line/debit
:journal-entry-line/location :journal-entry-line/location
:journal-entry-line/running-balance :journal-entry-line/running-balance
@@ -362,8 +369,8 @@
(defn sum-outstanding [ids] (defn sum-outstanding [ids]
(->> (->>
(dc/q {:find ['?id '?o] (dc/q {:find ['?id '?o]
:in ['$ '[?id ...]] :in ['$ '[?id ...]]
:where ['[?id :invoice/outstanding-balance ?o]]} :where ['[?id :invoice/outstanding-balance ?o]]}
(dc/db conn) (dc/db conn)
ids) ids)
@@ -375,8 +382,8 @@
(defn sum-total-amount [ids] (defn sum-total-amount [ids]
(->> (->>
(dc/q {:find ['?id '?o] (dc/q {:find ['?id '?o]
:in ['$ '[?id ...]] :in ['$ '[?id ...]]
:where ['[?id :invoice/total ?o]]} :where ['[?id :invoice/total ?o]]}
(dc/db conn) (dc/db conn)
ids) ids)
@@ -386,7 +393,7 @@
0.0))) 0.0)))
(defn fetch-page [request] (defn fetch-page [request]
(let [db (dc/db conn) (let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count {ids-to-retrieve :ids matching-count :count
all-ids :all-ids} (fetch-ids db request)] all-ids :all-ids} (fetch-ids db request)]
@@ -410,12 +417,12 @@
(if account-name (if account-name
[:div {:x-tooltip (hx/json (str "Running Balance: " (some->> (:journal-entry-line/running-balance jel) [:div {:x-tooltip (hx/json (str "Running Balance: " (some->> (:journal-entry-line/running-balance jel)
(format "$%,.2f"))))} (format "$%,.2f"))))}
[:div.text-left.underline.cursor-pointer {:x-ref "source"} [:div.text-left.underline.cursor-pointer {:x-ref "source"}
(:journal-entry-line/location jel) ": " (:journal-entry-line/location jel) ": "
(or (:account/numeric-code account) (:bank-account/numeric-code account)) (or (:account/numeric-code account) (:bank-account/numeric-code account))
" - " account-name]] " - " account-name]]
[:div.text-left (com/pill {:color :yellow} "Unassigned")]) [:div.text-left (com/pill {:color :yellow} "Unassigned")])
[:div.text-right.text-underline (format "$%,.2f" (key jel))])) [:div.text-right.text-underline (format "$%,.2f" (key jel))]))
(when-not (= 1 (count lines)) (when-not (= 1 (count lines))
[:div.col-span-2 (com/pill {:color :primary} "Total: " (->> lines [:div.col-span-2 (com/pill {:color :primary} "Total: " (->> lines
@@ -443,9 +450,9 @@
[:to nat-int?]]]]] [:to nat-int?]]]]]
[:numeric-code-gte {:optional true} [:maybe nat-int?]] [:numeric-code-gte {:optional true} [:maybe nat-int?]]
[:numeric-code-lte {:optional true} [:maybe nat-int?]] [:numeric-code-lte {:optional true} [:maybe nat-int?]]
[:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]] [:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]]
[:bank-account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :bank-account/name]}]]] [:bank-account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :bank-account/name]}]]]
[:account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :account/name]}]]] [:account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :account/name]}]]]
[:check-number {:optional true} [:maybe [:string {:decode/string strip}]]] [:check-number {:optional true} [:maybe [:string {:decode/string strip}]]]
[:invoice-number {:optional true} [:maybe [:string {:decode/string strip}]]] [:invoice-number {:optional true} [:maybe [:string {:decode/string strip}]]]
[:status {:optional true} [:maybe (ref->enum-schema "invoice-status")]] [:status {:optional true} [:maybe (ref->enum-schema "invoice-status")]]
@@ -459,17 +466,20 @@
[:maybe clj-date-schema]]]])) [:maybe clj-date-schema]]]]))
(def grid-page (def grid-page
(helper/build {:id "entity-table" (helper/build {:id "entity-table"
:nav com/main-aside-nav :nav com/main-aside-nav
:check-boxes? true :check-boxes? true
:check-box-warning? (fn [e] :check-box-warning? (fn [e]
(some? (:invoice/scheduled-payment e))) (some? (:invoice/scheduled-payment e)))
:page-specific-nav filters :page-specific-nav filters
: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))
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)]) :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)])
:query-schema query-schema :query-schema query-schema
:action-buttons (fn [request] :action-buttons (fn [request]
[(when-not (:external? (:route-params request)) (com/button {:color :primary [(when-not (:external? (:route-params request)) (com/button {:color :primary
@@ -485,7 +495,7 @@
:hx-confirm "Are you sure you want to void this invoice?"} :hx-confirm "Are you sure you want to void this invoice?"}
svg/trash)) svg/trash))
(when (and (can? (:identity request) {:subject :invoice :activity :edit}) (when (and (can? (:identity request) {:subject :invoice :activity :edit})
(#{:invoice-status/unpaid :invoice-status/paid} (:invoice/status entity))) (#{:invoice-status/unpaid :invoice-status/paid} (:invoice/status entity)))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
::route/edit-wizard ::route/edit-wizard
:db/id (:db/id entity))} :db/id (:db/id entity))}
@@ -497,14 +507,14 @@
:db/id (:db/id entity))} :db/id (:db/id entity))}
svg/undo))]) svg/undo))])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)} :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Ledger"]] "Ledger"]]
:title (fn [r] :title (fn [r]
(str (str
(some-> r :route-params :status name str/capitalize (str " ")) (some-> r :route-params :status name str/capitalize (str " "))
"Register")) "Register"))
:entity-name "register" :entity-name "register"
:route ::route/table :route ::route/table
:csv-route ::route/csv :csv-route ::route/csv
:break-table (fn [request entity] :break-table (fn [request entity]
(cond (cond
@@ -521,102 +531,102 @@
(for [je journal-entries (for [je journal-entries
jel (:journal-entry/line-items je)] jel (:journal-entry/line-items je)]
(merge jel je))) (merge jel je)))
:headers [{:key "id" :headers [{:key "id"
:name "Id" :name "Id"
:render-csv :db/id :render-csv :db/id
:render-for #{:csv}} :render-for #{:csv}}
{:key "client" {:key "client"
:name "Client" :name "Client"
:sort-key "client" :sort-key "client"
:hide? (fn [args] :hide? (fn [args]
(and (= (count (:clients args)) 1) (and (= (count (:clients args)) 1)
(= 1 (count (:client/locations (:client args)))))) (= 1 (count (:client/locations (:client args))))))
:render (fn [x] [:div.flex.items-center.gap-2 (-> x :journal-entry/client :client/name)]) :render (fn [x] [:div.flex.items-center.gap-2 (-> x :journal-entry/client :client/name)])
:render-csv (fn [x] (-> x :journal-entry/client :client/name))} :render-csv (fn [x] (-> x :journal-entry/client :client/name))}
{:key "vendor" {:key "vendor"
:name "Vendor" :name "Vendor"
:sort-key "vendor" :sort-key "vendor"
:render (fn [e] (or (-> e :journal-entry/vendor :vendor/name) :render (fn [e] (or (-> e :journal-entry/vendor :vendor/name)
[:span.italic.text-gray-400 (-> e :journal-entry/alternate-description)])) [:span.italic.text-gray-400 (-> e :journal-entry/alternate-description)]))
:render-csv (fn [e] (or (-> e :journal-entry/vendor :vendor/name) :render-csv (fn [e] (or (-> e :journal-entry/vendor :vendor/name)
(-> e :journal-entry/alternate-description)))} (-> e :journal-entry/alternate-description)))}
{:key "source" {:key "source"
:name "Source" :name "Source"
:sort-key "source" :sort-key "source"
:hide? (fn [args] :hide? (fn [args]
(not (:external? (:route-params args)))) (not (:external? (:route-params args))))
:render :journal-entry/source :render :journal-entry/source
:render-csv :journal-entry/source} :render-csv :journal-entry/source}
{:key "external-id" {:key "external-id"
:name "External Id" :name "External Id"
:sort-key "external-id" :sort-key "external-id"
:class "max-w-[12rem]" :class "max-w-[12rem]"
:hide? (fn [args] :hide? (fn [args]
(not (:external? (:route-params args)))) (not (:external? (:route-params args))))
:render (fn [x] [:p.truncate (:journal-entry/external-id x)]) :render (fn [x] [:p.truncate (:journal-entry/external-id x)])
:render-csv :journal-entry/external-id} :render-csv :journal-entry/external-id}
{:key "date" {:key "date"
:sort-key "date" :sort-key "date"
:name "Date" :name "Date"
:show-starting "lg" :show-starting "lg"
:render (fn [{:journal-entry/keys [date]}] :render (fn [{:journal-entry/keys [date]}]
(some-> date (atime/unparse-local atime/normal-date)))} (some-> date (atime/unparse-local atime/normal-date)))}
{:key "amount" {:key "amount"
:sort-key "amount" :sort-key "amount"
:name "Amount" :name "Amount"
:show-starting "lg" :show-starting "lg"
:render (fn [{:journal-entry/keys [amount]}] :render (fn [{:journal-entry/keys [amount]}]
(some->> amount (some->> amount
(format "$%,.2f")))} (format "$%,.2f")))}
{:key "account" {:key "account"
:name "Account" :name "Account"
:sort-key "account" :sort-key "account"
:class "text-right" :class "text-right"
:render-csv #(or (-> % :journal-entry-line/account :account/name) :render-csv #(or (-> % :journal-entry-line/account :account/name)
(-> % :journal-entry-line/account :bank-account/name)) (-> % :journal-entry-line/account :bank-account/name))
:render-for #{:csv}} :render-for #{:csv}}
{:key "debit" {:key "debit"
:name "Debit" :name "Debit"
:class "text-right" :class "text-right"
:render (partial render-lines :journal-entry-line/debit) :render (partial render-lines :journal-entry-line/debit)
:render-csv :journal-entry-line/debit} :render-csv :journal-entry-line/debit}
{:key "credit" {:key "credit"
:name "Credit" :name "Credit"
:class "text-right" :class "text-right"
:render (partial render-lines :journal-entry-line/credit) :render (partial render-lines :journal-entry-line/credit)
:render-csv :journal-entry-line/credit} :render-csv :journal-entry-line/credit}
{:key "links" {:key "links"
:name "Links" :name "Links"
:show-starting "lg" :show-starting "lg"
:class "w-8" :class "w-8"
:render (fn [i] :render (fn [i]
(link-dropdown (link-dropdown
(cond-> [] (cond-> []
(-> i :journal-entry/original-entity :invoice/invoice-number) (-> i :journal-entry/original-entity :invoice/invoice-number)
(conj (conj
{:link (hu/url (bidi/path-for ssr-routes/only-routes {:link (hu/url (bidi/path-for ssr-routes/only-routes
::invoice-route/all-page) ::invoice-route/all-page)
{:exact-match-id (:db/id (:journal-entry/original-entity i))}) {:exact-match-id (:db/id (:journal-entry/original-entity i))})
:color :primary :color :primary
:content (format "Invoice '%s'" (-> i :journal-entry/original-entity :invoice/invoice-number))}) :content (format "Invoice '%s'" (-> i :journal-entry/original-entity :invoice/invoice-number))})
(-> i :journal-entry/original-entity :invoice/source-url) (-> i :journal-entry/original-entity :invoice/source-url)
{:link (-> i :journal-entry/original-entity :invoice/source-url) {:link (-> i :journal-entry/original-entity :invoice/source-url)
:color :secondary :color :secondary
:content (str "File")} :content (str "File")}
(-> i :journal-entry/original-entity :transaction/description-original) (-> i :journal-entry/original-entity :transaction/description-original)
(conj (conj
{:link (hu/url (bidi/path-for ssr-routes/only-routes {:link (hu/url (bidi/path-for ssr-routes/only-routes
::transaction-routes/all-page) ::transaction-routes/all-page)
{:exact-match-id (:db/id (:journal-entry/original-entity i))}) {:exact-match-id (:db/id (:journal-entry/original-entity i))})
:color :primary :color :primary
:content (format "Transaction '%s'" (-> i :journal-entry/original-entity :transaction/description-original))}) :content (format "Transaction '%s'" (-> i :journal-entry/original-entity :transaction/description-original))})
(-> i :journal-entry/memo) (-> i :journal-entry/memo)
(conj {:color :secondary (conj {:color :secondary
:content (str "Memo: " (:journal-entry/memo i))})))) :content (str "Memo: " (:journal-entry/memo i))}))))
:render-for #{:html}}]})) :render-for #{:html}}]}))
(def row* (partial helper/row* grid-page)) (def row* (partial helper/row* grid-page))

View File

@@ -53,7 +53,7 @@
[:div {:id "exact-match-id-tag"}])) [:div {:id "exact-match-id-tag"}]))
(defn filters [request] (defn filters [request]
[:form#payment-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form#payment-filters {"hx-trigger" "datesApplied, change delay:500ms, 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"

View File

@@ -29,7 +29,7 @@
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [params] (defn filters [params]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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
:pos-cash-drawer-shift-table) :pos-cash-drawer-shift-table)
"hx-target" "#cash-drawer-shift-table" "hx-target" "#cash-drawer-shift-table"

View File

@@ -34,7 +34,7 @@
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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
:pos-expected-deposit-table) :pos-expected-deposit-table)
"hx-target" "#expected-deposit-table" "hx-target" "#expected-deposit-table"

View File

@@ -29,7 +29,7 @@
[:client {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :client/name]}]]]] [:client {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :client/name]}]]]]
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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
:pos-refund-table) :pos-refund-table)
"hx-target" "#refund-table" "hx-target" "#refund-table"

View File

@@ -34,7 +34,7 @@
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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
:pos-sales-table) :pos-sales-table)
"hx-target" "#sales-table" "hx-target" "#sales-table"

View File

@@ -44,7 +44,7 @@
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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"

View File

@@ -22,7 +22,7 @@
;; always should be fast ;; always should be fast
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "datesApplied, change delay:500ms, 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
:pos-tender-table) :pos-tender-table)
"hx-target" "#tender-table" "hx-target" "#tender-table"

View File

@@ -316,7 +316,7 @@
:content (:bank-account/name ba)}))}))))]) :content (:bank-account/name ba)}))}))))])
(defn filters [request] (defn filters [request]
[:form#transaction-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form#transaction-filters {"hx-trigger" "datesApplied, change delay:500ms, 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"

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