migrates accounts
This commit is contained in:
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
|
|
||||||
.choices {
|
.choices {
|
||||||
@apply border-0 !important;
|
@apply border-0 mb-0 !important;
|
||||||
}
|
}
|
||||||
.choices__list--multiple {
|
.choices__list--multiple {
|
||||||
}
|
}
|
||||||
@@ -110,3 +110,19 @@
|
|||||||
@apply bg-green-500 border-gray-500 ring-blue-500 border-blue-500 !important;
|
@apply bg-green-500 border-gray-500 ring-blue-500 border-blue-500 !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.choices__list--single .choices__item {
|
||||||
|
@apply w-auto flex !important;
|
||||||
|
}
|
||||||
|
.choices__list--single {
|
||||||
|
@apply w-auto !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices__list--single button {
|
||||||
|
@apply block relative m-0 h-auto !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices[data-type*="select-one"] .choices__button {
|
||||||
|
right:auto !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1344,6 +1344,10 @@ input:checked + .toggle-bg {
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-16 {
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-3 {
|
.w-3 {
|
||||||
width: 0.75rem;
|
width: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -1352,6 +1356,10 @@ input:checked + .toggle-bg {
|
|||||||
width: 0.875rem;
|
width: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-32 {
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-36 {
|
.w-36 {
|
||||||
width: 9rem;
|
width: 9rem;
|
||||||
}
|
}
|
||||||
@@ -1384,6 +1392,10 @@ input:checked + .toggle-bg {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-96 {
|
||||||
|
width: 24rem;
|
||||||
|
}
|
||||||
|
|
||||||
.max-w-2xl {
|
.max-w-2xl {
|
||||||
max-width: 42rem;
|
max-width: 42rem;
|
||||||
}
|
}
|
||||||
@@ -1404,6 +1416,10 @@ input:checked + .toggle-bg {
|
|||||||
max-width: 1024px;
|
max-width: 1024px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-w-4xl {
|
||||||
|
max-width: 56rem;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
@@ -1420,6 +1436,14 @@ input:checked + .toggle-bg {
|
|||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shrink-0 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow-0 {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.basis-1\/4 {
|
.basis-1\/4 {
|
||||||
flex-basis: 25%;
|
flex-basis: 25%;
|
||||||
}
|
}
|
||||||
@@ -1518,6 +1542,10 @@ input:checked + .toggle-bg {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-cols-2 {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.grid-cols-3 {
|
.grid-cols-3 {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -2257,6 +2285,11 @@ input:checked + .toggle-bg {
|
|||||||
color: rgb(97 145 37 / var(--tw-text-opacity));
|
color: rgb(97 145 37 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-red-500 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 3 3 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-red-600 {
|
.text-red-600 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(204 2 2 / var(--tw-text-opacity));
|
color: rgb(204 2 2 / var(--tw-text-opacity));
|
||||||
@@ -2485,6 +2518,7 @@ input:checked + .toggle-bg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.choices {
|
.choices {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
border-width: 0px !important;
|
border-width: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2628,6 +2662,26 @@ input:checked + .toggle-bg {
|
|||||||
--tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)) !important;
|
--tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choices__list--single .choices__item {
|
||||||
|
display: flex !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices__list--single {
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices__list--single button {
|
||||||
|
position: relative !important;
|
||||||
|
margin: 0px !important;
|
||||||
|
display: block !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices[data-type*="select-one"] .choices__button {
|
||||||
|
right:auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:scale-105:hover {
|
.hover\:scale-105:hover {
|
||||||
--tw-scale-x: 1.05;
|
--tw-scale-x: 1.05;
|
||||||
--tw-scale-y: 1.05;
|
--tw-scale-y: 1.05;
|
||||||
|
|||||||
@@ -844,6 +844,8 @@
|
|||||||
@(dc/transact conn
|
@(dc/transact conn
|
||||||
(edn/read-string {:readers {'db/id id-literal
|
(edn/read-string {:readers {'db/id id-literal
|
||||||
'db/fn construct}} (slurp (io/resource "functions.edn")))))
|
'db/fn construct}} (slurp (io/resource "functions.edn")))))
|
||||||
|
(defn all-schema []
|
||||||
|
(edn/read-string (slurp (io/resource "schema.edn"))))
|
||||||
|
|
||||||
(defn transact-schema [conn]
|
(defn transact-schema [conn]
|
||||||
@(dc/transact conn
|
@(dc/transact conn
|
||||||
|
|||||||
292
src/clj/auto_ap/ssr/admin/accounts.clj
Normal file
292
src/clj/auto_ap/ssr/admin/accounts.clj
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
(ns auto-ap.ssr.admin.accounts
|
||||||
|
(:require
|
||||||
|
[auto-ap.datomic
|
||||||
|
:refer [add-sorter-fields
|
||||||
|
apply-pagination
|
||||||
|
apply-sort-3
|
||||||
|
conn
|
||||||
|
merge-query
|
||||||
|
pull-many
|
||||||
|
query2]]
|
||||||
|
[auto-ap.query-params :as query-params]
|
||||||
|
[auto-ap.routes.utils
|
||||||
|
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
|
[auto-ap.ssr.components :as com]
|
||||||
|
[auto-ap.ssr.grid-page-helper :as helper]
|
||||||
|
[auto-ap.ssr.svg :as svg]
|
||||||
|
[auto-ap.ssr.utils
|
||||||
|
:refer [entity-id
|
||||||
|
forced-vector
|
||||||
|
html-response
|
||||||
|
map->db-id-decoder
|
||||||
|
ref->enum-schema
|
||||||
|
ref->select-options
|
||||||
|
temp-id
|
||||||
|
wrap-schema-decode]]
|
||||||
|
[bidi.bidi :as bidi]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[datomic.api :as dc]
|
||||||
|
[hiccup2.core :as hiccup]
|
||||||
|
[malli.core :as mc]
|
||||||
|
[ring.middleware.nested-params :refer [wrap-nested-params]]))
|
||||||
|
|
||||||
|
(defn filters [request]
|
||||||
|
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
|
||||||
|
"hx-get" (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin-account-table)
|
||||||
|
"hx-target" "#account-table"
|
||||||
|
"hx-indicator" "#account-table"}
|
||||||
|
|
||||||
|
[:fieldset.space-y-6
|
||||||
|
(com/field {:label "Name"}
|
||||||
|
(com/text-input {:name "name"
|
||||||
|
:id "name"
|
||||||
|
:class "hot-filter"
|
||||||
|
:value (:name (:parsed-query-params request))
|
||||||
|
:placeholder "Cash"
|
||||||
|
:size :small}))
|
||||||
|
|
||||||
|
(com/field {:label "Code"}
|
||||||
|
(com/text-input {:name "code"
|
||||||
|
:id "code"
|
||||||
|
:class "hot-filter"
|
||||||
|
:value (:code (:parsed-query-params request))
|
||||||
|
:placeholder "11101"
|
||||||
|
:size :small}))]])
|
||||||
|
|
||||||
|
(def default-read '[:db/id
|
||||||
|
:account/code
|
||||||
|
:account/name
|
||||||
|
:account/numeric-code
|
||||||
|
:account/location
|
||||||
|
{[:account/type :xform iol-ion.query/ident] [:db/ident]
|
||||||
|
[:account/invoice-allowance :xform iol-ion.query/ident] [:db/ident]
|
||||||
|
[:account/vendor-allowance :xform iol-ion.query/ident] [:db/ident]
|
||||||
|
[:account/applicability :xform iol-ion.query/ident] [:db/ident]
|
||||||
|
:account/client-overrides [{:account-client-override/client [:client/name :db/id]}
|
||||||
|
:account-client-override/name
|
||||||
|
:db/id]}])
|
||||||
|
|
||||||
|
(defn fetch-ids [db request]
|
||||||
|
(let [query-params (:parsed-query-params request)
|
||||||
|
query (cond-> {:query {:find []
|
||||||
|
:in '[$ ]
|
||||||
|
:where '[]}
|
||||||
|
:args [db ]}
|
||||||
|
(:sort query-params) (add-sorter-fields {"name" ['[?e :account/name ?n]
|
||||||
|
'[(clojure.string/upper-case ?n) ?sort-name]]
|
||||||
|
"code" ['[(get-else $ ?e :account/numeric-code 0) ?sort-code]]
|
||||||
|
|
||||||
|
"type" ['[?e :account/type ?t]
|
||||||
|
'[?t :db/ident ?ti]
|
||||||
|
'[(name ?ti) ?sort-type]]}
|
||||||
|
query-params)
|
||||||
|
(some->> query-params :name not-empty)
|
||||||
|
(merge-query {:query {:find []
|
||||||
|
:in ['?ns]
|
||||||
|
:where ['[?e :account/name ?an]
|
||||||
|
'[(clojure.string/upper-case ?an) ?upper-an]
|
||||||
|
'[(clojure.string/includes? ?upper-an ?ns)]]}
|
||||||
|
:args [(str/upper-case (:name query-params))]})
|
||||||
|
|
||||||
|
(some->> query-params :code)
|
||||||
|
(merge-query {:query {:find []
|
||||||
|
:in ['?nc]
|
||||||
|
:where ['[?e :account/numeric-code ?nc]
|
||||||
|
]}
|
||||||
|
:args [(:code query-params)]})
|
||||||
|
|
||||||
|
true
|
||||||
|
(merge-query {:query {:find ['?sort-default '?e]
|
||||||
|
:where ['[?e :account/code ?un]
|
||||||
|
'[(clojure.string/upper-case ?un) ?sort-default]]}}))]
|
||||||
|
|
||||||
|
(cond->> (query2 query)
|
||||||
|
true (apply-sort-3 query-params)
|
||||||
|
true (apply-pagination query-params))))
|
||||||
|
|
||||||
|
(defn hydrate-results [ids db _]
|
||||||
|
(let [results (->> (pull-many db default-read ids)
|
||||||
|
(group-by :db/id))
|
||||||
|
refunds (->> ids
|
||||||
|
(map results)
|
||||||
|
(map first))]
|
||||||
|
refunds))
|
||||||
|
|
||||||
|
(defn fetch-page [request]
|
||||||
|
(let [db (dc/db conn)
|
||||||
|
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
|
||||||
|
|
||||||
|
[(->> (hydrate-results ids-to-retrieve db request))
|
||||||
|
matching-count]))
|
||||||
|
|
||||||
|
(def grid-page
|
||||||
|
(helper/build {:id "account-table"
|
||||||
|
:nav (com/admin-aside-nav)
|
||||||
|
:page-specific-nav filters
|
||||||
|
:fetch-page fetch-page
|
||||||
|
:parse-query-params (comp
|
||||||
|
(query-params/parse-key :code query-params/parse-long)
|
||||||
|
(helper/default-parse-query-params grid-page))
|
||||||
|
:row-buttons (fn [request entity]
|
||||||
|
[(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin-account-edit-dialog
|
||||||
|
:db/id (doto (:db/id entity) println)))
|
||||||
|
:hx-target "#modal-holder"
|
||||||
|
:hx-swap "outerHTML"}
|
||||||
|
svg/pencil)])
|
||||||
|
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin)}
|
||||||
|
"Admin"]
|
||||||
|
|
||||||
|
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin-accounts)}
|
||||||
|
"Accounts"]]
|
||||||
|
:title "Accounts"
|
||||||
|
:entity-name "Account"
|
||||||
|
:route :admin-account-table
|
||||||
|
:headers [{:key "code"
|
||||||
|
:name "Code"
|
||||||
|
:sort-key "code"
|
||||||
|
:render :account/numeric-code}
|
||||||
|
|
||||||
|
{:key "name"
|
||||||
|
:name "Name"
|
||||||
|
:sort-key "name"
|
||||||
|
:render :account/name}
|
||||||
|
{:key "type"
|
||||||
|
:name "Type"
|
||||||
|
:sort-key "type"
|
||||||
|
:render #(some->> % :account/type name (com/pill {:color :primary}))}
|
||||||
|
{:key "location"
|
||||||
|
:name "Location"
|
||||||
|
:sort-key "location"
|
||||||
|
:render :account/location}]}))
|
||||||
|
|
||||||
|
(def row* (partial helper/row* grid-page))
|
||||||
|
(def table* (partial helper/table* grid-page))
|
||||||
|
|
||||||
|
(defn account-edit-save [{:keys [params route-params] :as request}]
|
||||||
|
(let [_ @(dc/transact conn [[:upsert-entity (-> params (assoc :db/id (:db/id route-params)) (dissoc :id))]])
|
||||||
|
new-account (some-> route-params :db/id (#(dc/pull (dc/db conn) default-read %)))]
|
||||||
|
|
||||||
|
(html-response
|
||||||
|
(row* identity new-account {:flash? true})
|
||||||
|
:headers {"hx-trigger" "closeModal"
|
||||||
|
"hx-retarget" (format "#account-table tr[data-id=\"%d\"]" (:db/id new-account))})))
|
||||||
|
|
||||||
|
(defn client-override* [override]
|
||||||
|
[:div.flex.gap-2.mb-2.client-override
|
||||||
|
[:div.w-96
|
||||||
|
(com/typeahead {:name (format "account/client-overrides[%s][account-client-override/client]" (:db/id override))
|
||||||
|
:placeholder "Search..."
|
||||||
|
:url (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company-search)
|
||||||
|
:id (str "account-client-override-" (:db/id override))
|
||||||
|
:value [(:db/id (:account-client-override/client override))
|
||||||
|
(:client/name (:account-client-override/client override))]})]
|
||||||
|
[:div.w-96
|
||||||
|
(com/text-input {:name (format "account/client-overrides[%s][account-client-override/name]" (:db/id override))
|
||||||
|
:class "w-full"
|
||||||
|
:value (:account-client-override/name override)})]
|
||||||
|
[:div (com/a-icon-button {"_" (hiccup/raw "on click halt the event then transition the closest <.client-override />'s opacity to 0 then remove closest <.client-override />") } svg/x)]])
|
||||||
|
|
||||||
|
(defn account-edit-dialog [request]
|
||||||
|
(prn (:route-params request))
|
||||||
|
(let [account (some-> request
|
||||||
|
:route-params
|
||||||
|
:db/id
|
||||||
|
(#(dc/pull (dc/db conn) default-read %)))]
|
||||||
|
(html-response
|
||||||
|
(com/modal
|
||||||
|
{:modal-class "max-w-4xl"}
|
||||||
|
[:form#edit-form {:hx-ext "response-targets"
|
||||||
|
:hx-post (str (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin-account-edit-save
|
||||||
|
:request-method :post
|
||||||
|
:db/id (:db/id account )))
|
||||||
|
:hx-swap "outerHTML swap:300ms"
|
||||||
|
:hx-target-400 "#form-errors .error-content"}
|
||||||
|
[:fieldset {:class "hx-disable"}
|
||||||
|
(com/modal-card
|
||||||
|
{}
|
||||||
|
[:div.flex [:div.p-2 "Account"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:account/numeric-code account) " - " (:account/name account)]]
|
||||||
|
[:div.space-y-6
|
||||||
|
(com/field {:label "Name"}
|
||||||
|
(com/text-input {:name "account/name"
|
||||||
|
:autofocus true
|
||||||
|
:class "w-32"
|
||||||
|
:value (:account/name account)}))
|
||||||
|
(com/field {:label "Account Type"}
|
||||||
|
(com/select {:name "account/type"
|
||||||
|
:class "w-36"
|
||||||
|
:id "type"
|
||||||
|
:value (some-> account :account/type name)
|
||||||
|
:options (ref->select-options "account-type")}))
|
||||||
|
(com/field {:label "Location"}
|
||||||
|
(com/text-input {:name "account/location"
|
||||||
|
:class "w-16"
|
||||||
|
:value (:account/location account)}))
|
||||||
|
|
||||||
|
(com/field {:label "Invoice Allowance"}
|
||||||
|
(com/select {:name "account/invoice-allowance"
|
||||||
|
:value (name (:account/invoice-allowance account))
|
||||||
|
:class "w-36"
|
||||||
|
:options (ref->select-options "allowance")}))
|
||||||
|
(com/field {:label "Vendor Allowance"}
|
||||||
|
(com/select {:name "account/vendor-allowance"
|
||||||
|
:class "w-36"
|
||||||
|
:value (name (:account/vendor-allowance account))
|
||||||
|
:options (ref->select-options "allowance")}))
|
||||||
|
(com/field {:label "Applicability"}
|
||||||
|
(com/select {:name "account/applicability"
|
||||||
|
:class "w-36"
|
||||||
|
:value (name (:account/applicability account))
|
||||||
|
:options (ref->select-options "account-applicability")}))
|
||||||
|
|
||||||
|
(com/field {:label "Client Overrides" :id "client-overrides"}
|
||||||
|
(for [override (:account/client-overrides account)]
|
||||||
|
(client-override* override)))
|
||||||
|
(com/a-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin-account-client-override-new)
|
||||||
|
:hx-target "#client-overrides"
|
||||||
|
:hx-swap "beforeend"}
|
||||||
|
"New override")
|
||||||
|
[:div#form-errors [:span.error-content]]
|
||||||
|
(com/button {:color :primary :form "edit-form" :type "submit"}
|
||||||
|
"Save")]
|
||||||
|
[:div])]]))))
|
||||||
|
|
||||||
|
(defn new-client-override [request]
|
||||||
|
(html-response
|
||||||
|
(client-override* {:db/id (str (java.util.UUID/randomUUID))})))
|
||||||
|
|
||||||
|
(def key->handler
|
||||||
|
{:admin-accounts (wrap-admin (helper/page-route grid-page))
|
||||||
|
:admin-account-table (wrap-admin (helper/table-route grid-page))
|
||||||
|
:admin-account-client-override-new (-> new-client-override wrap-admin wrap-client-redirect-unauthenticated)
|
||||||
|
:admin-account-edit-save (-> account-edit-save
|
||||||
|
wrap-admin
|
||||||
|
wrap-client-redirect-unauthenticated
|
||||||
|
(wrap-schema-decode
|
||||||
|
:route-schema (mc/schema [:map [:db/id entity-id]])
|
||||||
|
:params-schema (mc/schema
|
||||||
|
[:map
|
||||||
|
[:account/name :string]
|
||||||
|
[:account/location [:maybe :string]]
|
||||||
|
[:account/type (ref->enum-schema "account-type")]
|
||||||
|
[:account/applicability (ref->enum-schema "account-applicability")]
|
||||||
|
[:account/invoice-allowance (ref->enum-schema "allowance")]
|
||||||
|
[:account/vendor-allowance (ref->enum-schema "allowance")]
|
||||||
|
[:account/client-overrides {:decode/json map->db-id-decoder}
|
||||||
|
(forced-vector [:map
|
||||||
|
[:db/id [:or entity-id temp-id]]
|
||||||
|
[:account-client-override/client [:or entity-id :string]]
|
||||||
|
[:account-client-override/name :string]])]]))
|
||||||
|
(wrap-nested-params))
|
||||||
|
:admin-account-edit-dialog (-> account-edit-dialog
|
||||||
|
wrap-admin
|
||||||
|
wrap-client-redirect-unauthenticated
|
||||||
|
(wrap-schema-decode
|
||||||
|
:route-schema (mc/schema [:map [:db/id entity-id]])))})
|
||||||
@@ -299,12 +299,12 @@
|
|||||||
"Vendors")]
|
"Vendors")]
|
||||||
[:li
|
[:li
|
||||||
(menu-button- {:icon svg/user
|
(menu-button- {:icon svg/user
|
||||||
:href (bidi/path-for client-routes/routes
|
:href (bidi/path-for ssr-routes/only-routes
|
||||||
:admin-users)}
|
:users)}
|
||||||
"Users")]
|
"Users")]
|
||||||
[:li
|
[:li
|
||||||
(menu-button- {:icon svg/accounts
|
(menu-button- {:icon svg/accounts
|
||||||
:href (bidi/path-for client-routes/routes
|
:href (bidi/path-for ssr-routes/only-routes
|
||||||
:admin-accounts)}
|
:admin-accounts)}
|
||||||
"Accounts")]
|
"Accounts")]
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[:div
|
[: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
|
[: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() ")}
|
"_" (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"}
|
[:div {:class (str "relative w-full max-h-full " (or (:modal-class params) " max-w-2xl "))}
|
||||||
(into [:div#modal-content]
|
(into [:div#modal-content]
|
||||||
children)]
|
children)]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
(:require
|
(:require
|
||||||
[hiccup2.core :as hiccup]))
|
[hiccup2.core :as hiccup]))
|
||||||
|
|
||||||
|
|
||||||
(defn select- [params & children]
|
(defn select- [params & children]
|
||||||
(into
|
(into
|
||||||
[:select (-> params
|
[:select (-> params
|
||||||
(dissoc :allow-blank? :value :options)
|
(dissoc :allow-blank? :value :options)
|
||||||
(update
|
(update
|
||||||
: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"))
|
: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 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")
|
||||||
|
|
||||||
|
)
|
||||||
(cond->>
|
(cond->>
|
||||||
(map (fn [[k v]]
|
(map (fn [[k v]]
|
||||||
[:option {:value k :selected (= k (:value params))} v])
|
[:option {:value k :selected (= k (:value params))} v])
|
||||||
@@ -16,21 +19,22 @@
|
|||||||
children))
|
children))
|
||||||
|
|
||||||
(defn typeahead- [params]
|
(defn typeahead- [params]
|
||||||
[:div {}
|
[:select (-> params
|
||||||
[:select (-> params
|
(dissoc :url)
|
||||||
(dissoc :url)
|
(dissoc :value)
|
||||||
(dissoc :value)
|
)
|
||||||
(assoc :width "")
|
(for [[k v] (if (:multiple params)
|
||||||
)
|
(:value params)
|
||||||
(for [[k v] (:value params)]
|
[(:value params)])
|
||||||
[:option {:value k :selected true} v]
|
:when k]
|
||||||
)
|
[:option {:value k :selected true} v]
|
||||||
]
|
)
|
||||||
|
|
||||||
[:script {:lang "javascript"}
|
[:script {:lang "javascript"}
|
||||||
(hiccup/raw (format "
|
(hiccup/raw (format "
|
||||||
(function () {
|
(function () {
|
||||||
var element = document.getElementById('%s');
|
var element = document.getElementById('%s');
|
||||||
var c = new Choices(element, {removeItems: true, removeItemButton:true, searchFloor: 3});
|
var c = new Choices(element, {removeItems: true, removeItemButton:true, searchFloor: 3, searchPlaceholderValue: '%s'});
|
||||||
|
|
||||||
element.addEventListener('search', function (e) {
|
element.addEventListener('search', function (e) {
|
||||||
let data = fetch('%s?q=' + e.detail.value)
|
let data = fetch('%s?q=' + e.detail.value)
|
||||||
@@ -46,6 +50,7 @@ c.clearChoices();
|
|||||||
|
|
||||||
"
|
"
|
||||||
(:id params)
|
(:id params)
|
||||||
|
(:placeholder params)
|
||||||
(:url params)
|
(:url params)
|
||||||
))]])
|
))]])
|
||||||
|
|
||||||
@@ -58,7 +63,7 @@ c.clearChoices();
|
|||||||
[:input
|
[:input
|
||||||
(-> params
|
(-> params
|
||||||
(update
|
(update
|
||||||
: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")
|
: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 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")
|
||||||
(update :class #(str % (use-size size)))
|
(update :class #(str % (use-size size)))
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
@@ -67,7 +72,7 @@ c.clearChoices();
|
|||||||
[:input
|
[:input
|
||||||
(-> params
|
(-> params
|
||||||
(update
|
(update
|
||||||
:class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full 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 text-right appearance-none"
|
:class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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 text-right appearance-none"
|
||||||
)
|
)
|
||||||
(update :class #(str % (use-size size)))
|
(update :class #(str % (use-size size)))
|
||||||
(assoc :type "number"
|
(assoc :type "number"
|
||||||
@@ -80,7 +85,7 @@ c.clearChoices();
|
|||||||
[:input
|
[:input
|
||||||
(-> params
|
(-> params
|
||||||
(update
|
(update
|
||||||
:class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full 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")
|
:class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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")
|
||||||
(assoc :type "text")
|
(assoc :type "text")
|
||||||
(assoc "_" (hiccup/raw "init initDatepicker(me)"))
|
(assoc "_" (hiccup/raw "init initDatepicker(me)"))
|
||||||
(assoc "hx-on" (hiccup/raw "changeDate: htmx.trigger(this, \"change\")
|
(assoc "hx-on" (hiccup/raw "changeDate: htmx.trigger(this, \"change\")
|
||||||
@@ -90,6 +95,6 @@ c.clearChoices();
|
|||||||
|
|
||||||
(defn field- [params & rest]
|
(defn field- [params & rest]
|
||||||
(into
|
(into
|
||||||
[:div
|
[:div {:id (:id params)}
|
||||||
[:label {:class "block mb-2 text-sm font-medium text-gray-900 dark:text-white"} (:label params)]]
|
[:label {:class "block mb-2 text-sm font-medium text-gray-900 dark:text-white"} (:label params)]]
|
||||||
rest))
|
rest))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||||
[auto-ap.ssr.company.company-1099 :as company-1099]
|
[auto-ap.ssr.company.company-1099 :as company-1099]
|
||||||
[auto-ap.ssr.company.plaid :as company-plaid]
|
[auto-ap.ssr.company.plaid :as company-plaid]
|
||||||
|
[auto-ap.ssr.admin.accounts :as admin-accounts]
|
||||||
[auto-ap.ssr.company.reports :as company-reports]
|
[auto-ap.ssr.company.reports :as company-reports]
|
||||||
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
||||||
[auto-ap.ssr.invoice.glimpse :as invoice-glimpse]
|
[auto-ap.ssr.invoice.glimpse :as invoice-glimpse]
|
||||||
@@ -71,5 +72,6 @@
|
|||||||
(into pos-tenders/key->handler)
|
(into pos-tenders/key->handler)
|
||||||
(into pos-cash-drawer-shifts/key->handler)
|
(into pos-cash-drawer-shifts/key->handler)
|
||||||
(into pos-refunds/key->handler)
|
(into pos-refunds/key->handler)
|
||||||
(into users/key->handler)))
|
(into users/key->handler)
|
||||||
|
(into admin-accounts/key->handler)))
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
[:link {:rel "icon" :type "image/png" :href "/favicon.png"}]
|
[:link {:rel "icon" :type "image/png" :href "/favicon.png"}]
|
||||||
[:link {:rel "stylesheet", :href "/output.css"}]
|
[:link {:rel "stylesheet", :href "/output.css"}]
|
||||||
|
|
||||||
#_[:script {:src "https://code.jquery.com/jquery-3.7.1.min.js"}]
|
|
||||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}]
|
[: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/@popperjs/core@2.11.8/dist/umd/popper.min.js"}]
|
||||||
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
|
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
|
||||||
@@ -37,9 +36,9 @@
|
|||||||
[:script {:type "text/javascript" :src "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker-full.min.js"}]
|
[:script {:type "text/javascript" :src "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker-full.min.js"}]
|
||||||
|
|
||||||
|
|
||||||
#_[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/base.min.css"}]
|
|
||||||
[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/choices.min.css"}]
|
[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/choices.min.css"}]
|
||||||
[:script {:src "https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/scripts/choices.min.js"}]
|
[:script {:src "https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/scripts/choices.min.js"}]
|
||||||
|
[:script {:src "https://unpkg.com/htmx.org/dist/ext/response-targets.js"}]
|
||||||
|
|
||||||
[:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.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"}]
|
[:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}]
|
||||||
|
|||||||
@@ -10,21 +10,25 @@
|
|||||||
query2]]
|
query2]]
|
||||||
[auto-ap.query-params :as query-params]
|
[auto-ap.query-params :as query-params]
|
||||||
[auto-ap.routes.auth :as auth]
|
[auto-ap.routes.auth :as auth]
|
||||||
[auto-ap.routes.utils :refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
[auto-ap.routes.utils
|
||||||
|
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
||||||
[auto-ap.ssr-routes :as ssr-routes]
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
[auto-ap.ssr.components :as com]
|
[auto-ap.ssr.components :as com]
|
||||||
[auto-ap.ssr.grid-page-helper :as helper]
|
[auto-ap.ssr.grid-page-helper :as helper]
|
||||||
[auto-ap.ssr.svg :as svg]
|
[auto-ap.ssr.svg :as svg]
|
||||||
[auto-ap.ssr.utils :refer [html-response]]
|
[auto-ap.ssr.utils
|
||||||
|
:refer [entity-id
|
||||||
|
forced-vector
|
||||||
|
html-response
|
||||||
|
ref->enum-schema
|
||||||
|
wrap-schema-decode]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[buddy.sign.jwt :as jwt]
|
[buddy.sign.jwt :as jwt]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.api :as dc]
|
[datomic.api :as dc]
|
||||||
[malli.core :as mc]
|
[malli.core :as mc]))
|
||||||
[malli.transform :as mt2]
|
|
||||||
[manifold.time :as mt]))
|
|
||||||
|
|
||||||
(defn filters [request]
|
(defn filters [request]
|
||||||
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
|
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
|
||||||
@@ -180,10 +184,10 @@
|
|||||||
:row-buttons (fn [request entity]
|
:row-buttons (fn [request entity]
|
||||||
[(com/button {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
[(com/button {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
||||||
:user-impersonate))
|
:user-impersonate))
|
||||||
:hx-vals (format "{\"user-id\": \"%s\"}" (:db/id entity))} "Impersonate")
|
:hx-vals (format "{\"db/id\": \"%s\"}" (:db/id entity))} "Impersonate")
|
||||||
(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||||
:user-edit-dialog
|
:user-edit-dialog
|
||||||
:user-id (:db/id entity)))
|
:db/id (:db/id entity)))
|
||||||
:hx-target "#modal-holder"
|
:hx-target "#modal-holder"
|
||||||
:hx-swap "outerHTML"}
|
:hx-swap "outerHTML"}
|
||||||
svg/pencil)])
|
svg/pencil)])
|
||||||
@@ -228,12 +232,7 @@
|
|||||||
(def table* (partial helper/table* grid-page))
|
(def table* (partial helper/table* grid-page))
|
||||||
|
|
||||||
(defn impersonate [request]
|
(defn impersonate [request]
|
||||||
(let [user (some-> request
|
(let [user (some-> request :params :db/id (#(dc/pull (dc/db conn) default-read %))) ]
|
||||||
:form-params
|
|
||||||
(get "user-id")
|
|
||||||
not-empty
|
|
||||||
Long/parseLong
|
|
||||||
(#(dc/pull (dc/db conn) default-read %))) ]
|
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"hx-redirect" (str "/?jwt=" (jwt/sign (auth/user->jwt user "FAKE_TOKEN")
|
:headers {"hx-redirect" (str "/?jwt=" (jwt/sign (auth/user->jwt user "FAKE_TOKEN")
|
||||||
(:jwt-secret env)
|
(:jwt-secret env)
|
||||||
@@ -242,19 +241,9 @@
|
|||||||
:session {:identity (dissoc (auth/user->jwt user "FAKE_TOKEN")
|
:session {:identity (dissoc (auth/user->jwt user "FAKE_TOKEN")
|
||||||
:exp)}}))
|
:exp)}}))
|
||||||
|
|
||||||
(defn user-edit-save [{:keys [form-params] :as request}]
|
(defn user-edit-save [{:keys [params route-params] :as request}]
|
||||||
(let [user (some-> request
|
(let [_ @(dc/transact conn [[:upsert-entity (-> params (assoc :db/id (:db/id route-params)) (dissoc :id))]])
|
||||||
:params
|
user (some-> request :route-params :db/id (#(dc/pull (dc/db conn) default-read %)))]
|
||||||
:user-id
|
|
||||||
(#(dc/pull (dc/db conn) default-read %)))
|
|
||||||
_ @(dc/transact conn [
|
|
||||||
[:upsert-entity {:db/id (:db/id user)
|
|
||||||
:user/role (keyword "user-role" (get form-params "role"))
|
|
||||||
:user/clients (some-> request :params :clients)}]])
|
|
||||||
user (some-> request
|
|
||||||
:params
|
|
||||||
:user-id
|
|
||||||
(#(dc/pull (dc/db conn) default-read %)))]
|
|
||||||
|
|
||||||
(html-response
|
(html-response
|
||||||
(row* identity user {:flash? true})
|
(row* identity user {:flash? true})
|
||||||
@@ -263,24 +252,26 @@
|
|||||||
|
|
||||||
(defn user-edit-dialog [request]
|
(defn user-edit-dialog [request]
|
||||||
(let [user (some-> request
|
(let [user (some-> request
|
||||||
:params
|
:route-params
|
||||||
:user-id
|
:db/id
|
||||||
(#(dc/pull (dc/db conn) default-read %)))]
|
(#(dc/pull (dc/db conn) default-read %)))]
|
||||||
(html-response
|
(html-response
|
||||||
(com/modal
|
(com/modal
|
||||||
{}
|
{}
|
||||||
[:form {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
[:form {:hx-ext "response-targets"
|
||||||
|
:hx-post (str (bidi/path-for ssr-routes/only-routes
|
||||||
:user-edit-save
|
:user-edit-save
|
||||||
:request-method :post
|
:request-method :post
|
||||||
:user-id (:db/id user )))
|
:db/id (:db/id user )))
|
||||||
:hx-swap "outerHTML swap:300ms"}
|
:hx-swap "outerHTML swap:300ms"
|
||||||
|
:hx-target-400 "#form-errors .error-content"}
|
||||||
[:fieldset {:class "hx-disable"}
|
[:fieldset {:class "hx-disable"}
|
||||||
(com/modal-card
|
(com/modal-card
|
||||||
{}
|
{}
|
||||||
[:div.flex [:div.p-2 "User"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:user/name user)]]
|
[:div.flex [:div.p-2 "User"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:user/name user)]]
|
||||||
[:div.space-y-6
|
[:div.space-y-6
|
||||||
(com/field {:label "Role"}
|
(com/field {:label "Role"}
|
||||||
(com/select {:name "role"
|
(com/select {:name "user/role"
|
||||||
:class "w-36"
|
:class "w-36"
|
||||||
:autofocus true
|
:autofocus true
|
||||||
:id "role"
|
:id "role"
|
||||||
@@ -292,7 +283,7 @@
|
|||||||
["user" "User"]]
|
["user" "User"]]
|
||||||
:size :small}))
|
:size :small}))
|
||||||
(com/field {:label "Clients"}
|
(com/field {:label "Clients"}
|
||||||
(com/typeahead {:name "clients"
|
(com/typeahead {:name "user/clients"
|
||||||
:class "w-full"
|
:class "w-full"
|
||||||
:multiple "multiple"
|
:multiple "multiple"
|
||||||
:url (bidi/path-for ssr-routes/only-routes
|
:url (bidi/path-for ssr-routes/only-routes
|
||||||
@@ -302,68 +293,12 @@
|
|||||||
(fn [client]
|
(fn [client]
|
||||||
[(:db/id client) (:client/name client)])
|
[(:db/id client) (:client/name client)])
|
||||||
(:user/clients user))
|
(:user/clients user))
|
||||||
#_#_:value (name (:user/role user))
|
|
||||||
#_#_:options [["none" "None"]
|
|
||||||
["power-user" "Power user"]
|
|
||||||
["manager" "Manager"]
|
|
||||||
["admin" "Admin"]
|
|
||||||
["user" "User"]]
|
|
||||||
:size :small}))
|
:size :small}))
|
||||||
(com/button {:color :primary}
|
[:div#form-errors [:span.error-content]]
|
||||||
"Save")
|
(com/button {:color :primary :type "submit"}
|
||||||
]
|
"Save")]
|
||||||
|
|
||||||
[:div])]]))))
|
[:div])]]))))
|
||||||
|
|
||||||
(defn forced-vector [x]
|
|
||||||
[:vector {:decode/json {:enter (fn [x]
|
|
||||||
(if (sequential? x)
|
|
||||||
x
|
|
||||||
[x])
|
|
||||||
)}}
|
|
||||||
x])
|
|
||||||
|
|
||||||
(def entity-id (mc/schema nat-int?))
|
|
||||||
|
|
||||||
(defn wrap-schema-decode [handler & {:keys [form query params]}]
|
|
||||||
(fn [{:keys [form-params query-params] :as request}]
|
|
||||||
(try
|
|
||||||
(handler (cond-> request
|
|
||||||
(and (:params request) params)
|
|
||||||
(assoc :params
|
|
||||||
(mc/coerce
|
|
||||||
params
|
|
||||||
(:params request)
|
|
||||||
(mt2/transformer
|
|
||||||
(mt2/key-transformer {:encode name :decode keyword})
|
|
||||||
mt2/string-transformer
|
|
||||||
mt2/json-transformer) ))
|
|
||||||
|
|
||||||
(and form form-params)
|
|
||||||
(assoc :parsed-form-params
|
|
||||||
(mc/coerce
|
|
||||||
form
|
|
||||||
form-params
|
|
||||||
(mt2/transformer
|
|
||||||
(mt2/key-transformer {:encode name :decode keyword})
|
|
||||||
mt2/string-transformer
|
|
||||||
mt2/json-transformer) ))
|
|
||||||
|
|
||||||
(and query query-params)
|
|
||||||
(assoc :parsed-query-params
|
|
||||||
(mc/coerce
|
|
||||||
form
|
|
||||||
form-params
|
|
||||||
(mt2/transformer
|
|
||||||
(mt2/key-transformer {:encode name :decode keyword})
|
|
||||||
mt2/string-transformer
|
|
||||||
mt2/json-transformer) ))))
|
|
||||||
(catch Exception e
|
|
||||||
;; TODO
|
|
||||||
{:status 400
|
|
||||||
:body "error"}))))
|
|
||||||
|
|
||||||
|
|
||||||
(def key->handler
|
(def key->handler
|
||||||
{:users (wrap-admin (helper/page-route grid-page))
|
{:users (wrap-admin (helper/page-route grid-page))
|
||||||
:user-table (wrap-admin (helper/table-route grid-page))
|
:user-table (wrap-admin (helper/table-route grid-page))
|
||||||
@@ -371,15 +306,19 @@
|
|||||||
wrap-admin
|
wrap-admin
|
||||||
wrap-client-redirect-unauthenticated
|
wrap-client-redirect-unauthenticated
|
||||||
(wrap-schema-decode
|
(wrap-schema-decode
|
||||||
:params (mc/schema
|
:route-schema (mc/schema [:map [:db/id entity-id]])
|
||||||
|
:params-schema (mc/schema
|
||||||
[:map
|
[:map
|
||||||
[:user-id nat-int?]
|
[:user/clients (forced-vector entity-id)]
|
||||||
[:clients (forced-vector entity-id)]
|
[:user/role (ref->enum-schema "user-role")]])))
|
||||||
[:role [:enum {:decode/string #(keyword "user-role" %)} :user-role/admin :user-role/manager :user-role/power-user :user-role/user :user-role/none]]])))
|
|
||||||
:user-edit-dialog (-> user-edit-dialog
|
:user-edit-dialog (-> user-edit-dialog
|
||||||
wrap-admin
|
wrap-admin
|
||||||
wrap-client-redirect-unauthenticated
|
wrap-client-redirect-unauthenticated
|
||||||
(wrap-schema-decode
|
(wrap-schema-decode
|
||||||
:params (mc/schema [:map [:user-id entity-id]])))
|
:route-schema (mc/schema [:map [:db/id entity-id]])))
|
||||||
:user-impersonate (wrap-client-redirect-unauthenticated (wrap-admin impersonate))})
|
:user-impersonate (-> impersonate
|
||||||
|
wrap-admin
|
||||||
|
wrap-client-redirect-unauthenticated
|
||||||
|
(wrap-schema-decode
|
||||||
|
:params-schema (mc/schema [:map [:db/id entity-id]])))})
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
(ns auto-ap.ssr.utils
|
(ns auto-ap.ssr.utils
|
||||||
(:require
|
(:require
|
||||||
|
[auto-ap.datomic :refer [all-schema]]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
|
[clojure.string :as str]
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[hiccup2.core :as hiccup]
|
[hiccup2.core :as hiccup]
|
||||||
[clojure.string :as str]))
|
[malli.core :as mc]
|
||||||
|
[malli.error :as me]
|
||||||
|
[malli.transform :as mt2]
|
||||||
|
[ring.middleware.nested-params :refer [parse-nested-keys]]))
|
||||||
|
|
||||||
(defn html-response [hiccup & {:keys [status headers oob] :or {status 200 headers {} oob []}}]
|
(defn html-response [hiccup & {:keys [status headers oob] :or {status 200 headers {} oob []}}]
|
||||||
{:status status
|
{:status status
|
||||||
@@ -68,3 +73,119 @@
|
|||||||
(seq k)
|
(seq k)
|
||||||
(str/join "_" (map path->name k))
|
(str/join "_" (map path->name k))
|
||||||
:else k))
|
:else k))
|
||||||
|
|
||||||
|
|
||||||
|
(defn forced-vector [x]
|
||||||
|
[:vector {:decode/json {:enter (fn [x]
|
||||||
|
(if (sequential? x)
|
||||||
|
x
|
||||||
|
[x])
|
||||||
|
)}}
|
||||||
|
x])
|
||||||
|
|
||||||
|
(def entity-id (mc/schema nat-int?))
|
||||||
|
(def temp-id (mc/schema :string))
|
||||||
|
|
||||||
|
(defn str->keyword [s]
|
||||||
|
(if (string? s)
|
||||||
|
(let [[ns k] (str/split s #"/")]
|
||||||
|
(if (and ns k)
|
||||||
|
(keyword ns k)
|
||||||
|
(keyword s)))
|
||||||
|
s))
|
||||||
|
|
||||||
|
(defn keyword->str [k]
|
||||||
|
(subs (str k) 1))
|
||||||
|
|
||||||
|
(defn wrap-schema-decode [handler & {:keys [form-schema query-schema route-schema params-schema]}]
|
||||||
|
(fn [{:keys [form-params query-params params] :as request}]
|
||||||
|
(try
|
||||||
|
|
||||||
|
(handler (cond-> request
|
||||||
|
(and (:params request) params-schema)
|
||||||
|
(assoc :params
|
||||||
|
(mc/coerce
|
||||||
|
params-schema
|
||||||
|
(:params request)
|
||||||
|
(mt2/transformer
|
||||||
|
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||||
|
mt2/string-transformer
|
||||||
|
mt2/json-transformer) ))
|
||||||
|
|
||||||
|
(and (:route-params request) route-schema)
|
||||||
|
(assoc :route-params
|
||||||
|
(mc/coerce
|
||||||
|
route-schema
|
||||||
|
(:route-params request)
|
||||||
|
(mt2/transformer
|
||||||
|
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||||
|
mt2/string-transformer
|
||||||
|
mt2/json-transformer) ))
|
||||||
|
|
||||||
|
(and form-schema form-params)
|
||||||
|
(assoc :parsed-form-params
|
||||||
|
(mc/coerce
|
||||||
|
form-schema
|
||||||
|
form-params
|
||||||
|
(mt2/transformer
|
||||||
|
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||||
|
mt2/string-transformer
|
||||||
|
mt2/json-transformer) ))
|
||||||
|
|
||||||
|
(and query-schema query-params)
|
||||||
|
(assoc :parsed-query-params
|
||||||
|
(mc/coerce
|
||||||
|
query-schema
|
||||||
|
query-params
|
||||||
|
(mt2/transformer
|
||||||
|
(mt2/key-transformer {:encode name :decode keyword})
|
||||||
|
mt2/string-transformer
|
||||||
|
mt2/json-transformer) ))))
|
||||||
|
(catch Exception e
|
||||||
|
(alog/warn ::validation-error :error e)
|
||||||
|
(html-response [:span.error-content.text-red-500 (str/join ", "
|
||||||
|
(mapcat identity
|
||||||
|
(-> e
|
||||||
|
(ex-data )
|
||||||
|
:data
|
||||||
|
:explain
|
||||||
|
me/humanize
|
||||||
|
vals)))]
|
||||||
|
:status 400)))))
|
||||||
|
|
||||||
|
(defn ref->enum-schema [n]
|
||||||
|
(into [:enum {:decode/string #(keyword n %)}]
|
||||||
|
(for [{:db/keys [ident]} (all-schema)
|
||||||
|
:when (= n (namespace ident))]
|
||||||
|
ident)))
|
||||||
|
|
||||||
|
(defn ref->select-options [n & {:keys [allow-nil?]}]
|
||||||
|
(into (if allow-nil?
|
||||||
|
[["" ""]]
|
||||||
|
[])
|
||||||
|
(for [{:db/keys [ident]} (all-schema)
|
||||||
|
:when (= n (namespace ident))]
|
||||||
|
[(name ident) (str/replace (str/capitalize (name ident)) "-" " ")])))
|
||||||
|
|
||||||
|
(def map->db-id-decoder
|
||||||
|
{:enter (fn [x]
|
||||||
|
(into []
|
||||||
|
(for [[k v] x]
|
||||||
|
(assoc v :db/id (cond (and (string? k) (re-find #"^\d+$" k))
|
||||||
|
(Long/parseLong k)
|
||||||
|
(keyword? k)
|
||||||
|
(name k)
|
||||||
|
:else
|
||||||
|
k)))))})
|
||||||
|
|
||||||
|
(defn namespaceize-decoder [n]
|
||||||
|
{:exit (fn [m]
|
||||||
|
(when m
|
||||||
|
(reduce
|
||||||
|
(fn [m [k v]]
|
||||||
|
(if (= k "id")
|
||||||
|
(assoc m :db/id v)
|
||||||
|
(assoc m (keyword n (name k)) v)))
|
||||||
|
m
|
||||||
|
m)))})
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
"clients/" {"" :admin-clients
|
"clients/" {"" :admin-clients
|
||||||
[:id] {"" :admin-specific-client
|
[:id] {"" :admin-specific-client
|
||||||
"/bank-accounts/" {[:bank-account] :admin-specific-bank-account}}}
|
"/bank-accounts/" {[:bank-account] :admin-specific-bank-account}}}
|
||||||
"users" :admin-users
|
|
||||||
"rules" :admin-rules
|
"rules" :admin-rules
|
||||||
"accounts" :admin-accounts
|
|
||||||
"import-batches" :admin-import-batches
|
"import-batches" :admin-import-batches
|
||||||
"jobs" :admin-jobs
|
"jobs" :admin-jobs
|
||||||
"vendors" :admin-vendors
|
"vendors" :admin-vendors
|
||||||
|
|||||||
@@ -16,8 +16,13 @@
|
|||||||
"/user" {"" :users
|
"/user" {"" :users
|
||||||
"/table" :user-table
|
"/table" :user-table
|
||||||
"/impersonate" :user-impersonate
|
"/impersonate" :user-impersonate
|
||||||
[[#"\d+" :user-id] "/edit"] {:get :user-edit-dialog
|
[[#"\d+" :db/id] "/edit"] {:get :user-edit-dialog
|
||||||
:post :user-edit-save}}
|
:post :user-edit-save}}
|
||||||
|
"/account" {"" :admin-accounts
|
||||||
|
"/table" :admin-account-table
|
||||||
|
"/override/new" :admin-account-client-override-new
|
||||||
|
["/" [#"\d+" :db/id] "/edit"] {:get :admin-account-edit-dialog
|
||||||
|
:post :admin-account-edit-save}}
|
||||||
"/ezcater-xls" :admin-ezcater-xls}
|
"/ezcater-xls" :admin-ezcater-xls}
|
||||||
"transaction" {"/insights" {"" :transaction-insights
|
"transaction" {"/insights" {"" :transaction-insights
|
||||||
"/table" :transaction-insight-table
|
"/table" :transaction-insight-table
|
||||||
|
|||||||
Reference in New Issue
Block a user