Merge branch 'try-tailwind'
This commit is contained in:
@@ -578,12 +578,14 @@
|
||||
|
||||
(defn add-sorter-fields [q sort-map args]
|
||||
(reduce
|
||||
(fn [q {:keys [sort-key]}]
|
||||
(fn [q {:keys [sort-key] :as z}]
|
||||
(prn z)
|
||||
(println (class sort-key))
|
||||
(merge-query q
|
||||
{:query {:find [(symbol (str "?sort-" sort-key))]
|
||||
:where (sort-map
|
||||
sort-key
|
||||
(println "Warning, trying to sort by unsupported field" sort-key))}}))
|
||||
(println "Warning, trying to sort by unsupported field" sort-key, "sort map" (pr-str sort-map)))}}))
|
||||
q
|
||||
(:sort args)))
|
||||
|
||||
@@ -620,9 +622,13 @@
|
||||
(range length)))]
|
||||
(sort comparator results )))
|
||||
|
||||
(defn apply-pagination [args results]
|
||||
(log/info (take 4 results))
|
||||
(defn apply-pagination-raw [args results]
|
||||
{:entries (->> results
|
||||
(drop (:start args 0))
|
||||
(take (:count args (or (:per-page args) default-pagination-size))))
|
||||
:count (count results)})
|
||||
|
||||
(defn apply-pagination [args results]
|
||||
{:ids (->> results
|
||||
(drop (:start args 0))
|
||||
(take (:count args (or (:per-page args) default-pagination-size)))
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
[clj-time.coerce :as c]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(def default-read '[:db/id :report/client :report/created :report/url :report/name :report/creator])
|
||||
|
||||
(defn raw-graphql-ids [db args]
|
||||
(let [query (cond-> {:query {:find []
|
||||
:in ['$ ]
|
||||
@@ -43,8 +45,7 @@
|
||||
(apply-pagination args))))
|
||||
|
||||
(defn graphql-results [ids db args]
|
||||
(let [results (->> (pull-many db '[:db/id :report/client :report/created :report/url :report/name :report/creator]
|
||||
ids)
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(map #(update % :report/created c/from-date))
|
||||
(group-by :db/id))]
|
||||
(->> ids
|
||||
@@ -63,8 +64,3 @@
|
||||
|
||||
[(->> (graphql-results ids-to-retrieve db args))
|
||||
matching-count]))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
[ring.util.response :as response]
|
||||
[unilog.context :as lc]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clj-time.core :as time]))
|
||||
[clj-time.core :as time]
|
||||
[cemerick.url :as url]))
|
||||
|
||||
(when (:aws-access-key-id env)
|
||||
(defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env)))
|
||||
@@ -161,9 +162,17 @@
|
||||
(let [end-time (time/plus (time/now) (time/days 14))]
|
||||
(assoc response :session (assoc session ::idle-timeout (coerce/to-date end-time)))))))))))
|
||||
|
||||
(defn wrap-hx-current-url-params
|
||||
[handler ]
|
||||
(fn [request]
|
||||
(let [query-params (some-> (get-in request [:headers "hx-current-url"]) (url/url ) :query)
|
||||
request (assoc request :hx-query-params query-params)]
|
||||
(handler request))))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(def app
|
||||
(-> route-handler
|
||||
(wrap-hx-current-url-params)
|
||||
(wrap-guess-route)
|
||||
(wrap-authorization auth-backend
|
||||
)
|
||||
|
||||
@@ -44,12 +44,14 @@
|
||||
extract)]))
|
||||
|
||||
(defn extract-sheet-details [bucket object]
|
||||
(-> (lambda/invoke {:function-name "xls-extractor" :payload
|
||||
(json/write-str
|
||||
(doto
|
||||
(-> (lambda/invoke {:function-name "xls-extractor" :payload
|
||||
(json/write-str
|
||||
{"s3_url" object "s3_bucket" bucket})})
|
||||
:payload
|
||||
slurp
|
||||
json/read-str))
|
||||
:payload
|
||||
slurp
|
||||
json/read-str)
|
||||
println))
|
||||
|
||||
(defn parse-file
|
||||
[file _]
|
||||
|
||||
@@ -20,18 +20,18 @@
|
||||
(:jwt-secret env)
|
||||
{:alg :hs512}))
|
||||
|
||||
(defn oauth [{{:strs [code]} :query-params {:strs [host]} :headers}]
|
||||
(defn oauth [{{:strs [code state]} :query-params {:strs [host]} :headers :as request}]
|
||||
(try
|
||||
(let [auth (-> "https://accounts.google.com/o/oauth2/token"
|
||||
(http/post
|
||||
{:form-params {"client_id" google-client-id
|
||||
"client_secret" google-client-secret
|
||||
"code" code
|
||||
"redirect_uri" (str (:scheme env) "://" host "/api/oauth")
|
||||
"grant_type" "authorization_code"}
|
||||
:as :json})
|
||||
(http/post
|
||||
{:form-params {"client_id" google-client-id
|
||||
"client_secret" google-client-secret
|
||||
"code" code
|
||||
"redirect_uri" (str (:scheme env) "://" host "/api/oauth")
|
||||
"grant_type" "authorization_code"}
|
||||
:as :json})
|
||||
:body)
|
||||
|
||||
|
||||
token (:access_token auth)
|
||||
profile (-> (http/get "https://www.googleapis.com/oauth2/v1/userinfo"
|
||||
{:headers {"Authorization" (str "Bearer " token)} :as :json})
|
||||
@@ -45,6 +45,7 @@
|
||||
:user/name (:name profile)})
|
||||
auth {:user (:name profile)
|
||||
:exp (time/plus (time/now) (time/days 30))
|
||||
:db/id (:db/id user)
|
||||
:user/clients (map (fn [c]
|
||||
(select-keys c [:client/code :db/id :client/locations]))
|
||||
(:user/clients user))
|
||||
@@ -53,19 +54,18 @@
|
||||
_ (mu/log ::logged-in-as
|
||||
:auth auth)]
|
||||
;; TODO - these namespaces are not being transmitted/deserialized properly
|
||||
|
||||
|
||||
(if (and token user)
|
||||
(let [jwt (jwt/sign auth
|
||||
(:jwt-secret env)
|
||||
{:alg :hs512})]
|
||||
|
||||
{:status 301
|
||||
:headers {"Location" (str "/?jwt=" jwt)}
|
||||
:headers {"Location" (str (or (not-empty state) "/") "?jwt=" jwt)}
|
||||
:session {:identity (dissoc auth :exp)}})
|
||||
{:status 401
|
||||
:body "Couldn't authenticate"}))
|
||||
(catch Exception e
|
||||
(log/warn e )
|
||||
(log/warn e)
|
||||
{:status 401
|
||||
:body (str "Couldn't authenticate " (.toString e))})))
|
||||
|
||||
|
||||
@@ -267,8 +267,9 @@
|
||||
))
|
||||
(into [["Vendor Name" "Address" "City" "State" "Zip" "Terms" "Account" "Account Code"]]))]
|
||||
{:body
|
||||
(into (list)
|
||||
data)})))
|
||||
(into []
|
||||
data)
|
||||
:headers {"content-disposition" "attachment; filename=\"vendors.csv\""}})))
|
||||
|
||||
(defn export-ledger [{:keys [identity query-params]}]
|
||||
(let [start-date (or (some-> (query-params "start-date")
|
||||
@@ -426,7 +427,7 @@
|
||||
"expected-deposit/" {#"export/?" {:get :export-expected-deposits}}
|
||||
"clients/" {#"export/?" {:get :export-clients}}
|
||||
"vendors/" {#"export/?" {:get :export-vendors}
|
||||
"/company" {#"export/?" {:get :export-company-vendors}}}
|
||||
"company/" {#"export" {:get :export-company-vendors}}}
|
||||
"ledger/" {#"export/?" {:get :export-ledger}}
|
||||
"accounts/" {#"export/?" {:get :export-accounts}}
|
||||
"transactions/" {#"export/?" {:get :export-transactions}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]
|
||||
[amazonica.aws.s3 :as s3]))
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.ssr.components :as com]))
|
||||
|
||||
(defn fmt-amount [a]
|
||||
(with-precision 2
|
||||
@@ -27,19 +28,14 @@
|
||||
(.setScale 2 java.math.RoundingMode/HALF_UP)
|
||||
(double))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn rows->maps [rows]
|
||||
(let [[headers & rows] rows]
|
||||
(for [r rows]
|
||||
(into {}
|
||||
(map vector headers r)))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn map->sales-order [r clients]
|
||||
(println r)
|
||||
(let [order-number (get r "Order Number")
|
||||
event-date (get r "Event Date")
|
||||
store-name (get r "Store Name")
|
||||
@@ -135,30 +131,29 @@
|
||||
(stream->sales-orders s)))
|
||||
|
||||
(defn page* []
|
||||
[:div
|
||||
[:h1.title "EZCater XLS Import"]
|
||||
[:div.card.block {:style {:width "500px"}}
|
||||
[:div.card-content
|
||||
"Please go to "
|
||||
[:a {:href "https://www.ezcater.com/ez_manage/reports/new" :target "_blank"} "EZCater's report page"]
|
||||
" to generate a new report. Then drop it below."]]
|
||||
[:div#page-notification.notification.block {:style {:display "none"}}]
|
||||
[:div.card.block
|
||||
[:div.card-content
|
||||
[:form {:action (bidi/path-for ssr-routes/only-routes
|
||||
:admin-ezcater-xls)
|
||||
:method "POST"
|
||||
:class "dropzone"
|
||||
:id "ezcater"}]]]
|
||||
[:script
|
||||
(hiccup/raw
|
||||
"
|
||||
[:div.mt-4
|
||||
(com/card {}
|
||||
[:div.px-4.py-3.space-y-4.flex.flex-col
|
||||
[:h1.text-2xl.mb-3.font-bold "EZCater XLS Import"]
|
||||
[:p.text-sm.italic
|
||||
"Please go to "
|
||||
(com/link {:href "https://www.ezcater.com/ez_manage/reports/new" :target "_blank"} "EZCater's report page")
|
||||
" to generate a new report. Then drop it below."]
|
||||
[:div#page-notification.notification.block {:style {:display "none"}}]
|
||||
[:form.bg-blue-300 {:action (bidi/path-for ssr-routes/only-routes
|
||||
:admin-ezcater-xls)
|
||||
:method "POST"
|
||||
:class "dropzone"
|
||||
:id "ezcater"}]
|
||||
[:script
|
||||
(hiccup/raw
|
||||
"
|
||||
Dropzone.options.ezcater = {
|
||||
success: function (file, response) {
|
||||
document.getElementById(\"page-notification\").innerHTML = response;
|
||||
document.getElementById(\"page-notification\").style[\"display\"] = \"block\";
|
||||
}
|
||||
}")]])
|
||||
}")]])])
|
||||
|
||||
(defn upload-xls [{:keys [identity] :as request}]
|
||||
|
||||
@@ -194,8 +189,23 @@
|
||||
(if (= :post request-method)
|
||||
(upload-xls request)
|
||||
(base-page
|
||||
request
|
||||
(page*)
|
||||
|
||||
(admin-side-bar matched-route))))
|
||||
request
|
||||
(com/page {:nav (com/admin-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)
|
||||
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:admin-ezcater-xls)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin)}
|
||||
"Admin"]
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin-ezcater-xls)}
|
||||
"EZCater XLS Import"])
|
||||
(page*))
|
||||
|
||||
"EZCater upload")))
|
||||
|
||||
|
||||
@@ -8,30 +8,33 @@
|
||||
(defn wrap-secure [handler]
|
||||
(fn [request]
|
||||
(cond (authenticated? request)
|
||||
(handler request)
|
||||
(handler request)
|
||||
|
||||
(get (:headers request) "hx-request")
|
||||
{:status 401
|
||||
:headers {"hx-redirect" (str "/login?"
|
||||
(url/map->query {"redirect-to" (:uri request)}))}}
|
||||
|
||||
(get (:headers request) "hx-request")
|
||||
{:status 401
|
||||
:headers {"hx-redirect" "/login"}}
|
||||
|
||||
:else
|
||||
{:status 302
|
||||
:headers {"Location" "/login" }})))
|
||||
:else
|
||||
{:status 302
|
||||
:headers {"Location" (str "/login?"
|
||||
(url/map->query {"redirect-to" (:uri request)}))}})))
|
||||
|
||||
(defn wrap-admin [handler]
|
||||
(fn [request]
|
||||
(if (is-admin? (:identity request))
|
||||
(handler request)
|
||||
(do
|
||||
(do
|
||||
(alog/warn ::unauthenticated)
|
||||
{:status 302
|
||||
:headers {"Location" "/login"}}))))
|
||||
:headers {"Location" (str "/login?"
|
||||
(url/map->query {"redirect-to" (:uri request)}))}}))))
|
||||
|
||||
(defn wrap-client-redirect-unauthenticated [handler]
|
||||
(fn [request]
|
||||
(let [response (handler request)]
|
||||
(if (= 401 (get response :status))
|
||||
(-> response
|
||||
(assoc-in [:headers "hx-redirect"] "/login/"))
|
||||
(-> response
|
||||
(assoc-in [:headers "hx-redirect"] (str "/login?"
|
||||
(url/map->query {"redirect-to" (:uri request)}))))
|
||||
response))))
|
||||
|
||||
@@ -206,3 +206,34 @@
|
||||
(index-documents impl index [i])))
|
||||
|
||||
|
||||
(defrecord InMemSolrClient [data-set-atom]
|
||||
SolrClient
|
||||
(index-documents [this index xs]
|
||||
(swap! data-set-atom
|
||||
(fn [data-set]
|
||||
(reduce
|
||||
(fn [data-set x]
|
||||
(let [thing (datomic->solr x)]
|
||||
(update data-set index conj [(str/join " " (vals x)) thing])))
|
||||
data-set
|
||||
xs)))
|
||||
nil)
|
||||
|
||||
(index-documents-raw [this index xs]
|
||||
(swap! data-set-atom
|
||||
(fn [data-set]
|
||||
(reduce
|
||||
(fn [data-set x]
|
||||
(update data-set index conj [(str/join " " (vals x)) x]))
|
||||
data-set
|
||||
xs))))
|
||||
|
||||
(query [this index q]
|
||||
(filter
|
||||
(fn [[x e]]
|
||||
(str/includes? x (get q "query")))
|
||||
(get @data-set-atom index)))
|
||||
(delete [this index]
|
||||
(swap! data-set-atom dissoc index)))
|
||||
|
||||
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
(ns auto-ap.ssr.admin
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.logging :as alog]
|
||||
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clojure.string :as str]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(defn tx-rows->changes [history]
|
||||
(->> history
|
||||
(group-by (fn [[a _ t]]
|
||||
[a t]))
|
||||
(map (fn [[[a t] changes]]
|
||||
(let [changes (-> (reduce
|
||||
(fn [acc [_ v _ added]]
|
||||
(if added
|
||||
(assoc acc :added v)
|
||||
(assoc acc :removed v)))
|
||||
{}
|
||||
changes))]
|
||||
[t a changes])))))
|
||||
|
||||
(def error-script
|
||||
(hiccup/raw "on htmx:responseError from me set event.detail.target's innerHTML to event.detail.xhr.responseText end"))
|
||||
|
||||
(defn format-value [v]
|
||||
(cond (inst? v)
|
||||
(-> v
|
||||
coerce/to-date-time
|
||||
atime/localize
|
||||
(atime/unparse atime/normal-date))
|
||||
|
||||
(nil? v)
|
||||
[:em "(none)"]
|
||||
|
||||
(and (integer? v)
|
||||
(> v 1000000))
|
||||
[:span
|
||||
[:a {:hx-get (str "/admin/history/" v)
|
||||
:hx-swap "innerHTML"
|
||||
:hx-push-url "true"
|
||||
:hx-target "#history-table"}
|
||||
v]
|
||||
" [" [:a
|
||||
{:hx-get (str "/admin/history/inspect/" v)
|
||||
:hx-swap "innerHTML"
|
||||
:hx-target "#inspector"
|
||||
:hx-trigger "click"
|
||||
"_" error-script}
|
||||
"snapshot"] "]"]
|
||||
|
||||
|
||||
:else
|
||||
(pr-str v)))
|
||||
|
||||
(defn page-template [& {:keys [table entity-id]}]
|
||||
[:div
|
||||
[:div.columns
|
||||
[:div.column.is-4
|
||||
[:form {"hx-target" "#history-table"
|
||||
"hx-post" "/admin/history/search"
|
||||
"hx-swap" "innerHTML"
|
||||
"_" (hiccup/raw "on htmx:beforeRequest toggle @disabled on me then toggle .is-loading on <#dig/> end
|
||||
on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end")
|
||||
}
|
||||
[:div.field.is-grouped
|
||||
[:p.control {}
|
||||
[:input.input {:type "text" :name "entity-id" :placeholder "Entity id" :value entity-id}]]
|
||||
[:p.control
|
||||
[:button#dig.button.is-primary {}
|
||||
"Dig"]]]]]]
|
||||
[:div#history-table
|
||||
table]])
|
||||
|
||||
(defn table [entity-id best-guess-entity history]
|
||||
[:div [:h1.title "History for "
|
||||
(str/capitalize best-guess-entity)
|
||||
" "
|
||||
entity-id]
|
||||
[:div.columns
|
||||
[:div.column.is-9
|
||||
[:table.table.compact.grid {:style "width: 100%"}
|
||||
[:thead
|
||||
[:tr
|
||||
[:td {:style "width: 14em"} "Date"]
|
||||
[:td {:style "width: 14em"} "User"]
|
||||
[:td {:style "width: 18em"} "Field"]
|
||||
[:td "From"]
|
||||
[:td "To"]]]
|
||||
[:tbody
|
||||
(for [[tx a c] history]
|
||||
[:tr
|
||||
[:td [:div [:div (some-> (:db/txInstant tx)
|
||||
coerce/to-date-time
|
||||
atime/localize
|
||||
(atime/unparse atime/standard-time))
|
||||
]
|
||||
[:div.tag (:db/id tx)]]]
|
||||
[:td (str (:audit/user tx))]
|
||||
[:td (namespace a) ": " (name a)]
|
||||
|
||||
[:td
|
||||
[:div.tag.is-danger.is-light
|
||||
[:span
|
||||
(format-value (:removed c))]]]
|
||||
[:td
|
||||
[:div.tag.is-primary.is-light
|
||||
[:span
|
||||
(format-value (:added c))]]]])]
|
||||
]]
|
||||
[:div.column.is-3
|
||||
[:div#inspector]]]])
|
||||
|
||||
(defn history-search [{:keys [form-params params] :as request}]
|
||||
(try
|
||||
(let [entity-id (Long/parseLong (or (some-> (:entity-id form-params) not-empty)
|
||||
(:entity-id params)
|
||||
(get params "entity-id")
|
||||
(get form-params "entity-id")))
|
||||
history (->>
|
||||
(dc/q '[:find ?a2 ?v (pull ?tx [:db/txInstant :audit/user :db/id]) ?ad
|
||||
:in $ $$ ?i
|
||||
:where
|
||||
[$$ ?i ?a ?v ?tx ?ad]
|
||||
[$ ?a :db/ident ?a2]]
|
||||
(dc/db conn)
|
||||
(dc/history (dc/db conn))
|
||||
entity-id )
|
||||
tx-rows->changes
|
||||
(sort-by (comp :db/id first))
|
||||
vec)
|
||||
best-guess-entity (or (->> history
|
||||
(group-by
|
||||
(comp
|
||||
namespace
|
||||
second)
|
||||
)
|
||||
(map (fn [[k v]]
|
||||
[k v]))
|
||||
(sort-by second)
|
||||
last
|
||||
first)
|
||||
"?")]
|
||||
|
||||
(if (get (:headers request) "hx-request")
|
||||
(html-response
|
||||
(table entity-id best-guess-entity history))
|
||||
(base-page request (page-template :table (table entity-id best-guess-entity history)
|
||||
:entity-id entity-id)
|
||||
(admin-side-bar :admin-history))))
|
||||
(catch NumberFormatException _
|
||||
(html-response
|
||||
[:div.notification.is-danger.is-light
|
||||
"Cannot parse the entity-id " (or (:entity-id form-params)
|
||||
(:entity-id params))
|
||||
|
||||
". It should be a number."]))))
|
||||
|
||||
(defn inspect [{{:keys [entity-id]} :params :as request}]
|
||||
(alog/info ::inspect
|
||||
:request request)
|
||||
(try
|
||||
(let [entity-id (Long/parseLong entity-id)
|
||||
data (dc/pull (dc/db conn)
|
||||
'[*]
|
||||
entity-id)]
|
||||
|
||||
(html-response
|
||||
[:div.box {:style {:position "sticky"
|
||||
:display "inline-block"
|
||||
:vertical-align "top"
|
||||
:overflow-y "auto"
|
||||
:max-height "100vh"
|
||||
:top "0px"
|
||||
:bottom "0px"}}
|
||||
[:div {:style {:display "inline-block"}}
|
||||
[:h1.title "Snapshot of "
|
||||
entity-id]
|
||||
[:ul
|
||||
(for [[k v] data]
|
||||
[:li [:strong k] ":" (format-value v)]
|
||||
)]]]))
|
||||
(catch NumberFormatException _
|
||||
(html-response
|
||||
[:div.notification.is-danger.is-light
|
||||
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
||||
|
||||
(defn history [{:keys [matched-route] :as request}]
|
||||
(base-page request
|
||||
(page-template )
|
||||
(admin-side-bar matched-route)))
|
||||
|
||||
|
||||
|
||||
|
||||
190
src/clj/auto_ap/ssr/admin/history.clj
Normal file
190
src/clj/auto_ap/ssr/admin/history.clj
Normal file
@@ -0,0 +1,190 @@
|
||||
(ns auto-ap.ssr.admin.history
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.logging :as alog]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clojure.string :as str]
|
||||
[datomic.api :as dc]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[bidi.bidi :as bidi]))
|
||||
|
||||
(defn tx-rows->changes [history]
|
||||
(->> history
|
||||
(group-by (fn [[a _ t]]
|
||||
[a t]))
|
||||
(map (fn [[[a t] changes]]
|
||||
(let [changes (-> (reduce
|
||||
(fn [acc [_ v _ added]]
|
||||
(if added
|
||||
(assoc acc :added v)
|
||||
(assoc acc :removed v)))
|
||||
{}
|
||||
changes))]
|
||||
[t a changes])))))
|
||||
|
||||
(defn format-value [v]
|
||||
(cond (inst? v)
|
||||
(-> v
|
||||
coerce/to-date-time
|
||||
atime/localize
|
||||
(atime/unparse atime/normal-date))
|
||||
|
||||
(nil? v)
|
||||
[:em "(none)"]
|
||||
|
||||
(and (integer? v)
|
||||
(> v 1000000))
|
||||
[:span
|
||||
(com/link
|
||||
{:href "#"
|
||||
:hx-get (str "/admin/history/" v)
|
||||
:hx-swap "innerHTML"
|
||||
:hx-push-url "true"
|
||||
:hx-select "#history-table"
|
||||
:hx-target "#history-table"}
|
||||
v)
|
||||
" ["
|
||||
(com/link
|
||||
{:href "#"
|
||||
:hx-get (str "/admin/history/inspect/" v)
|
||||
:hx-swap "innerHTML"
|
||||
:hx-target "#inspector"
|
||||
:hx-trigger "click"}
|
||||
"snapshot") "]"]
|
||||
|
||||
:else
|
||||
(pr-str v)))
|
||||
|
||||
|
||||
(defn inspect [{{:keys [entity-id]} :params :as request}]
|
||||
(alog/info ::inspect
|
||||
:request request)
|
||||
(try
|
||||
(let [entity-id (Long/parseLong entity-id)
|
||||
data (dc/pull (dc/db conn)
|
||||
'[*]
|
||||
entity-id)]
|
||||
|
||||
(html-response
|
||||
[:section.py-3.sm:py-5.max-w-lg
|
||||
(com/card {:class "p-2"}
|
||||
[:div {:style {:display "inline-block"}}
|
||||
[:h1.title "Snapshot of "
|
||||
entity-id]
|
||||
[:ul
|
||||
(for [[k v] data]
|
||||
[:li [:strong k] ":" (format-value v)])]])]))
|
||||
(catch NumberFormatException _
|
||||
(html-response
|
||||
[:div.notification.is-danger.is-light
|
||||
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
||||
|
||||
(defn result-table [{:keys [entity-id]}]
|
||||
(try
|
||||
(let [history (->>
|
||||
(dc/q '[:find ?a2 ?v (pull ?tx [:db/txInstant :audit/user :db/id]) ?ad
|
||||
:in $ $$ ?i
|
||||
:where
|
||||
[$$ ?i ?a ?v ?tx ?ad]
|
||||
[$ ?a :db/ident ?a2]]
|
||||
(dc/db conn)
|
||||
(dc/history (dc/db conn))
|
||||
entity-id)
|
||||
tx-rows->changes
|
||||
(sort-by (comp :db/id first))
|
||||
vec)
|
||||
best-guess-entity (or (->> history
|
||||
(group-by
|
||||
(comp
|
||||
namespace
|
||||
second))
|
||||
(map (fn [[k v]]
|
||||
[k v]))
|
||||
(sort-by second)
|
||||
last
|
||||
first)
|
||||
"?")]
|
||||
(com/data-grid-card {:id "history-table"
|
||||
:title (format "History for %s: %d" (str/capitalize best-guess-entity) entity-id)
|
||||
:route :history-table
|
||||
:paginate? false
|
||||
:total (count history)
|
||||
:subtitle nil
|
||||
:action-buttons nil
|
||||
:rows (for [[tx a c] history]
|
||||
(com/data-grid-row
|
||||
{}
|
||||
(com/data-grid-cell {} [:div [:div (some-> (:db/txInstant tx)
|
||||
coerce/to-date-time
|
||||
atime/localize
|
||||
(atime/unparse atime/standard-time))]
|
||||
[:div.tag (:db/id tx)]])
|
||||
(com/data-grid-cell {} (str (:audit/user tx)))
|
||||
(com/data-grid-cell {} (namespace a) ": " (name a))
|
||||
|
||||
(com/data-grid-cell {}
|
||||
(com/pill {:color :red}
|
||||
(format-value (:removed c))))
|
||||
(com/data-grid-cell {}
|
||||
[:div.tag.is-primary.is-light
|
||||
[:span
|
||||
(format-value (:added c))]])))
|
||||
:headers
|
||||
[(com/data-grid-header {}
|
||||
"Date")
|
||||
(com/data-grid-header {}
|
||||
"User")
|
||||
(com/data-grid-header {}
|
||||
"Field")
|
||||
(com/data-grid-header {}
|
||||
"From")
|
||||
(com/data-grid-header {}
|
||||
"To")]}))
|
||||
(catch NumberFormatException e
|
||||
(throw e))))
|
||||
|
||||
(defn search-box [{:keys [entity-id]}]
|
||||
[:div.mt-4
|
||||
[:form.flex.gap-2 {"hx-target" "#history-table"
|
||||
"hx-get" (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)
|
||||
"hx-select" "#history-table"
|
||||
"hx-swap" "innerHTML"
|
||||
"hx-ext" "debug"
|
||||
"hx-push-url" "true"}
|
||||
(com/text-input {:name "entity-id" :placeholder "Entity Id" :value entity-id
|
||||
:style {:width "300px"}})
|
||||
(com/button {:color :primary}
|
||||
"DIG")]])
|
||||
|
||||
(defn page [{:keys [matched-route route-params query-params] :as request}]
|
||||
(let [entity-id (or (some-> query-params (get "entity-id") Long/parseLong)
|
||||
(some-> route-params (get :entity-id) Long/parseLong))]
|
||||
(base-page request
|
||||
(com/page {:nav (com/admin-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)
|
||||
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin)}
|
||||
"Admin"]
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)}
|
||||
"History"])
|
||||
(search-box {:entity-id entity-id})
|
||||
[:div.flex.gap-4.flex-col.lg:flex-row
|
||||
(if entity-id
|
||||
(result-table {:entity-id entity-id})
|
||||
[:div#history-table])
|
||||
[:div#inspector]
|
||||
])
|
||||
"History")))
|
||||
67
src/clj/auto_ap/ssr/company.clj
Normal file
67
src/clj/auto_ap/ssr/company.clj
Normal file
@@ -0,0 +1,67 @@
|
||||
(ns auto-ap.ssr.company
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.clients :refer [full-read]]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[cemerick.url :as url]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[bidi.bidi :as bidi]))
|
||||
|
||||
(defn please-select-client-screen* []
|
||||
[:div.grid.grid-cols-3
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Please select a company"]
|
||||
])])
|
||||
|
||||
(defn main-content* [{:keys [client]}]
|
||||
(if-not client
|
||||
(please-select-client-screen*)
|
||||
(let [client (dc/pull (dc/db conn) full-read (:db/id client))]
|
||||
[:div.grid.grid-cols-3.gap-4
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
(:client/name client)]
|
||||
(when-let [address (-> client :client/address)]
|
||||
[:div.flex.flex-col.gap-1.text-lg.dark:text-white.text-gray-700
|
||||
[:p (-> address :address/street1)]
|
||||
[:p (-> address :address/street2)]
|
||||
[:p (-> address :address/city) " "
|
||||
(-> address :address/state) ", "
|
||||
(-> address :address/zip)]])]
|
||||
)
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Downloads"]
|
||||
[:a {:href (str (assoc (url/url (str (:base-url env) "/api/vendors/company/export"))
|
||||
:query {"client" (:client/code client)}))}
|
||||
(com/button {:color :primary}
|
||||
"Download vendor list"
|
||||
(com/button-icon {} svg/download))]])])))
|
||||
|
||||
(defn page [{:keys [identity matched-route] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav (com/company-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)
|
||||
:app-params {
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"])
|
||||
(main-content* {:client (:client (:session request))}))
|
||||
"My Company"))
|
||||
|
||||
@@ -1,367 +1,325 @@
|
||||
(ns auto-ap.ssr.company.company-1099
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn remove-nils]]
|
||||
[auto-ap.graphql.utils :refer [is-admin?]]
|
||||
[iol-ion.query :refer [can-see-client?]]
|
||||
[auto-ap.shared-views.company.sidebar :refer [company-side-bar]]
|
||||
[auto-ap.datomic :refer [apply-pagination-raw conn remove-nils]]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client is-admin?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.grid-page-helper :as helper]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils :refer [form-data->map html-response path->name]]
|
||||
[bidi.bidi :as bidi]
|
||||
[cemerick.url :as url]
|
||||
[clojure.string :as str]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
[iol-ion.query :refer [can-see-client?]]))
|
||||
|
||||
(def vendor-read '[:db/id
|
||||
:vendor/name
|
||||
{:vendor/legal-entity-1099-type [:db/ident]}
|
||||
{:vendor/legal-entity-tin-type [:db/ident]}
|
||||
{:vendor/address [:address/street1
|
||||
:address/city
|
||||
:address/state
|
||||
:address/zip]}
|
||||
:vendor/legal-entity-tin
|
||||
:vendor/legal-entity-name
|
||||
:vendor/legal-entity-first-name
|
||||
:vendor/legal-entity-middle-name
|
||||
:vendor/legal-entity-last-name])
|
||||
|
||||
(defn sum-for-client-vendor [client-id vendor-id]
|
||||
(ffirst (dc/q '[:find
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $ ?c ?v
|
||||
:where
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
client-id
|
||||
vendor-id)))
|
||||
|
||||
(defn get-1099-companies [user session]
|
||||
(defn get-1099-companies [user {:keys [client-id] :as args}]
|
||||
(let [clients (->> (dc/q '[:find ?c
|
||||
:in $ ?user
|
||||
:where [?c :client/code]
|
||||
[(iol-ion.query/can-see-client? ?user ?c)]]
|
||||
(dc/db conn) user)
|
||||
:in $ ?user
|
||||
:where [?c :client/code]
|
||||
[(iol-ion.query/can-see-client? ?user ?c)]]
|
||||
(dc/db conn) user)
|
||||
(map first)
|
||||
set)
|
||||
results (cond
|
||||
(and (some-> session :client :db/id)
|
||||
(can-see-client? user
|
||||
(some-> session :client :db/id)))
|
||||
(and client-id
|
||||
(can-see-client? user client-id))
|
||||
(dc/q '[:find
|
||||
(pull ?c [:client/code :db/id])
|
||||
(pull ?v [:db/id
|
||||
:vendor/name
|
||||
{:vendor/legal-entity-1099-type [:db/ident]}
|
||||
{:vendor/legal-entity-tin-type [:db/ident]}
|
||||
{:vendor/address [:address/street1
|
||||
:address/city
|
||||
:address/state
|
||||
:address/zip]}
|
||||
:vendor/legal-entity-tin
|
||||
:vendor/legal-entity-name
|
||||
:vendor/legal-entity-first-name
|
||||
:vendor/legal-entity-middle-name
|
||||
:vendor/legal-entity-last-name])
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $ ?c
|
||||
:where
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
(some-> session :client :db/id))
|
||||
(pull ?c [:client/code :db/id])
|
||||
(pull ?v vendor-read)
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $ ?c vendor-read
|
||||
:where
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
client-id
|
||||
vendor-read)
|
||||
|
||||
(is-admin? user)
|
||||
(dc/q '[:find
|
||||
(pull ?c [:client/code :db/id])
|
||||
(pull ?v [:db/id
|
||||
:vendor/name
|
||||
{:vendor/legal-entity-1099-type [:db/ident]}
|
||||
{:vendor/legal-entity-tin-type [:db/ident]}
|
||||
{:vendor/address [:address/street1
|
||||
:address/city
|
||||
:address/state
|
||||
:address/zip]}
|
||||
:vendor/legal-entity-tin
|
||||
:vendor/legal-entity-name
|
||||
:vendor/legal-entity-first-name
|
||||
:vendor/legal-entity-middle-name
|
||||
:vendor/legal-entity-last-name])
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $
|
||||
:where
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn))
|
||||
(pull ?c [:client/code :db/id])
|
||||
(pull ?v vendor-read)
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $ vendor-read
|
||||
:where
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
vendor-read)
|
||||
|
||||
:else
|
||||
(dc/q '[:find
|
||||
(pull ?c [:client/code :db/id])
|
||||
(pull ?v [:db/id
|
||||
:vendor/name
|
||||
{:vendor/legal-entity-1099-type [:db/ident]}
|
||||
{:vendor/legal-entity-tin-type [:db/ident]}
|
||||
{:vendor/address [:address/street1
|
||||
:address/city
|
||||
:address/state
|
||||
:address/zip]}
|
||||
:vendor/legal-entity-tin
|
||||
:vendor/legal-entity-name
|
||||
:vendor/legal-entity-first-name
|
||||
:vendor/legal-entity-middle-name
|
||||
:vendor/legal-entity-last-name])
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $ [?c ...]
|
||||
:where
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
clients))]
|
||||
(->> results
|
||||
(filter (fn [[_ _ a]]
|
||||
(>= (or a 0.0) 600.0)))
|
||||
(take 200)
|
||||
(sort-by (fn [[client _ amount]]
|
||||
[(:client/code client ) amount])))))
|
||||
(pull ?c [:client/code :db/id])
|
||||
(pull ?v vendor-read)
|
||||
(sum ?a)
|
||||
:with ?d
|
||||
:in $ [?c ...] vendor-read
|
||||
:where
|
||||
[?p :payment/client ?c]
|
||||
[?p :payment/date ?d ]
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
clients
|
||||
vendor-read))
|
||||
all (->> results
|
||||
(filter (fn [[_ _ a]]
|
||||
(>= (or a 0.0) 600.0)))
|
||||
(sort-by (fn [[client _ amount]]
|
||||
[(:client/code client ) amount]))
|
||||
(into []))
|
||||
paginated (apply-pagination-raw args all)]
|
||||
[(:entries paginated) (:count paginated)]))
|
||||
|
||||
(defn dialog [header content footer]
|
||||
[:div.modal.is-active
|
||||
[:div.modal-background {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]
|
||||
[:div.modal-card
|
||||
[:div.modal-card-head
|
||||
header]
|
||||
[:div.modal-card-body
|
||||
content]
|
||||
[:div.modal-card-foot
|
||||
footer]]
|
||||
[:button.modal-close.is-large {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]])
|
||||
(def grid-page {:id "vendor-table"
|
||||
:nav (com/company-aside-nav)
|
||||
:id-fn (comp :db/id second)
|
||||
:fetch-page (fn [user args]
|
||||
(get-1099-companies user args)
|
||||
#_(r/get-graphql (into args {:id user})))
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"]
|
||||
|
||||
(defn table [{:keys [identity session]} & {:keys [flash-id]}]
|
||||
[:div#vendor-table {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-table
|
||||
:request-method :get)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
"_" (hiccup/raw "on htmx:beforeRequest remove <table/> in me")
|
||||
:hx-swap "outerHTML swap:300ms"}
|
||||
[:table.table.grid.compact.is-fullwidth
|
||||
[:thead
|
||||
[:tr
|
||||
[:th {:style {:width "5em"}}"Client"]
|
||||
[:th "Vendor Name"]
|
||||
[:th "Name"]
|
||||
[:th {:style {:width "9em"}} "1099 Type"]
|
||||
[:th {:style {:width "8em"}} "TIN"]
|
||||
[:th "Address"]
|
||||
[:th "Amount Paid"]
|
||||
[:th {:style {:width "10em"}}]
|
||||
]]
|
||||
[:tbody
|
||||
(for [[client vendor amount] (get-1099-companies identity session)]
|
||||
[:tr (when (= flash-id
|
||||
(:db/id vendor))
|
||||
{:class "live-added"})
|
||||
[:td (:client/code client)]
|
||||
[:td (:vendor/name vendor)]
|
||||
[:td (or (-> vendor :vendor/legal-entity-name not-empty)
|
||||
(str (-> vendor :vendor/legal-entity-first-name) " "
|
||||
(-> vendor :vendor/legal-entity-middle-name) " "
|
||||
(-> vendor :vendor/legal-entity-last-name)))]
|
||||
[:td (some-> vendor :vendor/legal-entity-1099-type :db/ident name)] " "
|
||||
[:td
|
||||
(some-> vendor :vendor/legal-entity-tin-type :db/ident name) " "
|
||||
(-> vendor :vendor/legal-entity-tin)]
|
||||
[:td
|
||||
(-> vendor :vendor/address :address/street1) " "
|
||||
(-> vendor :vendor/address :address/street2) " "
|
||||
(-> vendor :vendor/address :address/city) " "
|
||||
(-> vendor :vendor/address :address/state) " "
|
||||
(-> vendor :vendor/address :address/zip)
|
||||
[:td "$" (Math/round amount)]
|
||||
[:td
|
||||
[:button.button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-dialog
|
||||
:vendor-id (:db/id vendor))
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "innerHTML"}
|
||||
[:span.icon [:i.fa.fa-pencil ]]]]]])]]
|
||||
[:div.container.htmx-indicator
|
||||
[:div.column.is-4.is-offset-4.has-text-centered
|
||||
[:div.loader.is-loading.is-active.big.is-centered]]]])
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099)}
|
||||
"1099 Vendor Info"]]
|
||||
:title "1099 Vendors"
|
||||
:entity-name "Vendors"
|
||||
:route :company-1099-vendor-table
|
||||
:action-buttons (fn [user]
|
||||
nil)
|
||||
:row-buttons (fn [user e]
|
||||
[(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-dialog
|
||||
:vendor-id (:db/id (second e)))
|
||||
"?"
|
||||
(url/map->query {:client-id (:db/id (first e))}))
|
||||
:hx-ext "debug"
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
svg/pencil)])
|
||||
:headers [{:key "Client"
|
||||
:name "Client"
|
||||
:sort-key "client"
|
||||
:render (comp :client/code first)}
|
||||
{:key "vendor-name"
|
||||
:name "Vendor Name"
|
||||
:sort-key "vendor"
|
||||
:render (fn [[_ vendor]]
|
||||
[:div.flex.whitespace-nowrap.items-center.gap-4
|
||||
[:div [:div (:vendor/name vendor)]
|
||||
[:div.text-sm.text-gray-400
|
||||
(or (-> vendor :vendor/legal-entity-name not-empty)
|
||||
(str (-> vendor :vendor/legal-entity-first-name) " "
|
||||
(-> vendor :vendor/legal-entity-middle-name) " "
|
||||
(-> vendor :vendor/legal-entity-last-name)))]]
|
||||
(when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)]
|
||||
(com/pill
|
||||
{:class "text-xs font-medium"
|
||||
:color :primary}
|
||||
(str/capitalize t99-type))
|
||||
)])}
|
||||
{:key "tin"
|
||||
:name "TIN"
|
||||
:sort-key "tin"
|
||||
:show-starting "md"
|
||||
:render (fn [[_ vendor]]
|
||||
[:div.flex.gap-4
|
||||
(when-let [tin (-> vendor :vendor/legal-entity-tin)]
|
||||
[:span {:class "text-xs font-medium py-0.5 "}
|
||||
tin])
|
||||
(when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)]
|
||||
(com/pill {:class "text-xs font-medium"
|
||||
:color :yellow}
|
||||
(name tin-type)))]
|
||||
)}
|
||||
{:key "address"
|
||||
:name "Address"
|
||||
:sort-key "address"
|
||||
:show-starting "lg"
|
||||
:render (fn [[_ vendor]]
|
||||
(if (-> vendor :vendor/address :address/street1)
|
||||
[:div
|
||||
[:div (-> vendor :vendor/address :address/street1)] " "
|
||||
[:div
|
||||
(-> vendor :vendor/address :address/street2)] " "
|
||||
[:div
|
||||
(-> vendor :vendor/address :address/city) " "
|
||||
(-> vendor :vendor/address :address/state) ","
|
||||
(-> vendor :vendor/address :address/zip)]]
|
||||
[:p.text-sm.italic.text-gray-400 "No address"]))}
|
||||
{:key "paid"
|
||||
:name "Paid"
|
||||
:sort-key "paid"
|
||||
:render (fn [[_ _ paid]]
|
||||
(com/pill {:class "text-xs font-medium"
|
||||
:color :primary}
|
||||
"Paid $" (Math/round paid)))}]})
|
||||
|
||||
(defn form-data->map [form-data]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond (and (string? v)
|
||||
(empty? v))
|
||||
acc
|
||||
|
||||
:else
|
||||
(assoc-in acc (->> (str/split k #"_")
|
||||
(mapv #(apply keyword (str/split % #"/"))))
|
||||
v)))
|
||||
{}
|
||||
form-data))
|
||||
|
||||
(defn path->name [k]
|
||||
(cond (keyword? k)
|
||||
(str (namespace k) "/" (name k))
|
||||
(def table* (partial helper/table* grid-page))
|
||||
(def row* (partial helper/row* grid-page))
|
||||
|
||||
(seq k)
|
||||
(str/join "_" (map path->name k))
|
||||
:else k))
|
||||
|
||||
(defn vendor-save [{:keys [form-params identity route-params] :as request}]
|
||||
@(dc/transact conn [(remove-nils
|
||||
(-> (form-data->map form-params)
|
||||
(assoc :db/id (Long/parseLong (:vendor-id route-params)))
|
||||
(update :vendor/legal-entity-1099-type #(some->> % not-empty (keyword "legal-entity-1099-type")))
|
||||
(update :vendor/legal-entity-tin-type #(some->> % not-empty (keyword "legal-entity-tin-type")))))])
|
||||
(html-response
|
||||
(table request :flash-id (Long/parseLong (:vendor-id route-params)))))
|
||||
(defn vendor-save [{:keys [form-params identity route-params query-params] :as request}]
|
||||
(let [client-id (Long/parseLong (get query-params "client-id"))
|
||||
vendor-id (Long/parseLong (:vendor-id route-params))]
|
||||
(assert-can-see-client identity client-id)
|
||||
@(dc/transact conn [(remove-nils
|
||||
(-> (form-data->map form-params)
|
||||
(assoc :db/id (Long/parseLong (:vendor-id route-params)))
|
||||
(update :vendor/legal-entity-1099-type #(some->> % not-empty (keyword "legal-entity-1099-type")))
|
||||
(update :vendor/legal-entity-tin-type #(some->> % not-empty (keyword "legal-entity-tin-type")))))])
|
||||
(html-response
|
||||
|
||||
(row* identity [(dc/pull (dc/db conn) [:db/id :client/code] client-id)
|
||||
(dc/pull (dc/db conn) vendor-read vendor-id)
|
||||
(sum-for-client-vendor client-id vendor-id)
|
||||
] {:flash? true})
|
||||
:headers {"hx-trigger" "closeModal"})))
|
||||
|
||||
(defn vendor-dialog [request]
|
||||
(let [vendor (dc/pull (dc/db conn) '[* {:vendor/legal-entity-1099-type [:db/ident]
|
||||
:vendor/legal-entity-tin-type [:db/ident]}] (Long/parseLong (:vendor-id (:params request))))] ;; TODO perms
|
||||
:vendor/legal-entity-tin-type [:db/ident]}] (Long/parseLong (:vendor-id (:params request))))] ;; TODO perms
|
||||
(html-response
|
||||
[:form {:hx-post (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-save
|
||||
:request-method :post
|
||||
:vendor-id (Long/parseLong (:vendor-id (:params request))))
|
||||
:hx-target "#vendor-table"
|
||||
:hx-swap "outerHTML swap:0.2s"
|
||||
"_" (hiccup/raw "on htmx:afterRequest transition <#modal-holder .modal-background, #modal-holder .modal-card />'s opacity from 1.0 to 0 over 100ms then remove <#modal-holder */> ")}
|
||||
(dialog
|
||||
[:h4.is-4.title "Vendor 1099 Info"]
|
||||
[:div
|
||||
[:h3.is-3.title (:vendor/name vendor)]
|
||||
|
||||
[:h4.is-4.title "Address"]
|
||||
[:hr]
|
||||
[:div.field
|
||||
[:p.help "Street1"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:autofocus true
|
||||
:name (path->name [:vendor/address :address/street1])
|
||||
:placeholder "1700 Pennsylvania Ave"
|
||||
:value (-> vendor :vendor/address :address/street1)}]]]
|
||||
[:div.field
|
||||
[:p.help "Street 2"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:name (path->name [:vendor/address :address/street2])
|
||||
:placeholder "SUite 400"
|
||||
:value (-> vendor :vendor/address :address/street2)}]]]
|
||||
[:div.level
|
||||
[:div.level-left
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "City"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:placeholder "Cupertino"
|
||||
:name (path->name [:vendor/address :address/city])
|
||||
:value (-> vendor :vendor/address :address/city)}]]]]
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "State"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:style {:width "3em"}
|
||||
:placeholder "CA"
|
||||
:name (path->name [:vendor/address :address/state])
|
||||
:value (-> vendor :vendor/address :address/state)}]]]]
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "Zip"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:placeholder "95014"
|
||||
:name (path->name [:vendor/address :address/zip])
|
||||
:value (-> vendor :vendor/address :address/zip)}]]]]]]
|
||||
[:h4.is-4.title "Legal Entity"]
|
||||
[:hr]
|
||||
[:div.field
|
||||
[:p.help "Legal Entity Name"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:placeholder "Good Restaurant LLC"
|
||||
:name (path->name [:vendor/legal-entity-name])
|
||||
:value (-> vendor :vendor/legal-entity-name)}]]]
|
||||
"OR"
|
||||
[:div.level
|
||||
[:div.level-left
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "First Name"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:placeholder "Josh"
|
||||
:name (path->name [:vendor/legal-entity-first-name])
|
||||
:value (-> vendor :vendor/legal-entity-first-name)}]]]]
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "Middle Name"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:placeholder "Caleb"
|
||||
:name (path->name [:vendor/legal-entity-middle-name])
|
||||
:value (-> vendor :vendor/legal-entity-middle-name)}]]]]
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "Last Name"]
|
||||
[:div.control
|
||||
[:input.input.is-expanded {:type "text"
|
||||
:placeholder "Smith"
|
||||
:name (path->name [:vendor/legal-entity-last-name])
|
||||
:value (-> vendor :vendor/legal-entity-last-name)}]]]]]]
|
||||
|
||||
[:div.level
|
||||
[:div.level-left
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "TIN"]
|
||||
[:div.control
|
||||
[:input.input {:type "text"
|
||||
:name (path->name [:vendor/legal-entity-tin])
|
||||
:placeholder "SSN or EIN"
|
||||
:size "12"
|
||||
:value (-> vendor :vendor/legal-entity-tin)}]]]]
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "TIN Type"]
|
||||
[:div.control
|
||||
[:div.select
|
||||
[:select {:name (path->name [:vendor/legal-entity-tin-type])}
|
||||
[:option {:value ""} ""]
|
||||
[:option {:value "ein" :selected (= (-> vendor :vendor/legal-entity-tin-type :db/ident) :legal-entity-tin-type/ein)} "EIN"]
|
||||
[:option {:value "ssn" :selected (= (-> vendor :vendor/legal-entity-tin-type :db/ident) :legal-entity-tin-type/ssn)} "SSN"]]]]]]
|
||||
[:div.level-item
|
||||
[:div.field
|
||||
[:p.help "1099 Type"]
|
||||
[:div.control
|
||||
[:div.select
|
||||
[:select {:name (path->name [:vendor/legal-entity-1099-type])}
|
||||
[:option {:value ""} ""]
|
||||
[:option {:value "none" :selected (= (-> vendor :vendor/legal-entity-1099-type :db/ident) :legal-entity-1099-type/none)} "None"]
|
||||
[:option {:value "misc" :selected (= (-> vendor :vendor/legal-entity-1099-type :db/ident) :legal-entity-1099-type/misc)} "Misc"]
|
||||
[:option {:value "landlord" :selected (= (-> vendor :vendor/legal-entity-1099-type :db/ident) :legal-entity-1099-type/landlord)} "Landlord"]]]]]]]]]
|
||||
[:button.button.is-primary.htmx-indicator "Save"])])))
|
||||
|
||||
(defn vendor-table [request]
|
||||
(html-response (table request)))
|
||||
|
||||
(defn page [{:keys [identity matched-route] :as request}]
|
||||
(base-page
|
||||
request
|
||||
[:div
|
||||
[:div#vendor-table {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-table
|
||||
:request-method :get)
|
||||
:hx-trigger "load, clientSelected from:body"
|
||||
:hx-swap "outerHTML swap:400ms"}
|
||||
[:div.container.htmx-indicator
|
||||
[:div.column.is-4.is-offset-4.has-text-centered
|
||||
[:div.loader.is-loading.is-active.big.is-centered]]]]]
|
||||
[:div
|
||||
(company-side-bar matched-route)]))
|
||||
(com/modal
|
||||
{}
|
||||
[:form {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-save
|
||||
:request-method :post
|
||||
:vendor-id (Long/parseLong (:vendor-id (:params request))))
|
||||
"?"
|
||||
(url/map->query {:client-id (:client-id (:params request))}))
|
||||
:hx-target (format "#vendor-table tr[data-id=\"%d\"]" (:db/id vendor))
|
||||
:hx-swap "outerHTML swap:300ms"}
|
||||
[:fieldset {:class "hx-disable"}
|
||||
(com/modal-card
|
||||
{}
|
||||
[:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name vendor)]]
|
||||
[:div.space-y-6
|
||||
[:div.grid.grid-cols-6.gap-4
|
||||
|
||||
[:h4.text-xl.border-b.col-span-6 "Address"]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Street 1"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/street1])
|
||||
:value (-> vendor :vendor/address :address/street1)
|
||||
:placeholder "1700 Pennsylvania Ave"
|
||||
:autofocus true}))]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Street 2"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/street2])
|
||||
:value (-> vendor :vendor/address :address/street2)
|
||||
:placeholder "Suite 200"}))]
|
||||
[:div.col-span-3
|
||||
(com/field {:label "City"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/city])
|
||||
:value (-> vendor :vendor/address :address/city)
|
||||
:placeholder "Cupertino"}))]
|
||||
[:div.col-span-1
|
||||
(com/field {:label "State"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/state])
|
||||
:value (-> vendor :vendor/address :address/state)
|
||||
:placeholder "CA"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Zip"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/zip])
|
||||
:value (-> vendor :vendor/address :address/zip)
|
||||
:placeholder "98102"}))]
|
||||
[:h4.text-xl.border-b.col-span-6 "Legal Entity"]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Legal Entity Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-name])
|
||||
:value (-> vendor :vendor/legal-entity-name)
|
||||
:placeholder "Good Restaurant LLC"}))]
|
||||
[:div.col-span-6.text-center " - OR -"]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "First Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-first-name])
|
||||
:value (-> vendor :vendor/legal-entity-first-name)
|
||||
:placeholder "John"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Middle Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-middle-name])
|
||||
:value (-> vendor :vendor/legal-entity-middle-name)
|
||||
:placeholder "C."}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Last Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-last-name])
|
||||
:value (-> vendor :vendor/legal-entity-last-name)
|
||||
:placeholder "Riley"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "TIN"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-tin])
|
||||
:value (-> vendor :vendor/legal-entity-tin)
|
||||
:placeholder "John"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "TIN Type"}
|
||||
(com/select {:name (path->name [:vendor/legal-entity-tin-type])
|
||||
:allow-blank? true
|
||||
:value (some-> vendor :vendor/legal-entity-tin-type :db/ident name)
|
||||
:options [["ein" "EIN"]
|
||||
["ssn" "SSN"]]}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "1099 Type"}
|
||||
(com/select {:name (path->name [:vendor/legal-entity-1099-type])
|
||||
:allow-blank? true
|
||||
:value (some-> vendor :vendor/legal-entity-1099-type :db/ident name)
|
||||
:options [["none" "None"]
|
||||
["misc" "Misc"]
|
||||
["landlord" "Landlord"]]}))]
|
||||
[:div.col-span-6
|
||||
(com/button {:color :primary}
|
||||
"Save")]]]
|
||||
[:div])]]))))
|
||||
|
||||
(def vendor-table (partial helper/table grid-page))
|
||||
(def page (partial helper/page grid-page))
|
||||
|
||||
83
src/clj/auto_ap/ssr/company/reports.clj
Normal file
83
src/clj/auto_ap/ssr/company/reports.clj
Normal file
@@ -0,0 +1,83 @@
|
||||
(ns auto-ap.ssr.company.reports
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.reports :as r]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client is-admin?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.grid-page-helper :as helper]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(def grid-page {:id "report-table"
|
||||
:nav (com/company-aside-nav)
|
||||
:id-fn :db/id
|
||||
:fetch-page (fn [user args]
|
||||
(r/get-graphql (into args {:id user})))
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"]
|
||||
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports)}
|
||||
"Reports"]]
|
||||
:title "Reports"
|
||||
:entity-name "Reports"
|
||||
:route :company-reports-table
|
||||
:action-buttons (fn [user]
|
||||
nil)
|
||||
:row-buttons (fn [user e]
|
||||
(com/a-icon-button {:href (:report/url e)}
|
||||
svg/download)[
|
||||
(when (is-admin? user)
|
||||
(com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports-delete
|
||||
:request-method :delete))
|
||||
:hx-target "closest tr"}
|
||||
svg/trash))])
|
||||
:headers [{:key "name"
|
||||
:name "Name"
|
||||
:sort-key "name"
|
||||
:render :report/name}
|
||||
{:key "created-by"
|
||||
:name "Created by"
|
||||
:sort-key "creator"
|
||||
:render (fn [report]
|
||||
(when (:report/creator report)
|
||||
(com/pill {:color :primary }
|
||||
(:report/creator report))))}
|
||||
{:key "created"
|
||||
:name "Created"
|
||||
:sort-key "created"
|
||||
:render #(atime/unparse-local (:report/created %)
|
||||
atime/normal-date)}]})
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
(def table* (partial helper/table* grid-page))
|
||||
(def table (partial helper/table grid-page))
|
||||
(def page (partial helper/page grid-page))
|
||||
|
||||
(defn delete-report [{:keys [form-params identity]}]
|
||||
|
||||
(let [[id-to-delete key] (first (dc/q '[:find ?i ?k
|
||||
:in $ ?i
|
||||
:where [?i :report/key ?k]]
|
||||
(dc/db conn)
|
||||
(some-> (get form-params "id") not-empty Long/parseLong)))
|
||||
report (dc/pull (dc/db conn) r/default-read id-to-delete)]
|
||||
(assert-can-see-client identity (:report/client report))
|
||||
(when id-to-delete
|
||||
(s3/delete-object :bucket-name (:data-bucket env)
|
||||
:key key)
|
||||
@(dc/transact conn [[:db/retractEntity id-to-delete]]))
|
||||
(html-response
|
||||
(row* identity
|
||||
report
|
||||
{:flash? true
|
||||
:delete-after-settle? true}))))
|
||||
|
||||
123
src/clj/auto_ap/ssr/company/yodlee.clj
Normal file
123
src/clj/auto_ap/ssr/company/yodlee.clj
Normal file
@@ -0,0 +1,123 @@
|
||||
(ns auto-ap.ssr.company.yodlee
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.yodlee2 :as yodlee2]
|
||||
[auto-ap.graphql.utils :refer [is-admin?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.grid-page-helper :as helper]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[auto-ap.yodlee.core2 :as yodlee]
|
||||
[bidi.bidi :as bidi]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(def default-read '[:db/id
|
||||
:yodlee-provider-account/last-updated
|
||||
:yodlee-provider-account/status
|
||||
:yodlee-provider-account/id
|
||||
:yodlee-provider-account/detailed-status
|
||||
{:yodlee-provider-account/accounts [:yodlee-account/name :yodlee-account/number]
|
||||
:yodlee-provider-account/client [:client/code]}])
|
||||
|
||||
|
||||
|
||||
(defn fastlink-dialog [{:keys [session]}]
|
||||
(html-response
|
||||
(com/modal
|
||||
{}
|
||||
(com/modal-card
|
||||
{}
|
||||
[:div.flex [:div.p-2 "Yodlee Fastlink"] ]
|
||||
[:div
|
||||
[:div#fa-spot]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw
|
||||
(format "
|
||||
fastlink.open({fastLinkURL: '%s',
|
||||
accessToken: '%s',
|
||||
params: {'configName': 'Aggregation'}},
|
||||
'fa-spot');
|
||||
|
||||
" (:yodlee2-fastlink env) (yodlee/get-access-token (:client/code (:client session)))))]
|
||||
]
|
||||
[:div]))))
|
||||
|
||||
(def grid-page {:id "yodlee-table"
|
||||
:nav (com/company-aside-nav)
|
||||
:id-fn :db/id
|
||||
:fetch-page (fn [user args]
|
||||
(yodlee2/get-graphql (assoc args :id user)))
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"]
|
||||
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee)}
|
||||
"Yodlee"]]
|
||||
:title "Yodlee Accounts"
|
||||
:entity-name "Yodlee accounts"
|
||||
:route :company-yodlee-table
|
||||
:action-buttons (fn [user]
|
||||
[(com/button {:color :primary
|
||||
:on-click "openFastlink()"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee-fastlink-dialog)
|
||||
:hx-target "#modal-holder"}
|
||||
(com/button-icon {} svg/refresh)
|
||||
"Link new account")])
|
||||
:row-buttons (fn [user e]
|
||||
[(when (is-admin? user)
|
||||
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee-provider-account-refresh)
|
||||
:hx-target "closest tr"}
|
||||
svg/refresh))])
|
||||
:headers [{:key "provider-account"
|
||||
:name "Provider Account"
|
||||
:sort-key "provider-account"
|
||||
:render :yodlee-provider-account/id}
|
||||
{:key "status"
|
||||
:name "Status"
|
||||
:sort-key "status"
|
||||
:render #(when-let [status (:yodlee-provider-account/status %)]
|
||||
(com/pill {:color (if (not= status "SUCCESS")
|
||||
:yellow
|
||||
:primary) }
|
||||
status))}
|
||||
{:key "detailed-status"
|
||||
:name "Detailed Status"
|
||||
:sort-key "detailed-status"
|
||||
:render #(when-let [status (:yodlee-provider-account/detailed-status %)]
|
||||
status)}
|
||||
|
||||
{:key "last-updated"
|
||||
:name "Last Updated"
|
||||
:sort-key "last-updated"
|
||||
:render #(atime/unparse-local (:yodlee-provider-account/last-updated %)
|
||||
atime/normal-date)}
|
||||
{:key "accounts"
|
||||
:name "Accounts"
|
||||
:show-starting "md"
|
||||
:render (fn [e]
|
||||
[:ul
|
||||
(for [a (:yodlee-provider-account/accounts e)]
|
||||
[:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]})
|
||||
|
||||
(def page (partial helper/page grid-page))
|
||||
(def table (partial helper/table grid-page))
|
||||
|
||||
;; TODO delete-after-settle
|
||||
(defn refresh-provider-account [{:keys [form-params identity]}]
|
||||
(let [provider-account (dc/pull (dc/db conn) default-read (some-> (get form-params "id") not-empty Long/parseLong))]
|
||||
(yodlee/refresh-provider-account (:client/code (:yodlee-provider-account/client provider-account))
|
||||
(:yodlee-provider-account/id provider-account))
|
||||
(html-response
|
||||
(helper/row*
|
||||
grid-page
|
||||
identity
|
||||
provider-account
|
||||
{:flash? true}))))
|
||||
@@ -1,116 +1,142 @@
|
||||
(ns auto-ap.ssr.company-dropdown
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client]]
|
||||
[iol-ion.query :refer [can-see-client?]]
|
||||
[auto-ap.datomic :refer [conn pull-many]]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client cleanse-query]]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components.navbar-dropdown :refer [navbar-dropdown]]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[bidi.bidi :as bidi]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
[hiccup2.core :as hiccup]
|
||||
[iol-ion.query :refer [can-see-client?]]))
|
||||
|
||||
(defn dropdown-contents [{:keys [identity]}]
|
||||
(let [options (->> (dc/q '[:find ?c ?n
|
||||
:in $ ?user
|
||||
:where [?c :client/name ?n]
|
||||
[(iol-ion.query/can-see-client? ?user ?c)]]
|
||||
(dc/db conn)
|
||||
identity)
|
||||
(map (fn [[k v]]
|
||||
{"key" k
|
||||
"value" v})))]
|
||||
(html-response
|
||||
[:div.navbar-dropdown {:style {:width "20em"}}
|
||||
[:a.navbar-item {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "click"}
|
||||
"All"]
|
||||
[:hr.navbar-divider]
|
||||
[:input#company-search.input.navbar-item {:placeholder "Company name"
|
||||
:name "search-text"
|
||||
:autoFocus true} ]
|
||||
[:input#company-search-value {:type "hidden"
|
||||
:autocomplete "off"
|
||||
:name "search-client"
|
||||
:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "change"} ]
|
||||
[:script
|
||||
(hiccup/raw
|
||||
(str "
|
||||
var z = new autoComplete({
|
||||
selector:\"#company-search\",
|
||||
placeholder: \"Company Name....\",
|
||||
data: {
|
||||
keys: [\"value\"],
|
||||
src: " (cheshire.core/encode
|
||||
options)
|
||||
(defn dropdown-search-results* [{:keys [options]}]
|
||||
[:ul
|
||||
(for [{:keys [id name]} options]
|
||||
[:li
|
||||
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
|
||||
|
||||
"
|
||||
},
|
||||
resultItem: {
|
||||
highlight:true,
|
||||
class: \"autocomplete-suggestion\",
|
||||
selected: \"highlighted\"
|
||||
[:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
|
||||
"_" (hiccup/raw "on click set value of <#company-search-value/> to @data-value then send selected to #company-dropdown")
|
||||
:data-value id}
|
||||
name]]])])
|
||||
|
||||
},
|
||||
resultsList: {
|
||||
tabSelect: true
|
||||
},
|
||||
submit: true
|
||||
|
||||
});
|
||||
z.input.addEventListener(\"selection\", function (event) {
|
||||
z.input.blur();
|
||||
z.input.value = event.detail.selection.value.value;
|
||||
document.getElementById(\"company-search-value\").value= event.detail.selection.value.key;
|
||||
document.getElementById(\"company-search-value\").dispatchEvent(new Event('change'));
|
||||
|
||||
});
|
||||
"))]])))
|
||||
(defn get-clients [identity query]
|
||||
(if-let [query (not-empty (cleanse-query query))]
|
||||
(let [search-query (str "name:(" query ")")]
|
||||
|
||||
(defn dropdown [request]
|
||||
(for [n (pull-many (dc/db conn) [:client/name :db/id]
|
||||
(for [{:keys [id name]} (solr/query solr/impl "clients" {"query" search-query
|
||||
"fields" "id, name"})
|
||||
:let [client-id (Long/parseLong id)]
|
||||
:when (can-see-client? identity client-id)]
|
||||
client-id))]
|
||||
{:id (:db/id n)
|
||||
:name (:client/name n)}))
|
||||
[]))
|
||||
|
||||
(let [client (get-in request [:session :client])]
|
||||
(defn dropdown-search-results [{:keys [identity] :as request}]
|
||||
(html-response
|
||||
(dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))
|
||||
:client (:client (:session request))})))
|
||||
|
||||
(navbar-dropdown
|
||||
"company-dropdown"
|
||||
[:span
|
||||
(if client
|
||||
(str "Company: " (:client/name client))
|
||||
"Company")
|
||||
[:script
|
||||
(hiccup/raw
|
||||
"localStorage.setItem(\"last-client-id\", \""(:db/id client)"\")")]]
|
||||
[:div {:hx-get
|
||||
(bidi/path-for ssr-routes/only-routes
|
||||
:company-dropdown-contents)
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "intersect delay:150ms"
|
||||
:hx-target "closest .navbar-dropdown"
|
||||
:style {:width "20em"
|
||||
:height "80px"}
|
||||
}
|
||||
[:div.loader.is-loading.is-active.is-centered]])))
|
||||
(defn dropdown [{:keys [client]}]
|
||||
[:div#company-dropdown
|
||||
{:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-include "#company-search-value"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "selected"}
|
||||
[:script
|
||||
(hiccup/raw
|
||||
"localStorage.setItem(\"last-client-id\", \""(:db/id client)"\")")]
|
||||
[:div
|
||||
[:button#company-dropdown-button { :class "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
:type "button"}
|
||||
(if client
|
||||
(:client/name client)
|
||||
"All Companies")
|
||||
[:div.w-4.h-4.ml-2
|
||||
svg/drop-down]]
|
||||
[:div#company-dropdown-list.hidden {"_" (hiccup/raw "init call initCompanyDropdown()")
|
||||
}
|
||||
[:div {:class "z-10 bg-white rounded-lg shadow w-64 dark:bg-gray-700 slide-up duration-500 transition-all"}
|
||||
[:div {:class "p-3"}
|
||||
[:label {:for "input-group-search", :class "sr-only"} "Search"]
|
||||
[:div {:class "relative"}
|
||||
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"}
|
||||
[:div.w-5.h-5.text-gray-500.dark:text-gray-400
|
||||
svg/search]]
|
||||
[:input#company-search {:placeholder "Company name"
|
||||
:name "search-text"
|
||||
:class "block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
:autoFocus true
|
||||
:tab-index -1
|
||||
:hx-trigger "keyup changed delay:500ms, search"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-dropdown-search-results)
|
||||
:hx-target "#company-search-results"
|
||||
:hx-swap "innerHTML"} ]]
|
||||
[:input#company-search-value {:type "hidden"
|
||||
:name "search-client"}]]
|
||||
[:div.divide-y.divide-gray-100
|
||||
[:div#company-search-results {:class "h-48 px-3 pb-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}]
|
||||
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
|
||||
|
||||
[:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
|
||||
:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "click"}
|
||||
"All"]]]
|
||||
]]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw
|
||||
"
|
||||
function initCompanyDropdown() {
|
||||
var $dropdownTargetEl = document.getElementById('company-dropdown-list');
|
||||
|
||||
// set the element that trigger the dropdown menu on click
|
||||
var $dropdownTriggerEl = document.getElementById('company-dropdown-button');
|
||||
|
||||
var dropdownOptions = {
|
||||
placement: 'bottom',
|
||||
triggerType: 'click',
|
||||
offsetSkidding: 0,
|
||||
offsetDistance: 10,
|
||||
delay: 300,
|
||||
onHide: () => {
|
||||
},
|
||||
onShow: () => {
|
||||
document.getElementById('company-search').focus()
|
||||
},
|
||||
onToggle: () => {
|
||||
}
|
||||
};
|
||||
var companyDrowdown = new Dropdown($dropdownTargetEl, $dropdownTriggerEl, dropdownOptions);
|
||||
}
|
||||
")]]])
|
||||
|
||||
(defn active-client [{:keys [identity params] :as request}]
|
||||
(let [client-id (some-> (or (:search-client params) (get params "search-client")) not-empty Long/parseLong)]
|
||||
(println (format "HERE CLIENT ID '%s'" client-id))
|
||||
(when client-id
|
||||
(assert-can-see-client identity client-id))
|
||||
(let [new-session (assoc (:session request) :client
|
||||
(when client-id
|
||||
(dc/pull (dc/db conn) [:db/id :client/name :client/code] client-id)))]
|
||||
(assoc
|
||||
(html-response
|
||||
(dropdown (assoc request :session new-session)))
|
||||
:session
|
||||
new-session
|
||||
:headers
|
||||
{"hx-trigger" "clientSelected"}))))
|
||||
(assoc
|
||||
(html-response
|
||||
(dropdown {:client (:client new-session)
|
||||
:identity identity}))
|
||||
:session
|
||||
new-session
|
||||
:headers
|
||||
{"hx-trigger" "clientSelected"}))))
|
||||
|
||||
56
src/clj/auto_ap/ssr/components.clj
Normal file
56
src/clj/auto_ap/ssr/components.clj
Normal file
@@ -0,0 +1,56 @@
|
||||
(ns auto-ap.ssr.components
|
||||
(:require [auto-ap.ssr.components.breadcrumbs :as breadcrumbs]
|
||||
[auto-ap.ssr.components.buttons :as buttons]
|
||||
[auto-ap.ssr.components.dialog :as dialog]
|
||||
[auto-ap.ssr.components.inputs :as inputs]
|
||||
[auto-ap.ssr.components.aside :as aside]
|
||||
[auto-ap.ssr.components.card :as card]
|
||||
[auto-ap.ssr.components.navbar :as navbar]
|
||||
[auto-ap.ssr.components.page :as page]
|
||||
[auto-ap.ssr.components.data-grid :as data-grid]
|
||||
[auto-ap.ssr.components.tags :as tags]
|
||||
[auto-ap.ssr.components.paginator :as paginator]))
|
||||
|
||||
|
||||
(def breadcrumbs breadcrumbs/breadcrumbs-)
|
||||
(def button buttons/button-)
|
||||
(def button-icon buttons/button-icon-)
|
||||
(def icon-button buttons/icon-button-)
|
||||
(def a-icon-button buttons/a-icon-button-)
|
||||
(def modal dialog/modal-)
|
||||
(def modal-card dialog/modal-card-)
|
||||
|
||||
(def text-input inputs/text-input-)
|
||||
(def select inputs/select-)
|
||||
(def field inputs/field-)
|
||||
|
||||
(def left-aside aside/left-aside-)
|
||||
(def company-aside-nav aside/company-aside-nav-)
|
||||
(def admin-aside-nav aside/admin-aside-nav-)
|
||||
(def main-aside-nav aside/main-aside-nav-)
|
||||
(def content-card card/content-card-)
|
||||
(def card card/card-)
|
||||
|
||||
(def navbar navbar/navbar-)
|
||||
|
||||
(def page page/page-)
|
||||
|
||||
(def pill tags/pill-)
|
||||
|
||||
(def data-grid data-grid/data-grid-)
|
||||
(def data-grid-header data-grid/header-)
|
||||
(def data-grid-sort-header data-grid/sort-header-)
|
||||
(def data-grid-row data-grid/row-)
|
||||
(def data-grid-cell data-grid/cell-)
|
||||
(def data-grid-right-stack-cell data-grid/right-stack-cell-)
|
||||
|
||||
(defn link [params & children]
|
||||
(into [:a (update params :class str " font-medium text-blue-600 dark:text-blue-500 hover:underline ")]
|
||||
children))
|
||||
|
||||
|
||||
|
||||
|
||||
(def paginator paginator/paginator-)
|
||||
(def data-grid-card data-grid/data-grid-card-)
|
||||
|
||||
326
src/clj/auto_ap/ssr/components/aside.clj
Normal file
326
src/clj/auto_ap/ssr/components/aside.clj
Normal file
@@ -0,0 +1,326 @@
|
||||
(ns auto-ap.ssr.components.aside
|
||||
(:require [auto-ap.ssr.svg :as svg]
|
||||
[hiccup2.core :as hiccup]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.client-routes :as client-routes]))
|
||||
|
||||
(defn menu-button- [params & children]
|
||||
[:div
|
||||
[:a (-> params
|
||||
(dissoc :icon)
|
||||
(assoc :type "button")
|
||||
(update :class str " cursor-pointer flex items-center p-2 w-full text-sm text-gray-600 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700")
|
||||
(assoc :hx-indicator "find .htmx-indicator")
|
||||
(assoc :hx-select "#app-contents")
|
||||
(assoc :hx-target "#app-contents")
|
||||
(assoc :hx-swap "outerHTML"))
|
||||
|
||||
(when (:icon params)
|
||||
[:span {:class "flex-shrink-0 w-6 h-6 text-gray-400 transition duration-75 group-hover:text-blue-500 dark:text-gray-400 group-hover:scale-110 dark:group-hover:text-white mr-3"}
|
||||
(:icon params)])
|
||||
|
||||
(into [:span {:class "flex-1 text-left whitespace-nowrap text-gray-600 dark:text-white"}] children)
|
||||
(when (:data-collapse-toggle params)
|
||||
[:svg {:aria-hidden "true", :class "w-6 h-6", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", :clip-rule "evenodd"}]])
|
||||
[:div.htmx-indicator.flex.items-center
|
||||
(svg/spinner-primary {:class "inline w-4 h-4 text-white"})]]])
|
||||
|
||||
(defn sub-menu- [params & children]
|
||||
[:ul {:id (:id params) :class "hidden py-2 space-y-2"}
|
||||
(for [c children]
|
||||
[:li
|
||||
(update-in c [1 1 :class ] str " flex items-center p-2 pl-11 w-full text-base font-normal text-gray-900 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700")])])
|
||||
|
||||
(defn left-aside- [{:keys [nav page-specific]} & children]
|
||||
[:aside {:id "left-nav", :class "fixed top-0 left-0 pt-16 z-20 w-64 h-screen transition-transform -translate-x-full lg:translate-x-0", :aria-labelledby "left-nav" :aria-hidden "true"
|
||||
"_" (hiccup/raw "init call initSidebarToggle()")}
|
||||
|
||||
[:div {:class "overflow-y-auto py-5 px-3 h-full bg-gray-50 border-r border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
|
||||
nav
|
||||
|
||||
[:ul {:class "pt-5 mt-5 space-y-2 border-t border-gray-200 dark:border-gray-700"}
|
||||
#_[:li
|
||||
[:a {:href "#", :class "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg transition duration-75 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white group"}
|
||||
[:svg {:aria-hidden "true", :class "flex-shrink-0 w-6 h-6 text-gray-400 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"}]
|
||||
[:path {:fill-rule "evenodd", :d "M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z", :clip-rule "evenodd"}]]
|
||||
[:span {:class "ml-3"} "Docs"]]]
|
||||
#_[:li
|
||||
[:a {:href "#", :class "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg transition duration-75 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white group"}
|
||||
[:svg {:aria-hidden "true", :class "flex-shrink-0 w-6 h-6 text-gray-400 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"}]]
|
||||
[:span {:class "ml-3"} "Components"]]]
|
||||
#_[:li
|
||||
[:a {:href "#", :class "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg transition duration-75 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white group"}
|
||||
[:svg {:aria-hidden "true", :class "flex-shrink-0 w-6 h-6 text-gray-400 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-2 0c0 .993-.241 1.929-.668 2.754l-1.524-1.525a3.997 3.997 0 00.078-2.183l1.562-1.562C15.802 8.249 16 9.1 16 10zm-5.165 3.913l1.58 1.58A5.98 5.98 0 0110 16a5.976 5.976 0 01-2.516-.552l1.562-1.562a4.006 4.006 0 001.789.027zm-4.677-2.796a4.002 4.002 0 01-.041-2.08l-.08.08-1.53-1.533A5.98 5.98 0 004 10c0 .954.223 1.856.619 2.657l1.54-1.54zm1.088-6.45A5.974 5.974 0 0110 4c.954 0 1.856.223 2.657.619l-1.54 1.54a4.002 4.002 0 00-2.346.033L7.246 4.668zM12 10a2 2 0 11-4 0 2 2 0 014 0z", :clip-rule "evenodd"}]]
|
||||
[:span {:class "ml-3"} "Help"]]]]
|
||||
page-specific]
|
||||
#_[:div {:class "hidden absolute bottom-0 left-0 justify-center p-4 space-x-4 w-full lg:flex bg-white dark:bg-gray-800 z-20 border-r border-gray-200 dark:border-gray-700"}
|
||||
[:a {:href "#", :class "inline-flex justify-center p-2 text-gray-500 rounded cursor-pointer dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600"}
|
||||
[:svg {:aria-hidden "true", :class "w-6 h-6", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM16 3a1 1 0 011 1v7.268a2 2 0 010 3.464V16a1 1 0 11-2 0v-1.268a2 2 0 010-3.464V4a1 1 0 011-1z"}]]]
|
||||
[:a {:href "#", :data-tooltip-target "tooltip-settings", :class "inline-flex justify-center p-2 text-gray-500 rounded cursor-pointer dark:text-gray-400 dark:hover:text-white hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600"}
|
||||
[:svg {:aria-hidden "true", :class "w-6 h-6", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z", :clip-rule "evenodd"}]]]
|
||||
[:div {:id "tooltip-settings", :role "tooltip", :class "inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip"} "Settings page"]
|
||||
[:button {:type "button", :data-dropdown-toggle "language-dropdown", :class "inline-flex justify-center p-2 text-gray-500 rounded cursor-pointer dark:hover:text-white dark:text-gray-400 hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600"}
|
||||
[:svg {:aria-hidden "true", :class "h-5 w-5 rounded-full mt-0.5", :xmlns "http://www.w3.org/2000/svg", :xmlns:xlink "http://www.w3.org/1999/xlink", :viewbox "0 0 3900 3900"}
|
||||
[:path {:fill "#b22234", :d "M0 0h7410v3900H0z"}]
|
||||
[:path {:d "M0 450h7410m0 600H0m0 600h7410m0 600H0m0 600h7410m0 600H0", :stroke "#fff", :stroke-width "300"}]
|
||||
[:path {:fill "#3c3b6e", :d "M0 0h2964v2100H0z"}]
|
||||
[:g {:fill "#fff"}
|
||||
[:g {:id "d"}
|
||||
[:g {:id "c"}
|
||||
[:g {:id "e"}
|
||||
[:g {:id "b"}
|
||||
[:path {:id "a", :d "M247 90l70.534 217.082-184.66-134.164h228.253L176.466 307.082z"}]
|
||||
[:use {:xlink:href "#a", :y "420"}]
|
||||
[:use {:xlink:href "#a", :y "840"}]
|
||||
[:use {:xlink:href "#a", :y "1260"}]]
|
||||
[:use {:xlink:href "#a", :y "1680"}]]
|
||||
[:use {:xlink:href "#b", :x "247", :y "210"}]]
|
||||
[:use {:xlink:href "#c", :x "494"}]]
|
||||
[:use {:xlink:href "#d", :x "988"}]
|
||||
[:use {:xlink:href "#c", :x "1976"}]
|
||||
[:use {:xlink:href "#e", :x "2470"}]]]]
|
||||
[:div {:class "hidden z-50 my-4 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700", :id "language-dropdown"}
|
||||
[:ul {:class "py-1", :role "none"}
|
||||
[:li
|
||||
[:a {:href "#", :class "block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600", :role "menuitem"}
|
||||
[:div {:class "inline-flex items-center"}
|
||||
[:svg {:aria-hidden "true", :class "h-3.5 w-3.5 rounded-full mr-2", :xmlns "http://www.w3.org/2000/svg", :id "flag-icon-css-us", :viewbox "0 0 512 512"}
|
||||
[:g {:fill-rule "evenodd"}
|
||||
[:g {:stroke-width "1pt"}
|
||||
[:path {:fill "#bd3d44", :d "M0 0h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0z", :transform "scale(3.9385)"}]
|
||||
[:path {:fill "#fff", :d "M0 10h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0zm0 20h247v10H0z", :transform "scale(3.9385)"}]]
|
||||
[:path {:fill "#192f5d", :d "M0 0h98.8v70H0z", :transform "scale(3.9385)"}]
|
||||
[:path {:fill "#fff", :d "M8.2 3l1 2.8H12L9.7 7.5l.9 2.7-2.4-1.7L6 10.2l.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8H45l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7L74 8.5l-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9L92 7.5l1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm-74.1 7l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7H65zm16.4 0l1 2.8H86l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm-74 7l.8 2.8h3l-2.4 1.7.9 2.7-2.4-1.7L6 24.2l.9-2.7-2.4-1.7h3zm16.4 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8H45l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9L92 21.5l1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm-74.1 7l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7H65zm16.4 0l1 2.8H86l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm-74 7l.8 2.8h3l-2.4 1.7.9 2.7-2.4-1.7L6 38.2l.9-2.7-2.4-1.7h3zm16.4 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8H45l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9L92 35.5l1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm-74.1 7l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7H65zm16.4 0l1 2.8H86l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm-74 7l.8 2.8h3l-2.4 1.7.9 2.7-2.4-1.7L6 52.2l.9-2.7-2.4-1.7h3zm16.4 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8H45l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9L92 49.5l1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm-74.1 7l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7H65zm16.4 0l1 2.8H86l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm-74 7l.8 2.8h3l-2.4 1.7.9 2.7-2.4-1.7L6 66.2l.9-2.7-2.4-1.7h3zm16.4 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8H45l-2.4 1.7 1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9zm16.4 0l1 2.8h2.8l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h3zm16.5 0l.9 2.8h2.9l-2.3 1.7.9 2.7-2.4-1.7-2.3 1.7.9-2.7-2.4-1.7h2.9zm16.5 0l.9 2.8h2.9L92 63.5l1 2.7-2.4-1.7-2.4 1.7 1-2.7-2.4-1.7h2.9z", :transform "scale(3.9385)"}]]] " \n English (US)"]]]
|
||||
[:li
|
||||
[:a {:href "#", :class "block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:text-white dark:hover:bg-gray-600", :role "menuitem"}
|
||||
[:div {:class "inline-flex items-center"}
|
||||
[:svg {:aria-hidden "true", :class "h-3.5 w-3.5 rounded-full mr-2", :xmlns "http://www.w3.org/2000/svg", :id "flag-icon-css-de", :viewbox "0 0 512 512"}
|
||||
[:path {:fill "#ffce00", :d "M0 341.3h512V512H0z"}]
|
||||
[:path {:d "M0 0h512v170.7H0z"}]
|
||||
[:path {:fill "#d00", :d "M0 170.7h512v170.6H0z"}]]]]]
|
||||
[:li
|
||||
[:a {:href "#", :class "block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:text-white dark:hover:bg-gray-600", :role "menuitem"}
|
||||
[:div {:class "inline-flex items-center"}
|
||||
[:svg {:aria-hidden "true", :class "h-3.5 w-3.5 rounded-full mr-2", :xmlns "http://www.w3.org/2000/svg", :id "flag-icon-css-it", :viewbox "0 0 512 512"}
|
||||
[:g {:fill-rule "evenodd", :stroke-width "1pt"}
|
||||
[:path {:fill "#fff", :d "M0 0h512v512H0z"}]
|
||||
[:path {:fill "#009246", :d "M0 0h170.7v512H0z"}]
|
||||
[:path {:fill "#ce2b37", :d "M341.3 0H512v512H341.3z"}]]]]]]
|
||||
[:li
|
||||
[:a {:href "#", :class "block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600", :role "menuitem"}
|
||||
[:div {:class "inline-flex items-center"}
|
||||
[:svg {:aria-hidden "true", :class "h-3.5 w-3.5 rounded-full mr-2", :xmlns "http://www.w3.org/2000/svg", :xmlns:xlink "http://www.w3.org/1999/xlink", :id "flag-icon-css-cn", :viewbox "0 0 512 512"}
|
||||
[:defs
|
||||
[:path {:id "a", :fill "#ffde00", :d "M1-.3L-.7.8 0-1 .6.8-1-.3z"}]]
|
||||
[:path {:fill "#de2910", :d "M0 0h512v512H0z"}]
|
||||
[:use {:width "30", :height "20", :transform "matrix(76.8 0 0 76.8 128 128)", :xlink:href "#a"}]
|
||||
[:use {:width "30", :height "20", :transform "rotate(-121 142.6 -47) scale(25.5827)", :xlink:href "#a"}]
|
||||
[:use {:width "30", :height "20", :transform "rotate(-98.1 198 -82) scale(25.6)", :xlink:href "#a"}]
|
||||
[:use {:width "30", :height "20", :transform "rotate(-74 272.4 -114) scale(25.6137)", :xlink:href "#a"}]
|
||||
[:use {:width "30", :height "20", :transform "matrix(16 -19.968 19.968 16 256 230.4)", :xlink:href "#a"}]] "中文 (繁體)"]]]]]]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw "
|
||||
function initSidebarToggle() {
|
||||
var $targetEl = document.getElementById('left-nav');
|
||||
|
||||
var $triggerEl = document.getElementById('left-nav-toggle');
|
||||
|
||||
var options = {
|
||||
onCollapse: () => {
|
||||
document.getElementById('main-content').classList.remove('lg:pl-64')
|
||||
},
|
||||
onExpand: () => {
|
||||
document.getElementById('main-content').classList.add('lg:pl-64')
|
||||
},
|
||||
onToggle: () => {
|
||||
}
|
||||
};
|
||||
|
||||
var collapse = new Collapse($targetEl, $triggerEl, options);
|
||||
}
|
||||
")]])
|
||||
|
||||
(defn main-aside-nav- []
|
||||
[:ul {:class "space-y-2"}
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/pie
|
||||
:href "/"}
|
||||
"Dashboard")]
|
||||
[:li
|
||||
(menu-button- {:aria-controls "dropdown-invoices",
|
||||
:data-collapse-toggle "dropdown-invoices"
|
||||
:icon svg/accounting-invoice-mail}
|
||||
"Invoices")
|
||||
(sub-menu- {:id "dropdown-invoices"}
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:invoices)}
|
||||
"All")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:paid-invoices)}
|
||||
"Paid")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:unpaid-invoices)}
|
||||
"Unpaid")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:voided-invoices)}
|
||||
"Voided"))]
|
||||
[:li
|
||||
(menu-button- {:aria-controls "dropdown-sales",
|
||||
:data-collapse-toggle "dropdown-sales"
|
||||
:icon svg/receipt-register-1}
|
||||
"Sales")
|
||||
(sub-menu- {:id "dropdown-sales"}
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:sales-orders)} "Sales")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:expected-deposits)} "Expected Deposits")
|
||||
#_(menu-button- {:href "Sales"} "Cash Shifts")
|
||||
#_(menu-button- {:href "Sales"} "Tenders"))]
|
||||
[:li
|
||||
(menu-button- {:aria-controls "dropdown-payments"
|
||||
:data-collapse-toggle "dropdown-payments"
|
||||
:icon svg/payments}
|
||||
"Payments")
|
||||
(sub-menu- {:id "dropdown-payments"}
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:payments)} "All")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:payments)} "Pending")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:payments)} "Cleared")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:payments)} "Voided"))]
|
||||
|
||||
[:li
|
||||
(menu-button- {:aria-controls "dropdown-transactions"
|
||||
:data-collapse-toggle "dropdown-transactions"
|
||||
:icon svg/bank}
|
||||
"Transactions")
|
||||
|
||||
(sub-menu- {:id "dropdown-transactions"}
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:transactions)} "All")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:unapproved-transactions)} "Unapproved")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:requires-feedback-transactions)} "Client Review")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:approved-transactions)} "Approved")
|
||||
(menu-button- {:href (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insights)} "Insights"))]
|
||||
[:li
|
||||
(menu-button- {:aria-controls "dropdown-ledger"
|
||||
:data-collapse-toggle "dropdown-ledger"
|
||||
:icon svg/receipt}
|
||||
"Ledger")
|
||||
(sub-menu- {:id "dropdown-ledger"}
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:ledger)} "Register")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:profit-and-loss)} "Profit & Loss")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:profit-and-loss-detail)} "Profit & Loss Detail")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:cash-flows)} "Cash Flows")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:balance-sheet)} "Balance Sheet")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:external-import-ledger)} "External Ledger Import"))]])
|
||||
|
||||
|
||||
(defn company-aside-nav- []
|
||||
[:ul {:class "space-y-2" :hx-boost "true"}
|
||||
[:li
|
||||
(menu-button- {:icon svg/vendors
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/report
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports)}
|
||||
"Reports")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/bank
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee)}
|
||||
"Yodlee Link")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/government-building
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099)}
|
||||
"1099 Vendor Info"
|
||||
)]])
|
||||
|
||||
(defn admin-aside-nav- []
|
||||
[:ul {:class "space-y-2"}
|
||||
[:li
|
||||
(menu-button- {:icon svg/dashboard
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin)}
|
||||
"Dashboard")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/restaurant
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin-clients)}
|
||||
"Clients")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/vendors
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin-vendors)}
|
||||
"Vendors")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/user
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin-users)}
|
||||
"Users")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/accounts
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin-accounts)}
|
||||
"Accounts")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/cog
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin-rules)}
|
||||
"Rules")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/question
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)
|
||||
:hx-boost "true"}
|
||||
"History")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/rabbit
|
||||
:href (bidi/path-for client-routes/routes
|
||||
:admin-jobs)}
|
||||
"Background Jobs")]
|
||||
[:li
|
||||
(menu-button- {:aria-controls "dropdown-import"
|
||||
:data-collapse-toggle "dropdown-import"
|
||||
:icon svg/arrow-in}
|
||||
"Import")
|
||||
|
||||
(sub-menu- {:id "dropdown-import"}
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:admin-excel-import)} "Excel Invoices")
|
||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||
:admin-import-batches)} "Import Batches")
|
||||
(menu-button- {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin-ezcater-xls)
|
||||
:hx-boost "true"} "EZCater XLS Import"))]])
|
||||
|
||||
|
||||
22
src/clj/auto_ap/ssr/components/breadcrumbs.clj
Normal file
22
src/clj/auto_ap/ssr/components/breadcrumbs.clj
Normal file
@@ -0,0 +1,22 @@
|
||||
(ns auto-ap.ssr.components.breadcrumbs
|
||||
(:require [auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn breadcrumbs- [params & steps]
|
||||
[:div {:class "inline-flex px-5 py-3 text-gray-700 border border-gray-200 rounded-lg bg-white dark:bg-gray-800 dark:border-gray-700"}
|
||||
[:ul {:class "inline-flex items-center space-x-1 md:space-x-3"}
|
||||
[:li {:class "inline-flex items-center"}
|
||||
[:a {:href "#", :class "inline-flex w-4 h-4 mr-2 items-center text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white"}
|
||||
[:div.w-4.h-4 svg/home]]]
|
||||
(for [p steps]
|
||||
[:li
|
||||
[:div {:class "flex items-center"}
|
||||
|
||||
[:div {:class "w-6 h-6 text-gray-400",}
|
||||
svg/breadcrumb-component]
|
||||
|
||||
(update-in p [1 :class] str " ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2 dark:text-gray-400 dark:hover:text-white")]])
|
||||
#_[:li {:aria-current "page"}
|
||||
[:div {:class "flex items-center"}
|
||||
[:svg {:aria-hidden "true", :class "w-6 h-6 text-gray-400", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", :clip-rule "evenodd"}]]
|
||||
[:span {:class "ml-1 text-sm font-medium text-gray-500 md:ml-2 dark:text-gray-400"} "Flowbite"]]]]])
|
||||
46
src/clj/auto_ap/ssr/components/buttons.clj
Normal file
46
src/clj/auto_ap/ssr/components/buttons.clj
Normal file
@@ -0,0 +1,46 @@
|
||||
(ns auto-ap.ssr.components.buttons
|
||||
(:require [auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn button-icon- [_ i]
|
||||
[:div.h-4.w-4 i])
|
||||
|
||||
(defn button- [params & children]
|
||||
[:button (update params
|
||||
:class #(cond-> %
|
||||
true (str " text-white focus:ring-4 font-bold rounded-lg text-sm px-5 py-2.5 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center")
|
||||
(= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700")
|
||||
(= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 ")))
|
||||
[:div.htmx-indicator.flex.items-center
|
||||
(svg/spinner {:class "inline w-4 h-4 text-white"})
|
||||
[:div.ml-3 "Loading..."]]
|
||||
(into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center ] children)])
|
||||
|
||||
(defn icon-button- [params & children]
|
||||
(into
|
||||
[:button
|
||||
|
||||
(update params :class
|
||||
#(cond-> %
|
||||
true (str " inline-flex items-center justify-center items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center rounded-lg ")
|
||||
(= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700")
|
||||
(= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 ")
|
||||
(= :primary-light (:color params)) (str " bg-green-200 hover:bg-green-300 focus:ring-green-200 dark:bg-green-700 dark:hover:bg-green-600 text-gray-800 dark:text-gray-200")
|
||||
(= :secondary-light (:color params)) (str " bg-blue-200 hover:bg-blue-300 focus:ring-blue-200 dark:bg-blue-700 dark:hover:bg-blue-600 text-gray-800 dark:text-gray-200")
|
||||
(= :danger-light (:color params)) (str " bg-red-200 hover:bg-red-300 focus:ring-red-200 dark:bg-red-700 dark:hover:bg-red-600 text-gray-800 dark:text-gray-200")
|
||||
(nil? (:color params))
|
||||
(str " bg-white dark:bg-gray-600 border-gray-300 dark:border-gray-700 text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-100")))
|
||||
[:div.htmx-indicator.flex.items-center
|
||||
(svg/spinner {:class "inline w-4 h-4 text-white"})]
|
||||
[:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center (into [:div.h-4.w-4] children)]]))
|
||||
|
||||
(defn a-icon-button- [params & children]
|
||||
(into
|
||||
[:a (update params :class str " inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100")
|
||||
[:div.h-4.w-4 children]]))
|
||||
|
||||
(defn save-button- [params & children]
|
||||
[:button { :class "text-white bg-green-500 hover:bg-green-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800 inline-flex items-center hover:scale-105 transition duration-300"}
|
||||
[:div.htmx-indicator.flex.items-center
|
||||
(svg/spinner {:class "inline w-4 h-4 text-white"})
|
||||
[:div.ml-3 "Loading..."]]
|
||||
(into [:div.htmx-indicator-hidden ] children)])
|
||||
12
src/clj/auto_ap/ssr/components/card.clj
Normal file
12
src/clj/auto_ap/ssr/components/card.clj
Normal file
@@ -0,0 +1,12 @@
|
||||
(ns auto-ap.ssr.components.card)
|
||||
|
||||
(defn card- [params & children]
|
||||
(into [:div (update params :class str " shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white overflow-hidden")]
|
||||
children))
|
||||
|
||||
(defn content-card- [params & children]
|
||||
[:section {:class " py-3 sm:py-5"}
|
||||
[:div {:class "max-w-screen-2xl"}
|
||||
(into
|
||||
[:div {:class "relative overflow-hidden shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white"}]
|
||||
children)]])
|
||||
100
src/clj/auto_ap/ssr/components/data_grid.clj
Normal file
100
src/clj/auto_ap/ssr/components/data_grid.clj
Normal file
@@ -0,0 +1,100 @@
|
||||
(ns auto-ap.ssr.components.data-grid
|
||||
(:require
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components.card :refer [content-card-]]
|
||||
[auto-ap.ssr.components.paginator :refer [paginator-]]
|
||||
[bidi.bidi :as bidi]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(defn header- [params & rest]
|
||||
(into [:th.px-4.py-3 {:scope "col" :class (:class params)
|
||||
"_" (hiccup/raw (when (:sort-key params ) (format "on click trigger sorted(key:\"%s\")", (:sort-key params))))
|
||||
:style (:style params)}]
|
||||
(if (:sort-key params)
|
||||
[(into [:a {:href "#"} ] rest)]
|
||||
rest)))
|
||||
|
||||
(defn sort-header- [params & rest]
|
||||
[:th.px-4.py-3 {:scope "col" :class (:class params)
|
||||
"_" (hiccup/raw (format "on click trigger sorted(key:\"%s\")", (:sort-key params)))}
|
||||
(into [:a {:href "#"} ] rest)])
|
||||
|
||||
(defn row- [params & rest]
|
||||
(into [:tr (update params
|
||||
:class str " border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700")] rest))
|
||||
|
||||
(defn cell- [params & rest]
|
||||
(into [:td.px-4.py-2 params ] rest))
|
||||
|
||||
(defn right-stack-cell- [params & rest]
|
||||
(cell- params (into [:div.flex.flex-row-reverse.items-center.justify-between
|
||||
rest]))
|
||||
)
|
||||
|
||||
(defn checkbox-header- [params & rest]
|
||||
[:th {:scope "col", :class "p-4"}
|
||||
[:div {:class "flex items-center"}
|
||||
[:input {:id "checkbox-all", :type "checkbox", :class "w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}]
|
||||
[:label {:for "checkbox-all", :class "sr-only"} "checkbox"]]])
|
||||
|
||||
(defn data-grid- [{:keys [headers thead-params]} & rest]
|
||||
[:table {:class "w-full text-sm text-left text-gray-500 dark:text-gray-400"}
|
||||
[:thead (assoc thead-params :class "text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400")
|
||||
(into
|
||||
[:tr]
|
||||
headers)]
|
||||
(into
|
||||
[:tbody]
|
||||
rest)])
|
||||
|
||||
;; needed for tailwind
|
||||
;; lg:table-cell md:table-cell
|
||||
|
||||
(defn data-grid-card- [{:keys [id
|
||||
route
|
||||
title
|
||||
paginate?
|
||||
action-buttons
|
||||
total
|
||||
subtitle
|
||||
thead-params
|
||||
start
|
||||
per-page
|
||||
flash-id
|
||||
headers
|
||||
rows] :as params}]
|
||||
[:div {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
route
|
||||
:request-method :get)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-swap "outerHTML swap:300ms"
|
||||
:id id}
|
||||
(content-card-
|
||||
{}
|
||||
[:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"}
|
||||
[:div
|
||||
[:h1.text-2xl.mb-3.font-bold title]
|
||||
[:div {:class "flex items-center flex-1 space-x-4"}
|
||||
[:h5
|
||||
(when subtitle
|
||||
[:span subtitle])]]]
|
||||
(into [:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"}]
|
||||
action-buttons)]
|
||||
[:div {:class "overflow-x-auto"}
|
||||
(data-grid- {:headers headers
|
||||
:thead-params thead-params}
|
||||
rows)]
|
||||
|
||||
(when (or paginate?
|
||||
(nil? paginate?))
|
||||
(paginator- {:start start
|
||||
:end (Math/min (+ start per-page) total)
|
||||
:per-page per-page
|
||||
:total total
|
||||
:a-params (fn [page]
|
||||
{:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||
route
|
||||
:request-method :get)
|
||||
"?start=" (* page per-page))
|
||||
:hx-target (str "#" id)
|
||||
:hx-swap "outerHTML show:#app:top"})})))])
|
||||
44
src/clj/auto_ap/ssr/components/dialog.clj
Normal file
44
src/clj/auto_ap/ssr/components/dialog.clj
Normal file
@@ -0,0 +1,44 @@
|
||||
(ns auto-ap.ssr.components.dialog
|
||||
(:require [hiccup2.core :as hiccup]))
|
||||
|
||||
(defn modal- [params & children]
|
||||
[:div
|
||||
[:div#modal-holder { :tabindex "-1", :class "fixed top-0 left-0 right-0 z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full flex justify-center hidden" :aria-hidden true
|
||||
"_" (hiccup/raw "on closeModal transition <#modal-holder .modal-content /> opacity to 0.0 over 300ms then call hideModal() ")}
|
||||
[:div {:class "relative w-full max-w-2xl max-h-full"}
|
||||
(into [:div#modal-content]
|
||||
children)]
|
||||
]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw "
|
||||
var modal_element = document.getElementById('modal-holder');
|
||||
var modal_options = {
|
||||
placement: 'center',
|
||||
backdrop: 'dynamic',
|
||||
backdropClasess: 'bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40',
|
||||
closable: true,
|
||||
onOpen: function() {
|
||||
modal_element.dispatchEvent('openModal');
|
||||
|
||||
},
|
||||
onHide: function() {
|
||||
modal_element.outerHTML='<div id=\"modal-holder\"><div class=\"relative w-full max-w-2xl max-h-full\"><div id=\"modal-content\"></div></div></div>';
|
||||
},
|
||||
};
|
||||
var curModal = new Modal(modal_element, modal_options);
|
||||
curModal.show();
|
||||
function hideModal() {
|
||||
curModal.hide();
|
||||
}
|
||||
")
|
||||
|
||||
]])
|
||||
|
||||
(defn modal-card- [params header content footer]
|
||||
[:div#modal-card params
|
||||
[:div {:class "relative bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white fade-in slide-up duration-300 transition-all modal-content"}
|
||||
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600"} header]
|
||||
[:div {:class "p-6 space-y-6"}
|
||||
content]
|
||||
[:div footer]]
|
||||
])
|
||||
24
src/clj/auto_ap/ssr/components/inputs.clj
Normal file
24
src/clj/auto_ap/ssr/components/inputs.clj
Normal file
@@ -0,0 +1,24 @@
|
||||
(ns auto-ap.ssr.components.inputs)
|
||||
|
||||
(defn select- [params & children]
|
||||
(into
|
||||
[:select {:class "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
:name (:name params)}
|
||||
(cond->>
|
||||
(map (fn [[k v]]
|
||||
[:option {:value k :selected (= v (:value params))} v])
|
||||
(:options params))
|
||||
(:allow-blank? params) (conj [:option {:value "" :selected (not (:value params))} ""]))]
|
||||
children))
|
||||
|
||||
(defn text-input- [params]
|
||||
[:input
|
||||
(update params
|
||||
:class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500")
|
||||
])
|
||||
|
||||
(defn field- [params & rest]
|
||||
(into
|
||||
[:div
|
||||
[:label {:class "block mb-2 text-sm font-medium text-gray-900 dark:text-white"} (:label params)]]
|
||||
rest))
|
||||
45
src/clj/auto_ap/ssr/components/navbar.clj
Normal file
45
src/clj/auto_ap/ssr/components/navbar.clj
Normal file
@@ -0,0 +1,45 @@
|
||||
(ns auto-ap.ssr.components.navbar
|
||||
(:require
|
||||
[auto-ap.graphql.utils :refer [is-admin?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.company-dropdown :as cd]
|
||||
[auto-ap.ssr.components.buttons :refer [icon-button-]]
|
||||
[auto-ap.ssr.components.user-dropdown :as user-dropdown]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[bidi.bidi :as bidi]))
|
||||
|
||||
(defn navbar- [{:keys [client identity]}]
|
||||
[:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
|
||||
[:div {:class "px-3 py-3 lg:px-5 lg:pl-3"}
|
||||
[:div {:class "flex items-center justify-between"}
|
||||
[:div {:class "flex items-center justify-start"}
|
||||
[:button {:aria-controls "left-nav", :id "left-nav-toggle" :type "button", :class "inline-flex items-center p-2 mt-2 ml-2 mr-2 text-sm text-gray-500 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"}
|
||||
[:span {:class "sr-only"} "Open sidebar"]
|
||||
[:svg {:class "w-6 h-6", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:clip-rule "evenodd", :fill-rule "evenodd", :d "M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"}]]]
|
||||
[:a {:href "/" :class "flex ml-2 md:mr-24"}
|
||||
[:img {:src "/img/logo-big2.png", :class "h-10 mr-16", :alt "Integreat logo"}]]]
|
||||
|
||||
[:div {:class "flex items-center gap-4"}
|
||||
|
||||
(when (is-admin? identity)
|
||||
[:button.mt-1.lg:w-96.relative.hidden.lg:block {:class "bg-gray-50 hover:bg-gray-200 dark:hover:bg-gray-700 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 w-full pl-10 py-4 pr-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 gap-4 "
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:search)
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-500"}
|
||||
[:div.w-4.h-4 svg/search]
|
||||
[:span.ml-2 "Search"]]])
|
||||
[:div {:class "hidden mr-3 -mb-1 sm:block"}
|
||||
[:span]]
|
||||
(icon-button-
|
||||
{:id "toggleSidebarMobileSearch", :type "button", :class "p-2 text-gray-500 rounded-lg lg:hidden hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:search)
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
svg/search)
|
||||
(cd/dropdown {:client client :identity identity})
|
||||
|
||||
(user-dropdown/dropdown {:identity identity})]]]])
|
||||
@@ -1,14 +0,0 @@
|
||||
(ns auto-ap.ssr.components.navbar-dropdown
|
||||
(:require [hiccup2.core :as hiccup]))
|
||||
|
||||
(defn navbar-dropdown [id header children]
|
||||
[:div {
|
||||
:id id
|
||||
:class (str "navbar-item has-dropdown")
|
||||
"_" (hiccup/raw "on click elsewhere remove .is-active from me")
|
||||
|
||||
}
|
||||
[:a {:class "navbar-link login"
|
||||
"_" (hiccup/raw "on click toggle .is-active on the parentElement of me then add .appear to next <.navbar-dropdown />")} header]
|
||||
(into [:div {:class "navbar-dropdown"}
|
||||
children])])
|
||||
36
src/clj/auto_ap/ssr/components/page.clj
Normal file
36
src/clj/auto_ap/ssr/components/page.clj
Normal file
@@ -0,0 +1,36 @@
|
||||
(ns auto-ap.ssr.components.page
|
||||
(:require
|
||||
[auto-ap.ssr.components.aside :refer [left-aside-]]
|
||||
[auto-ap.ssr.components.navbar :refer [navbar-]]
|
||||
[hiccup2.core :as hiccup]
|
||||
[auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn page- [{:keys [nav page-specific active-client identity app-params] :or {app-params {}}} & children]
|
||||
[:div#app
|
||||
(navbar- {:client active-client
|
||||
:identity identity})
|
||||
[:div#app-contents.flex.pt-16.overflow-hidden (assoc app-params :hx-disinherit "*")
|
||||
(left-aside- {:nav nav
|
||||
:page-specific page-specific})
|
||||
[:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content "
|
||||
"_" (hiccup/raw "on htmx:responseError put event.detail.xhr.response into #error-details then add .htmx-added to #error-holder then remove .hidden from #error-holder then wait 30ms then remove .htmx-added from #error-holder")}
|
||||
[:div#error-holder.hidden
|
||||
[:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg
|
||||
[:div.relative
|
||||
[:button.absolute.right-2.top-2.w-6.h-6.z-50.text-red-600
|
||||
{"_" (hiccup/raw "on click add .hidden to #error-holder")}
|
||||
svg/filled-x]]
|
||||
|
||||
[:div.m-4.overflow-auto.z-30.flex.center-items.justify-center.text-red-800.bg-red-50.dark:bg-gray-800.dark:text-red-400.border-red-300.rounded-lg.border.transition-all.duration-500.fade-in.slide-up.max-h-96
|
||||
|
||||
[:div {:class "p-4 mb-4 text-lg w-full" :role "alert"}
|
||||
[:div.inline-block.w-8.h-8.mr-2 svg/alert]
|
||||
[:span.font-medium "Oh, drat! An unexpected error has occurred."]
|
||||
[:div.text-sm
|
||||
[:p "Integreat staff have been notified and are looking into it. "]
|
||||
[:p "To see error details, " [:a.underline {:href "#" :data-collapse-toggle "error-details"} "click here"] "."]
|
||||
[:pre#error-details.text-xs.hidden]]]]]]
|
||||
(into
|
||||
[:div.p-4]
|
||||
children)]]
|
||||
[:div#modal-holder]])
|
||||
61
src/clj/auto_ap/ssr/components/paginator.clj
Normal file
61
src/clj/auto_ap/ssr/components/paginator.clj
Normal file
@@ -0,0 +1,61 @@
|
||||
(ns auto-ap.ssr.components.paginator)
|
||||
|
||||
(defn bound [x y z]
|
||||
(cond
|
||||
(< z x)
|
||||
x
|
||||
(< y x)
|
||||
x
|
||||
(> y z)
|
||||
z
|
||||
:else
|
||||
y))
|
||||
|
||||
(def elipsis-button
|
||||
[:p {:href "#", :class "flex items-center justify-center px-3 py-2 text-sm leading-tight text-gray-500 bg-white border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400"} "..."])
|
||||
|
||||
(defn paginator-internal- [{:keys [start per-page end total a-params]}]
|
||||
(let [per-page (or per-page 20)
|
||||
max-buttons 5
|
||||
buttons-before (Math/floor (/ max-buttons 2))
|
||||
total-pages (long (Math/max (long 1) (long (Math/ceil (/ total per-page)))))
|
||||
current-page (long (Math/floor (/ start per-page)))
|
||||
first-page-button (bound 0 (- current-page buttons-before) (- total-pages max-buttons))
|
||||
all-buttons (into [] (for [x (range total-pages)]
|
||||
[:li
|
||||
[:a (-> (a-params x)
|
||||
(update
|
||||
:class #(cond-> %
|
||||
true (str " flex items-center justify-center px-3 py-2 text-sm leading-tight border ")
|
||||
|
||||
(= current-page x)
|
||||
(str " text-primary-600 bg-primary-50 border-primary-300 hover:bg-primary-100 hover:text-primary-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white")
|
||||
|
||||
(not= current-page x)
|
||||
(str " text-gray-500 bg-white border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white")))
|
||||
(assoc :href "#"))
|
||||
(inc x)]]))
|
||||
|
||||
|
||||
last-page-button (Math/min (long total-pages) (long (+ max-buttons first-page-button)))
|
||||
|
||||
extended-last-page-button (when (not= last-page-button total-pages)
|
||||
(list
|
||||
elipsis-button
|
||||
(last all-buttons)))
|
||||
|
||||
extended-first-page-button (when (not= first-page-button 0)
|
||||
(list
|
||||
(first all-buttons)
|
||||
elipsis-button))]
|
||||
[:nav
|
||||
[:ul {:class "inline-flex items-stretch -space-x-px"}
|
||||
extended-first-page-button
|
||||
(apply list (subvec all-buttons first-page-button last-page-button))
|
||||
extended-last-page-button]]))
|
||||
|
||||
(defn paginator- [{:keys [start per-page end total a-params] :as params}]
|
||||
[:nav {:class "flex flex-col items-start justify-between p-4 space-y-3 md:flex-row md:items-center md:space-y-0", :aria-label "Table navigation"}
|
||||
[:span {:class "text-sm font-normal text-gray-500 dark:text-gray-400"}
|
||||
[:span {:class "font-semibold text-gray-900 dark:text-white"} (str (inc start)) "-" (str end) " of " (str total)]]
|
||||
(paginator-internal- params)])
|
||||
20
src/clj/auto_ap/ssr/components/tags.clj
Normal file
20
src/clj/auto_ap/ssr/components/tags.clj
Normal file
@@ -0,0 +1,20 @@
|
||||
(ns auto-ap.ssr.components.tags)
|
||||
|
||||
|
||||
(defn pill- [params & children]
|
||||
(into
|
||||
[:span (cond-> params
|
||||
true (update :class str " text-xs font-medium px-2 py-0.5 rounded whitespace-nowrap")
|
||||
|
||||
(= :primary (:color params))
|
||||
(update :class str " bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300 ")
|
||||
|
||||
(= :secondary (:color params))
|
||||
(update :class str " bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300 ")
|
||||
|
||||
(= :yellow (:color params))
|
||||
(update :class str " bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300")
|
||||
|
||||
(= :red (:color params))
|
||||
(update :class str " bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300"))]
|
||||
children))
|
||||
61
src/clj/auto_ap/ssr/components/user_dropdown.clj
Normal file
61
src/clj/auto_ap/ssr/components/user_dropdown.clj
Normal file
@@ -0,0 +1,61 @@
|
||||
(ns auto-ap.ssr.components.user-dropdown
|
||||
(:require
|
||||
[auto-ap.client-routes :as client-routes2]
|
||||
[auto-ap.datomic :refer [conn pull-attr]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[bidi.bidi :as bidi]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(defn dropdown [{:keys [identity]}]
|
||||
[:div {:class "flex items-center ml-3 mr-10"}
|
||||
[:div
|
||||
[:button#user-menu-button {:type "button", :class "flex text-sm bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600", :aria-expanded "false"
|
||||
"_" (hiccup/raw "init call initUserDropdown()")}
|
||||
[:span {:class "sr-only"} "Open user menu"]
|
||||
[:img {:class "w-8 h-8 rounded-full", :src (pull-attr (dc/db conn) :user/profile-image-url (:db/id identity)) :alt "user photo" :referrerpolicy "no-referrer"}]]]
|
||||
[:div#user-menu {:class "z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600 mr-10"}
|
||||
[:div {:class "px-4 py-3", :role "none"}
|
||||
[:p {:class "text-sm text-gray-900 dark:text-white", :role "none"} (:user/name identity)]
|
||||
[:p {:class "text-sm font-medium text-gray-900 truncate dark:text-gray-300", :role "none"} (pull-attr (dc/db conn) :user/email (:db/id identity))]
|
||||
#_(icon-button-
|
||||
{"_" (hiccup/raw "on click toggle .dark on <body />")}
|
||||
[:div.h-4.w-4
|
||||
[:div.hidden.dark:block svg/sun]
|
||||
[:div.dark:hidden svg/moon]])]
|
||||
[:ul {:class "py-1", :role "none"}
|
||||
[:li
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes :company), :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "My Company"]]
|
||||
(when (= "admin" (:user/role identity))
|
||||
[:a {:href (bidi/path-for client-routes2/routes :admin), :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "Admin"])
|
||||
[:li
|
||||
[:a {:href "#", :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"
|
||||
"_" (hiccup/raw "on click toggle .dark on <body />")}
|
||||
"Night Mode"]]
|
||||
[:li
|
||||
[:a {:href "/logout", :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "Sign out"]]]]
|
||||
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw
|
||||
"
|
||||
function initUserDropdown() {
|
||||
var $dropdownTargetEl = document.getElementById('user-menu');
|
||||
|
||||
// set the element that trigger the dropdown menu on click
|
||||
var $dropdownTriggerEl = document.getElementById('user-menu-button');
|
||||
|
||||
var userDrowdown = new Dropdown($dropdownTargetEl, $dropdownTriggerEl, {
|
||||
placement: 'bottom',
|
||||
triggerType: 'click',
|
||||
offsetSkidding: 0,
|
||||
offsetDistance: 10,
|
||||
delay: 5000,
|
||||
onHide: () => {
|
||||
},
|
||||
onShow: () => {
|
||||
},
|
||||
onToggle: () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
")]])
|
||||
@@ -2,32 +2,45 @@
|
||||
(:require
|
||||
[auto-ap.routes.utils
|
||||
:refer [wrap-admin wrap-client-redirect-unauthenticated wrap-secure]]
|
||||
[auto-ap.ssr.admin :as admin]
|
||||
[auto-ap.ssr.admin.history :as history]
|
||||
[auto-ap.ssr.auth :as auth]
|
||||
[auto-ap.ssr.transaction.insights :as insights]
|
||||
[auto-ap.ssr.company.company-1099 :as company-1099]
|
||||
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
||||
[auto-ap.ssr.search :as search]
|
||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||
[auto-ap.routes.ezcater-xls :as ezcater-xls]))
|
||||
[auto-ap.ssr.company.reports :as company-reports]
|
||||
[auto-ap.routes.ezcater-xls :as ezcater-xls]
|
||||
[auto-ap.ssr.company :as company]))
|
||||
|
||||
;; from auto-ap.ssr-routes, because they're shared
|
||||
|
||||
|
||||
(def key->handler {:logout auth/logout
|
||||
:admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/history)))
|
||||
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/history-search)))
|
||||
:admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/inspect)))
|
||||
:active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin company-dropdown/active-client)))
|
||||
:company-dropdown-contents (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-contents))
|
||||
:admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
|
||||
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
|
||||
:admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect)))
|
||||
:active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client)))
|
||||
:company-dropdown-search-results
|
||||
(wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results))
|
||||
:company (wrap-client-redirect-unauthenticated (wrap-secure company/page))
|
||||
:company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page))
|
||||
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
||||
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
||||
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))
|
||||
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-secure insights/page))
|
||||
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-secure insights/insight-table))
|
||||
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-secure insights/transaction-rows))
|
||||
:transaction-insight-approve (wrap-client-redirect-unauthenticated (wrap-secure insights/approve))
|
||||
:transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-secure insights/explain))
|
||||
:company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page))
|
||||
:company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table))
|
||||
:company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog))
|
||||
:company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account))
|
||||
:company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page))
|
||||
:company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table))
|
||||
:company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report))
|
||||
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page))
|
||||
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table))
|
||||
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows))
|
||||
:transaction-insight-approve (wrap-client-redirect-unauthenticated (wrap-admin insights/approve))
|
||||
:transaction-insight-disapprove (wrap-client-redirect-unauthenticated (wrap-admin insights/disapprove))
|
||||
:transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-admin insights/explain))
|
||||
:admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page))
|
||||
:search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))})
|
||||
|
||||
|
||||
186
src/clj/auto_ap/ssr/grid_page_helper.clj
Normal file
186
src/clj/auto_ap/ssr/grid_page_helper.clj
Normal file
@@ -0,0 +1,186 @@
|
||||
(ns auto-ap.ssr.grid-page-helper
|
||||
(:require
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[hiccup2.core :as hiccup]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[cemerick.url :as url]
|
||||
[clojure.string :as str]
|
||||
[auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn row* [gridspec user entity {:keys [flash? delete-after-settle?] :as options}]
|
||||
(let [cells (mapv (fn [header]
|
||||
(com/data-grid-cell {:class (if-let [show-starting (:show-starting header)]
|
||||
(format "hidden %s:table-cell" show-starting)
|
||||
(:class header))}
|
||||
((:render header) entity)))
|
||||
(:headers gridspec))
|
||||
cells (conj cells (com/data-grid-right-stack-cell {}
|
||||
(into [:form
|
||||
[:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]]
|
||||
((:row-buttons gridspec) user entity))))]
|
||||
(apply com/data-grid-row
|
||||
{:class (when flash?
|
||||
"live-added")
|
||||
"_" (hiccup/raw (when delete-after-settle?
|
||||
" on htmx:afterSettle wait 400ms then remove me"))
|
||||
|
||||
:data-id ((:id-fn gridspec) entity)}
|
||||
cells)))
|
||||
|
||||
(defn sort-icon [sort key]
|
||||
(->> sort
|
||||
(filter (comp #(= key %) :sort-key))
|
||||
first
|
||||
:sort-icon))
|
||||
|
||||
(defn sort-by-list [sort]
|
||||
(if (seq sort)
|
||||
(into
|
||||
[:div.flex.gap-2.items-center
|
||||
"sorted by"
|
||||
|
||||
]
|
||||
(for [{:keys [name sort-icon ]} sort]
|
||||
[:div.py-1.px-3.text-sm.rounded.bg-gray-100.dark:bg-gray-600.flex.items-center.gap-2.relative name [:div.h-4.w-4.mr-3 sort-icon]
|
||||
[:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white hover:scale-110 transition-all duration-300 bg-gray-400 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"}
|
||||
[:div.h-4.w-4 svg/x]
|
||||
]]
|
||||
))
|
||||
"default sort"))
|
||||
|
||||
(defn table* [grid-spec user {:keys [start per-page client flash-id sort request]}]
|
||||
(let [start (or start 0)
|
||||
per-page (or per-page 30)
|
||||
[entities total] ((:fetch-page grid-spec)
|
||||
user
|
||||
{:start start
|
||||
:per-page per-page
|
||||
:client-id (:db/id client)
|
||||
:sort sort
|
||||
:request request})]
|
||||
(com/data-grid-card {:id (:id grid-spec)
|
||||
:title (:title grid-spec)
|
||||
:route (:route grid-spec)
|
||||
:start start
|
||||
:per-page per-page
|
||||
:total total
|
||||
:subtitle [:div.flex.items-center.gap-2
|
||||
[:span (format "Total %s: %d, " (:entity-name grid-spec) total)]
|
||||
(sort-by-list sort)]
|
||||
:action-buttons ((:action-buttons grid-spec) user)
|
||||
:rows (for [entity entities]
|
||||
(row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))}))
|
||||
:thead-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
(:route grid-spec))
|
||||
:hx-target (str "#" (:id grid-spec))
|
||||
:hx-trigger "sorted once"
|
||||
:hx-vals "js:{\"toggle-sort\": event.detail.key || \"\"}"}
|
||||
:headers
|
||||
(conj
|
||||
(mapv
|
||||
(fn [h]
|
||||
(if (:sort-key h)
|
||||
(com/data-grid-sort-header {:class (if-let [show-starting (:show-starting h)]
|
||||
(format "hidden %s:table-cell" show-starting)
|
||||
(:class h))
|
||||
:sort-key (:sort-key h)}
|
||||
|
||||
[:div.flex.gap-4.items-center
|
||||
(:name h)
|
||||
[:div.h-6.w-6.text-gray-400.dark:text-gray-500 (sort-icon sort (:sort-key h))]])
|
||||
(com/data-grid-header {:class (if-let [show-starting (:show-starting h)]
|
||||
(format "hidden %s:table-cell" show-starting)
|
||||
(:class h))
|
||||
:sort-key (:sort-key h)}
|
||||
(:name h))
|
||||
|
||||
))
|
||||
(:headers grid-spec))
|
||||
(com/data-grid-header {}))})))
|
||||
|
||||
|
||||
|
||||
(defn parse-sort [grid-spec q]
|
||||
(if (not-empty q)
|
||||
(into []
|
||||
(map (fn [k]
|
||||
(let [[k v] (str/split k #":")]
|
||||
{:sort-key (str k)
|
||||
:asc (boolean (= "asc" v))
|
||||
:name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec))))
|
||||
:sort-icon (if (= (boolean (= "asc" v)) true)
|
||||
svg/sort-down
|
||||
svg/sort-up)}))
|
||||
(str/split q #",")))
|
||||
[]))
|
||||
|
||||
(defn toggle-sort [grid-spec q k]
|
||||
(if ((set (map :sort-key q)) k)
|
||||
(mapv
|
||||
(fn [s]
|
||||
(if (= (:sort-key s)
|
||||
k)
|
||||
(-> s
|
||||
(update :asc
|
||||
#(boolean (not %)))
|
||||
(update :sort-icon (fn [x]
|
||||
(if (= x svg/sort-down)
|
||||
svg/sort-up
|
||||
svg/sort-down))))
|
||||
s))
|
||||
q)
|
||||
(conj q {:sort-key k
|
||||
:asc true
|
||||
:name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec))))
|
||||
:sort-icon svg/sort-down})))
|
||||
|
||||
(defn sort->query [s]
|
||||
(str/join "," (map (fn [k] (format "%s:%s" (:sort-key k) (if (= true (:asc k))
|
||||
"asc"
|
||||
"desc")))
|
||||
s)))
|
||||
|
||||
(defn params->query-string [q]
|
||||
(-> q
|
||||
(dissoc :client :session)
|
||||
(update :sort sort->query)
|
||||
(url/map->query)))
|
||||
|
||||
(defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}]
|
||||
(let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params
|
||||
{q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params]
|
||||
(cond-> {}
|
||||
hx-start (assoc :start (some-> hx-start not-empty (Long/parseLong )))
|
||||
q-start (assoc :start (some-> q-start not-empty (Long/parseLong )))
|
||||
hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong )))
|
||||
q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong )))
|
||||
hx-sort (assoc :sort (parse-sort grid-spec hx-sort))
|
||||
q-sort (assoc :sort (parse-sort grid-spec q-sort))
|
||||
(not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) )
|
||||
(:session request) (assoc :session (:session request))
|
||||
(:client (:session request)) (assoc :client (:client (:session request))))))
|
||||
|
||||
(defn table [grid-spec {:keys [query-params hx-query-params identity session] :as request}]
|
||||
(let [params (extract-params grid-spec request)
|
||||
query-string (params->query-string params)]
|
||||
(html-response (table*
|
||||
grid-spec
|
||||
identity
|
||||
params
|
||||
)
|
||||
:headers {"hx-push-url" (str "?" query-string)})))
|
||||
|
||||
(defn page [grid-spec {:keys [identity] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav (:nav grid-spec)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)}
|
||||
(apply com/breadcrumbs {} (:breadcrumbs grid-spec))
|
||||
(table* grid-spec
|
||||
identity
|
||||
(extract-params grid-spec request)))
|
||||
(:title grid-spec)))
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns auto-ap.ssr.login-dropdown
|
||||
(:require
|
||||
[auto-ap.client-routes :as client-routes]
|
||||
[auto-ap.ssr.components.navbar-dropdown :refer [navbar-dropdown]]
|
||||
[bidi.bidi :as bidi]
|
||||
[hiccup2.core :as hiccup]
|
||||
[auto-ap.ssr-routes :as ssr-routes]))
|
||||
|
||||
(defn dropdown [{:keys [identity]}]
|
||||
(if identity
|
||||
(navbar-dropdown
|
||||
"login-dropdown"
|
||||
[:span [:span.icon [:i.fa.fa-user] ]
|
||||
[:span (:user/name identity)]]
|
||||
[:div
|
||||
[:a {:class "navbar-item"
|
||||
:href (bidi/path-for client-routes/routes :reports)} "My company"]
|
||||
#_[:a.dropdown-item {:on-click (dispatch-event-with-propagation [:vendor-dialog/started {}])} "New Vendor"] ;; double colorn
|
||||
#_[:a.dropdown-item {:on-click (dispatch-event-with-propagation [:vendor-dialog/edit {}])} "Edit Vendor"]
|
||||
(when (= "admin" (:user/role identity))
|
||||
[:a {:class "navbar-item" :href (bidi/path-for client-routes/routes :admin)} "Administration"])
|
||||
[:hr {:class "navbar-divider"}]
|
||||
[:a.navbar-item {"_" (hiccup/raw "on click call localStorage.removeItem(\"jwt\")")
|
||||
:href (bidi/path-for ssr-routes/only-routes :logout)}
|
||||
"Logout"]])
|
||||
[:a.navbar-item {:href (bidi/path-for client-routes/routes :login )} "Login"]))
|
||||
@@ -5,7 +5,9 @@
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[clojure.string :as str]
|
||||
[com.brunobonacci.mulog :as mu]))
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn try-cleanse-date [d]
|
||||
(try
|
||||
@@ -61,58 +63,65 @@
|
||||
(let [results (search-results q id)]
|
||||
[:div
|
||||
(if (seq results)
|
||||
(for [doc results]
|
||||
[:div.block
|
||||
[:div.card
|
||||
[:div.card-header.has-background-info-light
|
||||
(cond (= "transaction" (:type doc))
|
||||
[:div.card-header-icon.icon-task-list-text-1]
|
||||
[:div.flex.gap-8.flex-col
|
||||
(for [doc results]
|
||||
(com/card {}
|
||||
[:div.flex.flex-col.gap-4
|
||||
[:div.flex.items-center.p-2.gap-4.bg-gray-50.dark:bg-gray-800
|
||||
[:div.h-8.w-8.p-2
|
||||
(cond (= "transaction" (:type doc))
|
||||
svg/bank
|
||||
|
||||
|
||||
(= "invoice" (:type doc))
|
||||
[:div.card-header-icon.icon-accounting-invoice-mail]
|
||||
(= "invoice" (:type doc))
|
||||
svg/accounting-invoice-mail
|
||||
|
||||
|
||||
(= "payment" (:type doc))
|
||||
[:div.card-header-icon.icon-check-payment-sign]
|
||||
(= "payment" (:type doc))
|
||||
svg/payments
|
||||
|
||||
(= "journal-entry" (:type doc))
|
||||
[:div.card-header-icon.icon-receipt]
|
||||
(= "journal-entry" (:type doc))
|
||||
svg/receipt
|
||||
|
||||
:else
|
||||
nil)
|
||||
[:div.card-header-title (clojure.string/capitalize (:type doc))
|
||||
" "
|
||||
" "
|
||||
[:span.tags.ml-3
|
||||
[:span.tag.is-warning "client: " (:client_code doc)]
|
||||
[:span.tag.is-info "amount: $" (first (:amount doc))]
|
||||
:else
|
||||
nil)]
|
||||
(clojure.string/capitalize (:type doc))
|
||||
(com/pill {:color :primary}
|
||||
"client: " (:client_code doc))
|
||||
(com/pill {:color :secondary}
|
||||
"amount: $" (first (:amount doc)))
|
||||
(when-let [vendor-name (first (:vendor_name doc))]
|
||||
[:span.tag.is-primary "vendor: " vendor-name])]]
|
||||
[:a.card-header-icon.fa.fa-external-link {:href (str "/" (cond (= "invoice"
|
||||
(:type doc))
|
||||
"invoices"
|
||||
(com/pill {:color :yellow}
|
||||
"vendor: " vendor-name))
|
||||
[:div
|
||||
(com/link {:href (str "/" (cond (= "invoice"
|
||||
(:type doc))
|
||||
"invoices"
|
||||
|
||||
(= "transaction"
|
||||
(:type doc))
|
||||
"transactions"
|
||||
(= "transaction"
|
||||
(:type doc))
|
||||
"transactions"
|
||||
|
||||
(= "journal-entry"
|
||||
(:type doc))
|
||||
"ledger"
|
||||
(= "journal-entry"
|
||||
(:type doc))
|
||||
"ledger"
|
||||
|
||||
:else
|
||||
"payments") "/?exact-match-id=" (:id doc))
|
||||
:target "_blank"}]
|
||||
]
|
||||
|
||||
|
||||
[:div.card-content
|
||||
[:span
|
||||
:else
|
||||
"payments") "/?exact-match-id=" (:id doc))
|
||||
:target "_blank"}
|
||||
[:div.h-8.w-8.p-2
|
||||
svg/external-link])]
|
||||
]
|
||||
|
||||
[:strong (atime/unparse (atime/parse (:date doc) atime/solr-date) atime/normal-date)]
|
||||
": "
|
||||
(str (or (first (:description doc))
|
||||
(first (:number doc))))]]]]
|
||||
)
|
||||
|
||||
[:div.px-4.pb-2
|
||||
[:span
|
||||
|
||||
[:strong (atime/unparse (atime/parse (:date doc) atime/solr-date) atime/normal-date)]
|
||||
": "
|
||||
(str (or (first (:description doc))
|
||||
(first (:number doc))))]]])
|
||||
)]
|
||||
[:div.block "No results found."])]))
|
||||
|
||||
(defn dialog-contents [request]
|
||||
@@ -122,22 +131,26 @@
|
||||
(if-let [q (get (:form-params request) "q")]
|
||||
(html-response (search-results* q (:identity request)))
|
||||
(html-response
|
||||
[:div#search {:style {:height "400px" :overflow "auto"}}
|
||||
|
||||
[:div.block
|
||||
[:input#search-input.input {:type "search"
|
||||
:placeholder "5/5/2034 Magheritas"
|
||||
:name "q"
|
||||
:hx-post "/search"
|
||||
:hx-trigger "keyup changed delay:300ms, search"
|
||||
:hx-target "#search-results"
|
||||
:hx-indicator "#search"
|
||||
:value (:q (:params request))
|
||||
:autofocus true}]]
|
||||
[:i "Try dates, numbers, vendors. To filter to specific entity type, use 'invoice', 'transaction', 'journal-entry', 'payment'."]
|
||||
[:style
|
||||
".htmx-request #search-results {display: none} .htmx-request .htmx-indicator { display: block !important; }"]
|
||||
[:div#search-results
|
||||
]
|
||||
[:div.loader.is-loading.big.htmx-indicator {:style {:display "none"}}]])))
|
||||
(com/modal {}
|
||||
(com/modal-card {}
|
||||
[:div.p-2 "Search"]
|
||||
[:div#search.overflow-auto.space-y-6.p-2.h-96
|
||||
|
||||
(com/text-input {:id "search-input"
|
||||
:type "search"
|
||||
:placeholder "5/5/2034 Magheritas"
|
||||
:name "q"
|
||||
:hx-post "/search"
|
||||
:hx-trigger "keyup changed delay:300ms, search"
|
||||
:hx-target "#search-results"
|
||||
:hx-indicator "#search"
|
||||
:value (:q (:params request))
|
||||
:autofocus true})
|
||||
[:i.text-sm.text-gray-600.dark:text-gray-50 "Try dates, numbers, vendors. To filter to specific type, use 'invoice', 'transaction', 'journal-entry', 'payment'."]
|
||||
#_[:style
|
||||
".htmx-request #search-results {display: none} .htmx-request .htmx-indicator { display: block !important; }"]
|
||||
[:div#search-results
|
||||
]
|
||||
[:div.loader.is-loading.big.htmx-indicator ]]
|
||||
nil)))))
|
||||
|
||||
|
||||
433
src/clj/auto_ap/ssr/svg.clj
Normal file
433
src/clj/auto_ap/ssr/svg.clj
Normal file
@@ -0,0 +1,433 @@
|
||||
(ns auto-ap.ssr.svg)
|
||||
|
||||
(def pie
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "analytics-pie-2"]
|
||||
[:path {:d "M13.5.75v9h9A9,9,0,0,0,13.5.75Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M10.5,14.25v-9a9,9,0,1,0,5.561,16.077Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M22.5,12.75h-9l5.561,7.077A8.986,8.986,0,0,0,22.5,12.75Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])
|
||||
|
||||
|
||||
(def accounting-invoice-mail
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "accounting-document"]
|
||||
[:path {:d "M21.75,21.75a1.5,1.5,0,0,1-1.5,1.5H3.75a1.5,1.5,0,0,1-1.5-1.5V2.25A1.5,1.5,0,0,1,3.75.75H14.379a1.5,1.5,0,0,1,1.06.439l5.872,5.872a1.5,1.5,0,0,1,.439,1.06Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M21.75,8.25h-6a1.5,1.5,0,0,1-1.5-1.5v-6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M6.2,11.594a2.224,2.224,0,0,0,1.858.875c1.139,0,2.063-.693,2.063-1.547S9.2,9.376,8.062,9.376,6,8.683,6,7.828s.924-1.547,2.062-1.547a2.221,2.221,0,0,1,1.858.875", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.469", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.062", :y2 "13.5", :x2 "8.062"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "5.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.062", :y2 "6.281", :x2 "8.062"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "15", :stroke-linecap "round", :stroke-width "1.5px", :x1 "12", :y2 "15", :x2 "18"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "19.5", :stroke-linecap "round", :stroke-width "1.5px", :x1 "6.75", :y2 "19.5", :x2 "18"}]])
|
||||
|
||||
(def receipt-register-1
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:path {:d "M17.63,18h3a1.5,1.5,0,0,1,1.5,1.5v2.25a1.5,1.5,0,0,1-1.5,1.5H3.38a1.5,1.5,0,0,1-1.5-1.5V18H7.13", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "17.03", :stroke-linecap "round", :stroke-width "1.5px", :x1 "11.45", :y2 "14.35", :x2 "8.71"}]
|
||||
[:circle {:cx "12.48", :cy "18.11", :r "1.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M1.88,4.5H8.63L9.81,3.31A1.5,1.5,0,0,0,8.75.75H3.38a1.5,1.5,0,0,0-1.5,1.5V18", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.1", :stroke-linecap "round", :stroke-width "1.5px", :x1 "13.22", :y2 "6.59", :x2 "14.2"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "8.54", :stroke-linecap "round", :stroke-width "1.5px", :x1 "17.13", :y2 "9.25", :x2 "17.97"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.09", :stroke-linecap "round", :stroke-width "1.5px", :x1 "19.9", :y2 "13.03", :x2 "20.46"}]
|
||||
[:path {:d "M8.63,4.5V7.74A12.22,12.22,0,0,1,18.92,18", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:circle {:cx "7.13", :cy "12.75", :r "2.25", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
|
||||
(def payments
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "cash-payment-sign-2"]
|
||||
[:path {:d "M8.212,15.637l-3.712.53.53-3.712,9.546-9.546a2.25,2.25,0,1,1,3.182,3.182Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M5.25,8.25h-3a1.5,1.5,0,0,0-1.5,1.5v10.5a1.5,1.5,0,0,0,1.5,1.5h19.5a1.5,1.5,0,0,0,1.5-1.5V9.75a1.5,1.5,0,0,0-1.5-1.5h-1.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "18", :stroke-linecap "round", :stroke-width "1.5px", :x1 "20.25", :y2 "18", :x2 "12.75"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "14.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "20.25", :y2 "14.25", :x2 "15.75"}]])
|
||||
|
||||
(def bank
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "23.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "23.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "19.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "19.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "2", :y2 "16.25", :x2 "2"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "5.5", :y2 "16.25", :x2 "5.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "10.25", :y2 "16.25", :x2 "10.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "13.75", :y2 "16.25", :x2 "13.75"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "18.5", :y2 "16.25", :x2 "18.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "22", :y2 "16.25", :x2 "22"}]
|
||||
[:path {:d "M23.25,7.25H.75L11.19,1a1.49,1.49,0,0,1,1.62,0Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
|
||||
(def receipt
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "21.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "17.25", :y2 "21.75", :x2 "11.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "13.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "13.25", :x2 "15.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "9.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "9.75", :x2 "15.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "6.25", :x2 "15.25"}]
|
||||
[:path {:d "M20.25.75h-1.5v5.5h4.5V3.75A3,3,0,0,0,20.25.75Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M13.5,17.25H3.75a3,3,0,0,0-3,3v3h10.5V19.5a2.25,2.25,0,0,1,4.5,0v.75a1.5,1.5,0,0,0,1.5,1.5h0a1.5,1.5,0,0,0,1.5-1.5V.75H8.25a3,3,0,0,0-3,3v13.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
|
||||
(defn spinner [{:keys [class]}]
|
||||
[:svg {:aria-hidden "true", :role "status", :class (str "animate-spin " class) :viewbox "0 0 100 101", :fill "none", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", :fill "#E5E7EB"}]
|
||||
[:path {:d "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", :fill "currentColor"}]])
|
||||
|
||||
(defn spinner-primary [{:keys [class]}]
|
||||
[:svg {:aria-hidden "true", :role "status", :class (str "animate-spin " class) :viewbox "0 0 100 101", :fill "none", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", :fill "#79b52e"}]
|
||||
[:path {:d "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", :fill "currentColor"}]])
|
||||
|
||||
(def search
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "search"]
|
||||
[:circle {:r "9.063", :stroke "currentColor", :transform "translate(-3.056 4.62) rotate(-23.025)", :fill "none", :stroke-linejoin "round", :stroke-linecap "round", :stroke-width "1.5px", :cx "9.813", :cy "9.812"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "16.22", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.221", :y2 "23.25", :x2 "23.25"}]])
|
||||
|
||||
(def moon
|
||||
[:svg {:id "theme-toggle-dark-icon", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"}]])
|
||||
|
||||
(def sun
|
||||
[:svg {:id "theme-toggle-light-icon", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z", :fill-rule "evenodd", :clip-rule "evenodd"}]])
|
||||
|
||||
|
||||
(def home
|
||||
[:svg { :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"}]])
|
||||
|
||||
(def breadcrumb-component
|
||||
[:svg { :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", :clip-rule "evenodd"}]])
|
||||
|
||||
(def refresh
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :aria-hidden "true", :fill "none", :viewbox "0 0 24 24", :stroke-width "1.5", :stroke "currentColor"}
|
||||
[:path {:stroke-linecap "round", :stroke-linejoin "round", :d "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"}]])
|
||||
|
||||
|
||||
(def upload
|
||||
[:svg { :xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 24 24", :stroke-width "2", :stroke "currentColor", :aria-hidden "true"}
|
||||
[:path {:stroke-linecap "round", :stroke-linejoin "round", :d "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"}]])
|
||||
|
||||
(def vendors
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "messages-people-user-dollar"]
|
||||
[:circle {:cx "4.5", :cy "6.75", :r "3", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M8.25,17.25V15a3.75,3.75,0,0,0-7.5,0v2.25h1.5l.75,6H6l.75-6Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M17.625,5.25H15a1.341,1.341,0,0,0-.5,2.587l2.654.826a1.341,1.341,0,0,1-.5,2.587H13.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "5.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "15.75", :y2 "3.75", :x2 "15.75"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "15.75", :y2 "11.25", :x2 "15.75"}]
|
||||
[:path {:d "M11.251,15.748h3v4.5l4.5-4.5h3a1.5,1.5,0,0,0,1.5-1.5v-12a1.5,1.5,0,0,0-1.5-1.5h-12a1.5,1.5,0,0,0-1.5,1.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])
|
||||
|
||||
(def report
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "app-window-pie-chart"]
|
||||
[:rect {:y "2.253", :rx "1.5", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "21", :stroke-linecap "round", :stroke-width "1.5px", :x "1.51", :ry "1.5", :height "19.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.753", :stroke-linecap "round", :stroke-width "1.5px", :x1 "1.51", :y2 "6.753", :x2 "22.51"}]
|
||||
[:circle {:cx "9.01", :cy "14.253", :r "4.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "9.01 9.753 9.01 14.253 12.192 17.435", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "11.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "11.253", :x2 "19.51"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "14.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "14.253", :x2 "19.51"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "17.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "17.253", :x2 "19.51"}]])
|
||||
|
||||
(def government-building
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "3.5", :height "6"}]
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "10.5", :height "6"}]
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "17.5", :height "6"}]
|
||||
[:path {:d "M21.75,13.39a.87.87,0,0,1-.86.86H3.11a.86.86,0,0,1-.25-1.69l8.85-2.72a1,1,0,0,1,.58,0l8.85,2.72A.87.87,0,0,1,21.75,13.39Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "15.5 8.25 18 8.25 18 11.6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "6 11.6 6 8.25 8.5 8.25", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M6,8.25a6,6,0,0,1,12,0", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "0.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "12", :y2 "2.25", :x2 "12"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "23.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "23.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "20.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "2", :y2 "20.25", :x2 "22"}]]])
|
||||
|
||||
(def external-link
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "navigation-next"]
|
||||
[:path
|
||||
{:d "M23,9.5H12.387a4,4,0,0,0-4,4v2",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:polyline
|
||||
{:points "19 13.498 23 9.498 19 5.498",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d
|
||||
"M14.387,13v5.5a1,1,0,0,1-1,1h-12a1,1,0,0,1-1-1V6.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1V7",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def pencil
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "pencil"]
|
||||
[:rect
|
||||
{:y "1.09",
|
||||
:stroke "currentColor",
|
||||
:transform "translate(11.889 -5.238) rotate(45)",
|
||||
:fill "none",
|
||||
:stroke-linejoin "round",
|
||||
:width "6",
|
||||
:stroke-linecap "round",
|
||||
:x "9.268",
|
||||
:height "21.284"}]
|
||||
[:polygon
|
||||
{:points "2.621 17.136 0.5 23.5 6.864 21.379 2.621 17.136",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M21.914,6.328,17.672,2.086l.707-.707a3,3,0,0,1,4.242,4.242Z",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def drop-down
|
||||
[:svg {:class "w-4 h-4 ml-2", :aria-hidden "true", :fill "none", :stroke "currentColor", :viewbox "0 0 24 24", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:stroke-linecap "round", :stroke-linejoin "round", :stroke-width "2", :d "M19 9l-7 7-7-7"}]])
|
||||
|
||||
(def download
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "download-thick-bottom"]
|
||||
[:path
|
||||
{:d
|
||||
"M5.5,11.5c-.275,0-.341.159-.146.354l6.292,6.293a.5.5,0,0,0,.709,0l6.311-6.275c.2-.193.13-.353-.145-.355L15.5,11.5V1.5a1,1,0,0,0-1-1h-5a1,1,0,0,0-1,1V11a.5.5,0,0,1-.5.5Z",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M23.5,18.5v4a1,1,0,0,1-1,1H1.5a1,1,0,0,1-1-1v-4",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def trash
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "bin-1"]
|
||||
[:path
|
||||
{:d
|
||||
"M21,4.5,19.188,21.709A2,2,0,0,1,17.2,23.5H6.8a2,2,0,0,1-1.989-1.791L3,4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "0.5",
|
||||
:y1 "4.5",
|
||||
:x2 "23.5",
|
||||
:y2 "4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M7.5,4.5v-3a1,1,0,0,1,1-1h7a1,1,0,0,1,1,1v3",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "12",
|
||||
:y1 "9",
|
||||
:x2 "12",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "16.5",
|
||||
:y1 "9",
|
||||
:x2 "16",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "7.5",
|
||||
:y1 "9",
|
||||
:x2 "8",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def alert
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "alert-triangle"]
|
||||
[:path {:d "M22.553,22.581a.569.569,0,0,1-.553.894H2a.569.569,0,0,1-.553-.894L11.553,2.37c.246-.492.648-.492.894,0Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "12", :y1 "16.979", :x2 "12", :y2 "9.979", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M11.991,18.979a.246.246,0,0,0-.241.255.255.255,0,0,0,.254.245h.005a.246.246,0,0,0,.241-.255A.255.255,0,0,0,12,18.979h-.005", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
(def x
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "delete-2"]
|
||||
[:circle {:cx "12", :cy "12", :r "11.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "7", :y1 "7", :x2 "17", :y2 "17", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "17", :y1 "7", :x2 "7", :y2 "17", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
|
||||
(def filled-x
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:circle {:cx "12", :cy "12", :r "11.5", :fill "#FFF", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M24,12A12,12,0,1,0,12,24,12,12,0,0,0,24,12Zm-7.29,3.28a1,1,0,0,1,0,1.41,1,1,0,0,1-1.42,0l-3.11-3.11a.26.26,0,0,0-.35,0L8.72,16.69a1,1,0,0,1-1.41-1.41l3.11-3.11a.26.26,0,0,0,0-.35L7.31,8.71a1,1,0,0,1,0-1.42,1,1,0,0,1,1.41,0l3.11,3.11a.24.24,0,0,0,.35,0l3.11-3.11a1,1,0,1,1,1.42,1.42L13.6,11.82a.24.24,0,0,0,0,.35Z", :fill "currentColor"}]])
|
||||
|
||||
(def sort-down
|
||||
[:svg {:id "Regular", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "arrow-thick-down-4"]
|
||||
[:rect {:y "0.75", :rx "3", :stroke "currentColor", :transform "translate(0 24) rotate(-90)", :fill "none", :stroke-linejoin "round", :width "22.5", :stroke-linecap "round", :stroke-width "1.5px", :x "0.75", :ry "3", :height "22.5"}]
|
||||
[:path {:d "M9.75,6v7.5L6.53,10.28a.75.75,0,0,0-1.28.531v2.068a1.5,1.5,0,0,0,.439,1.06L11.47,19.72a.749.749,0,0,0,1.06,0l5.781-5.781a1.5,1.5,0,0,0,.439-1.06V10.811a.75.75,0,0,0-1.28-.531L14.25,13.5V6a.75.75,0,0,0-.75-.75h-3A.75.75,0,0,0,9.75,6Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])
|
||||
|
||||
(def sort-up
|
||||
[:svg {:id "Regular", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "arrow-thick-up-4"]
|
||||
[:rect {:y "0.75", :rx "3", :stroke "currentColor", :transform "translate(24 0) rotate(90)", :fill "none", :stroke-linejoin "round", :width "22.5", :stroke-linecap "round", :stroke-width "1.5px", :x "0.75", :ry "3", :height "22.5"}]
|
||||
[:path {:d "M14.25,18V10.5l3.22,3.22a.75.75,0,0,0,1.28-.531V11.121a1.5,1.5,0,0,0-.439-1.06L12.53,4.28a.749.749,0,0,0-1.06,0L5.689,10.061a1.5,1.5,0,0,0-.439,1.06v2.068a.75.75,0,0,0,1.28.531L9.75,10.5V18a.75.75,0,0,0,.75.75h3A.75.75,0,0,0,14.25,18Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])
|
||||
|
||||
(def dashboard
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "gauge-dashboard"]
|
||||
[:circle {:cx "12", :cy "14", :r "1.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "13.06", :y1 "12.939", :x2 "18.011", :y2 "7.99", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "2.5", :y1 "14.5", :x2 "4.5", :y2 "14.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "21.5", :y1 "14.5", :x2 "19.5", :y2 "14.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "20.776", :y1 "10.365", :x2 "18.929", :y2 "11.13", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "15.635", :y1 "5.223", :x2 "14.87", :y2 "7.071", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "12", :y1 "4.5", :x2 "12", :y2 "6.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "8.365", :y1 "5.223", :x2 "9.13", :y2 "7.071", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "5.283", :y1 "7.282", :x2 "6.695", :y2 "8.697", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "3.224", :y1 "10.365", :x2 "5.07", :y2 "11.13", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M12,2.5A11.5,11.5,0,0,0,.5,14v3.5a1,1,0,0,0,1,1h21a1,1,0,0,0,1-1V14A11.5,11.5,0,0,0,12,2.5Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
(def restaurant
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:line {:x1 "1", :y1 "14.5", :x2 "23", :y2 "14.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "6", :y1 "17.5", :x2 "11", :y2 "17.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "7.5", :y1 "17.5", :x2 "6", :y2 "23.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "9.5", :y1 "17.5", :x2 "11", :y2 "23.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "6.25", :y1 "22.5", :x2 "10.75", :y2 "22.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "13", :y1 "17.5", :x2 "18", :y2 "17.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "14.5", :y1 "17.5", :x2 "13", :y2 "23.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "16.5", :y1 "17.5", :x2 "18", :y2 "23.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "13.25", :y1 "22.5", :x2 "17.75", :y2 "22.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "2.5", :y1 "23.5", :x2 "2.5", :y2 "7.33", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "21.5", :y1 "23.5", :x2 "21.5", :y2 "7.33", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M16,14.5V12a.5.5,0,0,1,.5-.5h1a.5.5,0,0,1,.5.5v2.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M7,14.5l-.64-1.28a.5.5,0,0,1,.45-.72h4.38a.5.5,0,0,1,.45.72L11,14.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M6.5,4H.5V1.5a1,1,0,0,1,1-1h5Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:rect {:x "6.5", :y "0.5", :width "5.5", :height "3.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:rect {:x "12", :y "0.5", :width "5.5", :height "3.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M23.5,4h-6V.5h5a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M23.5,4v.5a3,3,0,0,1-6,0V4", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M6.5,4v.5a3,3,0,0,1-6,0V4", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M17.5,4v.75a2.75,2.75,0,0,1-5.5,0V4", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M12,4v.75a2.75,2.75,0,0,1-5.5,0V4", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]]])
|
||||
|
||||
(def user
|
||||
[:svg {:id "Light", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "single-man"]
|
||||
[:path {:d "M3,22.75a9,9,0,0,1,18,0Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:circle {:cx "12", :cy "6.75", :r "5.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M7.261,3.958A9.124,9.124,0,0,0,13.833,6.75a9.138,9.138,0,0,0,3.617-.744", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
|
||||
(def accounts
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "accounting-abacus"]
|
||||
[:rect {:y "0.5", :rx "1", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "21", :stroke-linecap "round", :x "1.504", :ry "1", :height "23"}]
|
||||
[:line {:x1 "15.504", :y1 "20.5", :x2 "15.504", :y2 "23.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "8.504", :y1 "17.5", :x2 "8.504", :y2 "23.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M10.5,4.5a1,1,0,0,1-1,1h-2a1,1,0,0,1-1-1h0a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M10.5,6.5a1,1,0,0,1-1,1h-2a1,1,0,0,1-1-1h0a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M17.5,13.5a1,1,0,0,1-1,1h-2a1,1,0,0,1-1-1h0a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M17.5,19.5a1,1,0,0,1-1,1h-2a1,1,0,0,1-1-1h0a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M10.5,16.5a1,1,0,0,1-1,1h-2a1,1,0,0,1-1-1h0a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M17.5,4.5a1,1,0,0,1-1,1h-2a1,1,0,0,1-1-1h0a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "8.504", :y1 "7.5", :x2 "8.504", :y2 "15.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "8.504", :y1 "3.5", :x2 "8.504", :y2 "0.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "15.504", :y1 "14.5", :x2 "15.504", :y2 "18.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "15.504", :y1 "5.5", :x2 "15.504", :y2 "12.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "15.504", :y1 "0.5", :x2 "15.504", :y2 "3.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
|
||||
(def cog
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "cash-toggle"]
|
||||
[:path {:d "M19.923,7.212a1.874,1.874,0,0,0,1.065,2.571l1.265.45a1.875,1.875,0,0,1,0,3.534l-1.265.45a1.874,1.874,0,0,0-1.065,2.571L20.5,18A1.874,1.874,0,0,1,18,20.5l-1.213-.576a1.874,1.874,0,0,0-2.571,1.065l-.45,1.265a1.875,1.875,0,0,1-3.534,0l-.45-1.265a1.874,1.874,0,0,0-2.571-1.065L6,20.5A1.874,1.874,0,0,1,3.5,18l.576-1.213a1.874,1.874,0,0,0-1.065-2.571l-1.265-.45a1.875,1.875,0,0,1,0-3.534l1.265-.45A1.874,1.874,0,0,0,4.077,7.212L3.5,6A1.874,1.874,0,0,1,6,3.5l1.213.576A1.874,1.874,0,0,0,9.783,3.012l.45-1.265a1.875,1.875,0,0,1,3.534,0l.45,1.265a1.874,1.874,0,0,0,2.571,1.065L18,3.5A1.874,1.874,0,0,1,20.5,6Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M13.5,9H11.467a1.342,1.342,0,0,0-.5,2.587l2.064.826a1.342,1.342,0,0,1-.5,2.587H10.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "12", :y1 "16", :x2 "12", :y2 "15", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "12", :y1 "9", :x2 "12", :y2 "8", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:circle {:cx "12", :cy "12", :r "6.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
(def question
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 24 24"}
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M9.5116 9.51125C9.5116 8.92057 9.68675 8.34316 10.0149 7.85203C10.3431 7.3609 10.8095 6.97811 11.3552 6.75207C11.9009 6.52603 12.5014 6.46689 13.0807 6.58212C13.6601 6.69736 14.1922 6.98179 14.6099 7.39946C15.0276 7.81714 15.312 8.34928 15.4272 8.92861C15.5425 9.50794 15.4833 10.1084 15.2573 10.6541C15.0312 11.1999 14.6485 11.6663 14.1573 11.9944C13.6662 12.3226 13.0888 12.4978 12.4981 12.4978V15.4843"}]
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M23.4486 12C23.4488 11.2025 23.2094 10.4233 22.7616 9.76338C22.3138 9.10345 21.6781 8.5932 20.937 8.29872C21.2535 7.56634 21.3428 6.75575 21.1931 5.97204C21.0435 5.18834 20.6619 4.46766 20.0977 3.90343C19.5336 3.3392 18.813 2.95744 18.0293 2.80766C17.2457 2.65787 16.4351 2.74695 15.7026 3.06336C15.4084 2.32189 14.8983 1.68587 14.2383 1.23773C13.5784 0.789596 12.799 0.550003 12.0013 0.550003C11.2036 0.550003 10.4243 0.789596 9.76434 1.23773C9.1044 1.68587 8.59426 2.32189 8.30005 3.06336C7.56756 2.74664 6.75681 2.65732 5.97294 2.80697C5.18907 2.95662 4.46825 3.33834 3.90396 3.90263C3.33967 4.46692 2.95794 5.18774 2.80829 5.97161C2.65864 6.75548 2.74797 7.56623 3.06469 8.29872C2.32321 8.59293 1.68719 9.10307 1.23906 9.76301C0.790923 10.423 0.551331 11.2023 0.551331 12C0.551331 12.7977 0.790923 13.577 1.23906 14.237C1.68719 14.8969 2.32321 15.4071 3.06469 15.7013C2.74814 16.4337 2.6589 17.2443 2.80854 18.028C2.95818 18.8117 3.3398 19.5324 3.90392 20.0966C4.46804 20.6608 5.18865 21.0426 5.97233 21.1924C6.75601 21.3421 7.56661 21.2531 8.29905 20.9366C8.59327 21.6781 9.1034 22.3141 9.76335 22.7623C10.4233 23.2104 11.2026 23.45 12.0003 23.45C12.7981 23.45 13.5774 23.2104 14.2373 22.7623C14.8973 22.3141 15.4074 21.6781 15.7016 20.9366C16.4341 21.2531 17.2447 21.3421 18.0283 21.1924C18.812 21.0426 19.5326 20.6608 20.0968 20.0966C20.6609 19.5324 21.0425 18.8117 21.1921 18.028C21.3418 17.2443 21.2525 16.4337 20.936 15.7013C21.6773 15.407 22.3132 14.8968 22.7612 14.2368C23.2092 13.5769 23.4487 12.7976 23.4486 12Z"}]
|
||||
[:path {:stroke "currentColor", :d "M12.4981 17.973C12.3606 17.973 12.2492 17.8616 12.2492 17.7242C12.2492 17.5867 12.3606 17.4753 12.4981 17.4753"}]
|
||||
[:path {:stroke "currentColor", :d "M12.4981 17.973C12.6356 17.973 12.747 17.8616 12.747 17.7242C12.747 17.5867 12.6356 17.4753 12.4981 17.4753"}]])
|
||||
|
||||
(def rabbit
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 24 24"}
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M9 21.5H6.5"}]
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M6.52169 19.4653H7.51807"}]
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M6.52268 21.4561C6.25869 21.4561 6.00551 21.3512 5.81884 21.1646C5.63217 20.9779 5.5273 20.7247 5.5273 20.4607C5.5273 20.1967 5.63217 19.9435 5.81884 19.7569C6.00551 19.5702 6.25869 19.4653 6.52268 19.4653"}]
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M7.51806 10.5069C9.50883 10.5069 11.3443 11.9999 12.495 15.4838C12.9927 16.9769 14.4857 21.4561 14.4857 21.4561H18.4673C18.7313 21.4561 18.9844 21.5609 19.1711 21.7476C19.3578 21.9343 19.4626 22.1875 19.4626 22.4515C19.4626 22.7154 19.3578 22.9686 19.1711 23.1553C18.9844 23.342 18.7313 23.4468 18.4673 23.4468H12.495C12.495 23.4468 10.979 20.9733 10.0483 19.4653C8.51345 16.9769 5.65172 15.5246 3.66096 14.1559C1.6702 12.7873 0.324441 9.73645 2.04346 7.52073C4.28208 4.63412 8.59606 5.77881 9.97367 7.37142L13.8049 11.2036C13.9426 11.3462 14.1074 11.46 14.2895 11.5382C14.4717 11.6165 14.6676 11.6577 14.8659 11.6594C15.0641 11.6611 15.2607 11.6233 15.4442 11.5483C15.6277 11.4732 15.7944 11.3623 15.9346 11.2221C16.0748 11.082 16.1857 10.9153 16.2607 10.7318C16.3358 10.5483 16.3736 10.3517 16.3719 10.1534C16.3701 9.95516 16.3289 9.75924 16.2507 9.57708C16.1724 9.39492 16.0587 9.23017 15.9161 9.09244L9.94381 3.10323C9.66371 2.823 9.50641 2.44298 9.5065 2.04677C9.5066 1.65057 9.66408 1.27062 9.94431 0.990528C10.2245 0.710433 10.6046 0.553129 11.0008 0.553223C11.397 0.553316 11.7769 0.710798 12.057 0.991026L22.2617 11.1957C22.6601 11.5395 22.974 11.9705 23.1791 12.4552C23.3841 12.9399 23.4748 13.4653 23.4442 13.9907C23.4442 15.4838 22.4488 16.4792 20.458 16.4792H17.9696L15.4751 18.2271"}]
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :d "M1.73291 7.98666C1.46709 7.9302 1.22172 7.80228 1.02324 7.61666C0.824763 7.43105 0.680706 7.19479 0.606596 6.93334C0.532486 6.6719 0.531133 6.39518 0.602684 6.13303C0.674235 5.87087 0.815976 5.63321 1.01263 5.44567C1.20928 5.25812 1.45339 5.1278 1.71864 5.06875C1.9839 5.00971 2.26024 5.02417 2.51788 5.11059C2.77551 5.197 3.00468 5.3521 3.18068 5.55915C3.35668 5.7662 3.47284 6.01735 3.51663 6.28555"}]
|
||||
[:path {:stroke "currentColor", :d "M20.458 13.742C20.3206 13.742 20.2092 13.6305 20.2092 13.4931C20.2092 13.3557 20.3206 13.2443 20.458 13.2443"}]
|
||||
[:path {:stroke "currentColor", :d "M20.458 13.742C20.5955 13.742 20.7069 13.6305 20.7069 13.4931C20.7069 13.3557 20.5955 13.2443 20.458 13.2443"}]])
|
||||
|
||||
|
||||
(def arrow-in
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "download-thick-box"]
|
||||
[:polygon {:points "15.5 14.5 15.5 8.5 8.5 8.5 8.5 14.5 5.5 14.5 12 21 18.469 14.5 15.5 14.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M22.629,4.572A6.22,6.22,0,0,1,23,6.5v16a1,1,0,0,1-1,1H2a1,1,0,0,1-1-1V6.5a6.22,6.22,0,0,1,.371-1.928L2.629,1.428A1.6,1.6,0,0,1,4,.5H20a1.6,1.6,0,0,1,1.371.928Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "12", :y1 "6", :x2 "12", :y2 "0.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "1.034", :y1 "6", :x2 "22.966", :y2 "6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
(def thumbs-up
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "like"]
|
||||
[:path {:d "M19.5,16.065h0a1.5,1.5,0,0,1,0,3h-1a1.5,1.5,0,0,1,0,3H12c-4,0-3-2-11-2v-9H4a7.949,7.949,0,0,0,7.5-8c0-1.581,3-1.781,3,1.219a31.593,31.593,0,0,1-1,5.781h8a1.5,1.5,0,0,1,0,3h-1a1.5,1.5,0,0,1,0,3h-1", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
(def thumbs-down
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "dislike"]
|
||||
[:path {:d "M4.5,8h0a1.5,1.5,0,0,1,0-3h1a1.5,1.5,0,0,1,0-3H12c4,0,3,1.87,11,1.87V13H20a7.811,7.811,0,0,0-7.5,7.856c0,1.582-3,1.813-3-1.187A29.774,29.774,0,0,1,10.5,14h-8a1.5,1.5,0,0,1,0-3h1a1.5,1.5,0,0,1,0-3h1", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
@@ -1,16 +1,18 @@
|
||||
(ns auto-ap.ssr.transaction.insights
|
||||
(:require
|
||||
[auto-ap.client-routes :as client-routes]
|
||||
[auto-ap.datomic :refer [conn visible-clients]]
|
||||
[auto-ap.shared-views.company.sidebar :refer [company-side-bar]]
|
||||
[auto-ap.rule-matching :refer [spread-cents]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[clj-time.coerce :as coerce]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]
|
||||
[clj-time.core :as time]))
|
||||
[iol-ion.tx :refer [random-tempid]]))
|
||||
|
||||
(def pull-expr [:transaction/description-original
|
||||
:db/id
|
||||
@@ -25,98 +27,139 @@
|
||||
|
||||
(defn transaction-recommendations [identity selected-client & {:keys [after]}]
|
||||
(let [visible-clients (visible-clients identity)]
|
||||
(->>
|
||||
(dc/q '[:find (pull ?t pull-expr)
|
||||
:in $ [?c ...] pull-expr
|
||||
:where [?t :transaction/recommended-account]
|
||||
[?t :transaction/client ?c]
|
||||
[?t :transaction/approval-status :transaction-approval-status/unapproved]
|
||||
(not [?t :transaction/vendor])]
|
||||
(dc/db conn)
|
||||
(if selected-client
|
||||
[selected-client]
|
||||
visible-clients)
|
||||
pull-expr)
|
||||
(map first)
|
||||
(sort-by :transaction/date)
|
||||
(reverse)
|
||||
(drop-while (fn [x]
|
||||
(if after
|
||||
(not= (Long/parseLong after) (:db/id x))
|
||||
false)))
|
||||
(#(if after
|
||||
(->>
|
||||
(dc/qseq {:query '[:find (pull ?t pull-expr)
|
||||
:in $ [?c ...] pull-expr
|
||||
:where [?t :transaction/recommended-account]
|
||||
[?t :transaction/client ?c]
|
||||
[?t :transaction/approval-status :transaction-approval-status/unapproved]
|
||||
(not [?t :transaction/vendor])]
|
||||
:args [(dc/db conn)
|
||||
(if selected-client
|
||||
[selected-client]
|
||||
visible-clients)
|
||||
pull-expr]})
|
||||
(map first)
|
||||
(sort-by :transaction/date)
|
||||
(reverse)
|
||||
(drop-while (fn [x]
|
||||
(if after
|
||||
(not= (Long/parseLong after) (:db/id x))
|
||||
false)))
|
||||
(#(if after
|
||||
(drop 1 %)
|
||||
%))
|
||||
(take 10)
|
||||
(into []))))
|
||||
(take 10)
|
||||
(into []))))
|
||||
|
||||
|
||||
(defn transaction-row [r & {:keys [hide-actions? class last?]}]
|
||||
[:tr (cond-> {:class class}
|
||||
last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-rows
|
||||
:after (:db/id r))
|
||||
:hx-trigger "intersect once"
|
||||
:hx-indicator "#insight-table"
|
||||
:hx-swap "afterend"))
|
||||
[:td {:style {:width "8em"}}(:client/code (:transaction/client r))]
|
||||
[:td {:style {:width "10em"}} (:bank-account/code (:transaction/bank-account r))]
|
||||
[:td {:style {:width "12em"}} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||
[:td {:style {:width "30em" :max-width "30em"}} (str (:transaction/description-original r))]
|
||||
[:td {:style {:width "10em"}}
|
||||
(if (> (:transaction/amount r) 0.0 )
|
||||
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
|
||||
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]
|
||||
[:td {:style {:width "12em"}}
|
||||
[:div [:div.tag (:vendor/name (:transaction/recommended-vendor r))]]
|
||||
[:div [:div.tag (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]
|
||||
[:div [:div.tag
|
||||
{:class (cond
|
||||
(> (:transaction/account-confidence r) 0.90)
|
||||
"is-success is-light"
|
||||
(> (:transaction/account-confidence r) 0.80)
|
||||
"is-info is-light"
|
||||
(com/data-grid-row
|
||||
(cond-> {:class class}
|
||||
last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-rows
|
||||
:after (:db/id r))
|
||||
:hx-trigger "intersect once"
|
||||
:hx-indicator "#insight-table"
|
||||
:hx-swap "afterend"))
|
||||
(com/data-grid-cell {} (:client/code (:transaction/client r)))
|
||||
(com/data-grid-cell {} (:bank-account/code (:transaction/bank-account r)))
|
||||
(com/data-grid-cell {} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date)))
|
||||
|
||||
:else
|
||||
"is-warning is-light")}
|
||||
(str "%" (Math/round (* 100.0 (:transaction/account-confidence r))))]]]
|
||||
[:td
|
||||
(when-not hide-actions?
|
||||
[:div.buttons
|
||||
[:button.button {:hx-post (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-approve
|
||||
:transaction-id (:db/id r))
|
||||
:hx-target "closest tr"}
|
||||
[:i.fa.fa-thumbs-up ]]
|
||||
[:button.button
|
||||
[:i.fa.fa-thumbs-down ]]
|
||||
[:a.button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-explain
|
||||
:transaction-id (:db/id r))
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "beforeend"}
|
||||
[:i.fa.fa-question ]]])]])
|
||||
(com/data-grid-cell {} (str (:transaction/description-original r)))
|
||||
(com/data-grid-cell {}
|
||||
(if (> (:transaction/amount r) 0.0)
|
||||
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
|
||||
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))]))
|
||||
(com/data-grid-cell {:style {:width "12em"}}
|
||||
[:div.flex.gap-2.flex-wrap {:style {:width "12em"}}
|
||||
(com/pill {:color :primary} (:vendor/name (:transaction/recommended-vendor r)))
|
||||
(com/pill {:color :secondary} (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r))))
|
||||
(com/pill {:class (cond
|
||||
(> (:transaction/account-confidence r) 0.90)
|
||||
"is-success is-light"
|
||||
(> (:transaction/account-confidence r) 0.80)
|
||||
"is-info is-light"
|
||||
|
||||
:else
|
||||
"is-warning is-light")} (str "%" (Math/round (* 100.0 (:transaction/account-confidence r)))))])
|
||||
(com/data-grid-right-stack-cell {}
|
||||
(when-not hide-actions?
|
||||
[:form.flex.gap-2
|
||||
[:input {:type :hidden :name "id" :value (:db/id r)}]
|
||||
(com/icon-button {:hx-post (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-approve
|
||||
:transaction-id (:db/id r))
|
||||
:hx-target "closest tr"
|
||||
:color :primary-light}
|
||||
svg/thumbs-up)
|
||||
(com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-disapprove
|
||||
:transaction-id (:db/id r))
|
||||
:hx-target "closest tr"
|
||||
:color :danger-light}
|
||||
svg/thumbs-down)
|
||||
(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-explain
|
||||
:transaction-id (:db/id r))
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
svg/question)]))))
|
||||
|
||||
(defn approve [{:keys [identity session] {:keys [transaction-id]} :route-params}]
|
||||
(html-response (transaction-row
|
||||
(dc/pull (dc/db conn)
|
||||
pull-expr
|
||||
(Long/parseLong transaction-id))
|
||||
:auto-remove? true
|
||||
:hide-actions? true
|
||||
:class "live-added")))
|
||||
(let [approval-details (dc/pull (dc/db conn) [{:transaction/recommended-account [:account/location :db/id]}
|
||||
:transaction/recommended-vendor
|
||||
:transaction/amount
|
||||
:db/id
|
||||
{:transaction/client [:client/locations]} ]
|
||||
(cond-> transaction-id
|
||||
string? (Long/parseLong)))
|
||||
cents-to-distribute (int (Math/round (Math/abs (* (:transaction/amount approval-details) 100))))
|
||||
valid-locations (or
|
||||
(some-> approval-details :transaction/recommended-account :account/location vector)
|
||||
(->> approval-details
|
||||
:transaction/client
|
||||
:client/locations))
|
||||
updated-transaction [:upsert-transaction {:db/id (:db/id approval-details)
|
||||
:transaction/approval-status :transaction-approval-status/approved
|
||||
:transaction/vendor (:transaction/recommended-vendor approval-details)
|
||||
:transaction/accounts (->> valid-locations
|
||||
(map
|
||||
(fn [cents location]
|
||||
{:db/id (random-tempid)
|
||||
:transaction-account/account (-> approval-details :transaction/recommended-account :db/id)
|
||||
:transaction-account/amount (* 0.01 cents)
|
||||
:transaction-account/location location})
|
||||
(spread-cents cents-to-distribute (count valid-locations))))}]]
|
||||
@(dc/transact conn [updated-transaction])
|
||||
(html-response (transaction-row
|
||||
(dc/pull (dc/db conn)
|
||||
pull-expr
|
||||
(Long/parseLong transaction-id))
|
||||
:auto-remove? true
|
||||
:hide-actions? true
|
||||
:class "live-added"))))
|
||||
|
||||
(defn disapprove [{:keys [identity session] {:keys [transaction-id]} :route-params}]
|
||||
(let [transaction-id (cond-> transaction-id string? (Long/parseLong))]
|
||||
@(dc/transact conn [[:upsert-transaction {:db/id transaction-id :transaction/recommended-account nil :transaction/recommended-vendor nil}]])
|
||||
(html-response (transaction-row
|
||||
(dc/pull (dc/db conn) pull-expr transaction-id)
|
||||
:auto-remove? true
|
||||
:hide-actions? true
|
||||
:class "live-removed"))))
|
||||
|
||||
(defn explain [{:keys [identity session] {:keys [transaction-id]} :route-params}]
|
||||
(let [r (dc/pull (dc/db conn)
|
||||
pull-expr
|
||||
(Long/parseLong transaction-id))
|
||||
pull-expr
|
||||
(Long/parseLong transaction-id))
|
||||
similar (->> (dc/q '[:find ?date ?do ?amt
|
||||
:in $ ?tr
|
||||
:where
|
||||
[(iol-ion.query/recent-date 180) ?start-date]
|
||||
[?tr :transaction/client ?c]
|
||||
[?tr :transaction/recommended-account ?a ]
|
||||
[?tr :transaction/recommended-vendor ?v ]
|
||||
[?tr :transaction/recommended-account ?a]
|
||||
[?tr :transaction/recommended-vendor ?v]
|
||||
[?t2 :transaction/client ?c]
|
||||
[?t2 :transaction/date ?date]
|
||||
[(>= ?date ?start-date)]
|
||||
@@ -130,38 +173,31 @@
|
||||
(take 5)
|
||||
sort
|
||||
reverse)]
|
||||
(html-response [:div.modal.is-active.wide
|
||||
[:div.modal-background {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]
|
||||
[:div.modal-card
|
||||
[:div.modal-card-head
|
||||
[:h1.title "Similar transactions"]
|
||||
[:div.tags
|
||||
[:div.tag.is-large.is-info.is-light (:vendor/name (:transaction/recommended-vendor r))]
|
||||
[:div.tag.is-large.is-info.is-light (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]]
|
||||
[:div.modal-card-body
|
||||
[:table.table
|
||||
[:thead
|
||||
[:tr
|
||||
[:td "Date"]
|
||||
[:td "Description"]
|
||||
[:td "Amount"]]]
|
||||
[:tbody
|
||||
[:tr
|
||||
[:th (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||
[:th (-> r :transaction/description-original)]
|
||||
[:th (if (> (-> r :transaction/amount) 0.0 )
|
||||
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
|
||||
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]]
|
||||
(for [[date description amt] similar]
|
||||
[:tr
|
||||
[:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||
[:td description]
|
||||
[:td (if (> amt 0.0 )
|
||||
[:div.tag.is-success.is-light (str "$" (Math/round amt))]
|
||||
[:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]]
|
||||
|
||||
]]
|
||||
[:button.modal-close.is-large {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]])))
|
||||
(html-response
|
||||
(com/modal {}
|
||||
(com/modal-card {:style {:width "900px"}}
|
||||
[:div.flex [:div.p-2 "Similar Transactions"]]
|
||||
[:table.w-full
|
||||
[:thead
|
||||
[:tr
|
||||
[:td "Date"]
|
||||
[:td "Description"]
|
||||
[:td "Amount"]]]
|
||||
[:tbody
|
||||
[:tr
|
||||
[:th.text-left (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||
[:th.text-left (-> r :transaction/description-original)]
|
||||
[:th.text-left (if (> (-> r :transaction/amount) 0.0)
|
||||
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
|
||||
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]]
|
||||
(for [[date description amt] similar]
|
||||
[:tr
|
||||
[:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||
[:td description]
|
||||
[:td (if (> amt 0.0)
|
||||
[:div.tag.is-success.is-light (str "$" (Math/round amt))]
|
||||
[:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]]
|
||||
[:div])))))
|
||||
|
||||
(defn transaction-rows* [{:keys [selected-client identity after]}]
|
||||
(let [recommendations (transaction-recommendations identity selected-client :after after)]
|
||||
@@ -178,28 +214,23 @@
|
||||
:after (:after route-params)})))
|
||||
|
||||
(defn insight-table* [{:keys [selected-client identity]}]
|
||||
[:div#insight-table {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insight-table
|
||||
:request-method :get)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-swap "outerHTML swap:100ms"}
|
||||
|
||||
[:table.table
|
||||
[:thead
|
||||
[:tr
|
||||
[:td "Client"]
|
||||
[:td "Account"]
|
||||
[:td "Date"]
|
||||
[:td "Description"]
|
||||
[:td "Amount"]
|
||||
[:td "Vendor / Account"]
|
||||
[:td "action"]]]
|
||||
[:tbody
|
||||
(transaction-rows* {:selected-client selected-client
|
||||
:identity identity})]]
|
||||
[:div.container.htmx-indicator
|
||||
[:div.column.is-4.is-offset-4.has-text-centered
|
||||
[:div.loader.is-loading.is-active.big.is-centered]]]])
|
||||
(let [recommendations (transaction-recommendations identity selected-client)]
|
||||
(com/data-grid-card {:id "insight-table"
|
||||
:title "Transaction Insights"
|
||||
:route :transaction-insight-table
|
||||
:paginate? false
|
||||
:total (count recommendations)
|
||||
:action-buttons nil
|
||||
:rows (for [r recommendations
|
||||
:let [last? (= r (last recommendations))]]
|
||||
(transaction-row r :last? last?))
|
||||
:headers [(com/data-grid-header {} "Client")
|
||||
(com/data-grid-header {} "Account")
|
||||
(com/data-grid-header {} "Date")
|
||||
(com/data-grid-header {} "Description")
|
||||
(com/data-grid-header {} "Amount")
|
||||
(com/data-grid-header {:style {:width "4em"}} "Vendor / Account")
|
||||
(com/data-grid-header {})]})))
|
||||
|
||||
(defn insight-table [{:keys [session identity]}]
|
||||
(html-response (insight-table* {:selected-client
|
||||
@@ -208,10 +239,24 @@
|
||||
|
||||
(defn page [{:keys [identity matched-route session] :as request}]
|
||||
(base-page
|
||||
request
|
||||
[:div
|
||||
[:h1.title "Transaction Insights"]
|
||||
(insight-table* {:selected-client
|
||||
(-> session :client :db/id)
|
||||
:identity identity})]
|
||||
[:div (company-side-bar matched-route)]))
|
||||
request
|
||||
(com/page {:nav (com/main-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)
|
||||
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for client-routes/routes
|
||||
:transactions)}
|
||||
"Transactions"]
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:transaction-insights)}
|
||||
"Insights"])
|
||||
(insight-table* {:selected-client
|
||||
(-> session :client :db/id)
|
||||
:identity identity}))
|
||||
|
||||
"Transaction Insights"))
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
(ns auto-ap.ssr.ui
|
||||
(:require
|
||||
[auto-ap.client-routes :as client-routes]
|
||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||
[auto-ap.ssr.login-dropdown :as login-dropdown]
|
||||
[bidi.bidi :as bidi]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(defn html-page [hiccup]
|
||||
@@ -15,69 +11,32 @@
|
||||
{}
|
||||
hiccup))})
|
||||
|
||||
(defn base-page [request contents side-bar-contents]
|
||||
(html-page
|
||||
[:html.has-navbar-fixed-top
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:meta {:http-equiv "X-UA-Compatible", :content "IE=edge"}]
|
||||
[:meta {:name "viewport", :content "width=device-width, initial-scale=1"}]
|
||||
[:title "Integreat"]
|
||||
[:link {:rel "stylesheet", :href "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css", :integrity "sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=", :crossorigin "anonymous"}]
|
||||
[:link {:href "/css/font.min.css", :rel "stylesheet"}]
|
||||
[:link {:rel "stylesheet", :href "/css/bulma.min.css"}]
|
||||
[:link {:rel "stylesheet", :href "/css/bulma-calendar.min.css"}]
|
||||
[:link {:rel "stylesheet", :href "/css/bulma-badge.min.css"}]
|
||||
[:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}]
|
||||
[:link {:rel "stylesheet", :href "/css/animate.css"}]
|
||||
[:link {:rel "stylesheet", :href "/finance-font/style.css"}]
|
||||
[:link {:rel "stylesheet", :href "/css/main.css"}]
|
||||
[:link {:rel "stylesheet", :href "https://unpkg.com/placeholder-loading/dist/css/placeholder-loading.min.css"}]
|
||||
#_[:link {:rel "stylesheet", :href "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/css/autoComplete.min.css"}]
|
||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7"}]
|
||||
[:script {:src "https://unpkg.com/@popperjs/core@2"}]
|
||||
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:src "https://unpkg.com/htmx.org@1.9.0/dist/htmx.js"
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
||||
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
|
||||
[:script {:src "https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"}]
|
||||
[:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}]
|
||||
[:body
|
||||
[:div {:id "app"}
|
||||
[:div
|
||||
[:nav {:class "navbar has-shadow is-fixed-top is-grey"}
|
||||
|
||||
[:div {:class "container"}
|
||||
[:div {:class "navbar-brand"}
|
||||
[:a {:class "navbar-item", :href "../"}
|
||||
[:img {:src "/img/logo.png"}]]]
|
||||
[:div.navbar-menu {:id "navMenu"}
|
||||
[:div.navbar-start
|
||||
[:a.navbar-item {:href "/"}
|
||||
"Home" ]
|
||||
[:a.navbar-item {:href (bidi/path-for client-routes/routes :unpaid-invoices)}
|
||||
"Invoices" ]
|
||||
[:a.navbar-item {:href (bidi/path-for client-routes/routes :payments)}
|
||||
"Payments" ]
|
||||
[:a.navbar-item {:href (bidi/path-for client-routes/routes :sales-orders)}
|
||||
"POS" ]
|
||||
[:a.navbar-item {:href (bidi/path-for client-routes/routes :transactions)}
|
||||
"Transactions" ]
|
||||
[:a.navbar-item {:href (bidi/path-for client-routes/routes :ledger)}
|
||||
"Ledger" ]]
|
||||
[:div.navbar-end
|
||||
(company-dropdown/dropdown request)
|
||||
(login-dropdown/dropdown request)]]]]
|
||||
[:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"}
|
||||
[:aside {:class "column aside menu is-2 "}
|
||||
[:div {:class "main left-nav"}
|
||||
side-bar-contents]]
|
||||
[:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"}
|
||||
[:div {:class "inbox-messages"}
|
||||
contents]]]
|
||||
[:div]
|
||||
[:div {:id "dz-hidden"}]]]
|
||||
[:div#modal-holder]]]))
|
||||
(defn base-page [request contents page-name]
|
||||
(html-page
|
||||
[:html.has-navbar-fixed-top
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:meta {:http-equiv "X-UA-Compatible", :content "IE=edge"}]
|
||||
[:meta {:name "viewport", :content "width=device-width, initial-scale=1"}]
|
||||
[:title (str "Integreat | " page-name)]
|
||||
[:link {:href "/css/font.min.css", :rel "stylesheet"}]
|
||||
[:link {:rel "icon" :type "image/png" :href "/favicon.png"}]
|
||||
[:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}]
|
||||
[:link {:rel "stylesheet", :href "/output.css"}]
|
||||
|
||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}]
|
||||
[:script {:src "https://unpkg.com/@popperjs/core@2.11.8/dist/umd/popper.min.js"}]
|
||||
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:src "https://unpkg.com/htmx.org@1.9.0/dist/htmx.js"
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:src "https://unpkg.com/htmx.org/dist/ext/debug.js"}]
|
||||
[:script {:src "/js/htmx-disable.js"}]
|
||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async"}]]
|
||||
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
|
||||
[:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.min.js"}]
|
||||
[:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}]
|
||||
[:body {:hx-ext "disable-submit"}
|
||||
contents
|
||||
[:script {:src "/js/flowbite.min.js"}]]]))
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
(:require
|
||||
[auto-ap.logging :as alog]
|
||||
[config.core :refer [env]]
|
||||
[hiccup2.core :as hiccup]))
|
||||
[hiccup2.core :as hiccup]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn html-response [hiccup & {:keys [status headers] :or {status 200 headers {}}}]
|
||||
{:status status
|
||||
@@ -39,3 +40,24 @@
|
||||
(ex-message e)]
|
||||
:status 500)))))))
|
||||
|
||||
(defn form-data->map [form-data]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond (and (string? v)
|
||||
(empty? v))
|
||||
acc
|
||||
|
||||
:else
|
||||
(assoc-in acc (->> (str/split k #"_")
|
||||
(mapv #(apply keyword (str/split % #"/"))))
|
||||
v)))
|
||||
{}
|
||||
form-data))
|
||||
|
||||
(defn path->name [k]
|
||||
(cond (keyword? k)
|
||||
(str (namespace k) "/" (name k))
|
||||
|
||||
(seq k)
|
||||
(str/join "_" (map path->name k))
|
||||
:else k))
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
[clj-time.coerce :as coerce]
|
||||
[datomic.api :as dc]
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.clients :as d-clients]))
|
||||
[auto-ap.datomic.clients :as d-clients]
|
||||
[clojure.string :as str]))
|
||||
;; switch all of this to use tokens instead of passing around client codes, particularly because the codes
|
||||
;; need to be tweaked for repeats
|
||||
(defn client-code->login [client-code]
|
||||
@@ -61,7 +62,7 @@
|
||||
(client/post (merge {:headers (assoc base-headers
|
||||
"loginName" (:yodlee2-admin-user env)
|
||||
"Content-Type" "application/x-www-form-urlencoded")
|
||||
:body (str "clientId=" (:yodlee2-client-id env) " &secret=" (:yodlee2-client-secret env))
|
||||
:body (str "clientId=" (:yodlee2-client-id env) "&secret=" (:yodlee2-client-secret env))
|
||||
:as :json}
|
||||
other-config)
|
||||
)
|
||||
@@ -75,11 +76,13 @@
|
||||
(log/info "logging in as " client-code)
|
||||
(-> (str (:yodlee2-base-url env) "/auth/token")
|
||||
(client/post (merge {:headers (assoc base-headers
|
||||
"loginName" (if (<= (count client-code) 3)
|
||||
(str client-code client-code)
|
||||
client-code)
|
||||
"loginName" (if (:yodlee2-test-user env)
|
||||
(:yodlee2-test-user env)
|
||||
(if (<= (count client-code) 3)
|
||||
(str client-code client-code)
|
||||
client-code))
|
||||
"Content-Type" "application/x-www-form-urlencoded")
|
||||
:body (str "clientId=" (:yodlee2-client-id env) " &secret=" (:yodlee2-client-secret env))
|
||||
:body (str "clientId=" (:yodlee2-client-id env) "&secret=" (:yodlee2-client-secret env))
|
||||
:as :json}
|
||||
other-config)
|
||||
)
|
||||
@@ -278,10 +281,6 @@
|
||||
provider-accounts)
|
||||
vals)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(defn delete-provider-account [client-code id]
|
||||
(let [cob-session (login-user (client-code->login client-code))]
|
||||
|
||||
|
||||
@@ -5,20 +5,30 @@
|
||||
"admin" {"/history" {"" :admin-history
|
||||
"/" :admin-history
|
||||
#"/search/?" :admin-history-search
|
||||
["/" [#"\d+" :entity-id] #"/?"] :admin-history-search
|
||||
["/" [#"\d+" :entity-id] #"/?"] :admin-history
|
||||
["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect}
|
||||
"/ezcater-xls" :admin-ezcater-xls}
|
||||
"transaction" {"/insights" {"" :transaction-insights
|
||||
"/table" :transaction-insight-table
|
||||
["/approve/" [#"\d+" :transaction-id]] {:post :transaction-insight-approve}
|
||||
["/disapprove/" [#"\d+" :transaction-id]] {:delete :transaction-insight-disapprove}
|
||||
["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows}
|
||||
["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}}
|
||||
"company" {"/dropdown" :company-dropdown-contents
|
||||
"/active" {:put :active-client}
|
||||
"/1099" :company-1099
|
||||
"/1099/table" {:get :company-1099-vendor-table}
|
||||
"/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog
|
||||
:post :company-1099-vendor-save}}}})
|
||||
"company" {"" :company
|
||||
"/dropdown" :company-dropdown-search-results
|
||||
"/active" {:put :active-client}
|
||||
"/1099" :company-1099
|
||||
"/1099/table" {:get :company-1099-vendor-table}
|
||||
"/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog
|
||||
:post :company-1099-vendor-save}}
|
||||
"/reports" {"" {:get :company-reports
|
||||
:delete :company-reports-delete}
|
||||
"/table" :company-reports-table}
|
||||
"/yodlee" {"" {:get :company-yodlee}
|
||||
"/table" {:get :company-yodlee-table}
|
||||
"/fastlink" {:get :company-yodlee-fastlink-dialog}
|
||||
"/refresh" {:put :company-yodlee-provider-account-refresh}}
|
||||
}})
|
||||
|
||||
|
||||
(def only-routes ["/" routes])
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
(ns auto-ap.views.components.layouts
|
||||
(:require
|
||||
[auto-ap.events :as events]
|
||||
[auto-ap.forms :as forms]
|
||||
[auto-ap.forms.builder :as form-builder]
|
||||
[auto-ap.routes :as routes]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.views.components.modal :as modal]
|
||||
[auto-ap.views.components.search :as search]
|
||||
[auto-ap.views.components.vendor-dialog :as vendor-dialog]
|
||||
[auto-ap.views.utils
|
||||
:refer [active-when
|
||||
appearing
|
||||
dispatch-event-with-propagation
|
||||
login-url]]
|
||||
:refer [active-when appearing dispatch-event-with-propagation login-url]]
|
||||
[bidi.bidi :as bidi]
|
||||
[clojure.string :as str]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as r]
|
||||
[auto-ap.forms.builder :as form-builder]
|
||||
[vimsical.re-frame.cofx.inject :as inject]
|
||||
[auto-ap.forms :as forms]))
|
||||
[vimsical.re-frame.cofx.inject :as inject]))
|
||||
|
||||
(defn navbar-drop-down [{:keys [ class]} _]
|
||||
(let [!child (r/atom nil)]
|
||||
@@ -47,19 +45,19 @@
|
||||
(defn login-dropdown []
|
||||
(let [user (re-frame/subscribe [::subs/user])]
|
||||
(if @user
|
||||
[navbar-drop-down {:header [:span [:span.icon [:i.fa.fa-user] ]
|
||||
[navbar-drop-down {:header [:span [:span.icon [:i.fa.fa-user]]
|
||||
[:span (:user/name @user)]]
|
||||
:id ::account}
|
||||
[:div
|
||||
[:a {:class "navbar-item"
|
||||
:href (bidi/path-for routes/routes :reports)} "My company"]
|
||||
:href (bidi/path-for auto-ap.ssr-routes/only-routes :company)} "My company"]
|
||||
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New Vendor"]
|
||||
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit Vendor"]
|
||||
(when (= "admin" (:user/role @user))
|
||||
[:a {:class "navbar-item" :href (bidi/path-for routes/routes :admin)} "Administration"])
|
||||
[:hr {:class "navbar-divider"}]
|
||||
[:a.navbar-item {:on-click (fn [e] (.preventDefault e) (re-frame/dispatch [::events/logout]))} "Logout"]]]
|
||||
[:a.navbar-item {:href login-url} "Login"])))
|
||||
[:a.navbar-item {:on-click (fn [e] (.preventDefault e) (re-frame/dispatch [::events/logout]))} "Logout"]]]
|
||||
[:a.navbar-item {:href (login-url)} "Login"])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::client-search
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
(ns auto-ap.views.pages.login
|
||||
(:require
|
||||
[auto-ap.views.utils :refer [login-url]]
|
||||
[re-frame.core :as re-frame]))
|
||||
[re-frame.core :as re-frame]
|
||||
[cemerick.url :as url]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::logout-reason
|
||||
@@ -17,9 +18,9 @@
|
||||
[:div.notification.is-warning reason])
|
||||
[:h1.title "Login"]
|
||||
[:div.box.slideInFromBelow
|
||||
|
||||
|
||||
[:img {:src "/img/logo-big.png"}]
|
||||
[:div
|
||||
[:a.button.is-large.is-primary {:href login-url} "Login with Google"]]]
|
||||
[:a.button.is-large.is-primary {:href (doto (login-url (get (:query (url/url (.-location js/window))) "redirect-to")) println)} "Login with Google"]]]
|
||||
[:p.has-text-gray
|
||||
"Copyright Integreat 2018"]]]]]])
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
|
||||
(defn needs-activation-page []
|
||||
[:div
|
||||
[:div
|
||||
[:h2 "Sorry, your user is not activated yet. Please have Ben Skinner enable your account. Click "
|
||||
[:a {:on-click (fn []
|
||||
(re-frame/dispatch-sync [::relogin])
|
||||
true)
|
||||
:href login-url}
|
||||
:href (login-url)}
|
||||
"here"]
|
||||
" to try again."]])
|
||||
|
||||
@@ -21,56 +21,55 @@
|
||||
user @(re-frame/subscribe [::subs/user])
|
||||
client @(re-frame/subscribe [::subs/client])]
|
||||
[:div
|
||||
[:div [:p.menu-label "Type"]
|
||||
[:ul.menu-list
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :transactions)
|
||||
:class [(active-when ap = :transactions)]}
|
||||
[:span {:class "icon" :style {:font-size "25px"}}]
|
||||
[:span {:class "name"} "All"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :unapproved-transactions)
|
||||
:class [(active-when ap = :unapproved-transactions)]}
|
||||
[:span {:class "icon icon-task-list-text-1" :style {:font-size "25px"}}]
|
||||
[:span {:class "name"} "Unapproved"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :requires-feedback-transactions)
|
||||
:class [(active-when ap = :requires-feedback-transactions)]}
|
||||
[:div [:p.menu-label "Type"]
|
||||
[:ul.menu-list
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :transactions)
|
||||
:class [(active-when ap = :transactions)]}
|
||||
[:span {:class "icon" :style {:font-size "25px"}}]
|
||||
[:span {:class "name"} "All"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :unapproved-transactions)
|
||||
:class [(active-when ap = :unapproved-transactions)]}
|
||||
[:span {:class "icon icon-task-list-text-1" :style {:font-size "25px"}}]
|
||||
[:span {:class "name"} "Unapproved"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :requires-feedback-transactions)
|
||||
:class [(active-when ap = :requires-feedback-transactions)]}
|
||||
|
||||
[:span {:class "icon icon-task-list-question" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "name"} "Client Review"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :approved-transactions)
|
||||
:class [(active-when ap = :approved-transactions)]}
|
||||
[:span {:class "icon icon-task-list-question" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "icon icon-task-list-check-1" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "name"} "Approved"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :excluded-transactions)
|
||||
:class [(active-when ap = :excluded-transactions)]}
|
||||
[:span {:class "name"} "Client Review"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :approved-transactions)
|
||||
:class [(active-when ap = :approved-transactions)]}
|
||||
|
||||
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "name"} "Excluded"]]]
|
||||
[:span {:class "icon icon-task-list-check-1" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "name"} "Approved"]]]
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for routes/routes :excluded-transactions)
|
||||
:class [(active-when ap = :excluded-transactions)]}
|
||||
|
||||
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "name"} "Excluded"]]]
|
||||
(when @(re-frame/subscribe [::subs/is-admin?])
|
||||
[:li.menu-item
|
||||
[:a.item {:href (bidi/path-for ssr-routes/only-routes :transaction-insights)
|
||||
:class [(active-when ap = :transaction-insights)]}
|
||||
|
||||
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
|
||||
|
||||
[:span {:class "name"} "Insights"]]]
|
||||
]]
|
||||
(when client
|
||||
[:<>
|
||||
|
||||
[:span {:class "name"} "Insights"]]])]]
|
||||
(when client
|
||||
[:<>
|
||||
[:p.menu-label "Bank Account"]
|
||||
[:div
|
||||
[:div
|
||||
[bank-account-filter
|
||||
{:on-change-event [::data-page/filter-changed data-page :bank-account]
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :bank-account])
|
||||
:bank-accounts @(re-frame/subscribe [::subs/bank-accounts])}]]])
|
||||
|
||||
|
||||
[:p.menu-label "Financial Account"]
|
||||
[:div
|
||||
@@ -79,7 +78,7 @@
|
||||
{:query i
|
||||
:client-id (:id client)}
|
||||
[:name :id]])
|
||||
:entity->text (fn [x ] (str (:numeric-code x) " - " (:name x)))
|
||||
:entity->text (fn [x] (str (:numeric-code x) " - " (:name x)))
|
||||
:type "typeahead-v3"
|
||||
:on-change #(re-frame/dispatch [::data-page/filter-changed data-page :account (some-> % (select-keys [:name :id :numeric-code]))])
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :account])}]]
|
||||
@@ -94,13 +93,11 @@
|
||||
:on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %])
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]]
|
||||
|
||||
|
||||
|
||||
[:p.menu-label "Amount"]
|
||||
[:div
|
||||
[number-filter
|
||||
{:on-change-event [::data-page/filter-changed data-page :amount-range]
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]]
|
||||
[:div
|
||||
[number-filter
|
||||
{:on-change-event [::data-page/filter-changed data-page :amount-range]
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]]
|
||||
|
||||
[:p.menu-label "Date Range"]
|
||||
[:div
|
||||
@@ -108,33 +105,31 @@
|
||||
{:on-change-event [::data-page/filter-changed data-page :date-range]
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]]
|
||||
|
||||
|
||||
[:p.menu-label "Location"]
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "SC"
|
||||
:style {:width "3em"}
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :location])
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :location ])} ]]]
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :location])}]]]
|
||||
|
||||
[:p.menu-label "Description"]
|
||||
[:div
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "CHECK 123 ABC"
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :description])
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :description])} ]]]]
|
||||
[:p.menu-label "Description"]
|
||||
[:div
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "CHECK 123 ABC"
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :description])
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :description])}]]]]
|
||||
(when-let [exact-match-id @(re-frame/subscribe [::data-page/filter data-page :exact-match-id])]
|
||||
[:div
|
||||
[:p.menu-label "Specific Transaction"]
|
||||
[:span.tag.is-medium exact-match-id " "
|
||||
[:button.delete.is-small {:on-click
|
||||
(dispatch-event [::data-page/filter-changed data-page :exact-match-id nil])}]]])
|
||||
|
||||
|
||||
(when (= "admin" (:user/role user))
|
||||
[:<>
|
||||
|
||||
|
||||
[:p.menu-label "Admin only"]
|
||||
[:div
|
||||
[:div
|
||||
[switch-field {:id "unresolved-only"
|
||||
:checked (boolean @(re-frame/subscribe [::data-page/filter data-page :unresolved]))
|
||||
:on-change (fn [e]
|
||||
@@ -149,7 +144,7 @@
|
||||
[:button.delete.is-small {:on-click
|
||||
(dispatch-event [::data-page/filter-changed data-page :import-batch-id nil])}]]])
|
||||
|
||||
[:div
|
||||
[:div
|
||||
[switch-field {:id "potentially-duplicate"
|
||||
:checked (boolean @(re-frame/subscribe [::data-page/filter data-page :potential-duplicates]))
|
||||
:on-change (fn [e]
|
||||
|
||||
@@ -57,10 +57,16 @@
|
||||
|
||||
(when (apply f (into [active-page] rest)) " is-active"))
|
||||
|
||||
(def login-url
|
||||
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
|
||||
redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"))]
|
||||
(str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&max_auth_age=0&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile")))
|
||||
(defn login-url
|
||||
([] (login-url nil))
|
||||
([next]
|
||||
|
||||
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
|
||||
redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"
|
||||
))]
|
||||
(str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&max_auth_age=0&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile"
|
||||
(when next
|
||||
(str "&state=" (js/encodeURIComponent next)))))))
|
||||
|
||||
(defn dispatch-value-change [event]
|
||||
(fn [e]
|
||||
@@ -249,9 +255,9 @@
|
||||
:user/role)))))))
|
||||
|
||||
(defn query-params []
|
||||
(reduce-kv
|
||||
(reduce-kv
|
||||
(fn [result k v]
|
||||
(assoc result (keyword k) (edn/read-string v)))
|
||||
(assoc result (keyword k) (try (edn/read-string v) (catch js/Error e v))))
|
||||
{}
|
||||
(:query (cemerick.url/url (.-location js/window)))))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user