Merge branch 'try-tailwind' of bitbucket.org:brycecovertoperations/integreat into try-tailwind

This commit is contained in:
Bryce
2023-07-13 20:12:55 -07:00
23 changed files with 2536 additions and 394 deletions

1871
data/inference-outcome.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1 @@
1. Make admin sidebar
2. Make transaction sidebar
3. Test power users
4. Maybe make dark mode persist
5. redirect when not authenticated

View File

@@ -1380,6 +1380,10 @@ input:checked + .toggle-bg {
flex: 1 1 0%;
}
.flex-none {
flex: none;
}
.flex-shrink {
flex-shrink: 1;
}
@@ -1480,6 +1484,14 @@ input:checked + .toggle-bg {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.content-start {
align-content: flex-start;
}
.items-start {
align-items: flex-start;
}
@@ -1512,6 +1524,10 @@ input:checked + .toggle-bg {
justify-content: space-between;
}
.justify-items-start {
justify-items: start;
}
.gap-1 {
gap: 0.25rem;
}
@@ -1587,6 +1603,10 @@ input:checked + .toggle-bg {
border-color: rgb(243 244 246 / var(--tw-divide-opacity));
}
.justify-self-start {
justify-self: start;
}
.overflow-auto {
overflow: auto;
}
@@ -1797,6 +1817,21 @@ input:checked + .toggle-bg {
background-color: rgb(253 246 178 / var(--tw-bg-opacity));
}
.bg-green-200 {
--tw-bg-opacity: 1;
background-color: rgb(201 225 171 / var(--tw-bg-opacity));
}
.bg-blue-200 {
--tw-bg-opacity: 1;
background-color: rgb(153 215 247 / var(--tw-bg-opacity));
}
.bg-red-200 {
--tw-bg-opacity: 1;
background-color: rgb(255 154 154 / var(--tw-bg-opacity));
}
.bg-opacity-50 {
--tw-bg-opacity: 0.5;
}
@@ -2346,6 +2381,21 @@ input:checked + .toggle-bg {
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.hover\:bg-green-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(175 211 130 / var(--tw-bg-opacity));
}
.hover\:bg-blue-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(102 196 242 / var(--tw-bg-opacity));
}
.hover\:bg-red-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(255 104 104 / var(--tw-bg-opacity));
}
.hover\:text-blue-600:hover {
--tw-text-opacity: 1;
color: rgb(0 125 187 / var(--tw-text-opacity));
@@ -2437,6 +2487,21 @@ input:checked + .toggle-bg {
--tw-ring-color: rgb(121 181 46 / var(--tw-ring-opacity));
}
.focus\:ring-green-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(201 225 171 / var(--tw-ring-opacity));
}
.focus\:ring-blue-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(153 215 247 / var(--tw-ring-opacity));
}
.focus\:ring-red-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(255 154 154 / var(--tw-ring-opacity));
}
.group:hover .group-hover\:scale-110 {
--tw-scale-x: 1.1;
--tw-scale-y: 1.1;
@@ -2549,6 +2614,26 @@ input:checked + .toggle-bg {
background-color: rgb(99 49 18 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-green-500) {
--tw-bg-opacity: 1;
background-color: rgb(121 181 46 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-green-700) {
--tw-bg-opacity: 1;
background-color: rgb(73 109 28 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-blue-700) {
--tw-bg-opacity: 1;
background-color: rgb(0 94 140 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-red-700) {
--tw-bg-opacity: 1;
background-color: rgb(153 2 2 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-opacity-80) {
--tw-bg-opacity: 0.8;
}
@@ -2662,6 +2747,16 @@ input:checked + .toggle-bg {
background-color: rgb(73 109 28 / var(--tw-bg-opacity));
}
:is(.dark .dark\:hover\:bg-blue-600:hover) {
--tw-bg-opacity: 1;
background-color: rgb(0 125 187 / var(--tw-bg-opacity));
}
:is(.dark .dark\:hover\:bg-red-600:hover) {
--tw-bg-opacity: 1;
background-color: rgb(204 2 2 / var(--tw-bg-opacity));
}
:is(.dark .dark\:hover\:text-blue-500:hover) {
--tw-text-opacity: 1;
color: rgb(0 156 234 / var(--tw-text-opacity));

View File

@@ -79,7 +79,7 @@
:quote? (constantly true))))
(defn write-inference []
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/inference.csv")]
(with-open [f (io/writer "~/dev/transaction-training/input/inference.csv")]
(csv/write-csv f
(into [["transaction" "client" "bank" "bank_type" "description" "date" "amount"]]
(->>

View File

@@ -1,8 +1,8 @@
(ns ingest-ml
(:require [datomic.client.api :as dc]
(:require [datomic.api :as dc]
[clojure.java.io :as io]
[clojure.data.csv :as csv]
[iol-ion.tx :refer [upsert-entity]]
#_[iol-ion.tx :refer [upsert-entity]]
[auto-ap.datomic :refer [conn]]))
(println "hi")
@@ -13,37 +13,60 @@
(defn reset-inference []
(doseq [p (->>
(dc/q '[:find ?t
:where [?t :transaction/recommended-account]]
(dc/db conn))
(map (fn [[t]]
`(upsert-entity
~{:db/id t
:transaction/recommended-account nil
:tranasction/vendor-confidence nil
:transaction/account-confidence nil
:transaction/recommended-vendor nil})))
(partition-all 100))]
(dc/transact conn {:tx-data p})))
(doseq [p (->>
(dc/q '[:find ?t
:where [?t :transaction/recommended-account]]
(dc/db conn))
(map (fn [[t]]
[:upsert-entity {:db/id t
:transaction/recommended-account nil
:tranasction/vendor-confidence nil
:transaction/account-confidence nil
:transaction/recommended-vendor nil}]))
(partition-all 100))]
@(dc/transact conn p)))
(defn slurp-csv [c]
(with-open [s (io/reader c)]
(into [] (csv/read-csv s))))
(defn read-inference []
(with-open [reader (io/reader "/mnt/data/dev2/ml-test/inference-outcome.csv")]
(->> (csv/read-csv reader)
(into []
(comp
(drop 1)
(map (fn [[_ transaction best-vendor best-account account-confidence]]
{:db/id (Long/parseLong transaction)
:transaction/recommended-account (Long/parseLong best-account)
:transaction/account-confidence (Double/parseDouble account-confidence)
:transaction/recommended-vendor (Long/parseLong best-vendor)}
)))))))
(let [numeric-code->account (into {} (dc/q '[:find ?nc ?a
:in $
:where [?a :account/numeric-code ?nc]]
(dc/db conn)))
inference-account->account (into {}
(comp
(drop 1)
(map (fn [[a n]]
[(Long/parseLong a) (numeric-code->account (Long/parseLong n))])))
(slurp-csv "/home/notid/dev/transaction-training/input/accounts.csv"))
vendor-name->vendor (into {} (dc/q '[:find ?vn ?v
:in $
:where [?v :vendor/name ?vn]]
(dc/db conn)))
inference-vendor->vendor (into {}
(comp
(drop 1)
(map (fn [[a n]]
[(Long/parseLong a) (vendor-name->vendor (Long/parseLong n))])))
(slurp-csv "/home/notid/dev/transaction-training/input/vendors.csv"))]
inference-vendor->vendor
#_(with-open [reader (io/reader "/home/notid/dev/transaction-training/inference-outcome.csv")]
(->> (csv/read-csv reader)
(into []
(comp
(drop 1)
(map (fn [[_ transaction best-vendor best-account account-confidence]]
{:db/id (Long/parseLong transaction)
:transaction/recommended-account (Long/parseLong best-account)
:transaction/account-confidence (Double/parseDouble account-confidence)
:transaction/recommended-vendor (Long/parseLong best-vendor)}))))))))
(defn apply-inference [inference]
(doseq [p (->> inference (partition-all 100))]
(dc/transact conn {:tx-data p})))
@(dc/transact conn p)))
(defn check-applied-inference []
@@ -69,3 +92,22 @@
(dc/db conn))
(shuffle)
(take 10))))
(defn random-infer []
(let [n (rand-int 1000)
vs (into [] (map first (dc/q '[:find ?v :in $ :where [?v :vendor/name]] (dc/db conn))))
as (into [] (map first (dc/q '[:find ?v :in $ :where [?v :account/name]] (dc/db conn))))]
(->>
(dc/qseq {:query '[:find ?t
:where
[?t :transaction/approval-status :transaction-approval-status/unapproved]
(not [?t :transaction/vendor])]
:args [(dc/db conn)]})
(map first)
(take n)
(map (fn [t]
{:db/id t
:transaction/recommended-account (rand-nth as)
:transaction/account-confidence (double (/ (double (rand-int 100)) 100.0))
:transaction/recommended-vendor (rand-nth vs)})))))

View File

@@ -0,0 +1,83 @@
;; This buffer is for Clojure experiments and evaluation.
;; Press C-j to evaluate the last expression.
;; You can also press C-u C-j to evaluate the expression and pretty-print its result.
(dc/pull (dc/db conn) [:db/id] [:client/code "NGAK"])
(user/init-repl)
(require '[auto-ap.datomic :refer [pull-attr pull-many]])
(csv/write-csv *out*
(let [clients (pull-many (dc/db conn) '[:db/id :client/code :client/locations] [[:client/code "NGAK"] [:client/code "NGOP"] #_#_[:client/code "NGRV"] [:client/code "NGE1"]])
account->numeric-code (into {} (seq (dc/q '[:find ?i ?n
:in $ [?a ...]
:where [?i ?a ?n]]
(dc/db conn) [:account/numeric-code :bank-account/numeric-code])))
account->name (let [lookup (into {} (seq (dc/q '[:find ?i ?n
:in $
:where [?i :account/name ?n]]
(dc/db conn))))]
(fn [x]
(or (lookup x) "Bank Account")))
account->location (into {} (seq (dc/q '[:find ?i ?l
:in $
:where [?i :account/location ?l]]
(dc/db conn))))
used-accounts (into #{} (for [c clients
d (dc/index-range (dc/db conn)
:journal-entry-line/client+account+location+date
[(:db/id c)]
[(inc (:db/id c))])
:let [[_ account] (:v d)]]
account))]
(for [client clients
:let [all-entries (->> (dc/index-pull (dc/db conn)
{:index :avet
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
:start [:journal-entry-line/client+account+location+date [(:db/id client)]]
:reverse false
:limit 1})
(take-while (fn [curr]
(= (first (:journal-entry-line/client+account+location+date curr))
(:db/id client)))))
account-lookup (->> all-entries
(reduce
(fn [acc curr]
(let [[client account location date] (:journal-entry-line/client+account+location+date curr)
numeric-code (account->numeric-code account)]
(assoc acc (format "%d-%d-%s-%s" client numeric-code location (atime/unparse-local (clj-time.coerce/to-date-time date) atime/iso-date)) (:journal-entry-line/running-balance curr))))
(sorted-map)))]
a (sort-by account->numeric-code used-accounts)
:let [numeric-code (account->numeric-code a)]
l (or (account->location a) (:client/locations client))
delta (range -60 0)
:let [date (t/plus (c/to-date-time #inst "2023-06-12T08:00:00") (t/days delta))
date-str (atime/unparse-local date atime/iso-date)]
:when (and numeric-code (>= numeric-code 40000))]
(conj [(:client/code client) l numeric-code (account->name a) date-str] (with-precision 2
(double (.setScale (bigdec (or
(some->> (subseq account-lookup >= (format "%d-%d-%s-%s" (:db/id client) numeric-code l "2000-01-01") <= (format "%d-%d-%s-%s" (:db/id client) numeric-code l date-str))
last
last)
0.0))
2 java.math.RoundingMode/HALF_UP))))))
)
(dc/q '[:find (count ?x)
:where [?x :journal-entry-line/dirty true]]
(dc/db conn))
(let [clients (map first
(dc/q '[:find (pull ?c [:client/code :db/id])
:where [?c :client/code]]
(dc/db conn)))]
(doseq [[c i] (map vector clients (range))]
(let [db (dc/db conn)
accounts-needing-rebuild (auto-ap.ledger/accounts-needing-rebuild db (:db/id c))]
(when (seq accounts-needing-rebuild)
(println "C" c "needs" (count accounts-needing-rebuild) "built")
#_(refresh-running-balance-accounts accounts-needing-rebuild clients c i db)
#_(mu/log ::client-completed)))))
,

View File

@@ -605,7 +605,7 @@
:keywords [#"reelproduce.com"]
:extract {:date #"([0-9]+/[0-9]+/[0-9]+)"
:customer-identifier #"Bill To(?:.*?)\n\n\s+(.*?)\s{2,}"
:invoice-number #"Invoice #\n.*?([\d\-]+)\n"
:invoice-number #"Invoice #\n.*?\n.*?([\d\-]+)\n"
:total #"Total\s*\n\s+\$([\d\-,]+\.\d{2,2}+)"}
:parser {:date [:clj-time "MM/dd/yy"]
:total [:trim-commas-and-negate nil]}}])

View 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})
@@ -54,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))})))

View File

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

View File

@@ -126,16 +126,17 @@ function initCompanyDropdown() {
(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 {:client (:client new-session)
:identity identity}))
:session
new-session
:headers
{"hx-trigger" "clientSelected"}))))
(assoc
(html-response
(dropdown {:client (:client new-session)
:identity identity}))
:session
new-session
:headers
{"hx-trigger" "clientSelected"}))))

View File

@@ -150,69 +150,90 @@
(defn main-aside-nav- []
[:ul {:class "space-y-2"}
[:li
(menu-button- {:icon svg/pie}
"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 "http://google.com"}
"All")
(menu-button- {:href "http://google.com"}
"Paid")
(menu-button- {:href "http://google.com"}
"Unpaid")
(menu-button- {:href "http://google.com"}
"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 "Sales"} "Sales")
(menu-button- {:href "Sales"} "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 "Sales"} "All")
(menu-button- {:href "Sales"} "Pending")
(menu-button- {:href "Sales"} "Cleared")
(menu-button- {:href "Sales"} "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 "Sales"} "All")
(menu-button- {:href "Sales"} "Unapproved")
(menu-button- {:href "Sales"} "Client Review")
(menu-button- {:href "Sales"} "Approved")
(menu-button- {:href "Sales"} "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 "Sales"} "Register")
(menu-button- {:href "Sales"} "Profit & Loss")
(menu-button- {:href "Sales"} "Profit & Loss Detail")
(menu-button- {:href "Sales"} "Cash Flows")
(menu-button- {:href "Sales"} "Balance Sheet")
(menu-button- {:href "Sales"} "External Ledger Import"))]])
[: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- []
@@ -301,3 +322,5 @@
(menu-button- {:href (bidi/path-for ssr-routes/only-routes
:admin-ezcater-xls)
:hx-boost "true"} "EZCater XLS Import"))]])

View File

@@ -16,11 +16,22 @@
(into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center ] children)])
(defn icon-button- [params & children]
(into
[:button (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.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)]]))
(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

View File

@@ -8,7 +8,8 @@
(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))))}]
"_" (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)))
@@ -23,7 +24,7 @@
: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 {:class (:class 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

View File

@@ -35,7 +35,7 @@ curModal.hide();
]])
(defn modal-card- [params header content footer]
[:div#modal-card
[: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"}

View File

@@ -1,50 +1,45 @@
(ns auto-ap.ssr.components.navbar
(:require
[auto-ap.datomic :refer [conn pull-attr]]
[auto-ap.client-routes :as client-routes2]
[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]
[datomic.api :as dc]
[hiccup2.core :as hiccup]
[auto-ap.ssr.components.user-dropdown :as user-dropdown]))
[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"}
[: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})
]]]])
[: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})]]]])

View File

@@ -20,7 +20,7 @@
: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-admin company-dropdown/active-client)))
: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))
@@ -35,11 +35,11 @@
: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-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))
: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-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))})

View File

@@ -419,3 +419,15 @@
[: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"}]])

View File

@@ -1,7 +1,6 @@
(ns auto-ap.ssr.transaction.insights
(:require
[auto-ap.datomic :refer [conn visible-clients]]
[auto-ap.shared-views.company.sidebar :refer [company-side-bar]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.ui :refer [base-page]]
[auto-ap.ssr.utils :refer [html-response]]
@@ -10,7 +9,9 @@
[clj-time.coerce :as coerce]
[datomic.api :as dc]
[hiccup2.core :as hiccup]
[clj-time.core :as time]))
[auto-ap.ssr.components :as com]
[auto-ap.client-routes :as client-routes]
[auto-ap.ssr.svg :as svg]))
(def pull-expr [:transaction/description-original
:db/id
@@ -25,77 +26,84 @@
(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-approve
:transaction-id (:db/id r))
:hx-swap "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
@@ -108,15 +116,15 @@
(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 +138,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 +179,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 +204,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})]
"Transaction Insights"))
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"))

View File

@@ -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

View File

@@ -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"]]]]]])

View File

@@ -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."]])

View File

@@ -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]

View File

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