Builds client SSR approach, sunsets old cljs.
This commit is contained in:
@@ -143,3 +143,10 @@
|
||||
(defn update! [cursor v]
|
||||
"Replaces value supplied by cursor with value v."
|
||||
(-transact! cursor (constantly v)))
|
||||
|
||||
(defn ensure-path! [cursor p default]
|
||||
(let [next-to-last (get-in cursor (butlast p))
|
||||
next-to-last-v @next-to-last]
|
||||
(when (not (get next-to-last-v (last p)))
|
||||
(transact! next-to-last #(assoc % (last p) default))))
|
||||
cursor)
|
||||
|
||||
@@ -1,182 +1,12 @@
|
||||
(ns auto-ap.graphql.clients
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [audit-transact conn]]
|
||||
[auto-ap.datomic.clients :as d-clients]
|
||||
[auto-ap.graphql.utils
|
||||
:refer [->graphql
|
||||
assert-admin
|
||||
attach-tracing-resolvers
|
||||
can-see-client?
|
||||
<-graphql
|
||||
result->page
|
||||
is-admin?]]
|
||||
[auto-ap.routes.queries :as q]
|
||||
[auto-ap.square.core3 :as square]
|
||||
[auto-ap.utils :refer [heartbeat]]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as str]
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[datomic.api :as dc]
|
||||
[iol-ion.tx :refer [random-tempid]]
|
||||
[mount.core :as mount]
|
||||
[yang.scheduler :as scheduler]
|
||||
[auto-ap.solr :as solr])
|
||||
(:import
|
||||
(java.util UUID)
|
||||
(org.apache.commons.codec.binary Base64)))
|
||||
|
||||
(defn assert-client-code-is-unique [code]
|
||||
(when (seq (dc/q {:find '[?id]
|
||||
:in ['$ '?code]
|
||||
:where ['[?id :client/code ?code]]}
|
||||
(dc/db conn) code))
|
||||
(throw (ex-info "Client is not unique" {:validation-error (str "Client code '" code "' is not unique.")}))))
|
||||
|
||||
(defn upload-signature-data [signature-data]
|
||||
(let [prefix "data:image/jpeg;base64,"]
|
||||
(when signature-data
|
||||
(when-not (str/starts-with? signature-data prefix)
|
||||
(throw (ex-info "Invalid signature image" {:validation-error (str "Invalid signature image.")})))
|
||||
(let [signature-id (str (UUID/randomUUID))
|
||||
raw-bytes (Base64/decodeBase64 (subs signature-data (count prefix)))]
|
||||
(s3/put-object :bucket-name "integreat-signature-images" #_(:data-bucket env)
|
||||
:key (str signature-id ".jpg")
|
||||
:input-stream (io/make-input-stream raw-bytes {})
|
||||
:metadata {:content-type "image/jpeg"
|
||||
:content-length (count raw-bytes)}
|
||||
:canned-acl "public-read")
|
||||
(str "https://integreat-signature-images.s3.amazonaws.com/" signature-id ".jpg")))))
|
||||
|
||||
(defn assert-no-shared-transaction-sources [client-code txes]
|
||||
(let [new-db (:db-after (dc/with (dc/db conn)
|
||||
txes))]
|
||||
(when (seq (->> (dc/q '[:find ?src (count ?ba)
|
||||
:in $ ?c
|
||||
:where [?c :client/bank-accounts ?ba]
|
||||
(or
|
||||
[?ba :bank-account/intuit-bank-account ?src]
|
||||
[?ba :bank-account/plaid-account ?src]
|
||||
[?ba :bank-account/yodlee-account-id ?src])]
|
||||
new-db [:client/code client-code])
|
||||
(filter (fn [[_ cnt]]
|
||||
(> cnt 1)))))
|
||||
(throw (ex-info "Cannot reuse yodlee/plaid/intuit account" {:validation-error (str "Cannot reuse yodlee/plaid/intuit account")})))))
|
||||
|
||||
(defn edit-client [context {:keys [edit_client]} _]
|
||||
(assert-admin (:id context))
|
||||
(when-not (:id edit_client)
|
||||
(assert-client-code-is-unique (:code edit_client)))
|
||||
|
||||
(let [client (when (:id edit_client) (d-clients/get-by-id (:id edit_client)))
|
||||
id (or (:db/id client) "new-client")
|
||||
signature-file (upload-signature-data (:signature_data edit_client))
|
||||
client-code (if (str/blank? (:client/code client))
|
||||
(:code edit_client)
|
||||
(:client/code client))
|
||||
updated-entity (cond-> {:db/id id
|
||||
:client/code client-code
|
||||
:client/name (:name edit_client)
|
||||
:client/matches (:matches edit_client)
|
||||
:client/email (:email edit_client)
|
||||
:client/locked-until (some-> (:locked_until edit_client) (coerce/to-date))
|
||||
:client/locations (filter identity (:locations edit_client))
|
||||
:client/week-a-debits (:week_a_debits edit_client)
|
||||
:client/week-a-credits (:week_a_credits edit_client)
|
||||
:client/week-b-debits (:week_b_debits edit_client)
|
||||
:client/square-auth-token (:square_auth_token edit_client)
|
||||
:client/square-locations (map
|
||||
(fn [sl]
|
||||
{:db/id (or (:id sl) (random-tempid))
|
||||
:square-location/client-location (:client_location sl)})
|
||||
(:square_locations edit_client))
|
||||
|
||||
:client/emails (map (fn [e]
|
||||
{:db/id (or (:id e)
|
||||
(random-tempid))
|
||||
:email-contact/email (:email e)
|
||||
:email-contact/description (:description e)})
|
||||
(:emails edit_client))
|
||||
|
||||
:client/feature-flags (:feature_flags edit_client)
|
||||
:client/ezcater-locations (map
|
||||
(fn [el]
|
||||
{:db/id (or (:id el) (random-tempid))
|
||||
:ezcater-location/location (:location el)
|
||||
:ezcater-location/caterer (:caterer el)})
|
||||
(:ezcater_locations edit_client))
|
||||
:client/week-b-credits (:week_b_credits edit_client)
|
||||
:client/location-matches (->> (:location_matches edit_client)
|
||||
(filter (fn [lm] (and (:location lm) (:match lm))))
|
||||
(map (fn [lm] {:db/id (or (:id lm) (random-tempid))
|
||||
:location-match/location (:location lm)
|
||||
:location-match/matches [(:match lm)]})))
|
||||
:client/address (when (seq (filter identity (vals (:address edit_client))))
|
||||
{:db/id (or (:id (:address edit_client)) (random-tempid))
|
||||
:address/street1 (:street1 (:address edit_client))
|
||||
:address/street2 (:street2 (:address edit_client))
|
||||
:address/city (:city (:address edit_client))
|
||||
:address/state (:state (:address edit_client))
|
||||
:address/zip (:zip (:address edit_client))})
|
||||
:client/bank-accounts (map (fn [ba]
|
||||
{:db/id (or (:id ba) (random-tempid))
|
||||
:bank-account/code (:code ba)
|
||||
:bank-account/bank-name (:bank_name ba)
|
||||
:bank-account/bank-code (:bank_code ba)
|
||||
:bank-account/start-date (-> (:start_date ba) (coerce/to-date))
|
||||
:bank-account/routing (:routing ba)
|
||||
:bank-account/include-in-reports (:include_in_reports ba)
|
||||
|
||||
:bank-account/name (:name ba)
|
||||
:bank-account/visible (:visible ba)
|
||||
:bank-account/number (:number ba)
|
||||
:bank-account/check-number (:check_number ba)
|
||||
:bank-account/numeric-code (:numeric_code ba)
|
||||
:bank-account/sort-order (:sort_order ba)
|
||||
:bank-account/locations (:locations ba)
|
||||
:bank-account/use-date-instead-of-post-date? (boolean (:use_date_instead_of_post_date ba))
|
||||
|
||||
:bank-account/yodlee-account-id (:yodlee_account_id ba)
|
||||
:bank-account/type (keyword "bank-account-type" (name (:type ba)))
|
||||
:bank-account/yodlee-account (when (:yodlee_account ba)
|
||||
[:yodlee-account/id (:yodlee_account ba)])
|
||||
:bank-account/plaid-account (:plaid_account ba)
|
||||
:bank-account/intuit-bank-account (:intuit_bank_account ba)})
|
||||
(:bank_accounts edit_client))}
|
||||
signature-file (assoc :client/signature-file signature-file))
|
||||
|
||||
_ (mu/log ::upserting :up updated-entity)
|
||||
_ (assert-no-shared-transaction-sources client-code [[:upsert-entity updated-entity]])
|
||||
|
||||
result (audit-transact [[:upsert-entity updated-entity]] (:id context))]
|
||||
(when (:square_auth_token edit_client)
|
||||
@(square/upsert-locations (-> result :tempids (get id) (or id) d-clients/get-by-id)))
|
||||
(let [updated-client (-> result :tempids (get id) (or id) d-clients/get-by-id)]
|
||||
(when (:client/name updated-client)
|
||||
(solr/index-documents-raw solr/impl "clients"
|
||||
[{"id" (:db/id updated-client)
|
||||
"name" (conj (or (:client/matches updated-client) [])
|
||||
(:client/name updated-client))
|
||||
"code" (:client/code updated-client)
|
||||
"exact" (map str/upper-case (conj (or (:client/matches updated-client) [])
|
||||
(:client/name updated-client)))}]))
|
||||
(-> updated-client
|
||||
|
||||
(update :client/bank-accounts
|
||||
(fn [bas]
|
||||
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas)))
|
||||
(update :client/location-matches
|
||||
(fn [lms]
|
||||
(mapcat (fn [lm]
|
||||
(map (fn [m]
|
||||
{:location-match/match m
|
||||
:location-match/location (:location-match/location lm)})
|
||||
(:location-match/matches lm)))
|
||||
lms)))
|
||||
->graphql))))
|
||||
|
||||
(:require [auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.clients :as d-clients]
|
||||
[auto-ap.graphql.utils
|
||||
:refer [->graphql <-graphql assert-admin attach-tracing-resolvers
|
||||
can-see-client? is-admin? result->page]]
|
||||
[clojure.set :as set]
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(defn refresh-all-current-balance []
|
||||
(mu/with-context {:source "current-balance-refresh"}
|
||||
@@ -238,201 +68,11 @@
|
||||
bank-accounts))))))]
|
||||
(result->page clients clients-count :clients (:filters args))))
|
||||
|
||||
(def sales-summary-query
|
||||
"[:find ?d4 (sum ?total) (sum ?tax) (sum ?tip) (sum ?service-charge) (sum ?discount) (sum ?returns)
|
||||
:with ?s
|
||||
:in $
|
||||
:where
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[(ground #inst \"2040-01-01\") ?max-d]
|
||||
[?c :client/code \"%s\"]
|
||||
[(iol-ion.query/sales-orders-in-range $ ?c ?min-d ?max-d) [?s ...]]
|
||||
[?s :sales-order/date ?d]
|
||||
[?s :sales-order/total ?total]
|
||||
[?s :sales-order/tax ?tax]
|
||||
[?s :sales-order/tip ?tip]
|
||||
[?s :sales-order/service-charge ?service-charge]
|
||||
[?s :sales-order/returns ?returns]
|
||||
[?s :sales-order/discount ?discount]
|
||||
[(iol-ion.query/excel-date ?d) ?d4]
|
||||
]")
|
||||
|
||||
(def sales-category-query
|
||||
"[:find ?d4 ?n ?n2 (sum ?total) (sum ?tax) (sum ?discount)
|
||||
:with ?s ?li
|
||||
:in $
|
||||
:where
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[(ground #inst \"2040-01-01\") ?max-d]
|
||||
[?c :client/code \"%s\"]
|
||||
[(iol-ion.query/sales-orders-in-range $ ?c ?min-d ?max-d) [?s ...]]
|
||||
[?s :sales-order/date ?d]
|
||||
[?s :sales-order/line-items ?li]
|
||||
[?li :order-line-item/category ?n]
|
||||
[(get-else $ ?li :order-line-item/item-name \"\") ?n2]
|
||||
[?li :order-line-item/total ?total]
|
||||
[?li :order-line-item/tax ?tax]
|
||||
[?li :order-line-item/discount ?discount]
|
||||
[(iol-ion.query/excel-date ?d) ?d4]]")
|
||||
|
||||
(def expected-deposits-query
|
||||
"[:find ?d4 ?t ?f
|
||||
:in $
|
||||
:where
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[?c :client/code \"%s\"]
|
||||
[?s :expected-deposit/client ?c]
|
||||
[?s :expected-deposit/sales-date ?date]
|
||||
[(>= ?date ?min-d)]
|
||||
[?s :expected-deposit/total ?t]
|
||||
[?s :expected-deposit/fee ?f]
|
||||
[(iol-ion.query/excel-date ?date) ?d4]
|
||||
]")
|
||||
|
||||
(def tenders-query
|
||||
"[:find ?d4 ?type ?p2 (sum ?total) (sum ?tip)
|
||||
:with ?charge
|
||||
:in $
|
||||
:where
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[?c :client/code \"%s\"]
|
||||
[?s :sales-order/client ?c]
|
||||
[?s :sales-order/date ?date]
|
||||
[(>= ?date ?min-d)]
|
||||
[?s :sales-order/charges ?charge]
|
||||
[?charge :charge/type-name ?type]
|
||||
[?charge :charge/total ?total]
|
||||
[?charge :charge/tip ?tip]
|
||||
[(get-else $ ?charge :charge/processor :na) ?ccp]
|
||||
[(get-else $ ?ccp :db/ident :na) ?p]
|
||||
[(name ?p) ?p2]
|
||||
[(iol-ion.query/excel-date ?date) ?d4]
|
||||
]")
|
||||
|
||||
(def tenders2-query
|
||||
"[:find ?d4 ?type ?p2 (sum ?total) (sum ?tip)
|
||||
:with ?charge
|
||||
:in $
|
||||
:where
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[?charge :charge/date ?date]
|
||||
[(>= ?date ?min-d)]
|
||||
[?charge :charge/client ?c]
|
||||
[?c :client/code \"%s\"]
|
||||
[?charge :charge/type-name ?type]
|
||||
[?charge :charge/total ?total]
|
||||
[?charge :charge/tip ?tip]
|
||||
(or
|
||||
|
||||
(and [_ :expected-deposit/charges ?charge ]
|
||||
[(ground :settlement) ?ccp]
|
||||
[(ground :settlement) ?p])
|
||||
(and
|
||||
(not [_ :expected-deposit/charges ?charge])
|
||||
[(get-else $ ?charge :charge/processor :na) ?ccp]
|
||||
[(get-else $ ?ccp :db/ident :na) ?p]
|
||||
))
|
||||
[(name ?p) ?p2]
|
||||
[(iol-ion.query/excel-date ?date) ?d4]]
|
||||
" )
|
||||
|
||||
(def refunds-query
|
||||
"[:find ?d4 ?t (sum ?total) (sum ?fee)
|
||||
:with ?r
|
||||
:in $
|
||||
:where
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[?r :sales-refund/client [:client/code \"%s\"]]
|
||||
[?r :sales-refund/date ?date]
|
||||
[(>= ?date ?min-d)]
|
||||
[?r :sales-refund/total ?total]
|
||||
[?r :sales-refund/fee ?fee]
|
||||
[?r :sales-refund/type ?t]
|
||||
[(iol-ion.query/excel-date ?date) ?d4]
|
||||
]")
|
||||
|
||||
(def cash-drawer-shift-query
|
||||
"[:find ?d4 (sum ?paid-in) (sum ?paid-out) (sum ?expected-cash) (sum ?opened-cash)
|
||||
:with ?cds
|
||||
:in $
|
||||
:where
|
||||
[?cds :cash-drawer-shift/date ?date]
|
||||
[(ground (iol-ion.query/recent-date 120)) ?min-d]
|
||||
[(>= ?date ?min-d)]
|
||||
[?cds :cash-drawer-shift/client [:client/code \"%s\"]]
|
||||
[?cds :cash-drawer-shift/paid-in ?paid-in]
|
||||
[?cds :cash-drawer-shift/paid-out ?paid-out]
|
||||
[?cds :cash-drawer-shift/expected-cash ?expected-cash]
|
||||
[?cds :cash-drawer-shift/opened-cash ?opened-cash]
|
||||
[(iol-ion.query/excel-date ?date) ?d4]]")
|
||||
|
||||
|
||||
|
||||
|
||||
(defn setup-sales-queries-impl [client-id]
|
||||
(let [{client-code :client/code feature-flags :client/feature-flags} (dc/pull (dc/db conn) '[:client/code :client/feature-flags] client-id)
|
||||
is-new-square? ((set feature-flags) "new-square")]
|
||||
(q/put-query (str (UUID/randomUUID))
|
||||
(format sales-summary-query client-code)
|
||||
(str "sales query for " client-code)
|
||||
(str client-code "-sales-summary")
|
||||
[:client/code client-code]
|
||||
)
|
||||
(q/put-query (str (UUID/randomUUID))
|
||||
(format sales-category-query client-code)
|
||||
(str "sales category query for " client-code)
|
||||
(str client-code "-sales-category")
|
||||
[:client/code client-code]
|
||||
)
|
||||
(q/put-query (str (UUID/randomUUID))
|
||||
(format expected-deposits-query client-code)
|
||||
(str "expected deposit query for " client-code)
|
||||
(str client-code "-expected-deposit")
|
||||
[:client/code client-code]
|
||||
)
|
||||
(q/put-query (str (UUID/randomUUID))
|
||||
(format (if is-new-square? tenders2-query tenders-query) client-code)
|
||||
(str "tender query for " client-code)
|
||||
(str client-code "-tender")
|
||||
[:client/code client-code]
|
||||
)
|
||||
|
||||
(q/put-query (str (UUID/randomUUID))
|
||||
(format refunds-query client-code)
|
||||
(str "refunds query for " client-code)
|
||||
(str client-code "-refund")
|
||||
[:client/code client-code])
|
||||
|
||||
(q/put-query (str (UUID/randomUUID))
|
||||
(format cash-drawer-shift-query client-code)
|
||||
(str "cash drawer shift query for " client-code)
|
||||
(str client-code "-cash-drawer-shift")
|
||||
[:client/code client-code])
|
||||
(let [sales-summary-id (:saved-query/guid (dc/pull (dc/db conn) [:saved-query/guid] [:saved-query/lookup-key (str client-code "-sales-summary")]))
|
||||
sales-category-id (:saved-query/guid (dc/pull (dc/db conn) [:saved-query/guid] [:saved-query/lookup-key (str client-code "-sales-category")]))
|
||||
expected-deposit-id (:saved-query/guid (dc/pull (dc/db conn) [:saved-query/guid] [:saved-query/lookup-key (str client-code "-expected-deposit")]))
|
||||
tender-id (:saved-query/guid (dc/pull (dc/db conn) [:saved-query/guid] [:saved-query/lookup-key (str client-code "-tender")]))
|
||||
refund-id (:saved-query/guid (dc/pull (dc/db conn) [:saved-query/guid] [:saved-query/lookup-key (str client-code "-refund")]))
|
||||
cash-drawer-shift-id (:saved-query/guid (dc/pull (dc/db conn) [:saved-query/guid] [:saved-query/lookup-key (str client-code "-cash-drawer-shift")]))]
|
||||
{:message (str/join "\n"
|
||||
[
|
||||
(str "For " client-code ":")
|
||||
(str "Sales: " "https://app.integreatconsult.com/api/queries/" sales-summary-id "/results/json")
|
||||
(str "Sales Category: " "https://app.integreatconsult.com/api/queries/" sales-category-id "/results/json")
|
||||
(str "Expected Deposits: " "https://app.integreatconsult.com/api/queries/" expected-deposit-id "/results/json")
|
||||
(str "Tenders: " "https://app.integreatconsult.com/api/queries/" tender-id "/results/json")
|
||||
(str "Refund: " "https://app.integreatconsult.com/api/queries/" refund-id "/results/json")
|
||||
(str "Cash Drawer Shift: " "https://app.integreatconsult.com/api/queries/" cash-drawer-shift-id "/results/json")])})))
|
||||
|
||||
|
||||
(defn setup-sales-queries [context args _]
|
||||
(assert-admin (:id context))
|
||||
(setup-sales-queries-impl (:client_id args)))
|
||||
|
||||
|
||||
(defn reset-all-queries []
|
||||
(doseq [[c] (dc/q '[:find ?c :where [?c :client/code]] (dc/db conn))]
|
||||
(setup-sales-queries-impl c)))
|
||||
|
||||
|
||||
(def objects
|
||||
@@ -535,82 +175,15 @@
|
||||
:resolve :get-client-page}})
|
||||
|
||||
(def mutations
|
||||
{:edit_client {:type :client
|
||||
:args {:edit_client {:type :edit_client}}
|
||||
:resolve :mutation/edit-client}
|
||||
:setup_sales_queries {:type :message
|
||||
:args {:client_id {:type :id}}
|
||||
:resolve :mutation/setup-sales-queries}})
|
||||
{})
|
||||
|
||||
(def input-objects
|
||||
{:edit_location_match {:fields {:location {:type 'String}
|
||||
:match {:type 'String}
|
||||
:id {:type :id}}}
|
||||
|
||||
:client_filters
|
||||
{ :client_filters
|
||||
{:fields {:code {:type 'String}
|
||||
:name_like {:type 'String}
|
||||
:start {:type 'Int}
|
||||
:per_page {:type 'Int}
|
||||
:sort {:type '(list :sort_item)}}}
|
||||
|
||||
:edit_square_location {:fields {:client_location {:type 'String}
|
||||
:id {:type :id}}}
|
||||
|
||||
:edit_ezcater_location {:fields {:location {:type 'String}
|
||||
:caterer {:type :id}
|
||||
:id {:type :id}}}
|
||||
|
||||
:edit_forecasted_transaction {:fields {:identifier {:type 'String}
|
||||
:id {:type :id}
|
||||
:day_of_month {:type 'Int}
|
||||
:amount {:type :money}}}
|
||||
:edit_email_contact {:fields {:id {:type :id}
|
||||
:email {:type 'String}
|
||||
:description {:type 'String}}}
|
||||
:edit_client {:fields {:id {:type :id}
|
||||
:name {:type 'String}
|
||||
:locked_until {:type :iso_date}
|
||||
:code {:type 'String}
|
||||
:square_auth_token {:type 'String}
|
||||
:feature_flags {:type '(list String)}
|
||||
:signature_data {:type 'String}
|
||||
:email {:type 'String}
|
||||
:emails {:type '(list :edit_email_contact)}
|
||||
:week_a_credits {:type :money}
|
||||
:week_a_debits {:type :money}
|
||||
:week_b_credits {:type :money}
|
||||
:week_b_debits {:type :money}
|
||||
:address {:type :add_address}
|
||||
:locations {:type '(list String)}
|
||||
:matches {:type '(list String)}
|
||||
:location_matches {:type '(list :edit_location_match)}
|
||||
:square_locations {:type '(list :edit_square_location)}
|
||||
:ezcater_locations {:type '(list :edit_ezcater_location)}
|
||||
:bank_accounts {:type '(list :edit_bank_account)}
|
||||
:forecasted_transactions {:type '(list :edit_forecasted_transaction)}}}
|
||||
|
||||
:edit_bank_account
|
||||
{:fields {:id {:type :id}
|
||||
:code {:type 'String}
|
||||
:type {:type :bank_account_type}
|
||||
:start_date {:type :iso_date}
|
||||
:number {:type 'String}
|
||||
:check_number {:type 'Int}
|
||||
:numeric_code {:type 'Int}
|
||||
:visible {:type 'Boolean}
|
||||
:include_in_reports {:type 'Boolean}
|
||||
:sort_order {:type 'Int}
|
||||
:name {:type 'String}
|
||||
:bank_code {:type 'String}
|
||||
:routing {:type 'String}
|
||||
:bank_name {:type 'String}
|
||||
:locations {:type '(list String)}
|
||||
:yodlee_account_id {:type 'Int}
|
||||
:use_date_instead_of_post_date {:type 'Boolean}
|
||||
:intuit_bank_account {:type :id}
|
||||
:plaid_account {:type :id}
|
||||
:yodlee_account {:type 'Int}}}})
|
||||
:sort {:type '(list :sort_item)}}} })
|
||||
|
||||
(def enums
|
||||
{:bank_account_type {:values [{:enum-value :check}
|
||||
@@ -620,9 +193,7 @@
|
||||
(def resolvers
|
||||
{:get-client get-client
|
||||
:get-admin-client get-admin-client
|
||||
:get-client-page get-client-page
|
||||
:mutation/edit-client edit-client
|
||||
:mutation/setup-sales-queries setup-sales-queries})
|
||||
:get-client-page get-client-page })
|
||||
|
||||
|
||||
(defn attach [schema]
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
(pull-many (dc/db conn)
|
||||
d-clients/full-read))]
|
||||
|
||||
(mu/with-context {:clients (map :client/code clients)}
|
||||
(mu/with-context {:clients (take 10 (map :client/code clients))}
|
||||
(handler (assoc request
|
||||
:clients clients
|
||||
:client (when (= 1 (count clients))
|
||||
@@ -273,7 +273,7 @@
|
||||
(handler request)))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(def app
|
||||
(defonce app
|
||||
(-> route-handler
|
||||
(wrap-hx-current-url-params)
|
||||
(wrap-guess-route)
|
||||
@@ -293,7 +293,7 @@
|
||||
(byte-array
|
||||
[42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])} )})
|
||||
|
||||
(wrap-reload)
|
||||
#_(wrap-reload)
|
||||
(wrap-params)
|
||||
(mp/wrap-multipart-params)
|
||||
(wrap-edn-params)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.bulk-journal-import
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.graphql.ledger :refer [import-ledger]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.close-auto-invoices
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.current-balance-cache
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.graphql.clients :as clients]
|
||||
[auto-ap.jobs.core :refer [execute]]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.ezcater-upsert
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
[auto-ap.ezcater.core :as ezcater]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.intuit
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.import.intuit :as intuit]
|
||||
[auto-ap.jobs.core :refer [execute]]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.ledger-reconcile
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
[auto-ap.ledger :as ledger]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.load-historical-sales
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.plaid
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.import.plaid :as plaid]
|
||||
[auto-ap.jobs.core :refer [execute]]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.register-invoice-import
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [audit-transact conn pull-attr]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.square
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
[auto-ap.square.core3 :as square3]))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.vendor-usages
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.jobs.yodlee2
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.import.yodlee2 :as yodlee2]
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns auto-ap.server
|
||||
(:gen-class)
|
||||
#_(:gen-class)
|
||||
(:require
|
||||
[auto-ap.handler :refer [app]]
|
||||
[auto-ap.jobs.restore-from-backup :as job-restore-from-backup]
|
||||
|
||||
@@ -36,14 +36,20 @@
|
||||
(sort-by :created-at)
|
||||
reverse))
|
||||
|
||||
(defn is-background-job? [task]
|
||||
|
||||
(defn is-background-job?
|
||||
"This function checks whether a given task is a background job.
|
||||
It does this by checking the environment of the task's container definitions for an environment variable
|
||||
with the name 'INTEGREAT_JOB'. If such a variable exists, the function returns true, otherwise, it returns false.
|
||||
Parameters: task - a map representing the task to be checked.
|
||||
Returns: true if the task is a background job, false otherwise."
|
||||
[task]
|
||||
(->> task
|
||||
:task-definition
|
||||
:container-definitions
|
||||
(mapcat :environment)
|
||||
(filter (comp #{"INTEGREAT_JOB"} :name))
|
||||
seq))
|
||||
|
||||
(defn task-definition->job-name [task-definition]
|
||||
(->> (:container-definitions task-definition)
|
||||
(mapcat :environment)
|
||||
|
||||
1721
src/clj/auto_ap/ssr/admin/clients.clj
Normal file
1721
src/clj/auto_ap/ssr/admin/clients.clj
Normal file
File diff suppressed because it is too large
Load Diff
0
src/clj/auto_ap/ssr/admin/sales_powerqueries.clj
Normal file
0
src/clj/auto_ap/ssr/admin/sales_powerqueries.clj
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
32
src/clj/auto_ap/ssr/common_handlers.clj
Normal file
32
src/clj/auto_ap/ssr/common_handlers.clj
Normal file
@@ -0,0 +1,32 @@
|
||||
(ns auto-ap.ssr.common-handlers
|
||||
(:require [auto-ap.ssr.form-cursor :as fc]
|
||||
[auto-ap.ssr.utils :refer [html-response wrap-schema-enforce]]))
|
||||
|
||||
|
||||
(defn add-new-entity-handler
|
||||
([path render-fn] (add-new-entity-handler path
|
||||
render-fn
|
||||
(fn default-data [base _]
|
||||
base)))
|
||||
([path render-fn build-data]
|
||||
(-> (fn new-entity [{{:keys [index]} :query-params :as request}]
|
||||
(html-response
|
||||
(fc/start-form-with-prefix (conj path (or index 0))
|
||||
(build-data {:db/id (str (java.util.UUID/randomUUID))
|
||||
:new? true} request)
|
||||
[]
|
||||
(render-fn fc/*current* request))))
|
||||
(wrap-schema-enforce :query-schema [:map
|
||||
[:index {:optional true
|
||||
:default 0} [nat-int? {:default 0}]]]))))
|
||||
|
||||
(defn add-new-primitive-handler [path default-value render-fn]
|
||||
(-> (fn new-location-match [{{:keys [index]} :query-params}]
|
||||
(html-response
|
||||
(fc/start-form-with-prefix (conj path (or index 0))
|
||||
default-value
|
||||
[]
|
||||
(render-fn fc/*current*))))
|
||||
(wrap-schema-enforce :query-schema [:map
|
||||
[:index {:optional true
|
||||
:default 0} [nat-int? {:default 0}]]])))
|
||||
@@ -1,87 +1,162 @@
|
||||
(ns auto-ap.ssr.company
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn pull-attr]]
|
||||
[auto-ap.datomic.clients :refer [full-read]]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[bidi.bidi :as bidi]
|
||||
[cemerick.url :as url]
|
||||
[clojure.string :as str]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[ring.middleware.json :refer [wrap-json-response]]))
|
||||
(:require [amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [conn pull-attr]]
|
||||
[auto-ap.datomic.clients :refer [full-read]]
|
||||
[auto-ap.permissions :as permissions]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.hx :as hx]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[bidi.bidi :as bidi]
|
||||
[cemerick.url :as url]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[ring.middleware.json :refer [wrap-json-response]])
|
||||
(:import [java.util UUID]
|
||||
(org.apache.commons.codec.binary Base64)))
|
||||
|
||||
(defn please-select-client-screen* []
|
||||
[:div.grid.grid-cols-3
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Please select a company"]
|
||||
])])
|
||||
"Please select a company"]])])
|
||||
|
||||
(defn main-content* [{:keys [client]}]
|
||||
(defn signature [request]
|
||||
(let [signature-file (pull-attr (dc/db conn) :client/signature-file (:db/id (:client request)))]
|
||||
(com/content-card {:class " w-[748px]"
|
||||
:hx-target "this"
|
||||
:hx-swap "outerHTML"}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6 space-y-4 overflow-visible "
|
||||
:x-data (hx/json {"signature" nil
|
||||
"editing" false
|
||||
"existing" (boolean signature-file)})
|
||||
:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:company-update-signature)
|
||||
:hx-trigger "accepted"
|
||||
:hx-vals "js:{signatureData: event.detail.signatureData}"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Signature"]
|
||||
[:div.htmx-indicator
|
||||
[:div.bg-gray-100.flex.items-center.text-green-500.justify-center.rounded.rounded-lg.border.border-gray-400 {:style {:width "696px" :height "261px"}}
|
||||
(svg/spinner {:class "w-4 h-4 text-primary-300"})
|
||||
[:div.ml-3 "Loading..."]]]
|
||||
|
||||
[:div.htmx-indicator-hidden
|
||||
(when signature-file
|
||||
[:img.rounded.rounded-lg.border.border-gray-300.bg-gray-50 {:src signature-file
|
||||
:width 696
|
||||
:height 261
|
||||
:x-show "existing && !editing"}])
|
||||
[:canvas.rounded.rounded-lg.border.border-gray-300
|
||||
|
||||
|
||||
{:style {:width 696
|
||||
:height 261}
|
||||
:x-init "signature= new SignaturePad($el); signature.off()"
|
||||
":class" "editing ? 'bg-white' : 'bg-gray-50' "
|
||||
:width 696
|
||||
:height 261
|
||||
:x-show "existing ? editing: true"}]]
|
||||
|
||||
[:div.flex.gap-2.justify-end
|
||||
(com/button {:color :primary
|
||||
:x-show "!editing"
|
||||
"@click" "signature.clear(); signature.on(); editing=true;"}
|
||||
"New signature")
|
||||
(com/button {:color :primary
|
||||
:x-show "editing"
|
||||
"@click" "signature.clear();"}
|
||||
"Clear")
|
||||
|
||||
(com/button {:color :primary
|
||||
"@click" "$data.signatureData=signature.toDataURL('image/png'); signature.off(); editing=false; $dispatch('accepted', {signatureData: $data.signatureData}) "
|
||||
:x-show "editing"}
|
||||
"Accept")]])))
|
||||
|
||||
(defn upload-signature-data [{{:strs [signatureData]} :form-params client :client :as request}]
|
||||
(let [prefix "data:image/png;base64,"]
|
||||
(when signatureData
|
||||
(when-not (str/starts-with? signatureData prefix)
|
||||
(throw (ex-info "Invalid signature image" {:validation-error (str "Invalid signature image.")})))
|
||||
(let [signature-id (str (UUID/randomUUID))
|
||||
raw-bytes (Base64/decodeBase64 (subs signatureData (count prefix)))]
|
||||
(s3/put-object :bucket-name "integreat-signature-images" #_(:data-bucket env)
|
||||
:key (str signature-id ".png")
|
||||
:input-stream (io/make-input-stream raw-bytes {})
|
||||
:metadata {:content-type "image/png"
|
||||
:content-length (count raw-bytes)}
|
||||
:canned-acl "public-read")
|
||||
@(dc/transact conn [{:db/id (:db/id client)
|
||||
:client/signature-file (str "https://integreat-signature-images.s3.amazonaws.com/" signature-id ".png")}])
|
||||
(html-response
|
||||
(signature request))))))
|
||||
|
||||
(defn main-content* [{:keys [client identity] :as request}]
|
||||
(if-not client
|
||||
(please-select-client-screen*)
|
||||
(let [client (dc/pull (dc/db conn) full-read (:db/id client))]
|
||||
[:div.grid.grid-cols-3.gap-4
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
(:client/name client)]
|
||||
(when-let [address (-> client :client/address)]
|
||||
[:div.flex.flex-col.gap-1.text-lg.dark:text-white.text-gray-700
|
||||
[:p (-> address :address/street1)]
|
||||
[:p (-> address :address/street2)]
|
||||
[:p (-> address :address/city) " "
|
||||
(-> address :address/state) ", "
|
||||
(-> address :address/zip)]])]
|
||||
)
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Downloads"]
|
||||
[:a {:href (str (assoc (url/url (str (:base-url env) "/api/vendors/company/export"))
|
||||
:query {"client" (:client/code client)}))}
|
||||
(com/button {:color :primary}
|
||||
"Download vendor list"
|
||||
(com/button-icon {} svg/download))]])])))
|
||||
[:div
|
||||
[:div.grid.grid-cols-3.gap-4
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
(:client/name client)]
|
||||
(when-let [address (-> client :client/address)]
|
||||
[:div.flex.flex-col.gap-1.text-lg.dark:text-white.text-gray-700
|
||||
[:p (-> address :address/street1)]
|
||||
[:p (-> address :address/street2)]
|
||||
[:p (-> address :address/city) " "
|
||||
(-> address :address/state) ", "
|
||||
(-> address :address/zip)]])])
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Downloads"]
|
||||
[:a {:href (str (assoc (url/url (str (:base-url env) "/api/vendors/company/export"))
|
||||
:query {"client" (:client/code client)}))}
|
||||
(com/button {:color :primary}
|
||||
"Download vendor list"
|
||||
(com/button-icon {} svg/download))]])
|
||||
[:div]]
|
||||
(when (permissions/can? identity {:client client :subject :signature :activity :edit})
|
||||
(signature request))])))
|
||||
|
||||
(defn page [{:keys [identity matched-route] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav (com/company-aside-nav)
|
||||
:client-selection (:client-selection (:session request))
|
||||
:client (:client request)
|
||||
:identity (:identity request)
|
||||
:app-params {
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"])
|
||||
(main-content* {:client (:client request)}))
|
||||
"My Company"))
|
||||
request
|
||||
(com/page {:nav (com/company-aside-nav)
|
||||
:client-selection (:client-selection (:session request))
|
||||
:client (:client request)
|
||||
:identity (:identity request)
|
||||
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"])
|
||||
(main-content* request))
|
||||
"My Company"))
|
||||
|
||||
(defn search [{:keys [clients query-params]}]
|
||||
(let [valid-client-ids (set (map :db/id clients))
|
||||
name-like-ids (when (not-empty (get query-params "q"))
|
||||
(set (map (comp #(Long/parseLong %) :id)
|
||||
(solr/query solr/impl "clients"
|
||||
{"query" (format "_text_:(%s*)" (str/upper-case (solr/escape (get query-params "q"))))
|
||||
"fields" "id"
|
||||
"limit" 300}))))
|
||||
(set (map (comp #(Long/parseLong %) :id)
|
||||
(solr/query solr/impl "clients"
|
||||
{"query" (format "_text_:(%s*)" (str/upper-case (solr/escape (get query-params "q"))))
|
||||
"fields" "id"
|
||||
"limit" 300}))))
|
||||
valid-clients (for [n name-like-ids
|
||||
:when (valid-client-ids n)]
|
||||
{"value" n "label" (pull-attr (dc/db conn) :client/name n)}
|
||||
)]
|
||||
{"value" n "label" (pull-attr (dc/db conn) :client/name n)})]
|
||||
{:body (take 10 valid-clients)}))
|
||||
|
||||
(def search (wrap-json-response search))
|
||||
@@ -109,13 +184,13 @@
|
||||
(defn bank-account-typeahead* [{:keys [client-id name value]}]
|
||||
(if client-id
|
||||
(com/typeahead {:name name
|
||||
:class "w-96"
|
||||
:placeholder "Search..."
|
||||
:url (bidi/path-for ssr-routes/only-routes :bank-account-search
|
||||
:db/id client-id)
|
||||
:value value
|
||||
:value-fn (some-fn :db/id identity)
|
||||
:content-fn (some-fn :bank-account/name #(pull-attr (dc/db conn) :bank-account/name %))})
|
||||
:class "w-96"
|
||||
:placeholder "Search..."
|
||||
:url (bidi/path-for ssr-routes/only-routes :bank-account-search
|
||||
:db/id client-id)
|
||||
:value value
|
||||
:value-fn (some-fn :db/id identity)
|
||||
:content-fn (some-fn :bank-account/name #(pull-attr (dc/db conn) :bank-account/name %))})
|
||||
[:span.text-xs.text-gray-500 "Please select a client before selecting a bank account."
|
||||
[:input {:type "hidden"
|
||||
:name name}]]))
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
[auto-ap.ssr.components.tags :as tags]
|
||||
[auto-ap.ssr.components.paginator :as paginator]
|
||||
[auto-ap.ssr.components.radio :as radio]))
|
||||
|
||||
|
||||
;; potemkin can be used here
|
||||
(def breadcrumbs breadcrumbs/breadcrumbs-)
|
||||
(def button buttons/button-)
|
||||
(def validated-save-button buttons/validated-save-button-)
|
||||
@@ -24,8 +23,7 @@
|
||||
(def button-group-button buttons/group-button-)
|
||||
(def modal dialog/modal-)
|
||||
(def modal-card dialog/modal-card-)
|
||||
(def stacked-modal-card dialog/stacked-modal-card-)
|
||||
(def stacked-modal-card-2 dialog/stacked-modal-card-2-)
|
||||
(def modal-card-advanced dialog/modal-card-advanced-)
|
||||
(def modal-header dialog/modal-header-)
|
||||
(def modal-header-attachment dialog/modal-header-attachment-)
|
||||
(def modal-body dialog/modal-body-)
|
||||
@@ -70,12 +68,9 @@
|
||||
(def data-grid-new-row data-grid/new-row-)
|
||||
|
||||
(defn link [params & children]
|
||||
(into [:a (update params :class str " font-medium text-blue-600 dark:text-blue-500 hover:underline ")]
|
||||
(into [:a (update params :class str " font-medium text-blue-600 dark:text-blue-500 hover:underline cursor-pointer")]
|
||||
children))
|
||||
|
||||
|
||||
|
||||
|
||||
(def paginator paginator/paginator-)
|
||||
(def data-grid-card data-grid/data-grid-card-)
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
|
||||
[auto-ap.ssr.hiccup-helper :as hh]
|
||||
[auto-ap.routes.admin.import-batch :as ib-routes]
|
||||
[auto-ap.routes.admin.clients :as ac-routes]
|
||||
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
||||
[auto-ap.routes.admin.vendors :as v-routes]))
|
||||
[auto-ap.routes.admin.vendors :as v-routes]
|
||||
[auto-ap.graphql.clients :as clients]))
|
||||
|
||||
(defn menu-button- [params & children]
|
||||
[:div
|
||||
@@ -199,7 +201,7 @@
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/restaurant
|
||||
:href (bidi/path-for client-routes/routes :admin-clients)
|
||||
:href (bidi/path-for ssr-routes/only-routes ::ac-routes/page)
|
||||
:target "_new"}
|
||||
"Clients")]
|
||||
[:li
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
|
||||
|
||||
(defn validated-save-button- [{:keys [errors class] :as params} & children]
|
||||
(button- (-> {:color (or (:color params) :primary)
|
||||
(button- (-> {:color (or (:color params) :primary)
|
||||
:type "submit" :class (cond-> (or class "")
|
||||
true (hh/add-class "w-32")
|
||||
(seq errors) (hh/add-class "animate-shake"))}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
(ns auto-ap.ssr.components.card
|
||||
(:require [auto-ap.ssr.hiccup-helper :as hh]
|
||||
[auto-ap.ssr.hx :as hx]))
|
||||
[auto-ap.ssr.hx :as hx]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn card- [params & children]
|
||||
(into [:div (update params :class str " shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white overflow-hidden")]
|
||||
(into [:div (update params :class
|
||||
#(cond-> (or % "")
|
||||
(not (str/includes? % "bg-")) (hh/add-class "dark:bg-gray-800 bg-white ")
|
||||
true (hh/add-class "shadow-md sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 overflow-hidden")))]
|
||||
children))
|
||||
|
||||
(defn content-card- [params & children]
|
||||
[:section {:class (hh/add-class " py-3 sm:py-5" (:class params))}
|
||||
[:section (merge params {:class (hh/add-class " py-3 sm:py-5" (:class params))})
|
||||
[:div {:class "max-w-screen-2xl"}
|
||||
(into
|
||||
[:div {:class "relative overflow-hidden shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white"}]
|
||||
|
||||
@@ -3,36 +3,19 @@
|
||||
[auto-ap.ssr.hiccup-helper :as hh]
|
||||
[auto-ap.ssr.hx :as hx]))
|
||||
|
||||
(defn modal- [params & children]
|
||||
|
||||
(defn modal-
|
||||
"This modal function is used to create a modal window with a stack that allows for transitioning between modals.
|
||||
|
||||
:params should include the following keys:
|
||||
- :handle-unexpected-error? (default: true) - A boolean indicating whether to handle unexpected errors.
|
||||
- :class (optional) - A string representing additional CSS classes to add to the modal.
|
||||
|
||||
&children should include the child components to be rendered within the modal."
|
||||
[{:as params} & children]
|
||||
[:div (-> params
|
||||
(assoc "@click.outside" "open=false"
|
||||
:x-data (hx/json {:index 0 :hidingIndex -1 :unexpectedError false :transitioning false})
|
||||
"x-on:htmx:response-error" "unexpectedError=true"
|
||||
"x-on:htmx:before-request" "unexpectedError=false"
|
||||
:x-ref "modalStack"
|
||||
"@modalnext.window"
|
||||
" $refs.modalStack.children[index].setAttribute('x-transition:leave-end', '-translate-x-full scale-0 opacity-0' );
|
||||
$refs.modalStack.children[index + 1].setAttribute('x-transition:enter-start', 'translate-x-full scale-0 opacity-0' );
|
||||
hidingIndex = index;
|
||||
setTimeout(() => {index ++; transitioning=true; hidingIndex = -1; }, 150);
|
||||
setTimeout(() => transitioning=false, 320)"
|
||||
|
||||
"@modalprevious.window"
|
||||
" $refs.modalStack.children[index].setAttribute('x-transition:leave-end', 'translate-x-full scale-0 opacity-0' );
|
||||
$refs.modalStack.children[index - 1].setAttribute('x-transition:enter-start', '-translate-x-full scale-0 opacity-0' );
|
||||
hidingIndex = index;
|
||||
setTimeout(() => { index --; hidingIndex = -1; transitioning=true; }, 150);
|
||||
setTimeout(() => transitioning=false, 320)"
|
||||
|
||||
"@modalpop.window"
|
||||
" $refs.modalStack.children[index].setAttribute('x-transition:leave-end', 'translate-x-full scale-0 opacity-0' );
|
||||
$refs.modalStack.children[index - 1].setAttribute('x-transition:enter-start', '-translate-x-full scale-0 opacity-0' );
|
||||
hidingIndex = index;
|
||||
setTimeout(() => {index --; transitioning=true;}, 150);
|
||||
setTimeout(() => { $refs.modalStack.removeChild($refs.modalStack.children[index+1]); hidingIndex=-1; }, 300);
|
||||
setTimeout(() => transitioning=false, 320)
|
||||
"
|
||||
)
|
||||
(assoc "@click.outside" "open=false")
|
||||
(dissoc :handle-unexpected-error?)
|
||||
(update :class (fnil hh/add-class "") "w-full h-full modal-stack"))
|
||||
children])
|
||||
|
||||
@@ -40,11 +23,10 @@
|
||||
[:div (update params
|
||||
:class (fn [c] (-> c
|
||||
(or "")
|
||||
(hh/add-class "w-full p-4 h-full modal-card")
|
||||
)))
|
||||
(hh/add-class "w-full p-4 h-full modal-card"))))
|
||||
[:div {:class "bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content w-full flex flex-col h-full"}
|
||||
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600 shrink-0"} header]
|
||||
[:div {:class "px-6 space-y-6 overflow-y-scroll w-full shrink"}
|
||||
[:div {:class "px-6 py-2 space-y-6 overflow-y-scroll w-full shrink"}
|
||||
#_[:div.bg-green-300.w-full.h-64
|
||||
"hello"]
|
||||
content]
|
||||
@@ -55,28 +37,6 @@
|
||||
[:div {:class "shrink-0"}
|
||||
footer]])]])
|
||||
|
||||
|
||||
(defn stacked-modal-card- [index params header content footer]
|
||||
[:div (merge params
|
||||
{:class (hh/add-class "bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content flex flex-col h-full" (:class params ""))
|
||||
:x-data (hx/json {:i index})
|
||||
:x-show "index == i && hidingIndex != i"
|
||||
"x-trap" "index == i && hidingIndex == -1 && !transitioning"
|
||||
"x-transition:enter" "transition duration-150",
|
||||
"x-transition:enter-end" "translate-x-0 scale-100 opacity-100",
|
||||
"x-transition:leave" "transition duration-150",
|
||||
"x-transition:leave-start" "translate-x-0 scale-100 opacity-100",
|
||||
})
|
||||
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600 shrink-0"} header] ;; todo componentize these
|
||||
[:div {:class "px-6 space-y-6 overflow-y-scroll w-full shrink"}
|
||||
|
||||
content] ;; TODO componentize
|
||||
(when footer [:div {:class "p-4"}
|
||||
[:span.items-center.bg-red-100.text-red-800.text-xs.font-medium.mb-2.p-1.rounded-full.inline-flex (hx/alpine-appear {:x-show "unexpectedError" :class "dark:bg-red-900 dark:text-red-300"})
|
||||
[:span {:class "w-2 h-2 bg-red-500 rounded-full"}]
|
||||
[:span.px-2.py-0.5 "An unexpected error has occured. Integreat staff have been notified."]]
|
||||
[:div {:class "shrink-0"}]footer])])
|
||||
|
||||
(defn modal-header- [params & children]
|
||||
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600 shrink-0"}
|
||||
children])
|
||||
@@ -92,21 +52,14 @@
|
||||
|
||||
(defn modal-footer- [params & children]
|
||||
[:div {:class "p-4"}
|
||||
[:span.items-center.bg-red-100.text-red-800.text-xs.font-medium.mb-2.p-1.rounded-full.inline-flex (hx/alpine-appear {:x-show "unexpectedError" :class "dark:bg-red-900 dark:text-red-300"})
|
||||
[:span.items-center.bg-red-100.text-red-800.text-xs.font-medium.mb-2.p-1.rounded-full.inline-flex
|
||||
(hx/alpine-appear {:x-show "unexpectedError" :class "dark:bg-red-900 dark:text-red-300"})
|
||||
[:span {:class "w-2 h-2 bg-red-500 rounded-full"}]
|
||||
[:span.px-2.py-0.5 "An unexpected error has occured. Integreat staff have been notified."]]
|
||||
[:div {:class "shrink-0"}]
|
||||
children])
|
||||
|
||||
(defn stacked-modal-card-2- [index params & children]
|
||||
(defn modal-card-advanced- [params & children]
|
||||
[:div (merge params
|
||||
{:class (hh/add-class "bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content flex flex-col h-full" (:class params ""))
|
||||
:x-data (hx/json {:i index})
|
||||
:x-show "index == i && hidingIndex != i"
|
||||
"x-trap" "index == i && hidingIndex == -1 && !transitioning"
|
||||
"x-transition:enter" "transition duration-150",
|
||||
"x-transition:enter-end" "translate-x-0 scale-100 opacity-100",
|
||||
"x-transition:leave" "transition duration-150",
|
||||
"x-transition:leave-start" "translate-x-0 scale-100 opacity-100",
|
||||
})
|
||||
{:class (hh/add-class "bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content flex flex-col h-full" (:class params "")) })
|
||||
children])
|
||||
|
||||
349
src/clj/auto_ap/ssr/components/multi_modal.clj
Normal file
349
src/clj/auto_ap/ssr/components/multi_modal.clj
Normal file
@@ -0,0 +1,349 @@
|
||||
(ns auto-ap.ssr.components.multi-modal
|
||||
(:require [auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.form-cursor :as fc]
|
||||
|
||||
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
|
||||
[auto-ap.ssr.utils
|
||||
:refer [ html-response
|
||||
assert-schema
|
||||
main-transformer
|
||||
modal-response
|
||||
wrap-form-4xx-2
|
||||
wrap-schema-enforce]]
|
||||
[auto-ap.ssr.components.timeline :as timeline]
|
||||
[bidi.bidi :as bidi]
|
||||
[hiccup.util :as hu]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.hx :as hx]
|
||||
[malli.core :as mc]
|
||||
[hiccup2.core :as hiccup2]
|
||||
[hiccup2.core :as hiccup]
|
||||
[auto-ap.cursor :as cursor]
|
||||
[malli.core :as m]
|
||||
[auto-ap.logging :as alog])
|
||||
(:import [auto_ap.cursor VecCursor]))
|
||||
|
||||
|
||||
(def default-form-props {:hx-ext "response-targets"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-target-400 "#form-errors .error-content"
|
||||
:hx-trigger "submit"
|
||||
:hx-target "this"
|
||||
"x-trap" "true"
|
||||
:class "h-full w-full" })
|
||||
|
||||
(defprotocol ModalWizardStep
|
||||
(step-key [this])
|
||||
(edit-path [this request])
|
||||
(render-step [this request])
|
||||
(step-schema [this])
|
||||
(step-name [this]))
|
||||
|
||||
(defprotocol Initializable
|
||||
(init-step-params [this request]))
|
||||
|
||||
(defprotocol Discardable
|
||||
(can-discard? [this step-params])
|
||||
(discard-changes [this request]))
|
||||
|
||||
|
||||
(defn- init-step-params- [step request]
|
||||
(if (satisfies? Initializable step)
|
||||
(init-step-params step request)
|
||||
{}))
|
||||
|
||||
(defprotocol LinearModalWizard
|
||||
(hydrate-from-request [this request])
|
||||
(get-current-step [this])
|
||||
(navigate [this step-key])
|
||||
|
||||
(form-schema [this])
|
||||
(steps [this])
|
||||
(get-step [this step-key])
|
||||
(render-wizard [this request])
|
||||
(submit [this request]))
|
||||
|
||||
(defrecord MultiStepFormState [snapshot edit-path step-params])
|
||||
(defn select-state [multi-form-state edit-path default]
|
||||
(->MultiStepFormState (:snapshot multi-form-state)
|
||||
edit-path
|
||||
(or (get-in (:snapshot multi-form-state) edit-path)
|
||||
default)))
|
||||
|
||||
|
||||
(defn merge-multi-form-state [{:keys [snapshot edit-path step-params] :as multi-form-state}]
|
||||
(let [cursor (cursor/cursor (or snapshot {}))
|
||||
;; this hack makes sure that, in the event of a missing vector entry, will make sure to add it first
|
||||
edit-cursor (cond-> cursor
|
||||
(seq edit-path) (cursor/ensure-path! edit-path {})
|
||||
(seq edit-path) (get-in edit-path {}))
|
||||
|
||||
_ (cursor/transact! edit-cursor (fn [spot]
|
||||
(merge spot step-params)))]
|
||||
(assoc multi-form-state
|
||||
:snapshot @cursor
|
||||
:edit-path []
|
||||
:step-params @cursor)))
|
||||
|
||||
(def step-key-schema (mc/schema [:orn {:decode/arbitrary clojure.edn/read-string
|
||||
:encode/arbitrary pr-str}
|
||||
[:sub-step [:cat :keyword [:or :int :string]]]
|
||||
[:step :keyword]]))
|
||||
|
||||
(def encode-step-key
|
||||
(m/-instrument {:schema [:=> [:cat step-key-schema] :any]}
|
||||
(fn encode-step-key [sk]
|
||||
(mc/encode step-key-schema sk main-transformer))))
|
||||
|
||||
|
||||
|
||||
(defn render-timeline [linear-wizard current-step validation-route]
|
||||
(let [step-names (map #(step-name (get-step linear-wizard %)) (steps linear-wizard))
|
||||
active-index (.indexOf step-names (step-name current-step))]
|
||||
(timeline/vertical-timeline
|
||||
{}
|
||||
(for [[n i] (map vector (steps linear-wizard) (range))]
|
||||
(timeline/vertical-timeline-step (cond-> {}
|
||||
(= i active-index) (assoc :active? true)
|
||||
(< i active-index) (assoc :visited? true)
|
||||
(= i (dec (count step-names))) (assoc :last? true))
|
||||
[:a.cursor-pointer.whitespace-nowrap {:x-data (hx/json {:timelineIndex i})
|
||||
:hx-put (hu/url (bidi/path-for ssr-routes/only-routes validation-route)
|
||||
{:from (encode-step-key (step-key current-step))
|
||||
:to (encode-step-key (step-key (get-step linear-wizard n)))})}
|
||||
(step-name (get-step linear-wizard n))])))))
|
||||
(defn back-button [linear-wizard step validation-route]
|
||||
[:a.cursor-pointer.whitespace-nowrap {:hx-put (hu/url (bidi/path-for ssr-routes/only-routes validation-route)
|
||||
{:from (encode-step-key (step-key step))
|
||||
:to (encode-step-key (->> (partition-all 2 1 (steps linear-wizard))
|
||||
(filter (fn [[from to]]
|
||||
(= to (step-key step))))
|
||||
ffirst))})}
|
||||
"Back"])
|
||||
|
||||
(defn default-next-button [linear-wizard step validation-route]
|
||||
(let [steps (steps linear-wizard)
|
||||
last? (= (step-key step) (last steps))
|
||||
next-step (when-not last? (->> steps
|
||||
(drop-while #(not= (step-key step)
|
||||
%))
|
||||
(drop 1)
|
||||
first
|
||||
(get-step linear-wizard)))]
|
||||
(com/validated-save-button (cond-> {:errors (seq fc/*form-errors*)
|
||||
;;:x-data (hx/json {})
|
||||
:x-ref "next"
|
||||
:class "w-48"}
|
||||
(not last?) (assoc :hx-put (hu/url (bidi/path-for ssr-routes/only-routes validation-route)
|
||||
{:from (encode-step-key (step-key step))
|
||||
:to (encode-step-key (step-key next-step))})))
|
||||
|
||||
(if next-step
|
||||
(step-name next-step)
|
||||
"Save")
|
||||
(when-not last?
|
||||
[:div.w-5.h-5 svg/arrow-right]))))
|
||||
|
||||
(defn default-step-body [params & children]
|
||||
[:div.space-y-1 {:class "w-[600px] h-[700px]"}
|
||||
children])
|
||||
|
||||
(defn default-step-footer [linear-wizard step & {:keys [validation-route
|
||||
discard-button
|
||||
next-button]}]
|
||||
[:div.flex.justify-end
|
||||
[:div.flex.items-baseline.gap-x-4
|
||||
(com/form-errors {:errors (:errors (:step-params fc/*form-errors*))})
|
||||
(when (not= (first (steps linear-wizard))
|
||||
(step-key step))
|
||||
(when validation-route
|
||||
(back-button linear-wizard step validation-route)))
|
||||
(when (and (satisfies? Discardable step) (can-discard? step @fc/*current*))
|
||||
discard-button)
|
||||
(cond next-button
|
||||
next-button
|
||||
|
||||
validation-route
|
||||
(default-next-button linear-wizard step validation-route)
|
||||
|
||||
:else
|
||||
[:div "No action possible."])]])
|
||||
|
||||
(defn default-render-step [linear-wizard step & {:keys [head body footer validation-route discard-route]}]
|
||||
(let [is-last? (= (step-key step) (last (steps linear-wizard)))]
|
||||
(com/modal-card-advanced
|
||||
{"@keydown.enter.prevent.stop" "$refs.next.click()"
|
||||
:class (str (when is-last? "last-modal-step")
|
||||
" transition duration-300 ease-in-out
|
||||
")
|
||||
":class" (hiccup/raw "{
|
||||
\"htmx-swapping:-translate-x-2/3 htmx-swapping:opacity-0 htmx-swapping:scale-0 htmx-added:translate-x-2/3 htmx-added:opacity-0 htmx-added:scale-0 scale-100 translate-x-0 opacity-100\": $data.transitionType=='forward',
|
||||
\"htmx-swapping:translate-x-2/3 htmx-swapping:opacity-0 htmx-swapping:scale-0 htmx-added:-translate-x-2/3 htmx-added:opacity-0 htmx-added:scale-0 scale-100 translate-x-0 opacity-100\": $data.transitionType=='backward'
|
||||
}
|
||||
")
|
||||
"x-data" ""}
|
||||
(com/modal-header {}
|
||||
head)
|
||||
#_(com/modal-header-attachment {})
|
||||
[:div.flex.shrink
|
||||
[:div.grow-0.pr-6.pt-2.bg-gray-100.self-stretch #_{:style "margin-left:-20px"} (render-timeline linear-wizard step validation-route)]
|
||||
(com/modal-body {}
|
||||
body)]
|
||||
|
||||
(com/modal-footer {}
|
||||
footer))))
|
||||
|
||||
(defn wrap-ensure-step [handler]
|
||||
(->
|
||||
(fn [{:keys [wizard multi-form-state] :as request}]
|
||||
(assert-schema (step-schema (get-current-step wizard)) (:step-params multi-form-state))
|
||||
(handler request))
|
||||
(wrap-form-4xx-2 (fn [{:keys [wizard] :as request}] ;; THIS MAY BE BETTER TO JUST MAKE THE LINEAR WIZARD POPULATE FROM THE REQUEST
|
||||
(html-response
|
||||
(render-wizard wizard request)
|
||||
:headers {"x-transition-type" "none"
|
||||
"HX-reswap" "outerHTML"})))))
|
||||
|
||||
(defn get-transition-type [wizard from-step-key to-step-key]
|
||||
(let [to-step-index (.indexOf (steps wizard) to-step-key)
|
||||
|
||||
from-step-index (.indexOf (steps wizard)
|
||||
from-step-key)]
|
||||
(cond (= -1 to-step-index)
|
||||
nil
|
||||
(= -1 from-step-index)
|
||||
nil
|
||||
(= from-step-index to-step-index)
|
||||
nil
|
||||
(> from-step-index to-step-index)
|
||||
"backward"
|
||||
:else
|
||||
"forward")))
|
||||
|
||||
(def next-handler
|
||||
(-> (fn [{:keys [wizard] :as request}]
|
||||
(let [current-step (get-current-step wizard)
|
||||
to-step (:to (:query-params request))
|
||||
wizard (navigate wizard to-step)
|
||||
new-step (get-current-step wizard)
|
||||
transition-type (get-transition-type wizard (step-key current-step) to-step)]
|
||||
(html-response
|
||||
(render-wizard wizard
|
||||
(-> request
|
||||
(assoc :multi-form-state (-> (:multi-form-state request)
|
||||
(merge-multi-form-state)
|
||||
(select-state
|
||||
(edit-path new-step request)
|
||||
(init-step-params- new-step request))))))
|
||||
:headers {"HX-reswap" (when transition-type "outerHTML swap:0.15s")
|
||||
"x-transition-type" (or transition-type "none")})))
|
||||
(wrap-ensure-step)
|
||||
(wrap-schema-enforce :query-schema
|
||||
[:map
|
||||
[:to step-key-schema]])))
|
||||
|
||||
(def discard-handler
|
||||
(->
|
||||
(fn [{:keys [wizard multi-form-state] :as request}]
|
||||
(let [current-step (get-current-step wizard)
|
||||
to-step (:to (:query-params request))
|
||||
wizard (navigate wizard to-step)
|
||||
transition-type (get-transition-type wizard (step-key current-step) to-step)]
|
||||
(html-response
|
||||
(render-wizard wizard
|
||||
(-> request
|
||||
(assoc :multi-form-state (discard-changes current-step multi-form-state))))
|
||||
:headers {"HX-reswap" (when transition-type "outerHTML swap:0.15s")
|
||||
"x-transition-type" (or transition-type "none")})))
|
||||
(wrap-schema-enforce :query-schema
|
||||
[:map
|
||||
[:to step-key-schema]])))
|
||||
|
||||
(def submit-handler
|
||||
(-> (fn [{:keys [wizard multi-form-state] :as request}]
|
||||
(submit wizard (-> request
|
||||
(assoc :multi-form-state (merge-multi-form-state multi-form-state)))))
|
||||
(wrap-ensure-step)))
|
||||
|
||||
(defn default-render-wizard [linear-wizard {:keys [multi-form-state form-errors snapshot current-step] :as request} & {:keys [form-params]}]
|
||||
(let [current-step (get-current-step linear-wizard)
|
||||
edit-path (edit-path current-step request)]
|
||||
[:form#wizard-form form-params
|
||||
(fc/start-form multi-form-state (when form-errors {:step-params form-errors})
|
||||
(list
|
||||
(fc/with-field :snapshot
|
||||
(com/hidden {:name (fc/field-name)
|
||||
:value (pr-str (fc/field-value))}))
|
||||
(fc/with-field :edit-path
|
||||
(com/hidden {:name (fc/field-name)
|
||||
:value (pr-str (or edit-path []))}))
|
||||
(com/hidden {:name "current-step"
|
||||
:value (pr-str (step-key current-step))})
|
||||
|
||||
(fc/with-field :step-params
|
||||
(com/modal
|
||||
{:id "wizardmodal"}
|
||||
|
||||
(render-step current-step request)))))]))
|
||||
|
||||
(defn wrap-wizard [handler linear-wizard]
|
||||
(fn [request]
|
||||
(let [current-step-key (if-let [current-step (get (:form-params request) "current-step")]
|
||||
(mc/decode step-key-schema current-step main-transformer)
|
||||
(first (steps linear-wizard)))
|
||||
current-step (get-step linear-wizard current-step-key)
|
||||
multi-form-state (-> (:multi-form-state request)
|
||||
(update :snapshot (fn [snapshot]
|
||||
(mc/decode (form-schema linear-wizard)
|
||||
snapshot
|
||||
main-transformer)))
|
||||
(update :step-params (fn [step-params]
|
||||
(or
|
||||
(mc/decode (step-schema current-step)
|
||||
step-params
|
||||
main-transformer)
|
||||
{} ;; Todo add a defaultable
|
||||
))))
|
||||
request (-> request
|
||||
(assoc :multi-form-state multi-form-state))
|
||||
linear-wizard (navigate linear-wizard current-step-key)]
|
||||
(handler
|
||||
(assoc request :wizard (hydrate-from-request linear-wizard request))))))
|
||||
|
||||
(defn open-wizard-handler [{:keys [wizard current-step] :as request}]
|
||||
(modal-response
|
||||
[:div {:x-data (hx/json {"transitionType" "none"
|
||||
|
||||
}
|
||||
)
|
||||
"@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) { $data.transitionType = event.detail.xhr.getResponseHeader('x-transition-type');}"
|
||||
}
|
||||
(render-wizard wizard request)]))
|
||||
|
||||
|
||||
|
||||
(defn wrap-init-multi-form-state [handler get-multi-form-state]
|
||||
(->
|
||||
(fn init-multi-form [request]
|
||||
(handler (assoc request :multi-form-state (get-multi-form-state request))))
|
||||
(wrap-nested-form-params)))
|
||||
|
||||
(defn wrap-decode-multi-form-state [handler]
|
||||
(wrap-init-multi-form-state
|
||||
handler
|
||||
(fn parse-multi-form-state [request]
|
||||
(map->MultiStepFormState (mc/decode [:map
|
||||
[:snapshot {:optional true
|
||||
:decode/arbitrary
|
||||
#(clojure.edn/read-string {:readers clj-time.coerce/data-readers
|
||||
:eof nil}
|
||||
%)}
|
||||
[:maybe :any]]
|
||||
[:edit-path {:optional true :decode/arbitrary (fn [z]
|
||||
(clojure.edn/read-string z))} [:maybe [:sequential {:min 0} any?]]]
|
||||
[:step-params {:optional true}
|
||||
[:maybe
|
||||
:any]]]
|
||||
(:form-params request)
|
||||
main-transformer)))))
|
||||
@@ -2,9 +2,9 @@
|
||||
(:require [auto-ap.ssr.hiccup-helper :as hh]))
|
||||
|
||||
(defn timeline-step [{:keys [active? visited? last?]} & children]
|
||||
(if active?
|
||||
(if active?
|
||||
[:li {:class "flex items-center text-primary-600 font-medium dark:text-primary-500"}
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border-2 border-primary-600 rounded-full shrink-0 dark:border-primary-500"} ]
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border-2 border-primary-600 rounded-full shrink-0 dark:border-primary-500"}]
|
||||
children
|
||||
(when-not last?
|
||||
[:svg {:class "w-3 h-3 ml-2 sm:ml-4", :aria-hidden "true", :xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 12 10"}
|
||||
@@ -24,5 +24,24 @@
|
||||
[:ol {:class "flex items-center w-full space-x-2 text-xs text-center text-gray-500 bg-white dark:text-gray-400 sm:text-base dark:bg-gray-800 sm:space-x-4 px-2"}
|
||||
children
|
||||
#_[:li {:class "flex items-center"}
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border border-gray-500 rounded-full shrink-0 dark:border-gray-400"} ]]])
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border border-gray-500 rounded-full shrink-0 dark:border-gray-400"}]]])
|
||||
|
||||
(defn vertical-timeline-step [{:keys [active? visited? last?]} & children]
|
||||
(if active?
|
||||
[:li {:class "flex items-center text-primary-600 font-medium dark:text-primary-500"}
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border-2 border-primary-600 rounded-full shrink-0 dark:border-primary-500"}]
|
||||
children ]
|
||||
[:li {:class (cond-> "flex items-center"
|
||||
(not visited?) (hh/add-class "text-gray-400"))}
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border border-gray-500 rounded-full shrink-0 dark:border-gray-400"}
|
||||
(when visited?
|
||||
[:svg {:class "w-3 h-3 text-primary-600 dark:text-primary-500", :aria-hidden "true", :xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 16 12"}
|
||||
[:path {:stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "2", :d "M1 5.917 5.724 10.5 15 1.5"}]])]
|
||||
children ]))
|
||||
|
||||
(defn vertical-timeline [params & children]
|
||||
[:ol {:class "flex flex-col items-start space-y-2 text-xs text-center text-gray-500 bg-gray-100 dark:text-gray-400 sm:text-base dark:bg-gray-800 sm:space-y-4 px-2"}
|
||||
children
|
||||
#_[:li {:class "flex items-center"}
|
||||
[:span {:class "flex items-center justify-center w-5 h-5 mr-2 text-xs border border-gray-500 rounded-full shrink-0 dark:border-gray-400"}]]])
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[auto-ap.ssr.admin.import-batch :as import-batch]
|
||||
[auto-ap.ssr.admin.transaction-rules :as admin-rules]
|
||||
[auto-ap.ssr.admin.vendors :as admin-vendors]
|
||||
[auto-ap.ssr.admin.clients :as admin-clients]
|
||||
[auto-ap.ssr.auth :as auth]
|
||||
[auto-ap.ssr.company :as company]
|
||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||
@@ -54,6 +55,7 @@
|
||||
:company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table))
|
||||
:company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link))
|
||||
:company-plaid-relink (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/relink))
|
||||
:company-update-signature (wrap-client-redirect-unauthenticated (wrap-secure company/upload-signature-data))
|
||||
:company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page))
|
||||
:company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table))
|
||||
:company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog))
|
||||
@@ -89,5 +91,6 @@
|
||||
(into admin/key->handler)
|
||||
(into admin-jobs/key->handler)
|
||||
(into admin-vendors/key->handler)
|
||||
(into admin-clients/key->handler)
|
||||
(into admin-rules/key->handler)))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require [auto-ap.ssr.utils :refer [path->name2]]
|
||||
[auto-ap.cursor :as cursor]))
|
||||
|
||||
(def ^:dynamic *prefix* [])
|
||||
(def ^:dynamic *prefix* [])
|
||||
(def ^:dynamic *form-data*)
|
||||
(def ^:dynamic *form-errors*)
|
||||
(def ^:dynamic *prev-cursor* nil)
|
||||
@@ -22,18 +22,30 @@
|
||||
`(binding [*prefix* ~prefix]
|
||||
(start-form ~form-data ~errors ~@rest)))
|
||||
|
||||
(defmacro with-prefix [prefix & rest]
|
||||
`(binding [*prefix* (into (or *prefix* []) ~prefix)]
|
||||
~@rest))
|
||||
|
||||
(defmacro with-cursor [cursor & rest]
|
||||
`(binding [*current* ~cursor]
|
||||
~@rest))
|
||||
~@rest))
|
||||
|
||||
(defmacro with-field [field & rest]
|
||||
`(with-cursor (get *current* ~field )
|
||||
`(with-cursor (get *current* ~field)
|
||||
~@rest))
|
||||
|
||||
(defmacro with-field-default [field default & rest]
|
||||
`(with-cursor (get *current* ~field ~default)
|
||||
~@rest))
|
||||
|
||||
`(let [new-cursor# (get *current* ~field ~default)
|
||||
new-cursor2# (if (not (deref new-cursor#))
|
||||
(do
|
||||
(cursor/transact! *current*
|
||||
(fn [c#]
|
||||
(assoc c# ~field ~default)))
|
||||
(get *current* ~field ~default))
|
||||
|
||||
new-cursor#)]
|
||||
(with-cursor new-cursor2#
|
||||
~@rest)))
|
||||
|
||||
(defn field-name
|
||||
([] (field-name *current*))
|
||||
@@ -62,10 +74,10 @@
|
||||
(defn cursor-map
|
||||
([f] (cursor-map *current* f))
|
||||
([cursor f]
|
||||
(when (field-value)
|
||||
(when (seq (field-value))
|
||||
(doall
|
||||
(for [n cursor]
|
||||
(with-cursor n
|
||||
(f n)))))))
|
||||
(for [n cursor]
|
||||
(with-cursor n
|
||||
(f n)))))))
|
||||
|
||||
|
||||
|
||||
@@ -34,15 +34,14 @@
|
||||
(remove-wildcard [this wildcard]
|
||||
(if (sequential? wildcard)
|
||||
(reduce
|
||||
remove-wildcard
|
||||
this
|
||||
wildcard)
|
||||
remove-wildcard
|
||||
this
|
||||
wildcard)
|
||||
(reduce
|
||||
remove-class
|
||||
this
|
||||
(filter (fn [c]
|
||||
(str/starts-with? c wildcard)
|
||||
) @class-set)))
|
||||
remove-class
|
||||
this
|
||||
(filter (fn [c]
|
||||
(str/starts-with? c wildcard)) @class-set)))
|
||||
this)
|
||||
(replace-wildcard [this wildcard add]
|
||||
(remove-wildcard this wildcard)
|
||||
@@ -51,7 +50,7 @@
|
||||
(replace-tw [this add]
|
||||
;; TODO
|
||||
)
|
||||
Object
|
||||
Object
|
||||
(toString [this]
|
||||
(str/join " " @class-set)))))
|
||||
|
||||
@@ -60,8 +59,7 @@
|
||||
(add-class [this add]
|
||||
(add-class (string->class-list this) add))
|
||||
(remove-class [this remove]
|
||||
(remove-class (string->class-list this) remove)
|
||||
)
|
||||
(remove-class (string->class-list this) remove))
|
||||
(replace-class [this remove add]
|
||||
(replace-class (string->class-list this) remove add))
|
||||
(remove-wildcard [this wildcard]
|
||||
@@ -70,29 +68,11 @@
|
||||
(replace-wildcard (string->class-list this) wildcard add))
|
||||
(add-tw [this tw]
|
||||
(replace-tw (string->class-list this)
|
||||
tw)
|
||||
))
|
||||
tw)))
|
||||
|
||||
|
||||
(str (hiccup/html [:div {:class (-> "hello bryce hello-1 hello-2"
|
||||
(replace-wildcard ["hello-" "b"] ["hi" "there"]))}]))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(str (hiccup/html [:div {:class (-> "p-1.5 "
|
||||
(add-class "bg-blue-500")
|
||||
#_(replace-wildcard ["hello-" "b"] ["hi" "there"]))}]))
|
||||
|
||||
@@ -84,51 +84,51 @@
|
||||
matching-count]))
|
||||
|
||||
(def grid-page
|
||||
(helper/build
|
||||
{:id "cash-drawer-shift-table"
|
||||
:nav (com/main-aside-nav)
|
||||
:page-specific-nav filters
|
||||
:fetch-page fetch-page
|
||||
:oob-render
|
||||
(fn [request]
|
||||
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)])
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"POS"]
|
||||
{}
|
||||
#_(helper/build
|
||||
{:id "cash-drawer-shift-table"
|
||||
:nav (com/main-aside-nav)
|
||||
:page-specific-nav filters
|
||||
:fetch-page fetch-page
|
||||
:oob-render
|
||||
(fn [request]
|
||||
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)])
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"POS"]
|
||||
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:pos-cash-drawer-shifts)}
|
||||
"Cash Drawer Shifts"]]
|
||||
:title "Cash drawer shifts"
|
||||
:entity-name "Cash drawer shift"
|
||||
:route :pos-cash-drawer-shift-table
|
||||
:headers [{:key "client"
|
||||
:name "Client"
|
||||
:sort-key "client"
|
||||
:hide? (fn [args]
|
||||
(= (count (:clients args)) 1))
|
||||
:render #(-> % :cash-drawer-shift/client :client/code)}
|
||||
{:key "date"
|
||||
:name "Date"
|
||||
:sort-key "date"
|
||||
:render #(atime/unparse-local (:cash-drawer-shift/date %) atime/standard-time)}
|
||||
{:key "paid-in"
|
||||
:name "Paid in"
|
||||
:sort-key "paid-in"
|
||||
:render #(some->> % :cash-drawer-shift/paid-in (format "$%.2f"))}
|
||||
{:key "paid-out"
|
||||
:name "Paid out"
|
||||
:sort-key "paid-out"
|
||||
:render #(some->> % :cash-drawer-shift/paid-out (format "$%.2f"))}
|
||||
{:key "expected-cash"
|
||||
:name "Expected cash"
|
||||
:sort-key "expected-cash"
|
||||
:render #(some->> % :cash-drawer-shift/expected-cash (format "$%.2f"))}
|
||||
{:key "opened-cash"
|
||||
:name "Opened cash"
|
||||
:sort-key "opened-cash"
|
||||
:render #(some->> % :cash-drawer-shift/opened-cash (format "$%.2f"))}
|
||||
]}))
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:pos-cash-drawer-shifts)}
|
||||
"Cash Drawer Shifts"]]
|
||||
:title "Cash drawer shifts"
|
||||
:entity-name "Cash drawer shift"
|
||||
:route :pos-cash-drawer-shift-table
|
||||
:headers [{:key "client"
|
||||
:name "Client"
|
||||
:sort-key "client"
|
||||
:hide? (fn [args]
|
||||
(= (count (:clients args)) 1))
|
||||
:render #(-> % :cash-drawer-shift/client :client/code)}
|
||||
{:key "date"
|
||||
:name "Date"
|
||||
:sort-key "date"
|
||||
:render #(atime/unparse-local (:cash-drawer-shift/date %) atime/standard-time)}
|
||||
{:key "paid-in"
|
||||
:name "Paid in"
|
||||
:sort-key "paid-in"
|
||||
:render #(some->> % :cash-drawer-shift/paid-in (format "$%.2f"))}
|
||||
{:key "paid-out"
|
||||
:name "Paid out"
|
||||
:sort-key "paid-out"
|
||||
:render #(some->> % :cash-drawer-shift/paid-out (format "$%.2f"))}
|
||||
{:key "expected-cash"
|
||||
:name "Expected cash"
|
||||
:sort-key "expected-cash"
|
||||
:render #(some->> % :cash-drawer-shift/expected-cash (format "$%.2f"))}
|
||||
{:key "opened-cash"
|
||||
:name "Opened cash"
|
||||
:sort-key "opened-cash"
|
||||
:render #(some->> % :cash-drawer-shift/opened-cash (format "$%.2f"))}]}))
|
||||
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns auto-ap.ssr.svg)
|
||||
|
||||
(def pie
|
||||
(def pie
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "analytics-pie-2"]
|
||||
@@ -11,28 +11,28 @@
|
||||
|
||||
(def accounting-invoice-mail
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "accounting-document"]
|
||||
[:path {:d "M21.75,21.75a1.5,1.5,0,0,1-1.5,1.5H3.75a1.5,1.5,0,0,1-1.5-1.5V2.25A1.5,1.5,0,0,1,3.75.75H14.379a1.5,1.5,0,0,1,1.06.439l5.872,5.872a1.5,1.5,0,0,1,.439,1.06Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M21.75,8.25h-6a1.5,1.5,0,0,1-1.5-1.5v-6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M6.2,11.594a2.224,2.224,0,0,0,1.858.875c1.139,0,2.063-.693,2.063-1.547S9.2,9.376,8.062,9.376,6,8.683,6,7.828s.924-1.547,2.062-1.547a2.221,2.221,0,0,1,1.858.875", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.469", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.062", :y2 "13.5", :x2 "8.062"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "5.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.062", :y2 "6.281", :x2 "8.062"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "15", :stroke-linecap "round", :stroke-width "1.5px", :x1 "12", :y2 "15", :x2 "18"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "19.5", :stroke-linecap "round", :stroke-width "1.5px", :x1 "6.75", :y2 "19.5", :x2 "18"}]])
|
||||
[:defs]
|
||||
[:title "accounting-document"]
|
||||
[:path {:d "M21.75,21.75a1.5,1.5,0,0,1-1.5,1.5H3.75a1.5,1.5,0,0,1-1.5-1.5V2.25A1.5,1.5,0,0,1,3.75.75H14.379a1.5,1.5,0,0,1,1.06.439l5.872,5.872a1.5,1.5,0,0,1,.439,1.06Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M21.75,8.25h-6a1.5,1.5,0,0,1-1.5-1.5v-6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M6.2,11.594a2.224,2.224,0,0,0,1.858.875c1.139,0,2.063-.693,2.063-1.547S9.2,9.376,8.062,9.376,6,8.683,6,7.828s.924-1.547,2.062-1.547a2.221,2.221,0,0,1,1.858.875", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.469", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.062", :y2 "13.5", :x2 "8.062"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "5.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.062", :y2 "6.281", :x2 "8.062"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "15", :stroke-linecap "round", :stroke-width "1.5px", :x1 "12", :y2 "15", :x2 "18"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "19.5", :stroke-linecap "round", :stroke-width "1.5px", :x1 "6.75", :y2 "19.5", :x2 "18"}]])
|
||||
|
||||
(def receipt-register-1
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:path {:d "M17.63,18h3a1.5,1.5,0,0,1,1.5,1.5v2.25a1.5,1.5,0,0,1-1.5,1.5H3.38a1.5,1.5,0,0,1-1.5-1.5V18H7.13", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "17.03", :stroke-linecap "round", :stroke-width "1.5px", :x1 "11.45", :y2 "14.35", :x2 "8.71"}]
|
||||
[:circle {:cx "12.48", :cy "18.11", :r "1.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M1.88,4.5H8.63L9.81,3.31A1.5,1.5,0,0,0,8.75.75H3.38a1.5,1.5,0,0,0-1.5,1.5V18", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.1", :stroke-linecap "round", :stroke-width "1.5px", :x1 "13.22", :y2 "6.59", :x2 "14.2"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "8.54", :stroke-linecap "round", :stroke-width "1.5px", :x1 "17.13", :y2 "9.25", :x2 "17.97"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.09", :stroke-linecap "round", :stroke-width "1.5px", :x1 "19.9", :y2 "13.03", :x2 "20.46"}]
|
||||
[:path {:d "M8.63,4.5V7.74A12.22,12.22,0,0,1,18.92,18", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:circle {:cx "7.13", :cy "12.75", :r "2.25", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
[:g
|
||||
[:path {:d "M17.63,18h3a1.5,1.5,0,0,1,1.5,1.5v2.25a1.5,1.5,0,0,1-1.5,1.5H3.38a1.5,1.5,0,0,1-1.5-1.5V18H7.13", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "17.03", :stroke-linecap "round", :stroke-width "1.5px", :x1 "11.45", :y2 "14.35", :x2 "8.71"}]
|
||||
[:circle {:cx "12.48", :cy "18.11", :r "1.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M1.88,4.5H8.63L9.81,3.31A1.5,1.5,0,0,0,8.75.75H3.38a1.5,1.5,0,0,0-1.5,1.5V18", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.1", :stroke-linecap "round", :stroke-width "1.5px", :x1 "13.22", :y2 "6.59", :x2 "14.2"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "8.54", :stroke-linecap "round", :stroke-width "1.5px", :x1 "17.13", :y2 "9.25", :x2 "17.97"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "12.09", :stroke-linecap "round", :stroke-width "1.5px", :x1 "19.9", :y2 "13.03", :x2 "20.46"}]
|
||||
[:path {:d "M8.63,4.5V7.74A12.22,12.22,0,0,1,18.92,18", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:circle {:cx "7.13", :cy "12.75", :r "2.25", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
|
||||
(def payments
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
@@ -45,38 +45,38 @@
|
||||
|
||||
(def bank
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "23.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "23.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "19.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "19.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "2", :y2 "16.25", :x2 "2"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "5.5", :y2 "16.25", :x2 "5.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "10.25", :y2 "16.25", :x2 "10.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "13.75", :y2 "16.25", :x2 "13.75"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "18.5", :y2 "16.25", :x2 "18.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "22", :y2 "16.25", :x2 "22"}]
|
||||
[:path {:d "M23.25,7.25H.75L11.19,1a1.49,1.49,0,0,1,1.62,0Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
[:g
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "23.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "23.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "19.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "19.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "2", :y2 "16.25", :x2 "2"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "5.5", :y2 "16.25", :x2 "5.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "10.25", :y2 "16.25", :x2 "10.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "13.75", :y2 "16.25", :x2 "13.75"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "18.5", :y2 "16.25", :x2 "18.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "10.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "22", :y2 "16.25", :x2 "22"}]
|
||||
[:path {:d "M23.25,7.25H.75L11.19,1a1.49,1.49,0,0,1,1.62,0Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
|
||||
(def receipt
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "21.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "17.25", :y2 "21.75", :x2 "11.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "13.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "13.25", :x2 "15.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "9.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "9.75", :x2 "15.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "6.25", :x2 "15.25"}]
|
||||
[:path {:d "M20.25.75h-1.5v5.5h4.5V3.75A3,3,0,0,0,20.25.75Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M13.5,17.25H3.75a3,3,0,0,0-3,3v3h10.5V19.5a2.25,2.25,0,0,1,4.5,0v.75a1.5,1.5,0,0,0,1.5,1.5h0a1.5,1.5,0,0,0,1.5-1.5V.75H8.25a3,3,0,0,0-3,3v13.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
[:g
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "21.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "17.25", :y2 "21.75", :x2 "11.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "13.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "13.25", :x2 "15.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "9.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "9.75", :x2 "15.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "8.75", :y2 "6.25", :x2 "15.25"}]
|
||||
[:path {:d "M20.25.75h-1.5v5.5h4.5V3.75A3,3,0,0,0,20.25.75Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M13.5,17.25H3.75a3,3,0,0,0-3,3v3h10.5V19.5a2.25,2.25,0,0,1,4.5,0v.75a1.5,1.5,0,0,0,1.5,1.5h0a1.5,1.5,0,0,0,1.5-1.5V.75H8.25a3,3,0,0,0-3,3v13.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]])
|
||||
|
||||
(defn spinner [{:keys [class]}]
|
||||
[:svg {:aria-hidden "true", :role "status", :class (str "animate-spin " class) :viewbox "0 0 100 101", :fill "none", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", :fill "#E5E7EB"}]
|
||||
[:path {:d "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", :fill "currentColor"}]])
|
||||
[:path {:d "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", :fill "#E5E7EB"}]
|
||||
[:path {:d "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", :fill "currentColor"}]])
|
||||
|
||||
(defn spinner-primary [{:keys [class]}]
|
||||
[:svg {:aria-hidden "true", :role "status", :class (str "animate-spin " class) :viewbox "0 0 100 101", :fill "none", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", :fill "#79b52e"}]
|
||||
[:path {:d "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", :fill "currentColor"}]])
|
||||
|
||||
(def search
|
||||
(def search
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "search"]
|
||||
@@ -93,11 +93,11 @@
|
||||
|
||||
|
||||
(def home
|
||||
[:svg { :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:svg {:fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"}]])
|
||||
|
||||
(def breadcrumb-component
|
||||
[:svg { :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:svg {:fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", :clip-rule "evenodd"}]])
|
||||
|
||||
(def refresh
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
|
||||
(def upload
|
||||
[:svg { :xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 24 24", :stroke-width "2", :stroke "currentColor", :aria-hidden "true"}
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 24 24", :stroke-width "2", :stroke "currentColor", :aria-hidden "true"}
|
||||
[:path {:stroke-linecap "round", :stroke-linejoin "round", :d "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"}]])
|
||||
|
||||
(def vendors
|
||||
@@ -122,29 +122,29 @@
|
||||
|
||||
(def report
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "app-window-pie-chart"]
|
||||
[:rect {:y "2.253", :rx "1.5", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "21", :stroke-linecap "round", :stroke-width "1.5px", :x "1.51", :ry "1.5", :height "19.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.753", :stroke-linecap "round", :stroke-width "1.5px", :x1 "1.51", :y2 "6.753", :x2 "22.51"}]
|
||||
[:circle {:cx "9.01", :cy "14.253", :r "4.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "9.01 9.753 9.01 14.253 12.192 17.435", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "11.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "11.253", :x2 "19.51"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "14.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "14.253", :x2 "19.51"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "17.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "17.253", :x2 "19.51"}]])
|
||||
[:defs]
|
||||
[:title "app-window-pie-chart"]
|
||||
[:rect {:y "2.253", :rx "1.5", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "21", :stroke-linecap "round", :stroke-width "1.5px", :x "1.51", :ry "1.5", :height "19.5"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "6.753", :stroke-linecap "round", :stroke-width "1.5px", :x1 "1.51", :y2 "6.753", :x2 "22.51"}]
|
||||
[:circle {:cx "9.01", :cy "14.253", :r "4.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "9.01 9.753 9.01 14.253 12.192 17.435", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "11.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "11.253", :x2 "19.51"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "14.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "14.253", :x2 "19.51"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "17.253", :stroke-linecap "round", :stroke-width "1.5px", :x1 "16.51", :y2 "17.253", :x2 "19.51"}]])
|
||||
|
||||
(def government-building
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:g
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "3.5", :height "6"}]
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "10.5", :height "6"}]
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "17.5", :height "6"}]
|
||||
[:path {:d "M21.75,13.39a.87.87,0,0,1-.86.86H3.11a.86.86,0,0,1-.25-1.69l8.85-2.72a1,1,0,0,1,.58,0l8.85,2.72A.87.87,0,0,1,21.75,13.39Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "15.5 8.25 18 8.25 18 11.6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "6 11.6 6 8.25 8.5 8.25", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M6,8.25a6,6,0,0,1,12,0", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "0.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "12", :y2 "2.25", :x2 "12"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "23.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "23.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "20.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "2", :y2 "20.25", :x2 "22"}]]])
|
||||
[:g
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "3.5", :height "6"}]
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "10.5", :height "6"}]
|
||||
[:rect {:y "14.25", :stroke "currentColor", :fill "none", :stroke-linejoin "round", :width "3", :stroke-linecap "round", :stroke-width "1.5px", :x "17.5", :height "6"}]
|
||||
[:path {:d "M21.75,13.39a.87.87,0,0,1-.86.86H3.11a.86.86,0,0,1-.25-1.69l8.85-2.72a1,1,0,0,1,.58,0l8.85,2.72A.87.87,0,0,1,21.75,13.39Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "15.5 8.25 18 8.25 18 11.6", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:polyline {:points "6 11.6 6 8.25 8.5 8.25", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:path {:d "M6,8.25a6,6,0,0,1,12,0", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "0.75", :stroke-linecap "round", :stroke-width "1.5px", :x1 "12", :y2 "2.25", :x2 "12"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "23.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "0.75", :y2 "23.25", :x2 "23.25"}]
|
||||
[:line {:stroke "currentColor", :fill "none", :stroke-linejoin "round", :y1 "20.25", :stroke-linecap "round", :stroke-width "1.5px", :x1 "2", :y2 "20.25", :x2 "22"}]]])
|
||||
|
||||
(def external-link
|
||||
[:svg
|
||||
@@ -168,7 +168,7 @@
|
||||
"M14.387,13v5.5a1,1,0,0,1-1,1h-12a1,1,0,0,1-1-1V6.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1V7",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
(def play
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "-0.5 -0.5 24 24"}
|
||||
@@ -201,9 +201,19 @@
|
||||
{:d "M21.914,6.328,17.672,2.086l.707-.707a3,3,0,0,1,4.242,4.242Z",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def dollar-tag
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "tag-dollar"]
|
||||
[:path {:d "M17.5 5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 1 0 -3 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m14.257 12.571 -1.985 -1.985a1.107 1.107 0 0 0 -1.686 0c-0.925 0.924 1.2 4.46 0.272 5.384a1.171 1.171 0 0 1 -1.687 0l-1.985 -1.985", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m12.843 11.157 1.061 -1.061", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m7.54 16.46 1.061 -1.061", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "M0.854 12.646a1.207 1.207 0 0 0 0 1.708l8.792 8.792a1.207 1.207 0 0 0 1.708 0l11.439 -11.439A2.414 2.414 0 0 0 23.5 10V1.5a1 1 0 0 0 -1 -1H14a2.414 2.414 0 0 0 -1.707 0.707Z", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]])
|
||||
|
||||
(def drop-down
|
||||
[:svg {:aria-hidden "true", :fill "none", :stroke "currentColor", :viewbox "0 0 24 24", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:stroke-linecap "round", :stroke-linejoin "round", :stroke-width "2", :d "M19 9l-7 7-7-7"}]])
|
||||
@@ -224,63 +234,63 @@
|
||||
{:d "M23.5,18.5v4a1,1,0,0,1-1,1H1.5a1,1,0,0,1-1-1v-4",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def trash
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "bin-1"]
|
||||
[:path
|
||||
{:d
|
||||
"M21,4.5,19.188,21.709A2,2,0,0,1,17.2,23.5H6.8a2,2,0,0,1-1.989-1.791L3,4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "0.5",
|
||||
:y1 "4.5",
|
||||
:x2 "23.5",
|
||||
:y2 "4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M7.5,4.5v-3a1,1,0,0,1,1-1h7a1,1,0,0,1,1,1v3",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "12",
|
||||
:y1 "9",
|
||||
:x2 "12",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "16.5",
|
||||
:y1 "9",
|
||||
:x2 "16",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "7.5",
|
||||
:y1 "9",
|
||||
:x2 "8",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "bin-1"]
|
||||
[:path
|
||||
{:d
|
||||
"M21,4.5,19.188,21.709A2,2,0,0,1,17.2,23.5H6.8a2,2,0,0,1-1.989-1.791L3,4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "0.5",
|
||||
:y1 "4.5",
|
||||
:x2 "23.5",
|
||||
:y2 "4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M7.5,4.5v-3a1,1,0,0,1,1-1h7a1,1,0,0,1,1,1v3",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "12",
|
||||
:y1 "9",
|
||||
:x2 "12",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "16.5",
|
||||
:y1 "9",
|
||||
:x2 "16",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "7.5",
|
||||
:y1 "9",
|
||||
:x2 "8",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def alert
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
@@ -442,3 +452,39 @@
|
||||
[: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"}]])
|
||||
|
||||
(def dollar
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :fill "none", :viewbox "0 0 24 24"}
|
||||
[:path {:stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :d "M21 7H3a0.5 0.5 0 0 0 -0.5 0.5v9a0.5 0.5 0 0 0 0.5 0.5h18a0.5 0.5 0 0 0 0.5 -0.5v-9A0.5 0.5 0 0 0 21 7Z", :stroke-width "1"}]
|
||||
[:path {:stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :d "M22.5 5h-21a1 1 0 0 0 -1 1v12a1 1 0 0 0 1 1h21a1 1 0 0 0 1 -1V6a1 1 0 0 0 -1 -1Z", :stroke-width "1"}]
|
||||
[:path {:stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :d "M12 15a3 3 0 1 0 0 -6 3 3 0 0 0 0 6Z", :stroke-width "1"}]
|
||||
[:path {:stroke "currentcolor", :d "M4.996 9.75a0.25 0.25 0 0 1 0 -0.5", :stroke-width "1"}]
|
||||
[:path {:stroke "currentcolor", :d "M4.996 9.75a0.25 0.25 0 0 0 0 -0.5", :stroke-width "1"}]
|
||||
[:g
|
||||
[:path {:stroke "currentcolor", :d "M18.996 14.75a0.25 0.25 0 1 1 0 -0.5", :stroke-width "1"}]
|
||||
[:path {:stroke "currentcolor", :d "M18.996 14.75a0.25 0.25 0 1 0 0 -0.5", :stroke-width "1"}]]])
|
||||
|
||||
(def credit-card
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "credit-card-1"]
|
||||
[:path {:d "M2.504 4h19s2 0 2 2v12s0 2 -2 2h-19s-2 0 -2 -2V6s0 -2 2 -2", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m0.504 8 23 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m20.504 12 -3 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m11.504 12 -8 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m6.504 15 -3 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]])
|
||||
|
||||
(def check
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "check-payment-sign"]
|
||||
[:path {:d "m2.5 20.5 7 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m14 19.616 0.751 -0.751a1 1 0 0 1 1.677 0.465l0.072 0.286 0.818 -0.545a1 1 0 0 1 1.262 0.125l0.42 0.42h2", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m2.504 17.616 5 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m14.075 5.505 2.122 2.122", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m18.813 2.465 0.425 0.424a1.2 1.2 0 0 1 0 1.697l-8.698 8.697 0 0 -2.121 -2.121 0 0 8.697 -8.697a1.2 1.2 0 0 1 1.697 0Z", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m8.418 11.162 -1.414 3.536 3.536 -1.414", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m19.025 2.677 1.293 -1.293", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "M6.5 9.616H1a0.5 0.5 0 0 0 -0.5 0.5v12a0.5 0.5 0 0 0 0.5 0.5h22a0.5 0.5 0 0 0 0.5 -0.5v-12a0.5 0.5 0 0 0 -0.5 -0.5h-5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "M17.004 12.616h3s0.5 0 0.5 0.5v2s0 0.5 -0.5 0.5h-3s-0.5 0 -0.5 -0.5v-2s0 -0.5 0.5 -0.5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||
[:path {:d "m16.757 2.823 -0.8 -0.8a1 1 0 0 0 -1.414 0L12.5 4.07", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]])
|
||||
@@ -15,8 +15,6 @@
|
||||
hiccup))})
|
||||
|
||||
|
||||
|
||||
|
||||
(defn base-page [request contents page-name]
|
||||
(html-page
|
||||
[:html.has-navbar-fixed-top
|
||||
@@ -38,10 +36,11 @@
|
||||
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.js"
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.min.js"
|
||||
:crossorigin= "anonymous"}])
|
||||
:crossorigin= "anonymous"}])
|
||||
|
||||
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/ext/class-tools.js" :crossorigin= "anonymous"}]
|
||||
|
||||
[:script {:src "https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"}]
|
||||
[:script {:src "https://unpkg.com/htmx.org/dist/ext/debug.js"}]
|
||||
[:script {:src "/js/htmx-disable.js"}]
|
||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async"}]]
|
||||
@@ -57,7 +56,8 @@
|
||||
[:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}]
|
||||
[:script {:defer true :src "https://cdn.jsdelivr.net/npm/@alpinejs/focus@3.x.x/dist/cdn.min.js"}]
|
||||
[:script {:defer true :src "https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"}]
|
||||
|
||||
[:script {:src "https://cdn.jsdelivr.net/npm/signature_pad@4.1.7/dist/signature_pad.umd.min.js"}]
|
||||
|
||||
[:style
|
||||
"
|
||||
input::-webkit-outer-spin-button,
|
||||
@@ -74,14 +74,17 @@ input[type=number] {
|
||||
[:body {:hx-ext "disable-submit, class-tools"}
|
||||
contents
|
||||
[:script {:src "/js/flowbite.min.js"}]
|
||||
|
||||
|
||||
|
||||
[:div#modal-holder
|
||||
{ :class "fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen"
|
||||
{:class "fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen"
|
||||
"x-show" "open"
|
||||
":aria-hidden" "!open"
|
||||
"x-data" (hx/json {"open" false})
|
||||
"@modalopen.document" "open=true"
|
||||
"x-data" (hx/json {"open" false
|
||||
"unexpectedError" false})
|
||||
"x-on:htmx:response-error" "unexpectedError=true;"
|
||||
"x-on:htmx:before-request" "unexpectedError=false"
|
||||
"@modalopen.document" "open=true; unexpectedError=null"
|
||||
"@modalclose.document" "open=false"}
|
||||
|
||||
[:div {:class "bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40 md:p-12"
|
||||
@@ -94,21 +97,18 @@ input[type=number] {
|
||||
"x-transition:leave-start" "!bg-opacity-50"
|
||||
"x-transition:leave-end" "!bg-opacity-0"}
|
||||
|
||||
[:div {
|
||||
:class "flex h-full w-full justify-stretch md:justify-center items-stretch md:items-center "
|
||||
"x-trap.inert.noscroll" "open"
|
||||
"x-trap.inert" "open"
|
||||
"x-show" "open"
|
||||
"x-transition:enter" "ease-out duration-300"
|
||||
"x-transition:enter-start" "!bg-opacity-0 !translate-y-32"
|
||||
"x-transition:enter-end" "!bg-opacity-100 !translate-y-0"
|
||||
"x-transition:leave" "duration-300"
|
||||
"x-transition:leave-start" "!opacity-100 !translate-y-0"
|
||||
"x-transition:leave-end" "!opacity-0 !translate-y-32"}
|
||||
[:div {:class "flex h-full w-full justify-stretch md:justify-center items-stretch md:items-center "
|
||||
"x-trap.inert.noscroll" "open"
|
||||
"x-trap.inert" "open"
|
||||
"x-show" "open"
|
||||
"x-transition:enter" "ease-out duration-300"
|
||||
"x-transition:enter-start" "!bg-opacity-0 !translate-y-32"
|
||||
"x-transition:enter-end" "!bg-opacity-100 !translate-y-0"
|
||||
"x-transition:leave" "duration-300"
|
||||
"x-transition:leave-start" "!opacity-100 !translate-y-0"
|
||||
"x-transition:leave-end" "!opacity-0 !translate-y-32"}
|
||||
|
||||
[:div.flex.items-center.justify-center.max-w-6xl {:class "min-w-[700px] max-h-full "}
|
||||
|
||||
|
||||
[:div#modal-content.flex.flex-col.self-stretch {:class "min-w-[700px] md:p-12"} ;;.overflow-scroll
|
||||
|
||||
]
|
||||
]]]]]]))
|
||||
]]]]]]]))
|
||||
|
||||
@@ -16,51 +16,51 @@
|
||||
:headers (into {"Content-Type" "text/html"}
|
||||
headers)
|
||||
:body (str
|
||||
(hiccup/html
|
||||
{}
|
||||
hiccup)
|
||||
"\n"
|
||||
(str/join "\n"
|
||||
(map (fn [o]
|
||||
(hiccup/html
|
||||
{}
|
||||
o))
|
||||
oob)))})
|
||||
(hiccup/html
|
||||
{}
|
||||
hiccup)
|
||||
"\n"
|
||||
(str/join "\n"
|
||||
(map (fn [o]
|
||||
(hiccup/html
|
||||
{}
|
||||
o))
|
||||
oob)))})
|
||||
|
||||
(defn modal-response [hiccup & {:as opts}]
|
||||
(apply html-response
|
||||
(into
|
||||
[hiccup]
|
||||
(mapcat identity
|
||||
(-> opts
|
||||
(assoc-in [:headers "hx-trigger"] "modalopen")
|
||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||
(into
|
||||
[hiccup]
|
||||
(mapcat identity
|
||||
(-> opts
|
||||
(assoc-in [:headers "hx-trigger"] "modalopen")
|
||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||
|
||||
(defn next-step-modal-response [hiccup & {:as opts}]
|
||||
(apply html-response
|
||||
(into
|
||||
[hiccup]
|
||||
(mapcat identity
|
||||
(-> opts
|
||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||
(into
|
||||
[hiccup]
|
||||
(mapcat identity
|
||||
(-> opts
|
||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||
|
||||
|
||||
|
||||
(defn form-data->map [form-data]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond (and (string? v)
|
||||
(empty? v))
|
||||
acc
|
||||
(fn [acc k v]
|
||||
(cond (and (string? v)
|
||||
(empty? v))
|
||||
acc
|
||||
|
||||
:else
|
||||
(assoc-in acc (->> (str/split k #"_")
|
||||
(mapv #(apply keyword (str/split % #"/"))))
|
||||
v)))
|
||||
{}
|
||||
form-data))
|
||||
:else
|
||||
(assoc-in acc (->> (str/split k #"_")
|
||||
(mapv #(apply keyword (str/split % #"/"))))
|
||||
v)))
|
||||
{}
|
||||
form-data))
|
||||
|
||||
(defn path->name [k]
|
||||
(cond (keyword? k)
|
||||
@@ -75,29 +75,32 @@
|
||||
[:vector {:decode/json {:enter (fn [x]
|
||||
(if (sequential? x)
|
||||
x
|
||||
[x])
|
||||
)}}
|
||||
[x]))}}
|
||||
x])
|
||||
|
||||
(defn empty->nil [v]
|
||||
(if (and (string? v) (clojure.string/blank? v))
|
||||
nil
|
||||
v))
|
||||
(if (and (string? v) (clojure.string/blank? v))
|
||||
nil
|
||||
v))
|
||||
|
||||
(defn parse-empty-as-nil []
|
||||
(mt2/transformer
|
||||
{:decoders
|
||||
{:string empty->nil
|
||||
:double empty->nil
|
||||
:int empty->nil
|
||||
:long empty->nil
|
||||
'nat-int? empty->nil}}))
|
||||
(defn parse-empty-as-nil []
|
||||
(mt2/transformer
|
||||
{:decoders
|
||||
{:map (fn [m]
|
||||
(if (not (seq (filter identity (vals m))))
|
||||
nil
|
||||
m))
|
||||
:string empty->nil
|
||||
:double empty->nil
|
||||
:int empty->nil
|
||||
:long empty->nil
|
||||
'nat-int? empty->nil}}))
|
||||
|
||||
(def entity-id (mc/schema [nat-int? {:error/message "required"
|
||||
:decode/arbitrary (fn [e]
|
||||
(if (and (map? e) (:db/id e))
|
||||
(:db/id e)
|
||||
e))} ]))
|
||||
:decode/arbitrary (fn [e]
|
||||
(if (and (map? e) (:db/id e))
|
||||
(:db/id e)
|
||||
e))}]))
|
||||
|
||||
(def temp-id (mc/schema [:string {:min 1}]))
|
||||
(def money (mc/schema [:double]))
|
||||
@@ -110,12 +113,12 @@
|
||||
|
||||
(def regex (mc/schema [:fn {:error/message "not a regex"}
|
||||
(fn check-regx [x]
|
||||
(try
|
||||
(and (string? x)
|
||||
(. java.util.regex.Pattern (compile x java.util.regex.Pattern/CASE_INSENSITIVE)))
|
||||
true
|
||||
(catch Exception _
|
||||
false)))]))
|
||||
(try
|
||||
(and (string? x)
|
||||
(. java.util.regex.Pattern (compile x java.util.regex.Pattern/CASE_INSENSITIVE)))
|
||||
true
|
||||
(catch Exception _
|
||||
false)))]))
|
||||
|
||||
(def map->db-id-decoder
|
||||
{:enter (fn [x]
|
||||
@@ -127,12 +130,12 @@
|
||||
|
||||
(defn many-entity [params & keys]
|
||||
(mc/schema
|
||||
[:vector (merge params {:decode/json map->db-id-decoder
|
||||
:decode/arbitrary (fn [x]
|
||||
(if (sequential? x)
|
||||
x
|
||||
[x]))})
|
||||
(into [:map] keys)]))
|
||||
[:vector (merge params {:decode/json map->db-id-decoder
|
||||
:decode/arbitrary (fn [x]
|
||||
(if (sequential? x)
|
||||
x
|
||||
[x]))})
|
||||
(into [:map] keys)]))
|
||||
|
||||
(defn str->keyword [s]
|
||||
(if (string? s)
|
||||
@@ -156,23 +159,38 @@
|
||||
:form-validation-errors [m]}))))
|
||||
|
||||
(def main-transformer
|
||||
(mt2/transformer
|
||||
parse-empty-as-nil
|
||||
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||
mt2/string-transformer
|
||||
mt2/json-transformer
|
||||
(mt2/transformer {:name :arbitrary})
|
||||
mt2/default-value-transformer))
|
||||
(mt2/transformer
|
||||
parse-empty-as-nil
|
||||
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||
mt2/string-transformer
|
||||
mt2/json-transformer
|
||||
(mt2/transformer {:name :arbitrary})
|
||||
mt2/default-value-transformer))
|
||||
|
||||
(defn strip [s]
|
||||
(cond (and (string? s) (str/blank? s))
|
||||
nil
|
||||
nil
|
||||
|
||||
(string? s)
|
||||
(str/trim s)
|
||||
(string? s)
|
||||
(str/trim s)
|
||||
|
||||
:else
|
||||
s))
|
||||
(defn assert-schema [schema entity]
|
||||
(when-not (mc/validate schema entity)
|
||||
(throw (ex-info #_(->> (-> (mc/explain schema entity)
|
||||
(me/humanize {:errors (assoc me/default-errors
|
||||
::mc/missing-key {:error/message {:en "required"}})}))
|
||||
(map (fn [[k v]]
|
||||
(str (if (keyword? k)
|
||||
(name k)
|
||||
k) ": " (str/join ", " v))))
|
||||
(str/join ", "))
|
||||
"validation failed"
|
||||
{:type :schema-validation
|
||||
:decoded entity
|
||||
:error {:explain (mc/explain schema entity)}}))))
|
||||
|
||||
:else
|
||||
s))
|
||||
|
||||
(defn schema-enforce-request [{:keys [form-params query-params params] :as request} & {:keys [form-schema query-schema route-schema params-schema]}]
|
||||
(let [request (try
|
||||
@@ -180,35 +198,35 @@
|
||||
(and (:params request) params-schema)
|
||||
(assoc :params
|
||||
(mc/coerce
|
||||
params-schema
|
||||
(:params request)
|
||||
main-transformer))
|
||||
params-schema
|
||||
(:params request)
|
||||
main-transformer))
|
||||
|
||||
(and (:route-params request) route-schema)
|
||||
(assoc :route-params
|
||||
(mc/coerce
|
||||
route-schema
|
||||
(:route-params request)
|
||||
main-transformer))
|
||||
route-schema
|
||||
(:route-params request)
|
||||
main-transformer))
|
||||
|
||||
(and form-schema form-params)
|
||||
(assoc :form-params
|
||||
(mc/coerce
|
||||
form-schema
|
||||
form-params
|
||||
main-transformer))
|
||||
form-schema
|
||||
form-params
|
||||
main-transformer))
|
||||
|
||||
(and query-schema query-params)
|
||||
(assoc :query-params
|
||||
(mc/coerce
|
||||
query-schema
|
||||
query-params
|
||||
main-transformer)))
|
||||
|
||||
query-schema
|
||||
query-params
|
||||
main-transformer)))
|
||||
|
||||
(catch Exception e
|
||||
(alog/warn ::validation-error :error e)
|
||||
(throw (ex-info (->> (-> e
|
||||
(ex-data )
|
||||
(ex-data)
|
||||
:data
|
||||
:explain
|
||||
(me/humanize {:errors (assoc me/default-errors
|
||||
@@ -237,30 +255,30 @@
|
||||
(and (:params request) params-schema)
|
||||
(assoc :params
|
||||
(mc/decode
|
||||
params-schema
|
||||
(:params request)
|
||||
main-transformer))
|
||||
params-schema
|
||||
(:params request)
|
||||
main-transformer))
|
||||
|
||||
(and (:route-params request) route-schema)
|
||||
(assoc :route-params
|
||||
(mc/decode
|
||||
route-schema
|
||||
(:route-params request)
|
||||
main-transformer))
|
||||
route-schema
|
||||
(:route-params request)
|
||||
main-transformer))
|
||||
|
||||
(and form-schema form-params)
|
||||
(assoc :form-params
|
||||
(mc/decode
|
||||
form-schema
|
||||
form-params
|
||||
main-transformer))
|
||||
form-schema
|
||||
form-params
|
||||
main-transformer))
|
||||
|
||||
(and query-schema query-params)
|
||||
(assoc :query-params
|
||||
(mc/decode
|
||||
query-schema
|
||||
query-params
|
||||
main-transformer)))]
|
||||
query-schema
|
||||
query-params
|
||||
main-transformer)))]
|
||||
request))
|
||||
|
||||
(defn wrap-schema-decode [handler & {:keys [form-schema query-schema route-schema params-schema]}]
|
||||
@@ -275,8 +293,7 @@
|
||||
(into [:enum {:decode/string #(if (keyword? %)
|
||||
%
|
||||
(when (not-empty %)
|
||||
(keyword n %))
|
||||
)}]
|
||||
(keyword n %)))}]
|
||||
(for [{:db/keys [ident]} (all-schema)
|
||||
:when (= n (namespace ident))]
|
||||
ident)))
|
||||
@@ -301,41 +318,39 @@
|
||||
(defn wrap-form-4xx-2 [handler form-handler]
|
||||
(fn [request]
|
||||
(try+
|
||||
(handler request)
|
||||
(catch [:type :schema-validation] e
|
||||
|
||||
(let [humanized (-> e :error :explain (me/humanize {:errors (assoc me/default-errors
|
||||
::mc/missing-key {:error/message {:en "required"}})}))
|
||||
errors (map
|
||||
(fn [e]
|
||||
{:path (:in e)
|
||||
:message (get-in humanized (:in e))})
|
||||
(:errors (:explain (:error e))))]
|
||||
(alog/warn ::form-4xx :errors errors)
|
||||
(form-handler (assoc request
|
||||
:form-params (:decoded e)
|
||||
:field-validation-errors errors
|
||||
:form-errors humanized)))
|
||||
#_(html-response [:span.error-content.text-red-500 (:message &throw-context)]
|
||||
:status 400))
|
||||
(catch [:type :field-validation] e
|
||||
(form-handler (assoc request
|
||||
:form-params (:form e)
|
||||
:form-errors (:form-errors e))))
|
||||
(catch [:type :form-validation] e
|
||||
(form-handler (assoc request
|
||||
:form-params (:form e)
|
||||
:form-validation-errors (:form-validation-errors e)
|
||||
:form-errors {:errors (:form-validation-errors e)}))))))
|
||||
(handler request)
|
||||
(catch [:type :schema-validation] e
|
||||
(let [humanized (-> e :error :explain (me/humanize {:errors (assoc me/default-errors
|
||||
::mc/missing-key {:error/message {:en "required"}})}))
|
||||
errors (map
|
||||
(fn [e]
|
||||
{:path (:in e)
|
||||
:message (get-in humanized (:in e))})
|
||||
(:errors (:explain (:error e))))]
|
||||
(alog/warn ::form-4xx :errors errors)
|
||||
(form-handler (assoc request
|
||||
:form-params (:decoded e)
|
||||
:field-validation-errors errors
|
||||
:form-errors humanized)))
|
||||
#_(html-response [:span.error-content.text-red-500 (:message &throw-context)]
|
||||
:status 400))
|
||||
(catch [:type :field-validation] e
|
||||
(form-handler (assoc request
|
||||
:form-params (:form e)
|
||||
:form-errors (:form-errors e))))
|
||||
(catch [:type :form-validation] e
|
||||
(form-handler (assoc request
|
||||
:form-params (:form e)
|
||||
:form-validation-errors (:form-validation-errors e)
|
||||
:form-errors {:errors (:form-validation-errors e)}))))))
|
||||
|
||||
|
||||
(defn apply-middleware-to-all-handlers [key->handler f]
|
||||
(->> key->handler
|
||||
(reduce
|
||||
(fn [key-handler [k v]]
|
||||
(assoc key-handler k (f v)))
|
||||
key->handler)
|
||||
))
|
||||
(fn [key-handler [k v]]
|
||||
(assoc key-handler k (f v)))
|
||||
key->handler)))
|
||||
|
||||
(defn path->name2 [k & rest]
|
||||
(let [k->n (fn [k]
|
||||
@@ -354,10 +369,10 @@
|
||||
(defn wrap-entity [handler path read]
|
||||
(fn wrap-entity-request [request]
|
||||
(let [entity (some->>
|
||||
(get-in request path)
|
||||
(#(if (string? %) (Long/parseLong %) %))
|
||||
(dc/pull (dc/db conn) read ))]
|
||||
(get-in request path)
|
||||
(#(if (string? %) (Long/parseLong %) %))
|
||||
(dc/pull (dc/db conn) read))]
|
||||
(handler (if entity
|
||||
(assoc request
|
||||
:entity entity)
|
||||
request)))))
|
||||
request)))))
|
||||
344
src/clj/user.clj
344
src/clj/user.clj
@@ -1,10 +1,13 @@
|
||||
(ns user
|
||||
(ns user
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[clojure.tools.namespace.repl :refer [set-refresh-dirs refresh]]
|
||||
[auto-ap.datomic :refer [conn pull-attr random-tempid]]
|
||||
[auto-ap.ledger :as l ]
|
||||
[auto-ap.ledger :as l]
|
||||
[clj-http.core :as http]
|
||||
[clj-http.client :as client]
|
||||
[figwheel.main.api]
|
||||
[hawk.core]
|
||||
[auto-ap.server]
|
||||
[auto-ap.time :as atime]
|
||||
[auto-ap.utils :refer [by]]
|
||||
@@ -29,6 +32,7 @@
|
||||
(:import
|
||||
(org.apache.commons.io.input BOMInputStream)))
|
||||
|
||||
|
||||
(defn println-event [item]
|
||||
(printf "%s: %s - %s:%s by %s\n"
|
||||
(str (c/to-date-time (:mulog/timestamp item)))
|
||||
@@ -36,23 +40,22 @@
|
||||
(if (:mulog/duration item)
|
||||
(str " " (int (/ (:mulog/duration item) 1000000)) "ms")
|
||||
"")
|
||||
(:user-name item)
|
||||
)
|
||||
(:user-name item))
|
||||
(println (reduce
|
||||
(fn [acc [k v]]
|
||||
(assoc acc k v))
|
||||
{}
|
||||
(dissoc
|
||||
item
|
||||
:user)))
|
||||
#_(puget/cprint (reduce
|
||||
(fn [acc [k v]]
|
||||
(assoc acc k v))
|
||||
{}
|
||||
(dissoc
|
||||
item
|
||||
:user))
|
||||
{:seq-limit 10})
|
||||
(dissoc
|
||||
item
|
||||
:user)))
|
||||
#_(puget/cprint (reduce
|
||||
(fn [acc [k v]]
|
||||
(assoc acc k v))
|
||||
{}
|
||||
(dissoc
|
||||
item
|
||||
:user))
|
||||
{:seq-limit 10})
|
||||
(println))
|
||||
|
||||
|
||||
@@ -70,8 +73,7 @@
|
||||
(publish [_ buffer]
|
||||
;; items are pairs [offset <item>]
|
||||
(doseq [item (transform (map second (rb/items buffer)))]
|
||||
(println-event item)
|
||||
)
|
||||
(println-event item))
|
||||
(flush)
|
||||
(rb/clear buffer)))
|
||||
|
||||
@@ -91,23 +93,23 @@
|
||||
(defn load-accounts [conn]
|
||||
(let [[header & rows] (-> "master-account-list.csv" (io/resource) io/input-stream (BOMInputStream.) (io/reader) csv/read-csv)
|
||||
code->existing-account (by :account/numeric-code (map first (dc/q {:find ['(pull ?e [:account/numeric-code
|
||||
:db/id])]
|
||||
:db/id])]
|
||||
:in ['$]
|
||||
:where ['[?e :account/name]]}
|
||||
(dc/db conn))))
|
||||
(dc/db conn))))
|
||||
|
||||
also-merge-txes (fn [also-merge old-account-id]
|
||||
(if old-account-id
|
||||
(if old-account-id
|
||||
(let [[sunset-account]
|
||||
(first (dc/q {:find ['?a ]
|
||||
:in ['$ '?ac ]
|
||||
(first (dc/q {:find ['?a]
|
||||
:in ['$ '?ac]
|
||||
:where ['[?a :account/numeric-code ?ac]]}
|
||||
(dc/db conn) also-merge))]
|
||||
(into (mapv
|
||||
(fn [[entity id _]]
|
||||
[:db/add entity id old-account-id])
|
||||
(dc/q {:find ['?e '?id '?a ]
|
||||
:in ['$ '?ac ]
|
||||
(dc/q {:find ['?e '?id '?a]
|
||||
:in ['$ '?ac]
|
||||
:where ['[?a :account/numeric-code ?ac]
|
||||
'[?e ?at ?a]
|
||||
'[?at :db/ident ?id]]}
|
||||
@@ -122,7 +124,7 @@
|
||||
(into {} (map vector header r))))
|
||||
(map (fn parse-map [r]
|
||||
{:old-account-id (:db/id (code->existing-account
|
||||
(or
|
||||
(or
|
||||
(if (= (get r "IOL Account #")
|
||||
"NEW")
|
||||
nil
|
||||
@@ -157,8 +159,7 @@
|
||||
(if also-merge
|
||||
(into tx
|
||||
(also-merge-txes also-merge old-account-id))
|
||||
tx)
|
||||
))))
|
||||
tx)))))
|
||||
|
||||
|
||||
conj
|
||||
@@ -168,7 +169,7 @@
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn find-bad-accounts []
|
||||
(set (map second (dc/q {:find ['(pull ?x [*]) '?z]
|
||||
(set (map second (dc/q {:find ['(pull ?x [*]) '?z]
|
||||
:in ['$]
|
||||
:where ['[?e :account/numeric-code ?z]
|
||||
'[(<= ?z 9999)]
|
||||
@@ -177,32 +178,31 @@
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn delete-4-digit-accounts []
|
||||
@(dc/transact conn
|
||||
(transduce
|
||||
@(dc/transact conn
|
||||
(transduce
|
||||
(comp
|
||||
(map first)
|
||||
(map (fn [old-account-id]
|
||||
[:db/retractEntity old-account-id])))
|
||||
(map first)
|
||||
(map (fn [old-account-id]
|
||||
[:db/retractEntity old-account-id])))
|
||||
conj
|
||||
[]
|
||||
(dc/q {:find ['?e]
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$]
|
||||
:where ['[?e :account/numeric-code ?z]
|
||||
'[(<= ?z 9999)]]}
|
||||
(dc/db conn))))
|
||||
)
|
||||
(dc/db conn)))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn find-conflicting-accounts []
|
||||
(filter
|
||||
(filter
|
||||
(fn [[_ v]]
|
||||
(> (count v) 1))
|
||||
(reduce
|
||||
(reduce
|
||||
(fn [acc [e z]]
|
||||
(update acc z conj e))
|
||||
{}
|
||||
(dc/q {:find ['?e '?z]
|
||||
(dc/q {:find ['?e '?z]
|
||||
:in ['$]
|
||||
:where ['[?e :account/numeric-code ?z]]}
|
||||
(dc/db conn)))))
|
||||
@@ -214,31 +214,29 @@
|
||||
:in ['$ '?z]
|
||||
:where [['?e :client/code '?z]]}
|
||||
(dc/db conn) customer)))
|
||||
_ (println client-id)
|
||||
code->existing-account (by :account/numeric-code (map first (dc/q {:find ['(pull ?e [:account/numeric-code
|
||||
{:account/applicability [:db/ident]}
|
||||
:db/id])]
|
||||
:db/id])]
|
||||
:in ['$]
|
||||
:where ['[?e :account/name]]}
|
||||
(dc/db conn))))
|
||||
(dc/db conn))))
|
||||
|
||||
existing-account-overrides (dc/q {:find ['?e]
|
||||
:in ['$ '?client-id]
|
||||
:where [['?e :account-client-override/client '?client-id]]}
|
||||
(dc/db conn) client-id)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_ (when-let [bad-rows (seq (->> rows
|
||||
(group-by (fn [[_ account]]
|
||||
account))
|
||||
vals
|
||||
(filter #(> (count %) 1))
|
||||
(filter (fn [duplicates]
|
||||
(apply not= (map rest duplicates))))
|
||||
#_(map (fn [[[_ account]]]
|
||||
account))
|
||||
))]
|
||||
(group-by (fn [[_ account]]
|
||||
account))
|
||||
vals
|
||||
(filter #(> (count %) 1))
|
||||
(filter (fn [duplicates]
|
||||
(apply not= (map rest duplicates))))
|
||||
#_(map (fn [[[_ account]]]
|
||||
account))))]
|
||||
(throw (Exception. (str "These accounts are duplicated:" (str bad-rows)))))
|
||||
rows (vec (set (map rest rows)))
|
||||
|
||||
@@ -256,8 +254,7 @@
|
||||
(:db/ident (:account/applicability existing)))
|
||||
(and (not-empty override-name)
|
||||
(not-empty account-name)
|
||||
(not= override-name account-name)
|
||||
)))
|
||||
(not= override-name account-name))))
|
||||
[{:db/id (:db/id existing)
|
||||
:account/client-overrides [{:account-client-override/client client-id
|
||||
:account-client-override/name (or (not-empty override-name)
|
||||
@@ -284,34 +281,30 @@
|
||||
[:db/retractEntity x])
|
||||
existing-account-overrides)
|
||||
rows)]
|
||||
|
||||
|
||||
txes
|
||||
#_@(d/transact conn txes)))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn fix-transactions-without-locations [client-code location]
|
||||
(->>
|
||||
(dc/q {:find ['(pull ?e [*])]
|
||||
:in ['$ '?client-code]
|
||||
:where ['[?e :transaction/accounts ?ta]
|
||||
'[?e :transaction/matched-rule]
|
||||
'[?e :transaction/approval-status :transaction-approval-status/approved]
|
||||
'(not [?ta :transaction-account/location])
|
||||
'[?e :transaction/client ?c]
|
||||
'[?c :client/code ?client-code]
|
||||
]}
|
||||
(dc/db conn) client-code)
|
||||
(->>
|
||||
(dc/q {:find ['(pull ?e [*])]
|
||||
:in ['$ '?client-code]
|
||||
:where ['[?e :transaction/accounts ?ta]
|
||||
'[?e :transaction/matched-rule]
|
||||
'[?e :transaction/approval-status :transaction-approval-status/approved]
|
||||
'(not [?ta :transaction-account/location])
|
||||
'[?e :transaction/client ?c]
|
||||
'[?c :client/code ?client-code]]}
|
||||
(dc/db conn) client-code)
|
||||
(mapcat
|
||||
(fn [[{:transaction/keys [accounts]}]]
|
||||
(mapv
|
||||
(fn [a]
|
||||
{:db/id (:db/id a)
|
||||
:transaction-account/location location}
|
||||
)
|
||||
accounts)
|
||||
)
|
||||
)
|
||||
:transaction-account/location location})
|
||||
accounts)))
|
||||
vec))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
@@ -323,21 +316,21 @@
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn entity-history [i]
|
||||
(vec (sort-by first (dc/q
|
||||
{:find ['?tx '?z '?v ]
|
||||
:in ['?i '$]
|
||||
:where ['[?i ?a ?v ?tx ?ad]
|
||||
'[?a :db/ident ?z]
|
||||
'[(= ?ad true)]]}
|
||||
i (dc/history (dc/db conn))))))
|
||||
{:find ['?tx '?z '?v]
|
||||
:in ['?i '$]
|
||||
:where ['[?i ?a ?v ?tx ?ad]
|
||||
'[?a :db/ident ?z]
|
||||
'[(= ?ad true)]]}
|
||||
i (dc/history (dc/db conn))))))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn entity-history-with-revert [i]
|
||||
(vec (sort-by first (dc/q
|
||||
{:find ['?tx '?z '?v '?ad ]
|
||||
:in ['?i '$]
|
||||
:where ['[?i ?a ?v ?tx ?ad]
|
||||
'[?a :db/ident ?z]]}
|
||||
i (dc/history (dc/db conn))))))
|
||||
{:find ['?tx '?z '?v '?ad]
|
||||
:in ['?i '$]
|
||||
:where ['[?i ?a ?v ?tx ?ad]
|
||||
'[?a :db/ident ?z]]}
|
||||
i (dc/history (dc/db conn))))))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn tx-detail [i]
|
||||
@@ -357,47 +350,71 @@
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn start-db []
|
||||
(mu/start-publisher! {:type :dev})
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.datomic/conn })))
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.datomic/conn})))
|
||||
|
||||
|
||||
(defn- auto-reset-handler [ctx event]
|
||||
(require 'figwheel.main.api)
|
||||
(binding [*ns* *ns*]
|
||||
(clojure.tools.namespace.repl/refresh)
|
||||
ctx))
|
||||
|
||||
(defn auto-reset
|
||||
"Automatically reset the system when a Clojure or edn file is changed in
|
||||
`src` or `resources`."
|
||||
[]
|
||||
(println "starting auto reset")
|
||||
(hawk.core/watch! [{:paths ["src/"]
|
||||
:handler auto-reset-handler}]))
|
||||
|
||||
(defn start-http []
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.server/port #'auto-ap.server/jetty})))
|
||||
|
||||
|
||||
(defn start-dev []
|
||||
(set-refresh-dirs "src")
|
||||
(start-db)
|
||||
(start-http)
|
||||
(auto-reset))
|
||||
|
||||
#_(defn start-search []
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.graphql.vendors/indexer #'auto-ap.graphql.accounts/indexer})))
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.graphql.vendors/indexer #'auto-ap.graphql.accounts/indexer})))
|
||||
|
||||
(defn restart-db []
|
||||
#_(require 'datomic.dev-local)
|
||||
#_(datomic.dev-local/release-db {:system "dev" :db-name "prod-migration"})
|
||||
(mount.core/stop (mount.core/only #{#'auto-ap.datomic/conn }))
|
||||
(mount.core/stop (mount.core/only #{#'auto-ap.datomic/conn}))
|
||||
(start-db))
|
||||
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn spit-csv [columns data ]
|
||||
(defn spit-csv [columns data]
|
||||
(csv/write-csv *out*
|
||||
(into [(map name columns)]
|
||||
(for [r data]
|
||||
((apply juxt columns) r )))))
|
||||
((apply juxt columns) r)))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn find-queries [words]
|
||||
(let [obj (s3/list-objects-v2 :bucket-name (:data-bucket env)
|
||||
:prefix (str "queries/"))
|
||||
:prefix (str "queries/"))
|
||||
concurrent 30
|
||||
output-chan (async/chan)]
|
||||
(async/pipeline-blocking concurrent
|
||||
output-chan
|
||||
(comp
|
||||
(map #(do
|
||||
[(:key %)
|
||||
(str (slurp (:object-content (s3/get-object
|
||||
:bucket-name (:data-bucket env)
|
||||
:key (:key %)))))]))
|
||||
|
||||
(filter #(->> words
|
||||
(every? (fn [w] (str/includes? (second %) w)))))
|
||||
(map first)
|
||||
(map #(str/replace % #"queries/" ""))
|
||||
)
|
||||
(comp
|
||||
(map #(do
|
||||
[(:key %)
|
||||
(str (slurp (:object-content (s3/get-object
|
||||
:bucket-name (:data-bucket env)
|
||||
:key (:key %)))))]))
|
||||
|
||||
(filter #(->> words
|
||||
(every? (fn [w] (str/includes? (second %) w)))))
|
||||
(map first)
|
||||
(map #(str/replace % #"queries/" "")))
|
||||
(async/to-chan! (:object-summaries obj))
|
||||
true
|
||||
(fn [e]
|
||||
@@ -408,13 +425,13 @@
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn upsert-invoice-amounts [tsv]
|
||||
(let [data (with-open [reader (io/reader (char-array tsv))]
|
||||
(doall (csv/read-csv reader :separator \tab)))
|
||||
(doall (csv/read-csv reader :separator \tab)))
|
||||
db (dc/db conn)
|
||||
i->invoice-id (fn [i]
|
||||
(try (Long/parseLong i)
|
||||
(catch Exception e
|
||||
(:db/id (dc/pull db '[:db/id]
|
||||
[:invoice/original-id (Long/parseLong (first (str/split i #"-")))])))))
|
||||
i->invoice-id (fn [i]
|
||||
(try (Long/parseLong i)
|
||||
(catch Exception e
|
||||
(:db/id (dc/pull db '[:db/id]
|
||||
[:invoice/original-id (Long/parseLong (first (str/split i #"-")))])))))
|
||||
invoice-totals (->> data
|
||||
(drop 1)
|
||||
(group-by first)
|
||||
@@ -423,13 +440,11 @@
|
||||
(reduce + 0.0
|
||||
(->> values
|
||||
(map (fn [[_ _ _ _ amount]]
|
||||
(- (Double/parseDouble amount))))))
|
||||
]))
|
||||
(- (Double/parseDouble amount))))))]))
|
||||
(into {}))]
|
||||
(->>
|
||||
(->>
|
||||
(for [[i invoice-expense-account-id target-account target-date amount _ location] (drop 1 data)
|
||||
:let [
|
||||
invoice-id (i->invoice-id i)
|
||||
:let [invoice-id (i->invoice-id i)
|
||||
|
||||
invoice (dc/pull db '[FILL_IN] invoice-id)
|
||||
current-total (:invoice/total invoice)
|
||||
@@ -441,34 +456,32 @@
|
||||
(:db/id (first (:invoice/expense-accounts invoice)))
|
||||
(random-tempid))
|
||||
invoice-expense-account (when-not new-account?
|
||||
(dc/pull db '[FILL_IN]invoice-expense-account-id))
|
||||
current-account-id (:db/id (:invoice-expense-account/account invoice-expense-account))
|
||||
(dc/pull db '[FILL_IN] invoice-expense-account-id))
|
||||
current-account-id (:db/id (:invoice-expense-account/account invoice-expense-account))
|
||||
target-account-id (Long/parseLong (str/trim target-account))
|
||||
|
||||
target-date (clj-time.coerce/to-date (atime/parse target-date atime/normal-date))
|
||||
current-date (:invoice/date invoice)
|
||||
|
||||
|
||||
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
||||
|
||||
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
||||
target-expense-account-amount (- (Double/parseDouble amount))
|
||||
|
||||
|
||||
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
||||
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
||||
target-expense-account-location location
|
||||
|
||||
|
||||
[[_ _ invoice-payment]] (vec (dc/q
|
||||
'[:find ?p ?a ?ip
|
||||
:in $ ?i
|
||||
:where [?ip :invoice-payment/invoice ?i]
|
||||
[?ip :invoice-payment/amount ?a]
|
||||
[?ip :invoice-payment/payment ?p]
|
||||
]
|
||||
db invoice-id))]
|
||||
'[:find ?p ?a ?ip
|
||||
:in $ ?i
|
||||
:where [?ip :invoice-payment/invoice ?i]
|
||||
[?ip :invoice-payment/amount ?a]
|
||||
[?ip :invoice-payment/payment ?p]]
|
||||
db invoice-id))]
|
||||
:when current-total]
|
||||
|
||||
[
|
||||
(when (not (auto-ap.utils/dollars= current-total target-total))
|
||||
[(when (not (auto-ap.utils/dollars= current-total target-total))
|
||||
{:db/id invoice-id
|
||||
:invoice/total target-total})
|
||||
|
||||
@@ -486,7 +499,7 @@
|
||||
[:db/retractEntity invoice-payment])
|
||||
|
||||
(when (or new-account?
|
||||
(not (auto-ap.utils/dollars= current-expense-account-amount target-expense-account-amount)))
|
||||
(not (auto-ap.utils/dollars= current-expense-account-amount target-expense-account-amount)))
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/amount target-expense-account-amount})
|
||||
|
||||
@@ -495,7 +508,7 @@
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/location target-expense-account-location})
|
||||
|
||||
(when (not= current-account-id target-account-id )
|
||||
(when (not= current-account-id target-account-id)
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/account target-account-id})])
|
||||
(mapcat identity)
|
||||
@@ -506,18 +519,18 @@
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn get-schema [prefix]
|
||||
(->> (dc/q '[:find ?i
|
||||
:in $ ?p
|
||||
:where [_ :db/ident ?i]
|
||||
[(namespace ?i) ?p]] (dc/db auto-ap.datomic/conn) prefix)
|
||||
:in $ ?p
|
||||
:where [_ :db/ident ?i]
|
||||
[(namespace ?i) ?p]] (dc/db auto-ap.datomic/conn) prefix)
|
||||
(mapcat identity)
|
||||
vec))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn get-idents []
|
||||
(->> (dc/q '[:find ?i
|
||||
:in $
|
||||
:where [_ :db/ident ?i]]
|
||||
(dc/db conn) )
|
||||
:in $
|
||||
:where [_ :db/ident ?i]]
|
||||
(dc/db conn))
|
||||
(mapcat identity)
|
||||
(map str)
|
||||
(sort)
|
||||
@@ -532,45 +545,45 @@
|
||||
(defn sample-ledger-import
|
||||
([client-code]
|
||||
(sample-ledger-import client-code 10))
|
||||
([client-code n]
|
||||
([client-code n]
|
||||
(let [client-location (ffirst (d/q '[:find ?l :in $ ?c :where [?c :client/locations ?l]] (dc/db conn) [:client/code client-code]))]
|
||||
(clojure.data.csv/write-csv
|
||||
*out*
|
||||
(for [n (range n)
|
||||
:let [v (rand-nth (map first (d/q '[:find ?vn :where [_ :vendor/name ?vn]] (dc/db conn))))
|
||||
[{a-1 :account/numeric-code a-1-location :account/location}
|
||||
{a-2 :account/numeric-code a-2-location :account/location}] (->> (d/q '[:find (pull ?a [:account/numeric-code :account/location]) :where [?a :account/numeric-code]]
|
||||
(dc/db conn))
|
||||
(map first)
|
||||
(shuffle)
|
||||
(take 2))
|
||||
amount (rand-int 2000)
|
||||
d (-> (t/now)
|
||||
(t/minus (t/days (rand-int 60)))
|
||||
(atime/unparse atime/normal-date))
|
||||
id (rand-int 100000)]
|
||||
a [[(str id) client-code "synthetic" v d a-1 (or a-1-location client-location) 0 amount ]
|
||||
[(str id) client-code "synthetic" v d a-2 (or a-2-location client-location) amount 0]]]
|
||||
a)
|
||||
:separator \tab))))
|
||||
*out*
|
||||
(for [n (range n)
|
||||
:let [v (rand-nth (map first (d/q '[:find ?vn :where [_ :vendor/name ?vn]] (dc/db conn))))
|
||||
[{a-1 :account/numeric-code a-1-location :account/location}
|
||||
{a-2 :account/numeric-code a-2-location :account/location}] (->> (d/q '[:find (pull ?a [:account/numeric-code :account/location]) :where [?a :account/numeric-code]]
|
||||
(dc/db conn))
|
||||
(map first)
|
||||
(shuffle)
|
||||
(take 2))
|
||||
amount (rand-int 2000)
|
||||
d (-> (t/now)
|
||||
(t/minus (t/days (rand-int 60)))
|
||||
(atime/unparse atime/normal-date))
|
||||
id (rand-int 100000)]
|
||||
a [[(str id) client-code "synthetic" v d a-1 (or a-1-location client-location) 0 amount]
|
||||
[(str id) client-code "synthetic" v d a-2 (or a-2-location client-location) amount 0]]]
|
||||
a)
|
||||
:separator \tab))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn sample-manual-yodlee
|
||||
([client-code]
|
||||
(sample-ledger-import client-code 10))
|
||||
([client-code n]
|
||||
([client-code n]
|
||||
(let [bank-accounts (map first (d/q '[:find ?bac :in $ ?c :where [?c :client/bank-accounts ?b] [?b :bank-account/code ?bac]] (dc/db conn) [:client/code client-code]))]
|
||||
(clojure.data.csv/write-csv
|
||||
*out*
|
||||
(for [n (range n)
|
||||
:let [amount (rand-int 2000)
|
||||
d (-> (t/now)
|
||||
(t/minus (t/days (rand-int 60)))
|
||||
(atime/unparse atime/normal-date))
|
||||
id (rand-int 100000)]]
|
||||
["posted" d (str "Random Description - " id) "Travel" nil nil (- amount) nil nil nil nil nil (rand-nth bank-accounts) client-code])
|
||||
:separator \tab))))
|
||||
*out*
|
||||
(for [n (range n)
|
||||
:let [amount (rand-int 2000)
|
||||
d (-> (t/now)
|
||||
(t/minus (t/days (rand-int 60)))
|
||||
(atime/unparse atime/normal-date))
|
||||
id (rand-int 100000)]]
|
||||
["posted" d (str "Random Description - " id) "Travel" nil nil (- amount) nil nil nil nil nil (rand-nth bank-accounts) client-code])
|
||||
:separator \tab))))
|
||||
|
||||
|
||||
|
||||
@@ -580,8 +593,7 @@
|
||||
:in $
|
||||
:where [?i :invoice/invoice-number]
|
||||
(not [?i :invoice/status :invoice-status/voided])]
|
||||
:args [
|
||||
(dc/db conn)]})
|
||||
:args [(dc/db conn)]})
|
||||
(map first)
|
||||
(partition-all 1000))]
|
||||
(print ".")
|
||||
@@ -627,4 +639,4 @@
|
||||
(print ".")
|
||||
@(dc/transact auto-ap.datomic/conn n)))
|
||||
|
||||
|
||||
|
||||
@@ -31,3 +31,87 @@
|
||||
[?c :client/code ?cd]
|
||||
[?c :client/locations ?l]]
|
||||
(dc/db conn))
|
||||
|
||||
|
||||
(init-repl)
|
||||
|
||||
(seq (dc/q
|
||||
'[:find ?a ?n ?n2
|
||||
:where [?a :account/name ?n]
|
||||
[?a :account/numeric-code ?n2]
|
||||
(not [?a :account/code])]
|
||||
(dc/db conn)))
|
||||
|
||||
(dc/q
|
||||
'[:find (pull ?a [* {:account/applicability [:db/ident] :account/default-allowance [*]}])
|
||||
:where [?a :account/numeric-code 34090]]
|
||||
(dc/db conn))
|
||||
|
||||
|
||||
(dc/q
|
||||
'[:find ?a
|
||||
:where [?a :account/numeric-code ?nc]
|
||||
(not [?a :account/default-allowance])]
|
||||
(dc/since (dc/db conn) #inst "2023-02-01"))
|
||||
|
||||
@(dc/transact conn
|
||||
(->>
|
||||
(dc/q
|
||||
'[:find ?a
|
||||
:where [?a :account/numeric-code ?nc]
|
||||
(not [?a :account/default-allowance])]
|
||||
(dc/since (dc/db conn) #inst "2023-02-01"))
|
||||
(map (fn [[a]]
|
||||
{:db/id a
|
||||
:account/default-allowance :allowance/allowed})))
|
||||
|
||||
)
|
||||
|
||||
|
||||
(dc/q '[:find (pull ?l [*])
|
||||
:in $ ?a
|
||||
:where [?a :invoice/client]
|
||||
[?l :journal-entry/original-entity ?a]]
|
||||
(dc/db conn)
|
||||
17592316421929)
|
||||
|
||||
(dc/pull (dc/db conn) '[*] 17592316421929)
|
||||
|
||||
(entity-history 17592316421929)
|
||||
|
||||
|
||||
(dc/q '[:find (pull ?l [*])
|
||||
:in $ ?a
|
||||
:where [?a :invoice/client]
|
||||
[?l :journal-entry/original-entity ?a]]
|
||||
(dc/db conn)
|
||||
17592316421929)
|
||||
|
||||
|
||||
;; Find journal entries that have been divorced from the original entity
|
||||
@(dc/transact auto-ap.datomic/conn
|
||||
(->>
|
||||
(dc/q '[:find ?l
|
||||
:in $ $$ $$$
|
||||
:where [$$ ?l :journal-entry/amount]
|
||||
(not [$ ?l :journal-entry/external-id])
|
||||
[$ ?l :journal-entry/source "invoice"]
|
||||
(not [$ ?l :journal-entry/original-entity])
|
||||
[$ ?l :journal-entry/client ?c]
|
||||
[$ ?c :client/code ?cd]
|
||||
[$$$ ?l :journal-entry/original-entity _ ?tx false]]
|
||||
(dc/db conn)
|
||||
(dc/since (dc/db conn) #inst "2024-02-04")
|
||||
(dc/history (dc/db conn)))
|
||||
(map (fn [[jl]]
|
||||
[:db/retractEntity jl]))
|
||||
seq))
|
||||
|
||||
|
||||
(entity-history 13194269907490)
|
||||
|
||||
(user/tx-detail 13194269907766)
|
||||
|
||||
(dc/tx-range (dc/log conn)
|
||||
13194269907490
|
||||
13194269907490)
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
"needs-activation/" :needs-activation
|
||||
"needs-activation" :needs-activation
|
||||
"payments/" :payments
|
||||
"admin/" {"clients/" {"" :admin-clients
|
||||
[:id] {"" :admin-specific-client
|
||||
"/bank-accounts/" {[:bank-account] :admin-specific-bank-account}}}
|
||||
"vendors" :admin-vendors}
|
||||
"admin/" { "vendors" :admin-vendors}
|
||||
"invoices/" {"" :invoices
|
||||
"import" :import-invoices
|
||||
"unpaid" :unpaid-invoices
|
||||
@@ -22,8 +19,6 @@
|
||||
"requires-feedback" :requires-feedback-transactions
|
||||
"excluded" :excluded-transactions}
|
||||
"reports/" {"" :reports}
|
||||
"plaid" :plaid
|
||||
"yodlee2" :yodlee2
|
||||
"ledger/" {"" :ledger
|
||||
"profit-and-loss" :profit-and-loss
|
||||
"cash-flows" :cash-flows
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
(ns auto-ap.permissions)
|
||||
|
||||
;; TODO after getting rid of cljs, use malli schemas to decode this
|
||||
(defn get-client-id [client]
|
||||
(cond (nat-int? client)
|
||||
client
|
||||
|
||||
(:db/id client)
|
||||
(:db/id client)
|
||||
|
||||
:else
|
||||
nil))
|
||||
|
||||
(defn can? [user {:keys [client subject activity]}]
|
||||
(let [role (or (:user/role user) (:role user) user)]
|
||||
(println "ROLE IS" role)
|
||||
(let [role (or (:user/role user) (:role user) user)
|
||||
client-id (get-client-id client)]
|
||||
(cond (#{:user-role/admin "admin"} role)
|
||||
true
|
||||
|
||||
(and client-id (not (get (into #{} (map :db/id (:clients user))) client-id)))
|
||||
false
|
||||
|
||||
(#{:user-role/power-user "power-user"} role)
|
||||
(cond
|
||||
(#{:invoice-page :payment-page :my-company-page :transaction-page :ledger-page} subject)
|
||||
@@ -49,6 +63,9 @@
|
||||
(= [:vendor :edit] [subject activity])
|
||||
true
|
||||
|
||||
(= [:signature :edit] [subject activity])
|
||||
true
|
||||
|
||||
:else false)
|
||||
|
||||
:else
|
||||
|
||||
19
src/cljc/auto_ap/routes/admin/clients.cljc
Normal file
19
src/cljc/auto_ap/routes/admin/clients.cljc
Normal file
@@ -0,0 +1,19 @@
|
||||
(ns auto-ap.routes.admin.clients)
|
||||
(def routes {"" {:get ::page
|
||||
:put ::save
|
||||
:post ::save}
|
||||
"/table" ::table
|
||||
|
||||
"/navigate" ::navigate
|
||||
"/bank-accounts/sort" ::sort-bank-accounts
|
||||
"/discard" ::discard
|
||||
"/square-locations" ::refresh-square-locations
|
||||
|
||||
"/location/new" ::new-location
|
||||
"/match/new" ::new-match
|
||||
"/location-match/new" ::new-location-match
|
||||
"/email-contact/new" ::new-email-contact
|
||||
"/feature-flag/new" ::new-feature-flag
|
||||
"/new" {:get ::new-dialog}
|
||||
["/" [#"\d+" :db/id] "/sales-powerquery"] ::biweekly-sales-powerquery
|
||||
["/" [#"\d+" :db/id] "/edit"] ::edit-dialog})
|
||||
@@ -10,9 +10,10 @@
|
||||
"/account/typeahead" ::account-typeahead
|
||||
"/test" ::test
|
||||
"/new" {:get ::new-dialog}
|
||||
"/navigate" ::navigate
|
||||
["/" [#"\d+" :db/id] "/edit"] ::edit-dialog
|
||||
["/" [#"\d+" :db/id] "/delete"] ::delete
|
||||
["/" [#"\d+" :db/id] "/run"] {:get ::execute-dialog
|
||||
:post ::execute}
|
||||
:post ::execute}
|
||||
"/check-badges" ::check-badges
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"/account-override" ::new-account-override
|
||||
"/account-typeahead" ::account-typeahead
|
||||
"/validate" ::validate
|
||||
"/navigat" ::navigate
|
||||
"/new" {:get ::new}
|
||||
"/merge" {:get ::merge
|
||||
:put ::merge-submit}
|
||||
|
||||
@@ -30,16 +30,6 @@
|
||||
[:i {:class icon-class}]])
|
||||
[:span {:class "name"} label]]])
|
||||
|
||||
(defn menu-item [{:keys [label route test-route active-route icon-class icon-style]}]
|
||||
[:p.menu-item
|
||||
[:a.item {:href (bidi/path-for all-client-visible-routes route)
|
||||
:class (when (test-route active-route) "is-active")}
|
||||
(if icon-style
|
||||
[:span {:class icon-class :style icon-style}]
|
||||
[:span {:class "icon"}
|
||||
[:i {:class icon-class}]])
|
||||
[:span {:class "name"} label]]])
|
||||
|
||||
(defn company-side-bar-impl [active-route]
|
||||
[:div
|
||||
(menu-item {:label "Reports"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
||||
[auto-ap.routes.admin.import-batch :as ib-routes]
|
||||
[auto-ap.routes.admin.vendors :as v-routes]
|
||||
[auto-ap.routes.admin.clients :as ac-routes]
|
||||
[auto-ap.routes.admin.transaction-rules :as tr-routes]))
|
||||
|
||||
(def routes {"impersonate" :impersonate
|
||||
@@ -15,6 +16,7 @@
|
||||
"/update" {:patch :invoice-glimpse-update-textract-invoice}}}}}
|
||||
"account" {"/search" {:get :account-search}}
|
||||
"admin" {"" :auto-ap.routes.admin/page
|
||||
"/client" ac-routes/routes
|
||||
"/history" {"" :admin-history
|
||||
"/" :admin-history
|
||||
#"/search/?" :admin-history-search
|
||||
@@ -61,8 +63,10 @@
|
||||
"/table" {:get :pos-cash-drawer-shift-table}}}
|
||||
|
||||
"vendor" {"/search" :vendor-search}
|
||||
;; TODO Include IDS in routes for company-specific things, as opposed to headers
|
||||
"company" {"" :company
|
||||
"/dropdown" :company-dropdown-search-results
|
||||
"/signature" {"/put" :company-update-signature}
|
||||
"/search" :company-search
|
||||
"/bank-account/typeahead" :bank-account-typeahead
|
||||
["/" [#"\d+" :db/id] "/bank-account"] {"/search" :bank-account-search}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
(defn builder [{:keys [value on-change can-submit data-sub error-messages change-event submit-event id fullwidth? schema validation-error-string]}]
|
||||
(when (and change-event on-change)
|
||||
(throw "Either the form is to be managed by ::forms, or it should have value and on-change passed in"))
|
||||
(throw (js/Error. "Either the form is to be managed by ::forms, or it should have value and on-change passed in")))
|
||||
(let [data-sub (or data-sub [::forms/form id])
|
||||
change-event (when-not on-change
|
||||
(or change-event [::forms/change id]))
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
|
||||
[auto-ap.views.components.typeahead.vendor
|
||||
:refer [search-backed-typeahead]]
|
||||
[auto-ap.views.pages.admin.vendors.common :as common]
|
||||
[auto-ap.views.utils
|
||||
:refer [dispatch-event str->int with-is-admin? with-user]]
|
||||
[malli.core :as m]
|
||||
@@ -23,9 +22,25 @@
|
||||
;; Remaining cleanup todos:
|
||||
;; test minification
|
||||
|
||||
(def default-read [:id :name :hidden :terms [:default-account [:name :id :location]]
|
||||
[:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]]
|
||||
[:automatically-paid-when-due [:id :name]]
|
||||
[:terms-overrides [[:client [:id :name]] :id :terms]]
|
||||
[:schedule-payment-dom [[:client [:id :name]] :id :dom]]
|
||||
[:usage [:client-id :count]]
|
||||
[:primary-contact [:name :phone :email :id]]
|
||||
[:plaid-merchant [:name :id]]
|
||||
[:secondary-contact [:id :name :phone :email]]
|
||||
:print-as :invoice-reminder-schedule :code
|
||||
:legal-entity-name
|
||||
:legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name
|
||||
:legal-entity-tin :legal-entity-tin-type
|
||||
:legal-entity-1099-type
|
||||
[:address [:id :street1 :street2 :city :state :zip]]])
|
||||
|
||||
(def terms-override-schema (m/schema [:map
|
||||
[:client schema/reference]
|
||||
[:terms :int]]))
|
||||
[:client schema/reference]
|
||||
[:terms :int]]))
|
||||
|
||||
(def automatically-paid-schema (m/schema [:map
|
||||
[:client schema/reference]]))
|
||||
@@ -35,8 +50,8 @@
|
||||
[:dom [:int {:max 30}]]]))
|
||||
|
||||
(def account-override-schema (m/schema [:map
|
||||
[:client schema/reference]
|
||||
[:account schema/reference]]))
|
||||
[:client schema/reference]
|
||||
[:account schema/reference]]))
|
||||
|
||||
(def schema (m/schema [:map [:name schema/not-empty-string]
|
||||
[:print-as {:optional true}
|
||||
@@ -72,73 +87,73 @@
|
||||
(re-frame/reg-event-fx
|
||||
::save-complete
|
||||
[(forms/triggers-stop ::vendor-form)]
|
||||
(fn [_ [_ _ ]]
|
||||
{:dispatch [::modal/modal-closed ]}))
|
||||
(fn [_ [_ _]]
|
||||
{:dispatch [::modal/modal-closed]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save
|
||||
[with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)]
|
||||
(fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule plaid-merchant primary-contact automatically-paid-when-due schedule-payment-dom secondary-contact address default-account terms-overrides account-overrides id legal-entity-name legal-entity-tin legal-entity-tin-type legal-entity-first-name legal-entity-last-name legal-entity-middle-name legal-entity-1099-type] :as data} :data} :db} _]
|
||||
(if (m/validate schema data)
|
||||
(let [query [:upsert-vendor
|
||||
{:vendor (cond-> {:id id
|
||||
:name name
|
||||
:print-as print-as
|
||||
:terms (or terms
|
||||
nil)
|
||||
:default-account-id (:id default-account)
|
||||
:address address
|
||||
:primary-contact primary-contact
|
||||
:secondary-contact secondary-contact
|
||||
:invoice-reminder-schedule invoice-reminder-schedule}
|
||||
is-admin? (assoc :hidden hidden
|
||||
:terms-overrides (mapv
|
||||
(fn [{:keys [client terms id]}]
|
||||
{:id id
|
||||
:client-id (:id client)
|
||||
:terms (or (str->int terms) 0)})
|
||||
terms-overrides)
|
||||
:account-overrides (mapv
|
||||
(fn [{:keys [client account id]}]
|
||||
{:id id
|
||||
:client-id (:id client)
|
||||
:account-id (:id account)})
|
||||
account-overrides)
|
||||
:schedule-payment-dom (mapv
|
||||
(fn [{:keys [client dom id]}]
|
||||
{:id id
|
||||
:client-id (:id client)
|
||||
:dom (or (str->int dom)
|
||||
0)})
|
||||
schedule-payment-dom)
|
||||
:automatically-paid-when-due (mapv
|
||||
(comp :id :client)
|
||||
automatically-paid-when-due)
|
||||
:plaid-merchant (:id plaid-merchant)
|
||||
:legal-entity-name legal-entity-name
|
||||
:legal-entity-first-name legal-entity-first-name
|
||||
:legal-entity-middle-name legal-entity-middle-name
|
||||
:legal-entity-last-name legal-entity-last-name
|
||||
:legal-entity-tin legal-entity-tin
|
||||
:legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword)
|
||||
:legal-entity-1099-type (some-> legal-entity-1099-type clojure.core/name not-empty keyword)))}
|
||||
common/default-read]]
|
||||
{ :graphql
|
||||
{:token user
|
||||
:owns-state {:single ::vendor-form}
|
||||
:query-obj {:venia/operation
|
||||
{:operation/type :mutation
|
||||
:operation/name "UpsertVendor"} :venia/queries [{:query/data query}]}
|
||||
:on-success [::save-complete]}})
|
||||
::save
|
||||
[with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)]
|
||||
(fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule plaid-merchant primary-contact automatically-paid-when-due schedule-payment-dom secondary-contact address default-account terms-overrides account-overrides id legal-entity-name legal-entity-tin legal-entity-tin-type legal-entity-first-name legal-entity-last-name legal-entity-middle-name legal-entity-1099-type] :as data} :data} :db} _]
|
||||
(if (m/validate schema data)
|
||||
(let [query [:upsert-vendor
|
||||
{:vendor (cond-> {:id id
|
||||
:name name
|
||||
:print-as print-as
|
||||
:terms (or terms
|
||||
nil)
|
||||
:default-account-id (:id default-account)
|
||||
:address address
|
||||
:primary-contact primary-contact
|
||||
:secondary-contact secondary-contact
|
||||
:invoice-reminder-schedule invoice-reminder-schedule}
|
||||
is-admin? (assoc :hidden hidden
|
||||
:terms-overrides (mapv
|
||||
(fn [{:keys [client terms id]}]
|
||||
{:id id
|
||||
:client-id (:id client)
|
||||
:terms (or (str->int terms) 0)})
|
||||
terms-overrides)
|
||||
:account-overrides (mapv
|
||||
(fn [{:keys [client account id]}]
|
||||
{:id id
|
||||
:client-id (:id client)
|
||||
:account-id (:id account)})
|
||||
account-overrides)
|
||||
:schedule-payment-dom (mapv
|
||||
(fn [{:keys [client dom id]}]
|
||||
{:id id
|
||||
:client-id (:id client)
|
||||
:dom (or (str->int dom)
|
||||
0)})
|
||||
schedule-payment-dom)
|
||||
:automatically-paid-when-due (mapv
|
||||
(comp :id :client)
|
||||
automatically-paid-when-due)
|
||||
:plaid-merchant (:id plaid-merchant)
|
||||
:legal-entity-name legal-entity-name
|
||||
:legal-entity-first-name legal-entity-first-name
|
||||
:legal-entity-middle-name legal-entity-middle-name
|
||||
:legal-entity-last-name legal-entity-last-name
|
||||
:legal-entity-tin legal-entity-tin
|
||||
:legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword)
|
||||
:legal-entity-1099-type (some-> legal-entity-1099-type clojure.core/name not-empty keyword)))}
|
||||
default-read]]
|
||||
{:graphql
|
||||
{:token user
|
||||
:owns-state {:single ::vendor-form}
|
||||
:query-obj {:venia/operation
|
||||
{:operation/type :mutation
|
||||
:operation/name "UpsertVendor"} :venia/queries [{:query/data query}]}
|
||||
:on-success [::save-complete]}})
|
||||
|
||||
{:dispatch-n [[::forms/attempted-submit ::vendor-form]
|
||||
[::status/error ::vendor-form [{:message "Please fix the errors and try again."}]]]})))
|
||||
{:dispatch-n [[::forms/attempted-submit ::vendor-form]
|
||||
[::status/error ::vendor-form [{:message "Please fix the errors and try again."}]]]})))
|
||||
|
||||
(defn contact-field [{:keys [name field]}]
|
||||
[form-builder/with-scope {:scope field}
|
||||
[form-builder/vertical-control
|
||||
name
|
||||
[left-stack
|
||||
[left-stack
|
||||
[form-builder/vertical-control {:is-small? true}
|
||||
"Name"
|
||||
[:div.control.has-icons-left
|
||||
@@ -196,23 +211,22 @@
|
||||
[form-builder/section {:title "Terms"}
|
||||
[form-builder/field-v2 {:field :terms}
|
||||
"Terms"
|
||||
[number-input ]]
|
||||
[number-input]]
|
||||
(when is-admin?
|
||||
[form-builder/field-v2 {:field [:terms-overrides]}
|
||||
"Overrides"
|
||||
[multi-field-v2 {:template [[form-builder/raw-field-v2 {:field [:client]}
|
||||
[multi-field-v2 {:template [[form-builder/raw-field-v2 {:field [:client]}
|
||||
[typeahead-v3 {:entities clients
|
||||
:entity->text :name
|
||||
:style {:width "13em"}
|
||||
:type "typeahead-v3"
|
||||
}]]
|
||||
:type "typeahead-v3"}]]
|
||||
[form-builder/raw-field-v2 {:field :terms}
|
||||
[number-input]]]
|
||||
:schema [:sequential terms-override-schema]
|
||||
:key-fn :id
|
||||
:next-key (random-uuid)
|
||||
:new-text "New Terms Override"}]])]
|
||||
|
||||
|
||||
(when is-admin?
|
||||
[form-builder/section {:title "Schedule payment when due"}
|
||||
[form-builder/field-v2 {:field [:automatically-paid-when-due]}
|
||||
@@ -228,7 +242,7 @@
|
||||
|
||||
(when is-admin?
|
||||
[form-builder/section {:title "Schedule payment on day of month"}
|
||||
[form-builder/field-v2 {:field [:schedule-payment-dom]}
|
||||
[form-builder/field-v2 {:field [:schedule-payment-dom]}
|
||||
"Overrides"
|
||||
[multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :client}
|
||||
[typeahead-v3 {:entities clients
|
||||
@@ -249,8 +263,7 @@
|
||||
{:query i
|
||||
:allowance :vendor}
|
||||
[:name :id :warning]])
|
||||
:style {:width "19em"}}]
|
||||
]
|
||||
:style {:width "19em"}}]]
|
||||
(when (:warning (:default-account vendor))
|
||||
[:div.notification.is-warning.is-light
|
||||
(:warning (:default-account vendor))])
|
||||
@@ -258,23 +271,22 @@
|
||||
[form-builder/field-v2 {:field [:account-overrides]}
|
||||
"Overrides"
|
||||
[multi-field-v2 {:template (fn [entity]
|
||||
[[form-builder/raw-field-v2 {:field :client}
|
||||
[typeahead-v3 {:entities clients
|
||||
:entity->text :name
|
||||
:style {:width "19em"}
|
||||
}]]
|
||||
[form-builder/raw-field-v2 {:field :account}
|
||||
[search-backed-typeahead {:search-query (fn [i]
|
||||
[:search_account
|
||||
{:query i
|
||||
:client_id (:id (:client entity))
|
||||
:allowance :vendor}
|
||||
[:name :id :warning]])
|
||||
:style {:width "15em"}}]]])
|
||||
:schema [:sequential account-override-schema]
|
||||
:key-fn :id
|
||||
:next-key (random-uuid)
|
||||
:new-text "Add override"}]])]
|
||||
[[form-builder/raw-field-v2 {:field :client}
|
||||
[typeahead-v3 {:entities clients
|
||||
:entity->text :name
|
||||
:style {:width "19em"}}]]
|
||||
[form-builder/raw-field-v2 {:field :account}
|
||||
[search-backed-typeahead {:search-query (fn [i]
|
||||
[:search_account
|
||||
{:query i
|
||||
:client_id (:id (:client entity))
|
||||
:allowance :vendor}
|
||||
[:name :id :warning]])
|
||||
:style {:width "15em"}}]]])
|
||||
:schema [:sequential account-override-schema]
|
||||
:key-fn :id
|
||||
:next-key (random-uuid)
|
||||
:new-text "Add override"}]])]
|
||||
|
||||
[form-builder/section {:title "Address"}
|
||||
[:div {:style {:width "30em"}}
|
||||
@@ -319,8 +331,7 @@
|
||||
[form-builder/raw-field-v2 {:field :legal-entity-tin}
|
||||
[:input.input {:type "text"
|
||||
:placeholder "SSN or EIN"
|
||||
:size "12"
|
||||
}]]
|
||||
:size "12"}]]
|
||||
|
||||
[:div.control
|
||||
[form-builder/raw-field-v2 {:field :legal-entity-tin-type}
|
||||
@@ -336,25 +347,25 @@
|
||||
:allow-nil? true}]]])
|
||||
[form-builder/hidden-submit-button]]))
|
||||
|
||||
(defn vendor-dialog [ ]
|
||||
(defn vendor-dialog []
|
||||
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form])]
|
||||
[:div
|
||||
[form-content {:data data}]]))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::vendor-selected
|
||||
[with-user (forms/in-form ::select-vendor-form)]
|
||||
(fn [{{:keys [data]} :db :keys [user]} _]
|
||||
(if (:vendor data)
|
||||
{:graphql {:token user
|
||||
:query-obj {:venia/queries [[:vendor-by-id
|
||||
{:id (:id (:vendor data))}
|
||||
common/default-read]]}
|
||||
:owns-state {:single ::select-vendor-form}
|
||||
:on-success (fn [r]
|
||||
[::started (:vendor-by-id r)])}}
|
||||
{:dispatch-n [[::forms/attempted-submit ::select-vendor-form]
|
||||
[::status/error ::select-vendor-form [{:message "Please select a vendor."}]]]})))
|
||||
::vendor-selected
|
||||
[with-user (forms/in-form ::select-vendor-form)]
|
||||
(fn [{{:keys [data]} :db :keys [user]} _]
|
||||
(if (:vendor data)
|
||||
{:graphql {:token user
|
||||
:query-obj {:venia/queries [[:vendor-by-id
|
||||
{:id (:id (:vendor data))}
|
||||
default-read]]}
|
||||
:owns-state {:single ::select-vendor-form}
|
||||
:on-success (fn [r]
|
||||
[::started (:vendor-by-id r)])}}
|
||||
{:dispatch-n [[::forms/attempted-submit ::select-vendor-form]
|
||||
[::status/error ::select-vendor-form [{:message "Please select a vendor."}]]]})))
|
||||
|
||||
(defn select-vendor-form-content []
|
||||
[form-builder/builder {:submit-event [::vendor-selected]
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
[auto-ap.views.pages.ledger.profit-and-loss-detail :refer [profit-and-loss-detail-page]]
|
||||
[auto-ap.views.pages.login :refer [login-page]]
|
||||
[auto-ap.views.pages.payments :refer [payments-page]]
|
||||
[auto-ap.views.pages.home :refer [home-page]]
|
||||
[auto-ap.views.pages.admin.clients :refer [admin-clients-page]]
|
||||
[auto-ap.views.pages.admin.vendors :refer [admin-vendors-page]]))
|
||||
[auto-ap.views.pages.home :refer [home-page]]))
|
||||
|
||||
(defmulti page (fn [active-page] active-page))
|
||||
(defmethod page :unpaid-invoices [_]
|
||||
@@ -93,14 +91,6 @@
|
||||
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
|
||||
(balance-sheet-page)))
|
||||
|
||||
(defmethod page :admin-clients [_]
|
||||
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
|
||||
(admin-clients-page)))
|
||||
|
||||
(defmethod page :admin-specific-client [_]
|
||||
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
|
||||
(admin-clients-page)))
|
||||
|
||||
(defmethod page :admin-vendors [_]
|
||||
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
|
||||
(admin-vendors-page)))
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.clients
|
||||
(:require
|
||||
[auto-ap.routes :as routes]
|
||||
[auto-ap.status :as status]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||
[auto-ap.views.pages.data-page :as data-page]
|
||||
[auto-ap.views.components.grid :as grid]
|
||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||
[auto-ap.views.pages.admin.clients.form :as form]
|
||||
[auto-ap.views.pages.admin.clients.side-bar :as side-bar]
|
||||
[auto-ap.views.pages.admin.clients.table :as table]
|
||||
[auto-ap.views.pages.page-stack :as page-stack]
|
||||
[auto-ap.views.utils :refer [with-user]]
|
||||
[bidi.bidi :as bidi]
|
||||
[clojure.string :as str]
|
||||
[clojure.set :as set]
|
||||
[re-frame.core :as re-frame]
|
||||
[vimsical.re-frame.fx.track :as track]
|
||||
[auto-ap.events :as events]
|
||||
[auto-ap.forms :as forms]
|
||||
[auto-ap.db :as db]))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::received-intuit-bank-accounts
|
||||
(fn [db [_ result]]
|
||||
(assoc db ::subs/intuit-bank-accounts (:intuit-bank-accounts result))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::mounted
|
||||
[with-user]
|
||||
(fn [{:keys [user db]} _]
|
||||
{::track/register [{:id ::params
|
||||
:subscription [::data-page/params ::page]
|
||||
:event-fn (fn [params] [::params-change params])}
|
||||
{:id ::active-route
|
||||
:subscription [::subs/active-route]
|
||||
:event-fn (fn [params] [::params-change params])}]
|
||||
:db (-> db
|
||||
(forms/stop-form [::form/form]))
|
||||
:graphql {:token user
|
||||
:query-obj {:venia/queries [[:intuit_bank_accounts [:external_id :id :name]]]}
|
||||
:owns-state {:single [::load-intuit-bank-accounts]}
|
||||
:on-success [::received-intuit-bank-accounts]} }))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::unmounted
|
||||
(fn [{:keys [db]} _]
|
||||
{:db (dissoc db ::table/params ::side-bar/filter-params)
|
||||
::track/dispose [{:id ::params}
|
||||
{:id ::active-route}]}))
|
||||
|
||||
(defn data-params->query-params [params]
|
||||
{:start (:start params 0)
|
||||
:per-page (:per-page params)
|
||||
:sort (:sort params)
|
||||
:name-like (:name-like params)
|
||||
:code (:code params)})
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::params-change
|
||||
[with-user]
|
||||
(fn [{:keys [user]} [_ params]]
|
||||
{:graphql {:token user
|
||||
:owns-state {:single [::data-page/page ::page]}
|
||||
:query-obj {:venia/queries [[:client-page
|
||||
{:filters (data-params->query-params params)}
|
||||
[[:clients (events/client-detail-query user)]
|
||||
:total
|
||||
:start
|
||||
:end]]]}
|
||||
:on-success (fn [result]
|
||||
[::data-page/received ::page (set/rename-keys (:client-page result)
|
||||
{:clients :data})])}}))
|
||||
|
||||
(def admin-clients-content
|
||||
(with-meta
|
||||
(fn []
|
||||
[:div
|
||||
[page-stack/page-stack
|
||||
{:active @(re-frame/subscribe [::subs/active-route])
|
||||
:pages [{:key :admin-clients
|
||||
:breadcrumb "Clients"
|
||||
:content [:<>
|
||||
[:div.is-pulled-right
|
||||
[:a.button.is-primary.is-outlined {:href (bidi/path-for routes/routes :admin-specific-client :id "new")} "New client"]]
|
||||
[table/clients-table {:data-page ::page
|
||||
:id :clients}]]}
|
||||
|
||||
{:key :admin-specific-client
|
||||
:breadcrumb [:span [:a {:href (bidi/path-for routes/routes :admin-clients)}
|
||||
"Clients"]
|
||||
" / "
|
||||
(or (:name (:data @(re-frame/subscribe [::forms/form ::form/form])))
|
||||
[:i "New client"])]
|
||||
:content [form/new-client-form]}
|
||||
]}]])
|
||||
{:component-did-mount #(re-frame/dispatch [::mounted])
|
||||
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])}))
|
||||
|
||||
|
||||
(defn admin-clients-page []
|
||||
[side-bar-layout {:side-bar [admin-side-bar {}
|
||||
[side-bar/client-side-bar {:data-page ::page}]]
|
||||
:main [admin-clients-content]}])
|
||||
|
||||
@@ -1,761 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.clients.form
|
||||
(: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.subs :as subs]
|
||||
[auto-ap.views.components.address :refer [address2-field]]
|
||||
[react-signature-canvas]
|
||||
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
|
||||
[auto-ap.views.components.level :refer [left-stack] :as level]
|
||||
[auto-ap.views.components :as com]
|
||||
[auto-ap.views.components.typeahead.vendor
|
||||
:refer [search-backed-typeahead]]
|
||||
[auto-ap.views.utils
|
||||
:refer [date-picker
|
||||
with-user
|
||||
dispatch-event]]
|
||||
[bidi.bidi :as bidi]
|
||||
[cljs-time.coerce :as coerce]
|
||||
[cljs-time.core :as t]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as r]
|
||||
[vimsical.re-frame.cofx.inject :as inject]
|
||||
[auto-ap.schema :as schema]
|
||||
[malli.core :as m]))
|
||||
|
||||
(def signature-canvas (r/adapt-react-class (.-default react-signature-canvas)))
|
||||
|
||||
(def location-schema (m/schema [:map
|
||||
[:location schema/not-empty-string]]))
|
||||
|
||||
(def feature-flag-schema (m/schema [:map
|
||||
[:feature-flag schema/not-empty-string]]))
|
||||
|
||||
(def square-location-schema (m/schema [:map
|
||||
[:square-location schema/reference]
|
||||
[:client-location schema/not-empty-string]]))
|
||||
|
||||
(def ezcater-schema (m/schema [:map
|
||||
[:caterer schema/reference]
|
||||
[:client-location schema/not-empty-string]]))
|
||||
|
||||
(def name-match-schema (m/schema [:map
|
||||
[:match schema/not-empty-string]]))
|
||||
(def location-match-schema (m/schema [:map
|
||||
[:match schema/not-empty-string]
|
||||
[:location schema/not-empty-string]]))
|
||||
(def email-schema [:map
|
||||
[:email schema/not-empty-string]
|
||||
[:description schema/not-empty-string]])
|
||||
|
||||
(def client-schema [:map
|
||||
[:name schema/not-empty-string]
|
||||
[:code schema/code-string]
|
||||
[:locations [:sequential location-schema]]
|
||||
[:feature-flags {:optional true} [:maybe [:sequential feature-flag-schema]]]
|
||||
[:emails {:optional true}
|
||||
[:maybe [:sequential email-schema]]]
|
||||
[:matches {:optional true}
|
||||
[:maybe [:sequential name-match-schema]]]
|
||||
[:location-matches {:optional true}
|
||||
[:maybe [:sequential location-match-schema]]]
|
||||
[:selected-square-locations {:optional true}
|
||||
[:maybe [:sequential square-location-schema]]]])
|
||||
|
||||
(defn upload-replacement-button [{:keys [on-change]} text]
|
||||
(let [button (atom nil)]
|
||||
(r/create-class {:display-name "Upload button"
|
||||
:reagent-render
|
||||
(fn []
|
||||
[:<>
|
||||
[:label.button {:for "upload_replacement_signature"} text]
|
||||
[:input.button {:type "file" :id "upload_replacement_signature"
|
||||
:style {:display "none"}
|
||||
:on-change (fn []
|
||||
(let [fr (js/FileReader.)]
|
||||
(.addEventListener fr "load" (fn []
|
||||
(on-change (.-result fr))))
|
||||
|
||||
(.readAsDataURL fr (aget (.-files @button) 0)))
|
||||
)
|
||||
:ref (fn [i] (reset! button i))} ]])})))
|
||||
|
||||
(defn signature [_]
|
||||
(let [canvas (atom nil)
|
||||
edit-mode? (r/atom false)
|
||||
w (* 1.5 464)
|
||||
h (* 1.5 174)]
|
||||
(fn [{:keys [signature-file signature-data on-change]}]
|
||||
[:div
|
||||
(if @edit-mode?
|
||||
[:div
|
||||
[signature-canvas {"canvasProps" {"width" w
|
||||
"height" h
|
||||
"style" #js {"border" "1px solid #CCC"
|
||||
"border-radius" "10px"}}
|
||||
"backgroundColor" "#FFF"
|
||||
:ref (fn [el]
|
||||
(reset! canvas el))}]
|
||||
[:div.buttons
|
||||
[:a.button.is-primary.is-outlined {:on-click (fn []
|
||||
(on-change (.toDataURL @canvas "image/jpeg"))
|
||||
(reset! edit-mode? false))}
|
||||
"Accept"]
|
||||
[:a.button.is-warning.is-outlined {:on-click (fn []
|
||||
(.clear @canvas)
|
||||
(reset! edit-mode? false))}
|
||||
"Cancel"]]]
|
||||
(if (or signature-data signature-file)
|
||||
[:div
|
||||
[:img {:src (or signature-data signature-file)
|
||||
:style {:width w
|
||||
:height h
|
||||
:border "1px solid #CCC"
|
||||
:border-radius "10px"}}]
|
||||
[:div.buttons
|
||||
[:a.button {:on-click (fn []
|
||||
(reset! edit-mode? true))}
|
||||
"Replace Signature"]
|
||||
|
||||
[upload-replacement-button {:on-change on-change} "Upload replacement"]]]
|
||||
[:div
|
||||
[:div.has-text-centered.is-vcentered {:style {:width w
|
||||
:height h
|
||||
:margin-bottom "8px"
|
||||
:border "1px solid #CCC"
|
||||
:border-radius "10px"
|
||||
:background "#EEE"
|
||||
}}
|
||||
"No signature"]
|
||||
[:div.buttons
|
||||
[:a.button.is-primary.is-outlined {:on-click (fn []
|
||||
(reset! edit-mode? true))}
|
||||
"New Signature"]
|
||||
|
||||
[upload-replacement-button {:on-change on-change} "Upload signature"]]]))
|
||||
])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::new-client-request
|
||||
:<- [::forms/form ::form]
|
||||
(fn [{new-client-data :data} _]
|
||||
(cond->
|
||||
{:id (:id new-client-data),
|
||||
:name (:name new-client-data)
|
||||
:code (:code new-client-data) ;; TODO add validation can't change
|
||||
:emails (map #(select-keys % [:id :email :description])
|
||||
(:emails new-client-data))
|
||||
:square-auth-token (:square-auth-token new-client-data)
|
||||
:square-locations (map
|
||||
(fn [x]
|
||||
{:id (:id (:square-location x))
|
||||
:client-location (:client-location x)})
|
||||
(:selected-square-locations new-client-data))
|
||||
|
||||
:ezcater-locations (map
|
||||
(fn [x]
|
||||
{:id (:id x)
|
||||
:caterer (:id (:caterer x))
|
||||
:location (:location x)})
|
||||
(:ezcater-locations new-client-data))
|
||||
|
||||
:locked-until (:locked-until new-client-data)
|
||||
:locations (mapv :location (:locations new-client-data))
|
||||
:feature-flags (mapv :feature-flag (:feature-flags new-client-data))
|
||||
:matches (mapv :match (:matches new-client-data))
|
||||
:location-matches (:location-matches new-client-data)
|
||||
:week-a-credits (:week-a-credits new-client-data)
|
||||
:week-a-debits (:week-a-debits new-client-data)
|
||||
:week-b-credits (:week-b-credits new-client-data)
|
||||
:week-b-debits (:week-b-debits new-client-data)
|
||||
:address {:id (:id (:address new-client-data))
|
||||
:street1 (:street1 (:address new-client-data))
|
||||
:street2 (:street2 (:address new-client-data)),
|
||||
:city (:city (:address new-client-data))
|
||||
:state (:state (:address new-client-data))
|
||||
:zip (:zip (:address new-client-data))}
|
||||
:signature-data (:signature-data new-client-data)
|
||||
:forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}]
|
||||
{:id id
|
||||
:day-of-month (js/parseInt day-of-month)
|
||||
:identifier identifier
|
||||
:amount amount})
|
||||
(:forecasted-transactions new-client-data))
|
||||
:bank-accounts (map-indexed (fn [i {:keys [number name check-number plaid-account intuit-bank-account include-in-reports type id code numeric-code start-date bank-name routing bank-code new? visible locations yodlee-account use-date-instead-of-post-date]}]
|
||||
{:number number
|
||||
:name name
|
||||
:check-number check-number
|
||||
:numeric-code numeric-code
|
||||
:include-in-reports include-in-reports
|
||||
:start-date start-date
|
||||
:type type
|
||||
:id id
|
||||
:sort-order i
|
||||
:visible visible
|
||||
:locations (mapv :location locations)
|
||||
:use-date-instead-of-post-date use-date-instead-of-post-date
|
||||
:yodlee-account (:id yodlee-account)
|
||||
:plaid-account (:id plaid-account)
|
||||
:intuit-bank-account (:id intuit-bank-account)
|
||||
:code (if new?
|
||||
(str (:code new-client-data) "-" code)
|
||||
code)
|
||||
:bank-name bank-name
|
||||
:routing routing
|
||||
:bank-code bank-code})
|
||||
(:bank-accounts new-client-data))})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::mounted
|
||||
[with-user (re-frame/inject-cofx ::inject/sub [::subs/route-params])]
|
||||
(fn [{:keys [user db] ::subs/keys [route-params]} _]
|
||||
(when-let [id (some-> (:id route-params) (js/parseInt ) (#(if (js/Number.isNaN %) nil %)))]
|
||||
{:graphql {:token user
|
||||
:query-obj {:venia/queries [[:admin-client
|
||||
{:id id}
|
||||
(events/client-detail-query user)]]}
|
||||
:on-success (fn [result]
|
||||
[::received (:admin-client result)])}
|
||||
:db (-> db
|
||||
(forms/stop-form ::form))})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::received
|
||||
(fn [db [_ client]]
|
||||
(-> db
|
||||
(forms/stop-form ::form)
|
||||
(forms/start-form ::form (-> client
|
||||
(assoc :selected-square-locations (->> (:square-locations client)
|
||||
(filter :client-location )
|
||||
(mapv (fn [sl]
|
||||
{:id (:id sl)
|
||||
:square-location sl
|
||||
:client-location (:client-location sl)}))))
|
||||
(update :locations #(mapv (fn [l] {:location l
|
||||
:id (random-uuid)}) %))
|
||||
|
||||
(update :feature-flags #(mapv (fn [l] {:feature-flag l
|
||||
:id (random-uuid)}) %))
|
||||
(update :matches #(mapv (fn [l] {:match l
|
||||
:id (random-uuid)}) %))
|
||||
(update :bank-accounts
|
||||
(fn [bas]
|
||||
(mapv (fn [ba]
|
||||
(update ba :locations (fn [ls]
|
||||
(map (fn [l] {:location l
|
||||
:id (random-uuid)})
|
||||
ls))))
|
||||
bas))))))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save-new-client
|
||||
[(forms/in-form ::form)]
|
||||
(fn [_ _]
|
||||
|
||||
(let [new-client-req @(re-frame/subscribe [::new-client-request])
|
||||
user @(re-frame/subscribe [::subs/token])]
|
||||
|
||||
{:graphql
|
||||
{:token user
|
||||
:owns-state {:single ::form}
|
||||
:query-obj {:venia/operation {:operation/type :mutation
|
||||
:operation/name "EditClient"}
|
||||
:venia/queries [{:query/data [:edit-client
|
||||
{:edit-client new-client-req}
|
||||
(events/client-detail-query user)]}]}
|
||||
:on-success [::save-complete]
|
||||
:on-error [::forms/save-error ::form]}})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save-complete
|
||||
(fn [{:keys [db]} [_ client]]
|
||||
{:db
|
||||
(-> db
|
||||
#_(forms/stop-form ::form)
|
||||
|
||||
(assoc-in [:clients (:id (:edit-client client))] (update (:edit-client client) :bank-accounts (fn [bas] (->> bas (sort-by :sort-order) vec)))))
|
||||
:redirect (bidi/path-for routes/routes :admin-clients)}))
|
||||
|
||||
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-bank-account
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ type]]
|
||||
(update client :bank-accounts conj {:type type :active? true :new? true :visible true :sort-order (count (:bank-accounts client))})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::bank-account-activated
|
||||
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
|
||||
(fn [bank-accounts [_ index]]
|
||||
(update (vec (sort-by :sort-order bank-accounts)) index assoc :active? true)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::bank-account-deactivated
|
||||
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
|
||||
(fn [bank-accounts [_ index]]
|
||||
(update (vec (sort-by :sort-order bank-accounts)) index assoc :active? false)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::bank-account-removed
|
||||
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
|
||||
(fn [bank-accounts [_ index]]
|
||||
(vec (concat (take index bank-accounts)
|
||||
(drop (inc index) bank-accounts)))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::sort-swapped
|
||||
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
|
||||
(fn [bank-accounts [_ source dest]]
|
||||
(->> (-> bank-accounts
|
||||
(assoc-in [source :sort-order] (get-in bank-accounts [dest :sort-order]))
|
||||
(assoc-in [dest :sort-order] (get-in bank-accounts [source :sort-order]))
|
||||
|
||||
)
|
||||
(sort-by :sort-order)
|
||||
vec)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::toggle-visible
|
||||
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
|
||||
(fn [bank-accounts [_ account]]
|
||||
(-> (->> bank-accounts
|
||||
(sort-by :sort-order)
|
||||
vec)
|
||||
(update-in [account :visible] #(not %)))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00"))
|
||||
|
||||
(defn is-week-a? [d]
|
||||
(= 0 (mod (t/in-weeks (t/interval first-week-a d)) 2)))
|
||||
|
||||
|
||||
(re-frame/reg-sub
|
||||
::yodlee-accounts
|
||||
:<- [::subs/clients-by-id]
|
||||
(fn [clients [_ id]]
|
||||
|
||||
(if id
|
||||
(mapcat :accounts (:yodlee-provider-accounts (get clients id) ))
|
||||
[])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::plaid-accounts
|
||||
:<- [::subs/clients-by-id]
|
||||
(fn [clients [_ id]]
|
||||
|
||||
(if id
|
||||
(mapcat :accounts (:plaid-items (get clients id) ))
|
||||
[])))
|
||||
|
||||
|
||||
(defn bank-account-card [new-client {:keys [active? new? type visible code name sort-order]} first? last?]
|
||||
[:div.card {:style {:margin-bottom "1em"
|
||||
:width "600px"}}
|
||||
[:header.card-header.has-background-primary-light
|
||||
[:div.card-header-title {:style {:text-overflow "ellipsis"}}
|
||||
[:div.level {:style {:width "100%"}}
|
||||
[:div.level-left
|
||||
[:div.level-item
|
||||
[:span.icon.inline
|
||||
(cond
|
||||
(#{:check ":check"} type) [:span.icon-check-payment-sign]
|
||||
|
||||
(#{:credit ":credit"} type) [:span.icon-credit-card-1]
|
||||
|
||||
:else [:span.icon-accounting-bill])]]
|
||||
[:div.level-item code ": " name]]
|
||||
[:div.level-right
|
||||
[:div.level-item
|
||||
[:div.buttons
|
||||
[:a.button {:on-click (dispatch-event [::toggle-visible sort-order])} [:span.icon (if visible
|
||||
[:span.fa.fa-eye]
|
||||
[:span.fa.fa-eye-slash]
|
||||
)]]
|
||||
(when-not last?
|
||||
[:a.button {:on-click (dispatch-event [::sort-swapped sort-order (inc sort-order)])} [:span.icon [:span.fa.fa-sort-down]]])
|
||||
(when-not first?
|
||||
[:a.button {:on-click (dispatch-event [::sort-swapped sort-order (dec sort-order)])} [:span.icon [:span.fa.fa-sort-up]]])]]]]]
|
||||
(if active?
|
||||
[:a.card-header-icon
|
||||
{:on-click (dispatch-event [::bank-account-deactivated sort-order])}
|
||||
[:span.icon
|
||||
[:span.fa.fa-angle-up]]]
|
||||
[:a.card-header-icon
|
||||
{:on-click (dispatch-event [::bank-account-activated sort-order])}
|
||||
[:span.icon
|
||||
[:span.fa.fa-angle-down]]])]
|
||||
(when active?
|
||||
[:div.card-content
|
||||
[:label.label "General"]
|
||||
[level/left-stack
|
||||
[:div.control
|
||||
[:p.help "Account Code"]
|
||||
(if new?
|
||||
[:div.field.has-addons
|
||||
[:p.control [:a.button.is-static (:code new-client) "-" ]]
|
||||
[:p.control
|
||||
[form-builder/raw-field-v2 {:field :code}
|
||||
[:input.input {:type "text"}]]]]
|
||||
[:div.field [:p.control code]])]
|
||||
|
||||
[form-builder/field-v2 {:field :name}
|
||||
"Nickname"
|
||||
[:input.input {:placeholder "BOA Checking #1"
|
||||
:type "text"}]]
|
||||
[form-builder/field-v2 {:field :numeric-code}
|
||||
"Numeric Code"
|
||||
[com/number-input {:placeholder "20101"
|
||||
:style {:width "8em"}}]]
|
||||
[form-builder/field-v2 {:field :start-date}
|
||||
"Start date"
|
||||
[date-picker {:output :cljs-date}]]]
|
||||
|
||||
(when (#{:check ":check"} type )
|
||||
[:div
|
||||
|
||||
[:label.label "Bank"]
|
||||
[level/left-stack
|
||||
[form-builder/field-v2 {:field :bank-name}
|
||||
"Bank Name"
|
||||
[:input.input {:placeholder "Bank of America"
|
||||
:type "text"}]]
|
||||
[form-builder/field-v2 {:field [:routing]}
|
||||
"Routing #"
|
||||
[:input.input {:placeholder "104819123"
|
||||
:style {:width "9em"}
|
||||
:type "text"}]]
|
||||
[form-builder/field-v2 {:field :bank-code}
|
||||
"Bank code"
|
||||
[:input.input {:placeholder "12/10123"
|
||||
:type "text"}]]]
|
||||
|
||||
[level/left-stack
|
||||
[form-builder/field-v2 {:field :number}
|
||||
"Account #"
|
||||
[:input.input {:placeholder "123456789"
|
||||
:type "text"
|
||||
:style {:width "20em"}}]]
|
||||
|
||||
[form-builder/field-v2 {:field :check-number}
|
||||
"Check Number"
|
||||
[com/number-input {:style {:width "8em"}
|
||||
:placeholder "10000"}]]]
|
||||
|
||||
[form-builder/field-v2 {:field :yodlee-account}
|
||||
"Yodlee Account (new)"
|
||||
[typeahead-v3 {:entities (mapcat :accounts (:yodlee-provider-accounts new-client ))
|
||||
:entity->text (fn [m] (str (:name m) " - " (:number m)))}]]
|
||||
|
||||
|
||||
[form-builder/raw-field-v2 {:field :use-date-instead-of-post-date}
|
||||
[com/checkbox {:label " (Yodlee only) Use 'date' instead of 'postDate'"}]]
|
||||
|
||||
[form-builder/field-v2 {:field :intuit-bank-account}
|
||||
"Intuit Bank Account"
|
||||
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
|
||||
:entity->text (fn [m] (str (:name m)))}]]
|
||||
[form-builder/field-v2 {:field :plaid-account}
|
||||
"Plaid Account"
|
||||
[typeahead-v3 {:entities (mapcat :accounts (:plaid-items new-client ))
|
||||
:entity->text (fn [m] (str (:name m)))}]]])
|
||||
|
||||
(when (#{:credit ":credit"} type )
|
||||
[:div
|
||||
|
||||
|
||||
[:label.label "Account"]
|
||||
[form-builder/field-v2 {:field :bank-name}
|
||||
"Bank Name"
|
||||
[:input.input {:placeholder "Bank of America"
|
||||
:type "text"}]]
|
||||
|
||||
[form-builder/field-v2 {:field :number}
|
||||
"Account #"
|
||||
[:input.input {:placeholder "123456789"
|
||||
:type "text"
|
||||
:style {:width "20em"}}]]
|
||||
|
||||
[form-builder/field-v2 {:field :yodlee-account}
|
||||
"Yodlee Account (new)"
|
||||
[typeahead-v3 {:entities (mapcat :accounts (:yodlee-provider-accounts new-client ))
|
||||
:entity->text (fn [m] (str (:name m) " - " (:number m)))}]]
|
||||
|
||||
[form-builder/raw-field-v2 {:field :use-date-instead-of-post-date}
|
||||
[com/checkbox {:label "(Yodlee only) Use 'date' instead of 'postDate'"}]
|
||||
[:input {:type "checkbox"
|
||||
:field [:use-date-instead-of-post-date]}]]
|
||||
|
||||
[form-builder/field-v2 {:field :intuit-bank-account}
|
||||
"Intuit Bank Account"
|
||||
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
|
||||
:entity->text (fn [m] (str (:name m)))}]]
|
||||
|
||||
[form-builder/field-v2 {:field :plaid-account}
|
||||
"Plaid Account"
|
||||
[typeahead-v3 {:entities (mapcat :accounts (:plaid-items new-client ))
|
||||
:entity->text (fn [m] (str (:name m)))}]]])
|
||||
[:div.field
|
||||
[:label.label "Locations"]
|
||||
[:div.control
|
||||
[:p.help "If this account is location-specific, add the valid locations"]
|
||||
[form-builder/raw-field-v2 {:field :locations}
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :location}
|
||||
[com/select-field {:options (map (fn [l]
|
||||
[(:location l) (:location l)])
|
||||
(get-in new-client [:locations]))
|
||||
:allow-nil? true
|
||||
:style {:width "7em"}
|
||||
}]]]
|
||||
:schema [:sequential location-schema]
|
||||
:key-fn :id}]]]]
|
||||
|
||||
|
||||
|
||||
|
||||
[form-builder/raw-field-v2 {:field :include-in-reports}
|
||||
[com/checkbox {:label "Include in reports"}]
|
||||
]
|
||||
])
|
||||
|
||||
(when active?
|
||||
[:footer.card-footer
|
||||
[:a.card-footer-item {:href "#" :on-click (dispatch-event [::bank-account-deactivated sort-order])} "Done"]
|
||||
(when new?
|
||||
[:a.card-footer-item.is-warning {:href "#" :on-click (dispatch-event [::bank-account-removed sort-order])} "Remove"])])])
|
||||
|
||||
|
||||
(defn general-section []
|
||||
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
|
||||
[form-builder/section {:title "General"}
|
||||
[form-builder/field-v2 {:field :name}
|
||||
"Name"
|
||||
[:input.input {:type "text"
|
||||
:style {:width "20em"}}]]
|
||||
[form-builder/field-v2 {:field :code}
|
||||
"Client code"
|
||||
[:input.input {:type "code"
|
||||
:style {:width "5em"}
|
||||
:disabled (boolean (:id new-client))}]]
|
||||
[:div.field
|
||||
[:label.label "Feature Flags"]
|
||||
[:div.control
|
||||
[:p.help "These are specific new features that can be enabled or disabled on a per-client basis"]
|
||||
[form-builder/raw-field-v2 {:field :feature-flags}
|
||||
[com/multi-field-v2 {:allow-change? true
|
||||
:template [[form-builder/raw-field-v2 {:field :feature-flag}
|
||||
[com/select-field {:options [[nil nil]
|
||||
["new-square" "New Square+Ezcater (no effect)"]
|
||||
["manually-pay-cintas" "Manually Pay Cintas"]
|
||||
["include-in-ntg-corp-reports" "Include in NTG Corporate reports"]]
|
||||
:allow-nil? false
|
||||
:style {:width "18em"}}]]]
|
||||
:key-fn :id
|
||||
:schema [:sequential feature-flag-schema]
|
||||
:next-key (random-uuid)}]]]]
|
||||
|
||||
[form-builder/field-v2 {:field :locations}
|
||||
"Locations"
|
||||
[com/multi-field-v2 {:allow-change? false
|
||||
:template [[form-builder/raw-field-v2 {:field :location}
|
||||
[:input.input {:max-length 2
|
||||
:style {:width "4em"}}]]]
|
||||
:disable-remove? true
|
||||
:key-fn :id
|
||||
:schema [:sequential location-schema]
|
||||
:next-key (random-uuid)}]]
|
||||
|
||||
[form-builder/vertical-control
|
||||
"Signature"
|
||||
[signature {:signature-file (:signature-file new-client)
|
||||
:signature-data (:signature-data new-client)
|
||||
:on-change (fn [uri]
|
||||
(re-frame/dispatch [::forms/change ::form [:signature-data] uri]))}]]
|
||||
|
||||
[form-builder/field-v2 {:field :locked-until}
|
||||
"Locked Until"
|
||||
[date-picker {:output :cljs-date
|
||||
:style {:width "15em"}}]]]))
|
||||
|
||||
(defn contacts-section []
|
||||
[form-builder/section {:title "Contacts"}
|
||||
|
||||
[form-builder/field-v2 {:field :emails}
|
||||
"Emails (address/description)"
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :email}
|
||||
[:input.input {:type "email"
|
||||
:placeholder "tom@myspace.com"}]]
|
||||
[form-builder/raw-field-v2 {:field :description}
|
||||
[:input.input {:type "text"
|
||||
:placeholder "Manager"}]]]
|
||||
:key-fn :id
|
||||
:schema [:sequential email-schema]
|
||||
:next-key (random-uuid)}]]
|
||||
|
||||
[form-builder/vertical-control
|
||||
"Address"
|
||||
[:div {:style {:width "30em"}}
|
||||
[form-builder/raw-field-v2 {:field :address}
|
||||
[address2-field]]]]])
|
||||
|
||||
;; TODO Name matches, locations, bank account locations are all "single field multis", and require weird mounting and
|
||||
;; unmounting. A new field could sort that out easily
|
||||
(defn matching-section []
|
||||
[form-builder/section {:title "Matching"}
|
||||
[form-builder/field-v2 {:field :matches}
|
||||
"Name matches"
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field [:match]}
|
||||
[:input.input {:placeholder "Harry's burger joint"
|
||||
:style { :width "15em"}}]]]
|
||||
:key-fn :id
|
||||
:next-key (random-uuid)
|
||||
:schema [:sequential name-match-schema]}]]
|
||||
|
||||
|
||||
[form-builder/field-v2 {:field :location-matches}
|
||||
"Location Matches"
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :match}
|
||||
[:input.input {:placeholder "Downtown"
|
||||
:style { :width "15em"}}]]
|
||||
[form-builder/raw-field-v2 {:field :location}
|
||||
[:input.input {:placeholder "DT"
|
||||
:max-length 2
|
||||
:style { :width "4em"}}]]]
|
||||
:schema [:sequential location-match-schema]
|
||||
:next-key (random-uuid)
|
||||
:key-fn :id}]]])
|
||||
|
||||
(defn bank-accounts-section []
|
||||
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
|
||||
[form-builder/section {:title "Bank Accounts"}
|
||||
(for [bank-account (sort-by :sort-order (:bank-accounts new-client))]
|
||||
^{:key (:sort-order bank-account)}
|
||||
[form-builder/with-scope {:scope [:bank-accounts (:sort-order bank-account)]}
|
||||
[bank-account-card new-client bank-account (= 0 (:sort-order bank-account)) (= (:sort-order bank-account) (dec (count (:bank-accounts new-client))))]])
|
||||
|
||||
[:div.buttons
|
||||
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :credit])} "Add Credit Account"]
|
||||
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :check])} "Add Checking Account"]
|
||||
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]]))
|
||||
|
||||
(defn cash-flow-section []
|
||||
(let [next-week-a (if (is-week-a? (t/now))
|
||||
"This week"
|
||||
"Next week")
|
||||
next-week-b (if (is-week-a? (t/now))
|
||||
"Next week"
|
||||
"This week")]
|
||||
|
||||
|
||||
[form-builder/section {:title "Cash Flow"}
|
||||
[:label.label (str "Week A (" next-week-a ")")]
|
||||
[left-stack
|
||||
[form-builder/field-v2 {:field :week-a-credits}
|
||||
"Regular Credits"
|
||||
[com/money-input]]
|
||||
[form-builder/field-v2 {:field :week-a-debits}
|
||||
"Regular Debits"
|
||||
[com/money-input]]]
|
||||
[:label.label (str "Week B (" next-week-b ")")]
|
||||
[left-stack
|
||||
[form-builder/field-v2 {:field :week-b-credits}
|
||||
"Regular Credits"
|
||||
[com/money-input]]
|
||||
[form-builder/field-v2 {:field :week-b-debits}
|
||||
"Regular Debits"
|
||||
[com/money-input]]]
|
||||
|
||||
[form-builder/field-v2 {:field :forecasted-transactions}
|
||||
"Forecasted transactions"
|
||||
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :identifier}
|
||||
[:input.input {:type "text"
|
||||
:placeholder "Identifier"
|
||||
:style {:width "10em"}}]]
|
||||
[form-builder/raw-field-v2 {:field :day-of-month}
|
||||
[com/number-input {:placeholder "DOM"}]]
|
||||
[form-builder/raw-field-v2 {:field :amount
|
||||
:placeholder "AMT"}
|
||||
[com/money-input]]]
|
||||
:key-fn :id}]]]))
|
||||
|
||||
(defn square-section []
|
||||
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
|
||||
[form-builder/section {:title "Square Integration"}
|
||||
[form-builder/field-v2 {:field :square-auth-token}
|
||||
"Square Authentication Token"
|
||||
[:input.input {:type "text"
|
||||
:style {:width "40em"}}]]
|
||||
[form-builder/field-v2 {:field :selected-square-locations}
|
||||
"Square Locations"
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :square-location}
|
||||
[typeahead-v3 {:entities (:square-locations new-client)
|
||||
:entity->text :name
|
||||
:style {:width "15em"}}]]
|
||||
[form-builder/raw-field-v2 {:field :client-location}
|
||||
[com/select-field {:options (map (fn [l]
|
||||
[(:location l) (:location l)])
|
||||
(get-in new-client [:locations]))
|
||||
:allow-nil? true
|
||||
:style {:width "7em"}
|
||||
}]]]
|
||||
:disable-remove? true
|
||||
:key-fn :id
|
||||
:schema [:sequential square-location-schema]}]]]))
|
||||
|
||||
(defn ezcater-section []
|
||||
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
|
||||
[form-builder/section {:title "EZCater integration"}
|
||||
|
||||
[form-builder/field-v2 {:field :ezcater-locations}
|
||||
"EZCater Locations"
|
||||
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :caterer}
|
||||
[search-backed-typeahead {:search-query (fn [i]
|
||||
[:search_ezcater_caterer
|
||||
{:query i}
|
||||
[:name :id]])
|
||||
:entity->text :name
|
||||
:style {:width "20em"}}]]
|
||||
[form-builder/raw-field-v2 {:field [:location]}
|
||||
[com/select-field {:options (map (fn [l]
|
||||
[(:location l) (:location l)])
|
||||
(get-in new-client [:locations]))
|
||||
:allow-nil? true
|
||||
:style {:width "7em"}}]]]
|
||||
:key-fn :id
|
||||
:schema [:sequential ezcater-schema]
|
||||
:disable-remove? true}]]]))
|
||||
|
||||
|
||||
(defn form-content []
|
||||
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
|
||||
|
||||
^{:key (or (:id new-client)
|
||||
"new")}
|
||||
[form-builder/builder {:submit-event [::save-new-client ]
|
||||
:id ::form
|
||||
:fullwidth? false
|
||||
:schema client-schema}
|
||||
|
||||
[general-section]
|
||||
[contacts-section]
|
||||
[matching-section]
|
||||
[bank-accounts-section]
|
||||
[cash-flow-section]
|
||||
[square-section]
|
||||
[ezcater-section]
|
||||
[form-builder/error-notification]
|
||||
[form-builder/submit-button "Save"]]))
|
||||
|
||||
(def new-client-form
|
||||
(with-meta
|
||||
(fn []
|
||||
(let [_ @(re-frame/subscribe [::subs/route-params])]
|
||||
[form-content]))
|
||||
{:component-did-mount #(re-frame/dispatch [::mounted])}))
|
||||
@@ -1,23 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.clients.side-bar
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[auto-ap.views.utils :refer [dispatch-value-change]]
|
||||
[auto-ap.views.pages.data-page :as data-page]))
|
||||
|
||||
(defn client-side-bar [{:keys [data-page]}]
|
||||
[:div
|
||||
[:p.menu-label "Name"]
|
||||
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "Harry's Food Products"
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :name-like])
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :name-like])} ]]]
|
||||
|
||||
[:p.menu-label "Code"]
|
||||
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "CBC"
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :code])
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :code])} ]]]])
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.clients.table
|
||||
(:require [auto-ap.subs :as subs]
|
||||
[clojure.string :as str]
|
||||
[re-frame.core :as re-frame]
|
||||
[auto-ap.views.utils :refer [action-cell-width date->str with-user]]
|
||||
[auto-ap.views.components.grid :as grid]
|
||||
[auto-ap.views.components.modal :as modal]
|
||||
[auto-ap.views.components.buttons :as buttons]
|
||||
[auto-ap.status :as status]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.routes :as routes]
|
||||
[auto-ap.views.pages.data-page :as data-page]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::specific-params
|
||||
(fn [db]
|
||||
(::params db)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::params-changed
|
||||
(fn [{:keys [db]} [_ p]]
|
||||
{:db (assoc db ::params p)}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::sales-queries-setup
|
||||
(fn [_ [_ results]]
|
||||
{:dispatch [::modal/modal-requested {:title "Sales Queries"
|
||||
:body [:div [:pre (:message (:setup-sales-queries results))]]}]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::setup-sales-queries
|
||||
[with-user]
|
||||
(fn [{:keys [user]} [_ client-id]]
|
||||
{:graphql
|
||||
{:token user
|
||||
:owns-state {:multi ::setup-sales-queries
|
||||
:which client-id}
|
||||
:query-obj {:venia/operation {:operation/type :mutation
|
||||
:operation/name "SetupSalesQueries"}
|
||||
:venia/queries [{:query/data [:setup-sales-queries
|
||||
{:client-id client-id}
|
||||
[:message]]}]}
|
||||
:on-success [::sales-queries-setup]}}
|
||||
))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::params
|
||||
:<- [::specific-params]
|
||||
:<- [::subs/query-params]
|
||||
(fn [[specific-params query-params]]
|
||||
(merge (select-keys query-params #{:start :sort}) specific-params )))
|
||||
|
||||
|
||||
(defn integration-status-badge [name status]
|
||||
(condp = (:state status)
|
||||
:success
|
||||
[:div.tag.has-tooltip-right.has-tooltip-arrow {:data-tooltip (str "Last updated:" (date->str (:last-updated status))
|
||||
"\n"
|
||||
"Last Attempted:" (date->str (:last-attempt status)))} [:span.icon [:i.has-text-success.fa.fa-check]] [:span name]]
|
||||
|
||||
:failed
|
||||
[:div.tag.is-danger.is-light.has-tooltip-right.has-tooltip-arrow {:data-tooltip (str "Last updated:" (date->str (:last-updated status))
|
||||
"\n"
|
||||
"Last Attempted:" (date->str (:last-attempt status))
|
||||
"\n"
|
||||
(:message status))
|
||||
} [:span.icon [:i.has-text-danger.fa.fa-warning]] [:span name]]
|
||||
|
||||
:unauthorized
|
||||
[:div.tag.is-danger.is-light.has-tooltip-right.has-tooltip-arrow {:data-tooltip (str "Last updated:" (date->str (:last-updated status))
|
||||
"\n"
|
||||
"Last Attempted:" (date->str (:last-attempt status))
|
||||
"\n"
|
||||
"Your user is unauthorized. Detail:\n"
|
||||
(:message status))
|
||||
} [:span.icon [:i.has-text-danger.fa.fa-warning]] [:span name]]
|
||||
nil
|
||||
))
|
||||
|
||||
(defn clients-table [{:keys [data-page status]}]
|
||||
(let [states @(re-frame/subscribe [::status/multi ::setup-sales-queries])
|
||||
{:keys [data]} @(re-frame/subscribe [::data-page/page data-page])]
|
||||
[grid/grid {:on-params-change (fn [p]
|
||||
(re-frame/dispatch [::params-changed p]))
|
||||
:data-page data-page
|
||||
:status status
|
||||
:params @(re-frame/subscribe [::params])
|
||||
:column-count 5}
|
||||
[grid/controls data]
|
||||
[grid/table {:fullwidth true}
|
||||
[grid/header
|
||||
[grid/row {}
|
||||
[grid/header-cell {} "Name"]
|
||||
[grid/header-cell {:style {:width "20em"}} "Code"]
|
||||
[grid/header-cell {} "Locations"]
|
||||
[grid/header-cell {} "Status"]
|
||||
[grid/header-cell {} "Email"]
|
||||
[grid/header-cell {:style {:width (action-cell-width 2)}}]]]
|
||||
[grid/body
|
||||
(for [{:keys [id name email square-integration-status locked-until code locations bank-accounts]} (:data data)]
|
||||
^{:key (str name "-" id)}
|
||||
[grid/row {:id id}
|
||||
[grid/cell {} name]
|
||||
[grid/cell {} code]
|
||||
[grid/cell {} (str/join ", " locations)]
|
||||
[grid/cell {:class "expandable"} [:div.tags
|
||||
|
||||
[:div.tag (or (some-> locked-until date->str (#(str "Locked " %))) "Not locked")]
|
||||
[integration-status-badge "Square" square-integration-status]
|
||||
[:<>
|
||||
(for [bank-account bank-accounts
|
||||
:let [code (:code bank-account)
|
||||
integration-status (:integration-status bank-account)]
|
||||
:when (:id integration-status)]
|
||||
^{:key (:id integration-status)}
|
||||
[integration-status-badge code integration-status])]]]
|
||||
[grid/cell {} email]
|
||||
[grid/cell {} [:div.buttons [buttons/fa-icon {:event [::setup-sales-queries id]
|
||||
:class (status/class-for (get states id))
|
||||
:icon :fa-dollar}]
|
||||
[buttons/fa-icon {:href (bidi/path-for routes/routes :admin-specific-client :id id)
|
||||
:icon :fa-pencil}]]]])]]
|
||||
[grid/bottom-paginator data]]))
|
||||
@@ -1,94 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.vendors
|
||||
(:require
|
||||
[auto-ap.effects.forward :as forward]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||
[auto-ap.views.pages.admin.vendors.merge-dialog :as merge-dialog]
|
||||
[auto-ap.views.pages.admin.vendors.side-bar :as side-bar]
|
||||
[auto-ap.views.pages.admin.vendors.table :as table]
|
||||
[auto-ap.views.pages.data-page :as data-page]
|
||||
[auto-ap.views.utils :refer [dispatch-event with-user]]
|
||||
[clojure.set :as set]
|
||||
[re-frame.core :as re-frame]
|
||||
[vimsical.re-frame.fx.track :as track]
|
||||
[auto-ap.views.components.vendor-dialog :as vendor-dialog]))
|
||||
|
||||
(def default-read [:id :name :hidden :terms [:default-account [:name :id :location]]
|
||||
[:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]]
|
||||
[:automatically-paid-when-due [:id :name]]
|
||||
[:terms-overrides [[:client [:id :name]] :id :terms]]
|
||||
[:schedule-payment-dom [[:client [:id :name]] :id :dom]]
|
||||
[:usage [:client-id :count]]
|
||||
[:primary-contact [:name :phone :email :id]]
|
||||
[:secondary-contact [:id :name :phone :email]]
|
||||
[:plaid-merchant [:id :name]]
|
||||
:print-as :invoice-reminder-schedule :code
|
||||
:legal-entity-name
|
||||
:legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name
|
||||
:legal-entity-tin :legal-entity-tin-type
|
||||
:legal-entity-1099-type
|
||||
[:address [:id :street1 :street2 :city :state :zip]]])
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::params-change
|
||||
[with-user]
|
||||
(fn [{:keys [user]} [_ params]]
|
||||
{:graphql {:token user
|
||||
:owns-state {:single [::data-page/page ::page]}
|
||||
:query-obj {:venia/queries [{:query/data [:vendor
|
||||
{:sort (:sort params)
|
||||
:start (:start params 0)
|
||||
:per-page (:per-page params)
|
||||
:name-like (:name-like params)}
|
||||
[[:vendors default-read]
|
||||
:total
|
||||
:start
|
||||
:end]]
|
||||
:query/alias :result}]}
|
||||
:on-success (fn [result]
|
||||
[::data-page/received ::page
|
||||
(set/rename-keys (:result result)
|
||||
{:vendors :data})])}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::mounted
|
||||
(fn [_ _]
|
||||
{::forward/register [{:id ::merge-complete
|
||||
:events #{::merge-dialog/complete}
|
||||
:event-fn (fn [_]
|
||||
[::params-change {}])}
|
||||
{:id ::save-complete
|
||||
:events #{::vendor-dialog/save-complete}
|
||||
:event-fn (fn [_]
|
||||
[::params-change {}])}]
|
||||
::track/register {:id ::params
|
||||
:subscription [::data-page/params ::page]
|
||||
:event-fn (fn [params]
|
||||
[::params-change params])}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::unmounted
|
||||
(fn [_ _]
|
||||
{:dispatch [::data-page/dispose ::page]
|
||||
::forward/dispose [{:id ::merge-complete} {:id ::save-complete}]
|
||||
::track/dispose {:id ::params}}))
|
||||
|
||||
(defn admin-vendors-content []
|
||||
[(with-meta
|
||||
(fn []
|
||||
[:div.inbox-messages
|
||||
(when-let [banner (:banner @(re-frame/subscribe [::subs/admin]))]
|
||||
[:div.notification banner])
|
||||
[:div
|
||||
[:h1.title "Vendors"]
|
||||
[:div.is-pulled-right [:a.button.is-primary.is-outlined {:on-click (dispatch-event [::merge-dialog/show])} "Merge vendors"]]
|
||||
[table/vendors-table {:id :vendors
|
||||
:data-page ::page}]]])
|
||||
{:component-did-mount #(re-frame/dispatch [::mounted])
|
||||
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])})])
|
||||
|
||||
(defn admin-vendors-page []
|
||||
[side-bar-layout {:side-bar [admin-side-bar {}
|
||||
[side-bar/vendor-side-bar {:data-page ::page}]]
|
||||
:main [admin-vendors-content]}])
|
||||
@@ -1,17 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.vendors.common)
|
||||
|
||||
(def default-read [:id :name :hidden :terms [:default-account [:name :id :location]]
|
||||
[:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]]
|
||||
[:automatically-paid-when-due [:id :name]]
|
||||
[:terms-overrides [[:client [:id :name]] :id :terms]]
|
||||
[:schedule-payment-dom [[:client [:id :name]] :id :dom]]
|
||||
[:usage [:client-id :count]]
|
||||
[:primary-contact [:name :phone :email :id]]
|
||||
[:plaid-merchant [:name :id]]
|
||||
[:secondary-contact [:id :name :phone :email]]
|
||||
:print-as :invoice-reminder-schedule :code
|
||||
:legal-entity-name
|
||||
:legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name
|
||||
:legal-entity-tin :legal-entity-tin-type
|
||||
:legal-entity-1099-type
|
||||
[:address [:id :street1 :street2 :city :state :zip]]])
|
||||
@@ -1,80 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.vendors.merge-dialog
|
||||
(:require
|
||||
[auto-ap.forms :as forms]
|
||||
[auto-ap.forms.builder :as form-builder]
|
||||
[auto-ap.schema :as schema]
|
||||
[auto-ap.status :as status]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.views.components :as com]
|
||||
[auto-ap.views.components.modal :as modal]
|
||||
[auto-ap.views.utils :refer [dispatch-event]]
|
||||
[malli.core :as m]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(def merge-schema
|
||||
(m/schema [:map
|
||||
[:from schema/reference]
|
||||
[:to schema/reference]]))
|
||||
|
||||
(defn form []
|
||||
[form-builder/builder {:submit-event [::try-save]
|
||||
:id ::form
|
||||
:schema merge-schema}
|
||||
[form-builder/field-v2 {:field :from}
|
||||
"Form Vendor (will be deleted)"
|
||||
[com/search-backed-typeahead {:search-query (fn [i]
|
||||
[:search_vendor
|
||||
{:query i}
|
||||
[:name :id]])
|
||||
:auto-focus true}]]
|
||||
|
||||
|
||||
[form-builder/field-v2 {:field :to}
|
||||
"To Vendor"
|
||||
[com/search-backed-typeahead {:search-query (fn [i]
|
||||
[:search_vendor
|
||||
{:query i}
|
||||
[:name :id]])}]]
|
||||
[form-builder/hidden-submit-button]])
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::show
|
||||
(fn [{:keys [db]} _]
|
||||
{:dispatch [::modal/modal-requested {:title "Merge Vendors"
|
||||
:body [form]
|
||||
:confirm {:value "Merge"
|
||||
:status-from [::status/single ::form]
|
||||
:class "is-primary"
|
||||
:on-click (dispatch-event [::try-save])
|
||||
:close-event [::status/completed ::form]}}]
|
||||
:db (forms/start-form db ::form {})}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::complete
|
||||
(fn [{:keys [db]} _]
|
||||
{:db (forms/stop-form db ::form)
|
||||
:dispatch [::modal/modal-closed ]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save
|
||||
[(forms/in-form ::form)]
|
||||
(fn [{{{:keys [from to]} :data} :db} _]
|
||||
(let [user @(re-frame/subscribe [::subs/token])]
|
||||
{:graphql
|
||||
{:token user
|
||||
:owns-state {:single ::form}
|
||||
:query-obj {:venia/operation {:operation/type :mutation
|
||||
:operation/name "MergeVendors"}
|
||||
:venia/queries [{:query/data [:merge-vendors
|
||||
{:from (:id from) :to (:id to)} []]}]}
|
||||
:on-success [::complete]}})))
|
||||
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::try-save
|
||||
[(forms/in-form ::form)]
|
||||
(fn [{:keys [db]}]
|
||||
(if (not (m/validate merge-schema (:data db)))
|
||||
{:dispatch-n [[::status/error ::form [{:message "Please correct any errors and try again"}]]
|
||||
[::forms/attempted-submit ::form]]}
|
||||
{:dispatch [::save]})))
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.vendors.side-bar
|
||||
(:require
|
||||
[auto-ap.views.pages.data-page :as data-page]
|
||||
[auto-ap.views.utils :refer [dispatch-value-change]]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(defn vendor-side-bar [{:keys [data-page]}]
|
||||
[:div
|
||||
[:p.menu-label "Name"]
|
||||
[:div
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "HOME DEPOT"
|
||||
:value @(re-frame/subscribe [::data-page/filter data-page :name-like])
|
||||
:on-change (dispatch-value-change [::data-page/filter-changed data-page :name-like])} ]]]]])
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
(ns auto-ap.views.pages.admin.vendors.table
|
||||
(:require
|
||||
[auto-ap.views.components.buttons :as buttons]
|
||||
[auto-ap.views.components.grid :as grid]
|
||||
[auto-ap.views.components.vendor-dialog :as vendor-dialog]
|
||||
[auto-ap.views.pages.data-page :as data-page]
|
||||
[auto-ap.views.utils :refer [action-cell-width]]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(defn vendors-table [{:keys [data-page]}]
|
||||
(let [{:keys [data]} @(re-frame/subscribe [::data-page/page data-page])]
|
||||
[grid/grid {:data-page data-page
|
||||
:column-count 4}
|
||||
[grid/controls data]
|
||||
[grid/table {:fullwidth true}
|
||||
[grid/header
|
||||
[grid/row {}
|
||||
[grid/header-cell {} "Name"]
|
||||
[grid/header-cell {} "Email"]
|
||||
[grid/header-cell {} "Default Account"]
|
||||
[grid/header-cell {:style {:width (action-cell-width 1)}}]]]
|
||||
[grid/body
|
||||
(for [v (:data data)]
|
||||
^{:key (str (:id v))}
|
||||
[grid/row {:class (:class v) :id (:id v)}
|
||||
[grid/cell {} (:name v)
|
||||
(let [total-usage (reduce + 0 (map :count (:usage v)))]
|
||||
(if (> total-usage 0)
|
||||
[:div.mx-2.tag.is-info.is-light total-usage " usages, " (count (:usage v)) " clients"]
|
||||
[:div.mx-2.tag.is-warning.is-light "Unused"]))]
|
||||
[grid/cell {} (:email (:primary-contact v))]
|
||||
[grid/cell {} (-> v :default-account :name)]
|
||||
[grid/cell {}
|
||||
[buttons/fa-icon {:event [::vendor-dialog/started v]
|
||||
:icon "fa-pencil"}]]])]]
|
||||
[grid/bottom-paginator data]]))
|
||||
Reference in New Issue
Block a user