Simplified forms considerably

This commit is contained in:
2022-07-16 10:15:47 -07:00
parent d16b9c9a5e
commit 16a1d243e8
16 changed files with 519 additions and 592 deletions

View File

@@ -84,6 +84,10 @@
.modal.wide .modal-card { .modal.wide .modal-card {
width: 1024px; width: 1024px;
} }
.modal.semi-wide .modal-card {
width: 700px;
}
@keyframes grow-width { @keyframes grow-width {
from { from {
width: 0px; width: 0px;
@@ -347,10 +351,6 @@ nav.navbar .navbar-item.is-active {
.modal { .modal {
overflow: visible; overflow: visible;
} }
.modal-card-body {
overflow: visible;
}
.buttons .dropdown:not(:last-child):not(.is-fullwidth) .button { .buttons .dropdown:not(:last-child):not(.is-fullwidth) .button {
margin-right: 0.5em; margin-right: 0.5em;

View File

@@ -266,24 +266,30 @@
(throw (ex-info "Payment can't be undone because it isn't cleared." {:validation-error "Payment can't be undone because it isn't cleared."}))) (throw (ex-info "Payment can't be undone because it isn't cleared." {:validation-error "Payment can't be undone because it isn't cleared."})))
(if is-autopay-payment? (if is-autopay-payment?
(audit-transact (audit-transact
(->> [{:db/id (:db/id payment) (cond-> [{:db/id (:db/id payment)
:payment/status :payment-status/pending} :payment/status :payment-status/pending}
{:db/id transaction-id {:db/id transaction-id
:transaction/approval-status :transaction-approval-status/unapproved} :transaction/approval-status :transaction-approval-status/unapproved}
[:db/retractEntity (:db/id payment) ] [:db/retractEntity (:db/id payment) ]
[:db/retract transaction-id :transaction/payment (:db/id payment)] [:db/retract transaction-id :transaction/payment (:db/id payment)]
[:db/retract transaction-id :transaction/vendor (:db/id (:transaction/vendor transaction))] [:db/retract transaction-id :transaction/vendor (:db/id (:transaction/vendor transaction))]]
[:db/retract transaction-id :transaction/location (:transaction/location transaction)]]
(into (map (fn [a] (:transaction/location transaction)
[:db/retract transaction-id :transaction/accounts (:db/id a)]) (conj [:db/retract transaction-id :transaction/location (:transaction/location transaction)])
(:transaction/accounts transaction)))
(into (map (fn [[invoice-payment]] (seq (:transaction/accounts transaction))
[:db/retractEntity invoice-payment]) (into (map (fn [a]
(d/query {:query {:find ['?ip] [:db/retract transaction-id :transaction/accounts (:db/id a)])
:in ['$ '?p] (:transaction/accounts transaction)))
:where ['[?ip :invoice-payment/payment ?p]]}
:args [(d/db conn) (:db/id payment)]} )))) true
(into (map (fn [[invoice-payment]]
[:db/retractEntity invoice-payment])
(d/query {:query {:find ['?ip]
:in ['$ '?p]
:where ['[?ip :invoice-payment/payment ?p]]}
:args [(d/db conn) (:db/id payment)]} ))))
(:id context)) (:id context))
(audit-transact (audit-transact
(into (cond-> [{:db/id (:db/id payment) (into (cond-> [{:db/id (:db/id payment)

View File

@@ -5,12 +5,13 @@
[auto-ap.graphql.utils [auto-ap.graphql.utils
:refer [->graphql :refer [->graphql
<-graphql <-graphql
cleanse-query
assert-admin assert-admin
assert-failure assert-failure
cleanse-query
enum->keyword enum->keyword
is-admin? is-admin?
result->page]] result->page]]
[auto-ap.utils :refer [by]]
[clojure.set :as set] [clojure.set :as set]
[clojure.string :as str] [clojure.string :as str]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
@@ -31,9 +32,30 @@
(set (map :db/id (:user/clients id))))))) (set (map :db/id (:user/clients id)))))))
(defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} value] (defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} value]
(when (and id (not (can-user-edit-vendor? id (:id context)))) (when (and id (not (can-user-edit-vendor? id (:id context))))
(assert-failure "This vendor is managed by Integreat. Please reach out to ben@integreatconsult.com for your changes.")) (assert-failure "This vendor is managed by Integreat. Please reach out to ben@integreatconsult.com for your changes."))
(when (->> schedule_payment_dom
(group-by :client_id)
vals
(filter #(> (count %) 1))
seq)
(assert-failure "Only one schedule payment override allowed per client."))
(when (->> terms_overrides
(group-by :client_id)
vals
(filter #(> (count %) 1))
seq)
(assert-failure "Only one terms override allowed per client."))
(when (->> account_overrides
(group-by :client_id)
vals
(filter #(> (count %) 1))
seq)
(assert-failure "Only one account override allowed per client."))
(let [ (let [
hidden (if (is-admin? (:id context)) hidden (if (is-admin? (:id context))
hidden hidden

View File

@@ -18,23 +18,26 @@
(defn builder [{:keys [can-submit data-sub change-event submit-event id fullwidth?] :as z}] (defn builder [{:keys [can-submit data-sub change-event submit-event id fullwidth?] :as z}]
(let [data-sub (or data-sub [::forms/form id]) (let [data-sub (or data-sub [::forms/form id])
change-event (or change-event [::forms/change id]) change-event (or change-event [::forms/change id])
{:keys [data error]} @(re-frame/subscribe data-sub)] {:keys [data error]} @(re-frame/subscribe data-sub)
status @(re-frame/subscribe [::status/single id])]
(r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit) (r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit)
:change-event change-event :change-event change-event
:submit-event submit-event :submit-event submit-event
:error error :error error
:status @(re-frame/subscribe [::status/single id]) :status status
:id id :id id
:data data :data data
:fullwidth? fullwidth?}} :fullwidth? fullwidth?}}
(r/as-element (r/as-element
(into [:form {:on-submit (fn [e] [:form {:on-submit (fn [e]
(when (.-stopPropagation e) (when (.-stopPropagation e)
(.stopPropagation e) (.stopPropagation e)
(.preventDefault e)) (.preventDefault e))
(when can-submit (when can-submit
(re-frame/dispatch-sync (vec (conj submit-event {})))))}] (re-frame/dispatch-sync (vec (conj submit-event {})))))}
(r/children (r/current-component))))))) (into [:fieldset {:disabled (boolean (= :loading (:state status)))}]
(r/children (r/current-component)))]
))))
(defn raw-field [] (defn raw-field []
(let [[child] (r/children (r/current-component))] (let [[child] (r/children (r/current-component))]
@@ -55,6 +58,7 @@
:else :else
nil))) nil)))
(assoc-in [1 :subscription] (aget consume-form "data")) (assoc-in [1 :subscription] (aget consume-form "data"))
(assoc-in [1 :event] (aget consume-form "change-event")))]))]))])) (assoc-in [1 :event] (aget consume-form "change-event")))]))]))]))
@@ -62,6 +66,16 @@
(r/create-element FormScopeProvider #js {:value scope} (r/create-element FormScopeProvider #js {:value scope}
(r/as-element (into [:<>] (r/as-element (into [:<>]
(r/children (r/current-component)))))) (r/children (r/current-component))))))
(defn vertical-control [{:keys [is-small?]}]
(let [[label & children] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(r/as-element
[:div.field
(when label (if (or (aget consume "fullwidth?")
is-small?) [:p.help label]
[:label.label label]))
(into [:div.control ] children)]))]))
(defn field [] (defn field []
(let [[label child] (r/children (r/current-component))] (let [[label child] (r/children (r/current-component))]
@@ -111,6 +125,16 @@
fullwidth? (conj "is-fullwidth")) } fullwidth? (conj "is-fullwidth")) }
child])))])) child])))]))
(defn hidden-submit-button []
[:> Consumer {}
(fn [consume]
(let [status (aget consume "status")
can-submit (aget consume "can-submit")]
(r/as-element
[:div {:style {:display "none"}}
[:button.button.is-medium.is-primary {:disabled (or (status/disabled-for status)
(not can-submit))}]])))])
(defn error-notification [] (defn error-notification []
(let [[child] (r/children (r/current-component))] (let [[child] (r/children (r/current-component))]
[:> Consumer {} [:> Consumer {}

View File

@@ -65,7 +65,7 @@
[horizontal-field [horizontal-field
nil nil
[:div.control [:div.control
[:p.help "Address"] [:p.help "Street Address"]
[form-builder/raw-field [form-builder/raw-field
[:input.input.is-expanded {:type "text" [:input.input.is-expanded {:type "text"
:placeholder "1700 Pennsylvania Ave" :placeholder "1700 Pennsylvania Ave"

View File

@@ -0,0 +1,9 @@
(ns auto-ap.views.components.level
(:require [reagent.core :as r]))
(defn left-stack []
(let [children (r/children (r/current-component))]
[:div.level (r/props (r/current-component))
(into [:div.level-left]
(for [c children]
[:div.level-item c]))]))

View File

@@ -33,7 +33,8 @@
class (assoc :class class)) class (assoc :class class))
[:div.modal-background {:on-click (dispatch-event [::modal-closed])}] [:div.modal-background {:on-click (dispatch-event [::modal-closed])}]
[:div.modal-card [:div.modal-card (cond-> {}
class (assoc :class class))
[:header.modal-card-head [:header.modal-card-head
[:p.modal-card-title [:p.modal-card-title
title] title]

View File

@@ -1,91 +1,7 @@
(ns auto-ap.views.components.typeahead (ns auto-ap.views.components.typeahead
(:require [reagent.core :as r] (:require
[reagent.ratom :as ra] [auto-ap.views.components.typeahead.vendor :as internal]))
[clojure.string :as str]
[clojure.set :as set]
[downshift :as ds :refer [useCombobox]]
[react]))
(set! *warn-on-infer* true) (set! *warn-on-infer* true)
;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though (def typeahead-v3 internal/typeahead-v3)
(defn state-reducer [state actions-and-changes]
(cond
(= (aget actions-and-changes "type") (aget (aget useCombobox "stateChangeTypes" ) "InputChange"))
(doto (aget actions-and-changes "changes") (aset "selectedItem" nil))
(and (= (aget actions-and-changes "type") (aget (aget useCombobox "stateChangeTypes") "InputBlur"))
(not (aget state "selectedItem")))
(doto (aget actions-and-changes "changes" ) (aset "inputValue" nil))
:else
(aget actions-and-changes "changes")))
(defn typeahead-v3-internal [{:keys [class style disabled entities ^js entity->text entities-by-id entity-index on-change disabled value name auto-focus] :or {disabled false} :as i}]
(let [[items set-items] (react/useState (map clj->js entities))
[getLabelProps getMenuProps getComboboxProps getToggleButtonProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue]
(as-> (useCombobox (clj->js {:items items
:defaultHighlightedIndex 0
:defaultSelectedItem value
:onInputValueChange (fn [input]
(if entities-by-id
(do
(->> (.search entity-index (or (aget input "inputValue") "") #js {:fuzzy 0.2} )
clj->js
(take 10)
(set-items)))
(set-items (map clj->js (take 10 (filter (fn [x] (str/includes? (or (some-> (entity->text x) str/lower-case) "")
(or (some-> (aget input "inputValue") str/lower-case) "")))
entities))))))
:stateReducer state-reducer
:onSelectedItemChange (fn [z]
(when on-change
(on-change (js->clj (aget z "selectedItem") :keywordize-keys true))))})) $
(map #(aget $ %) ["getLabelProps" "getMenuProps" "getComboboxProps" "getToggleButtonProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))]
[:<>
[:div.typeahead (assoc (js->clj (getComboboxProps))
:style style)
(if selectedItem
^{:key "typeahead"} [:div.input (assoc (js->clj (getInputProps #js {:disabled (if disabled
"disabled"
"")}))
:on-key-up (fn [e]
(when (= 8 (aget e "keyCode" ))
(selectItem nil)
(setInputValue nil)
(when on-change
(on-change nil))))
:class class
:tab-index "0")
[:div.control
[:div.tags.has-addons
[:span.tag (entity->text (js->clj selectedItem :keywordize-keys true))]
(when name
[:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}])
(when-not disabled
[:a.tag.is-delete {:on-click (fn []
(setInputValue nil)
(selectItem nil)
(when on-change
(on-change nil)))}])]]]
^{:key "typeahead"} [:input.input (js->clj
(getInputProps #js {:disabled (if disabled
"disabled"
"")
:autoFocus (if auto-focus
"autoFocus"
"")}))])
[:div {:class (if (and isOpen (seq items)) "typeahead-menu")}
[:ul (js->clj (getMenuProps))
(if (and isOpen (seq items))
(for [[index item] (map vector (range) (js->clj items :keywordize-keys true))]
^{:key item}
[:li.typeahead-suggestion (assoc (js->clj (getItemProps #js {:item item :index index}))
:class (if (= index highlightedIndex)
"typeahead-highlighted"))
(entity->text item)]))]]]]))
(defn typeahead-v3 [{:keys [class disabled entities entity->text entities-by-id entity-index on-change value ] :as props}]
[:div
[:f> typeahead-v3-internal props]])

View File

@@ -3,26 +3,12 @@
[downshift :as ds :refer [useCombobox]] [downshift :as ds :refer [useCombobox]]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[auto-ap.views.utils :refer [with-user]] [auto-ap.views.utils :refer [with-user]]
[react]))
[clojure.string :as str]
[react :as react]))
(set! *warn-on-infer* true) (set! *warn-on-infer* true)
;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though
(defn state-reducer [^js/FakeStateObject state ^js/FakeActionsAndChanges actions-and-changes]
(let [useCombobox ^js/Downshift useCombobox]
(cond
(= (.-type actions-and-changes) (.-InputChange (.-stateChangeTypes ^js/Downshift useCombobox)))
(set! (.-selectedItem (.-changes actions-and-changes)) nil)
(and (= (.-type actions-and-changes) (.-InputBlur (.-stateChangeTypes ^js/Downshift useCombobox)))
(not (.-selectedItem state)))
(set! (.-inputValue (.-changes actions-and-changes ))
nil)
:else
nil))
(.-changes actions-and-changes))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::search-completed ::search-completed
(fn [_ [_ set-items set-loading-status result]] (fn [_ [_ set-items set-loading-status result]]
@@ -41,13 +27,30 @@
(when (> (count input-value) 2) (when (> (count input-value) 2)
(set-loading-status :loading) (set-loading-status :loading)
{:graphql {:token user {:graphql {:token user
:query-obj {:venia/queries [{:query/data (search-query input-value ) :query-obj {:venia/queries [{:query/data (search-query input-value )
:query/alias :search-results}]} :query/alias :search-results}]}
:on-success [::search-completed set-items set-loading-status] :on-success [::search-completed set-items set-loading-status]
:on-error [::search-failed set-loading-status]}}))) :on-error [::search-failed set-loading-status]}})))
;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though
(defn state-reducer [^js/FakeStateObject state ^js/FakeActionsAndChanges actions-and-changes]
(let [useCombobox ^js/Downshift useCombobox]
(cond
(= (.-type actions-and-changes) (.-InputChange (.-stateChangeTypes ^js/Downshift useCombobox)))
(set! (.-selectedItem (.-changes actions-and-changes)) nil)
(and (= (.-type actions-and-changes) (.-InputBlur (.-stateChangeTypes ^js/Downshift useCombobox)))
(not (.-selectedItem state)))
(set! (.-inputValue (.-changes actions-and-changes ))
nil)
:else
nil))
(.-changes actions-and-changes))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::input-value-changed ::input-value-changed
(fn [_ [_ input-value search-query set-items set-loading-status]] (fn [_ [_ input-value search-query set-items set-loading-status]]
@@ -58,73 +61,116 @@
:time 250 :time 250
:key ::input-value-settled}}))) :key ::input-value-settled}})))
(defn typeahead-v3-internal [{:keys [class style ^js on-change disabled value name search-query auto-focus] :or {disabled false} :as i}] (defn typeahead-v3-internal [{:keys [class entity->text entities on-input-change style ^js on-change disabled value name auto-focus] :or {disabled false} :as i}]
(let [[items set-items] (react/useState []) (let [[items set-items] (react/useState (or (clj->js entities)
[]))
[focused set-focus] (react/useState (boolean auto-focus))
[loading-status set-loading-status] (react/useState false) [loading-status set-loading-status] (react/useState false)
[getLabelProps getMenuProps getComboboxProps getToggleButtonProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue] [getMenuProps getComboboxProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue]
(as-> (useCombobox (clj->js {:items items (as-> (useCombobox (clj->js {:items items
:defaultHighlightedIndex 0 :defaultHighlightedIndex 0
:defaultSelectedItem value :defaultSelectedItem value
:itemToString (fn []
;; once an item is selected, you just use empty text
"")
:onInputValueChange (fn [input] :onInputValueChange (fn [input]
(re-frame/dispatch [::input-value-changed (aget input "inputValue") search-query set-items set-loading-status]) (on-input-change input set-items set-loading-status))
true)
:stateReducer state-reducer :stateReducer state-reducer
:onSelectedItemChange (fn [z] :onSelectedItemChange (fn [z]
(when on-change (when on-change
(on-change (js->clj (aget z "selectedItem") :keywordize-keys true))))})) $ (on-change (js->clj (aget z "selectedItem") :keywordize-keys true))))})) $
(map #(aget $ %) ["getLabelProps" "getMenuProps" "getComboboxProps" "getToggleButtonProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))] (map #(aget $ %) ["getMenuProps" "getComboboxProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))]
[:<> [:<>
[:div.typeahead (assoc (js->clj (getComboboxProps)) [:div.typeahead (assoc (js->clj (getComboboxProps))
:style style) :style style)
(cond [:div.input {:on-key-up (when selectedItem (fn [e]
selectedItem (when (= 8 (aget e "keyCode" ))
^{:key "typeahead"} (selectItem nil)
[:div.input (assoc (js->clj (getInputProps #js {:disabled (if disabled (setInputValue nil)
"disabled" (when on-change
"")})) (on-change nil)))))
:on-key-up (fn [e] :disabled (cond disabled
(when (= 8 (aget e "keyCode" )) "disabled"
(selectItem nil)
(setInputValue nil)
(when on-change
(on-change nil))))
:class (if (= :loading loading-status)
"is-loading"
class)
:tab-index "0")
[:div.control
[:div.tags.has-addons
[:span.tag (:name (js->clj selectedItem :keywordize-keys true))]
(when name
[:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}])
(when-not disabled
[:a.tag.is-delete {:on-click (fn []
(setInputValue nil)
(selectItem nil)
(when on-change
(on-change nil)))}])]]]
:else :else
^{:key "typeahead"} [:div.control {:class (when (= :loading loading-status) "")
"is-loading")} :class
[:input.input (js->clj (cond-> []
(getInputProps #js {:disabled (if disabled (sequential? class)
"disabled" (into class)
"")
:autoFocus (if auto-focus (not (sequential? class))
"autoFocus" (conj class)
"")}))]])
focused
(conj "is-focused")
)}
(when selectedItem
^{:key "hidden"}
[:div.level-item
[:div.control
[:div.tags.has-addons
[:span.tag (:name (js->clj selectedItem :keywordize-keys true))]
(when name
[:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}])
(when-not disabled
[:a.tag.is-delete {:on-click (fn []
(setInputValue nil)
(selectItem nil)
(when on-change
(on-change nil)))}])]]])
^{:key "main"}
[:div.control {:class (when (= :loading loading-status)
"is-loading")
:style {:padding "0px"
:width "100%"
:height "2em"
:margin "0px"}}
[:input (js->clj (getInputProps #js {:style #js {:border "0px"
:height "2em"
:width "100%"
"outline" "none"}
:disabled disabled
:onFocus #(set-focus true)
:onBlur #(set-focus false)
:autoFocus (if auto-focus
"autoFocus"
"")}))]]]
[:div {:class (when (and isOpen (seq items)) [:div {:class (when (and isOpen (seq items))
"typeahead-menu")} "typeahead-menu")}
[:ul (js->clj (getMenuProps)) [:ul (js->clj (getMenuProps))
(if (and isOpen (seq items)) (when (and isOpen (seq items))
(for [[index item] (map vector (range) (js->clj items :keywordize-keys true))] (for [[index item] (map vector (range) (js->clj items :keywordize-keys true))]
^{:key item} ^{:key item}
[:li.typeahead-suggestion (assoc (js->clj (getItemProps #js {:item item :index index})) [:li.typeahead-suggestion (assoc (js->clj (getItemProps #js {:item item :index index}))
:class (if (= index highlightedIndex) :class (when (= index highlightedIndex)
"typeahead-highlighted")) "typeahead-highlighted"))
(:name item)]))]]]])) (if entity->text
(entity->text item)
(:name item))]))]]]]))
(defn search-backed-typeahead [props] (defn search-backed-typeahead [{:keys [search-query] :as props}]
[:div [:div
[:f> typeahead-v3-internal props]]) [:f> typeahead-v3-internal (assoc props
:on-input-change
(fn [input set-items set-loading-status]
(re-frame/dispatch [::input-value-changed (aget input "inputValue") search-query set-items set-loading-status])
true))]])
(defn typeahead-v3 [{:keys [entities ^js entity->text entities-by-id entity-index] :as props}]
[:div
[:f> typeahead-v3-internal (assoc props
:on-input-change
(fn [input set-items]
(if entities-by-id
(do
(->> (.search entity-index (or (aget input "inputValue") "") #js {:fuzzy 0.2} )
clj->js
(take 10)
(set-items)))
(set-items (map clj->js (take 10 (filter (fn [x] (str/includes? (or (some-> (entity->text x) str/lower-case) "")
(or (some-> (aget input "inputValue") str/lower-case) "")))
entities)))))))]])

View File

@@ -3,23 +3,24 @@
[auto-ap.entities.contact :as contact] [auto-ap.entities.contact :as contact]
[auto-ap.entities.vendors :as entity] [auto-ap.entities.vendors :as entity]
[auto-ap.forms :as forms] [auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.status :as status] [auto-ap.status :as status]
[auto-ap.views.components.level :refer [left-stack]]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.address :refer [address2-field]] [auto-ap.views.components.address :refer [address2-field]]
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.views.components.typeahead :refer [typeahead-v3]] [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.pages.admin.vendors.common :as common]
[auto-ap.views.utils [auto-ap.views.utils
:refer [bind-field :refer [dispatch-event multi-field str->int with-is-admin? with-user]]
dispatch-event
horizontal-field
with-is-admin?
with-user]]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[auto-ap.forms.builder :as form-builder])) [reagent.core :as r]))
;; Remaining cleanup todos:
;; test minification
(re-frame/reg-sub (re-frame/reg-sub
::can-submit ::can-submit
@@ -27,37 +28,6 @@
(fn [form] (fn [form]
(s/valid? ::entity/vendor (:data form)))) (s/valid? ::entity/vendor (:data form))))
(re-frame/reg-event-db
::removed-override
[(forms/in-form ::vendor-form)]
(fn [form [_ override-key index]]
(update-in form [:data override-key]
(fn [overrides]
(reduce
(fn [overrides [i override]]
(if (= i index)
overrides
(conj overrides override)))
[]
(map vector (range) overrides))))))
(re-frame/reg-event-db
::changed
[(forms/settles {:key ::vendor-form
:time 500
:event [::settled]})]
(forms/change-handler
::vendor-form
(fn [data field _]
(let [[override-key? i?] field]
(if (and (#{:account-overrides :terms-overrides :schedule-payment-dom} override-key?)
(nil? (get-in data [override-key? i? :key])))
[[override-key? i? :key] (random-uuid)]
[])))))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::save-complete ::save-complete
@@ -74,7 +44,8 @@
{:vendor (cond-> {:id id {:vendor (cond-> {:id id
:name name :name name
:print-as print-as :print-as print-as
:terms terms :terms (or (str->int terms)
0)
:default-account-id (:id default-account) :default-account-id (:id default-account)
:address address :address address
:primary-contact primary-contact :primary-contact primary-contact
@@ -82,28 +53,29 @@
:invoice-reminder-schedule invoice-reminder-schedule} :invoice-reminder-schedule invoice-reminder-schedule}
is-admin? (assoc :hidden hidden is-admin? (assoc :hidden hidden
:terms-overrides (mapv :terms-overrides (mapv
(fn [{:keys [client override id]}] (fn [{:keys [client terms id]}]
{:id id {:id id
:client-id (:id client) :client-id (:id client)
:terms override}) :terms (or (str->int terms) 0)})
terms-overrides) terms-overrides)
:account-overrides (mapv :account-overrides (mapv
(fn [{:keys [client override id]}] (fn [{:keys [client account id]}]
{:id id {:id id
:client-id (:id client) :client-id (:id client)
:account-id (:id override)}) :account-id (:id account)})
account-overrides) account-overrides)
:schedule-payment-dom (mapv :schedule-payment-dom (mapv
(fn [{:keys [client override id]}] (fn [{:keys [client dom id]}]
{:id id {:id id
:client-id (:id client) :client-id (:id client)
:dom override}) :dom (or (str->int dom)
0)})
schedule-payment-dom) schedule-payment-dom)
:automatically-paid-when-due (mapv :automatically-paid-when-due (mapv
:id (comp :id :client)
automatically-paid-when-due) automatically-paid-when-due)
:legal-entity-first-name legal-entity-first-name :legal-entity-first-name legal-entity-first-name
:legal-entity-middle-name legal-entity-middle-name :legal-entity-middle-name legal-entity-middle-name
:legal-entity-last-name legal-entity-last-name :legal-entity-last-name legal-entity-last-name
:legal-entity-tin legal-entity-tin :legal-entity-tin legal-entity-tin
:legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword) :legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword)
@@ -118,239 +90,176 @@
:operation/name "UpsertVendor"} :venia/queries [{:query/data query}]} :operation/name "UpsertVendor"} :venia/queries [{:query/data query}]}
:on-success [::save-complete]}})))) :on-success [::save-complete]}}))))
(defn client-list [{:keys [override-key data]}] (defn pull-left []
(let [clients @(re-frame/subscribe [::subs/clients])] (into [:div {:style {:position "relative"
[form-builder/horizontal-control :left "-40px"}}]
"Client" (r/children (r/current-component))))
(doall
(for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))]
^{:key (or (:id override)
(:key override))}
[:div.level
[:div.level-left
[:div.level-item
[form-builder/raw-field
[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "13em"}
:type "typeahead-v3"
:field [override-key i]}]]]
[:div.level-item
[:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]]))]))
(defn default-with-overrides [{:keys [override-key default-key data mandatory?]} template] (defn contact-field [{:keys [name field]}]
(let [clients @(re-frame/subscribe [::subs/clients])] [form-builder/with-scope {:scope field}
[:div [form-builder/vertical-control
[form-builder/horizontal-control name
[:span "Default" [left-stack
(when mandatory? [form-builder/vertical-control {:is-small? true}
[:span.has-text-danger " *"])] "Name"
(template default-key nil)] [:div.control.has-icons-left
[form-builder/horizontal-control [form-builder/raw-field
"Overrides" [:input.input.is-expanded {:type "text"
(doall :field :name
(for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))] :spec ::contact/name}]]
^{:key (or [:span.icon.is-small.is-left
(:id override) [:i.fa.fa-user]]]]
(:key override) [form-builder/vertical-control {:is-small? true}
)} "Email"
[:div.level
[:div.level-left [:div.control.has-icons-left
[:div.level-item [:span.icon.is-small.is-left
[form-builder/raw-field [:i.fa.fa-envelope]]
[typeahead-v3 {:entities clients [form-builder/raw-field
:entity->text :name [:input.input {:type "email"
:style {:width "13em"} :field :email
:type "typeahead-v3" :spec ::contact/email}]]]]
:field [override-key i :client]}]]] [form-builder/vertical-control {:is-small? true}
[:div.level-item "Phone"
(template [:div.control.has-icons-left
[override-key i :override] [form-builder/raw-field
(get-in data [override-key i :client]))] [:input.input {:type "phone"
[:div.level-item :field :phone
[:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]]))]])) :spec ::contact/phone}]]
[:span.icon.is-small.is-left
[:i.fa.fa-phone]]]]]]])
(defn client-overrides [{:keys [override-key data]} template]
(let [clients @(re-frame/subscribe [::subs/clients])]
[form-builder/horizontal-control
"Overrides"
(doall
(for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))]
^{:key (or
(:id override)
(:key override))}
[:div.level
[:div.level-left
[:div.level-item
[form-builder/raw-field
[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "13em"}
:type "typeahead-v3"
:field [override-key i :client]}]]]
[:div.level-item
(template
[override-key i :override]
(get-in data [override-key i :client]))]
[:div.level-item
[:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]]))]))
(defn form-content [{:keys [data]}] (defn form-content [{:keys [data]}]
(let [is-admin? @(re-frame/subscribe [::subs/is-admin?])
(let [is-admin? @(re-frame/subscribe [::subs/is-admin?])] clients @(re-frame/subscribe [::subs/clients])]
[form-builder/builder {:submit-event [::save] [form-builder/builder {:submit-event [::save]
:can-submit [::can-submit] :can-submit [::can-submit]
:change-event [::changed]
:id ::vendor-form} :id ::vendor-form}
[:div [form-builder/field
[form-builder/horizontal-field [:span "Name " [:span.has-text-danger "*"]]
[:span "Name " [:span.has-text-danger "*"]] [:input.input {:type "text"
[:input.input {:type "text" :auto-focus true
:auto-focus true :field :name
:field :name :spec ::entity/name}]]
:spec ::entity/name}]]
[form-builder/horizontal-field [form-builder/field
"Print Checks As" "Print Checks As"
[:input.input {:type "text" [:input.input {:type "text"
:field :print-as :field :print-as
:spec ::entity/print-as}]] :spec ::entity/print-as}]]
(when is-admin?
[:div.field
[:label.checkbox
[form-builder/raw-field
[:input {:type "checkbox"
:field [:hidden]
:spec ::entity/hidden}]]
" Hidden"]])
[form-builder/section {:title "Terms"}
[form-builder/field
"Terms"
[:input.input {:type "number"
:step "1"
:style {:width "4em"}
:field [:terms]
:size 3
:spec ::entity/terms}]]
(when is-admin? (when is-admin?
[form-builder/horizontal-field [form-builder/field
"Hidden" "Overrides"
[:input {:type "checkbox" [multi-field {:type "multi-field"
:field :hidden :field [:terms-overrides]
:spec ::entity/hidden}]]) :template [[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "13em"}
:type "typeahead-v3"
:field [:client]}]
[:input.input {:type "number"
:step "1"
:style {:width "4em"}
:field [:terms]
:size 3
:spec ::entity/terms}]]}]])
]
(when is-admin?
[form-builder/section {:title "Schedule payment when due"}
[form-builder/field
"Client"
[multi-field {:type "multi-field"
:field [:automatically-paid-when-due]
:template [[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "13em"}
:type "typeahead-v3"
:field [:client]}]]}]]])
(if is-admin? (when is-admin?
[:<> [form-builder/section {:title "Schedule payment on day of month"}
[form-builder/section {:title "Terms"} [form-builder/field
[default-with-overrides {:data data "Overrides"
:default-key :terms [multi-field {:type "multi-field"
:override-key :terms-overrides} :field [:schedule-payment-dom]
(fn [field _] :template [[typeahead-v3 {:entities clients
[form-builder/raw-field :entity->text :name
[:input.input {:type "number" :style {:width "13em"}
:step "1" :type "typeahead-v3"
:style {:width "4em"} :field [:client]}]
:field field [:input.input {:type "number"
:size 3 :step "1"
:spec ::entity/terms}]])]]] :style {:width "4em"}
:field [:dom]
[form-builder/horizontal-field :size 3
[:span "Terms"] :spec ::entity/terms}]]}]]])
[:input.input {:type "number" [form-builder/section {:title "Expense Accounts"}
:step "1" [form-builder/field
:style {:width "4em"} "Default *"
:field :terms [search-backed-typeahead {:search-query (fn [i]
:size 3 [:search_account
:spec ::entity/terms}]]) {:query i}
[:name :id]])
:type "typeahead-v3"
:style {:width "19em"}
:field [:default-account]}]]
(when is-admin? (when is-admin?
[form-builder/section {:title "Schedule payment when due"} [form-builder/field
[client-list {:data data "Overrides"
:override-key :automatically-paid-when-due}]]) [multi-field {:type "multi-field"
:field [:account-overrides]
:template (fn [entity]
[[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "19em"}
:type "typeahead-v3"
:field [:client]}]
[search-backed-typeahead {:search-query (fn [i]
[:search_account
{:query i
:client_id (:id (:client entity))}
[:name :id]])
:type "typeahead-v3"
:style {:width "15em"}
:field [:account]}]])}]])]
(when is-admin? [form-builder/with-scope {:scope [:address ]}
[form-builder/section {:title "Schedule payment on day of month"} [form-builder/section {:title "Address"}
[client-overrides {:data data [:div {:style {:width "30em"}}
:mandatory? true [address2-field]]]]
:override-key :schedule-payment-dom}
(fn [field _] [form-builder/section {:title "Contact"}
[form-builder/raw-field [contact-field {:name "Primary"
[:input.input {:type "number" :field [:primary-contact]}]
:step "1" [contact-field {:name "Secondary"
:style {:width "5em"} :field [:secondary-contact]}]]
:field field
:size 3
:spec ::entity/dom}]])]])
(if is-admin?
[form-builder/section {:title "Expense Accounts"}
[default-with-overrides {:data data
:mandatory? true
:default-key :default-account
:override-key :account-overrides}
(fn [field client]
[form-builder/raw-field
[search-backed-typeahead {:search-query (fn [i]
[:search_account
{:query i
:client-id (:id client)}
[:name :id]])
:type "typeahead-v3"
:style {:width "15em"}
:field field}]])]]
[form-builder/horizontal-field (when is-admin?
"Expense Account" [form-builder/section {:title "Legal Entity"}
[search-backed-typeahead {:search-query (fn [i] [form-builder/vertical-control
[:search_account "Name"
{:query i} [left-stack
[:name :id]])
:type "typeahead-v3"
:field :default-account}]])
[form-builder/with-scope {:scope [:address ]}
[form-builder/section {:title "Address"}
[address2-field]]]
[form-builder/section {:title "Contact"}
[form-builder/horizontal-control
"Primary"
[:div.control.has-icons-left
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:field [:primary-contact :name]
:spec ::contact/name}]]
[:span.icon.is-small.is-left
[:i.fa.fa-user]]]
[:div.control.has-icons-left
[:span.icon.is-small.is-left
[:i.fa.fa-envelope]]
[form-builder/raw-field
[:input.input {:type "email"
:field [:primary-contact :email]
:spec ::contact/email}]]]
[:div.control.has-icons-left
[form-builder/raw-field
[:input.input {:type "phone"
:field [:primary-contact :phone]
:spec ::contact/phone}]]
[:span.icon.is-small.is-left
[:i.fa.fa-phone]]]]
[form-builder/horizontal-control
"Secondary"
[:div.control.has-icons-left
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:field [:secondary-contact :name]
:spec ::contact/name}]]
[:span.icon.is-small.is-left
[:i.fa.fa-user]]]
[:div.control.has-icons-left
[:span.icon.is-small.is-left
[:i.fa.fa-envelope]]
[form-builder/raw-field
[:input.input {:type "email"
:field [:secondary-contact :email]
:spec ::contact/email}]]]
[:div.control.has-icons-left
[form-builder/raw-field
[:input.input {:type "phone"
:field [:secondary-contact :phone]
:spec ::contact/phone}]]
[:span.icon.is-small.is-left
[:i.fa.fa-phone]]]]]
(when is-admin?
[form-builder/section {:title "Legal Entity"}
[form-builder/horizontal-control
"Name"
[:div.control [:div.control
[form-builder/raw-field [form-builder/raw-field
[:input.input {:type "text" [:input.input {:type "text"
@@ -370,16 +279,16 @@
[:input.input {:type "text" [:input.input {:type "text"
:placeholder "Last Name" :placeholder "Last Name"
:field [:legal-entity-last-name] :field [:legal-entity-last-name]
:spec ::contact/name}]]]] :spec ::contact/name}]]]]]
[form-builder/horizontal-control [form-builder/vertical-control
"TIN" "TIN"
[:div.control [left-stack
[form-builder/raw-field [form-builder/raw-field
[:input.input {:type "text" [:input.input {:type "text"
:placeholder "SSN or EIN" :placeholder "SSN or EIN"
:field [:legal-entity-tin] :field [:legal-entity-tin]
:size "12" :size "12"
:spec ::contact/name}]]] :spec ::contact/name}]]
[:div.control [:div.control
[:div.select [:div.select
@@ -388,19 +297,20 @@
:field [:legal-entity-tin-type]} :field [:legal-entity-tin-type]}
[:option {:value nil} ""] [:option {:value nil} ""]
[:option {:value "ein"} "EIN"] [:option {:value "ein"} "EIN"]
[:option {:value "ssn"} "SSN"]]]]]] [:option {:value "ssn"} "SSN"]]]]]]]
[form-builder/horizontal-control [form-builder/vertical-control
"1099 Type" "1099 Type"
[:div.control [:div.control
[:div.select [:div.select
[form-builder/raw-field [form-builder/raw-field
[:select {:type "select" [:select {:type "select"
:field [:legal-entity-1099-type]} :field [:legal-entity-1099-type]}
[:option {:value nil} ""] [:option {:value nil} ""]
[:option {:value "none"} "Don't 1099"] [:option {:value "none"} "Don't 1099"]
[:option {:value "misc"} "Misc"] [:option {:value "misc"} "Misc"]
[:option {:value "landlord"} "Landlord"]]]]]]])]])) [:option {:value "landlord"} "Landlord"]]]]]]])
[form-builder/hidden-submit-button]]))
(defn vendor-dialog [ ] (defn vendor-dialog [ ]
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form])] (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form])]
@@ -417,7 +327,6 @@
common/default-read]]} common/default-read]]}
:owns-state {:single ::select-vendor-form} :owns-state {:single ::select-vendor-form}
:on-success (fn [r] :on-success (fn [r]
(println (:vendor-by-id r))
[::started (:vendor-by-id r)])}})) [::started (:vendor-by-id r)])}}))
(re-frame/reg-sub (re-frame/reg-sub
@@ -442,7 +351,7 @@
:type "typeahead-v3" :type "typeahead-v3"
:auto-focus true :auto-focus true
:field [:vendor]}]] :field [:vendor]}]]
#_[form-builder/submit-button "Save"]]) [form-builder/hidden-submit-button]])
@@ -450,33 +359,17 @@
::started ::started
(fn [{:keys [db]} [_ vendor]] (fn [{:keys [db]} [_ vendor]]
{:db (-> db (forms/start-form ::vendor-form (-> vendor {:db (-> db (forms/start-form ::vendor-form (-> vendor
(update :account-overrides #(mapv (update :automatically-paid-when-due #(mapv (fn [apwd]
(fn [ao] {:id (:id apwd)
{:id (:id ao) :client apwd})
:client (:client ao) %))
:override (:account ao)})
%))
(update :schedule-payment-dom #(mapv
(fn [spdom]
{:id (:id spdom)
:client (:client spdom)
:override (:dom spdom)})
%))
(update :terms-overrides #(mapv
(fn [to]
{:id (:id to)
:client (:client to)
:override (:terms to)})
%))
(update :automatically-paid-when-due #(mapv identity %))
(update :hidden #(if (nil? %) (update :hidden #(if (nil? %)
false false
%))))) %)))))
:dispatch [::modal/modal-requested :dispatch [::modal/modal-requested
{:title "Vendor" {:title "Vendor"
:class "is-wide"
:body [vendor-dialog] :body [vendor-dialog]
:class "semi-wide"
:confirm {:value "Save Vendor" :confirm {:value "Save Vendor"
:status-from [::status/single ::vendor-form] :status-from [::status/single ::vendor-form]
:class "is-primary" :class "is-primary"
@@ -490,7 +383,6 @@
{:db (-> db (forms/start-form ::select-vendor-form {})) {:db (-> db (forms/start-form ::select-vendor-form {}))
:dispatch [::modal/modal-requested :dispatch [::modal/modal-requested
{:title "Select Vendor" {:title "Select Vendor"
:class "is-wide"
:body [select-vendor-form-content] :body [select-vendor-form-content]
:confirm {:value "Choose a vendor" :confirm {:value "Choose a vendor"
:status-from [::status/single ::select-vendor-form] :status-from [::status/single ::select-vendor-form]

View File

@@ -139,12 +139,12 @@
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field [:client-overrides] :field [:client-overrides]
:template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:style {:width "20em"} :style {:width "13em"}
:entity->text :name :entity->text :name
:type "typeahead-v3" :type "typeahead-v3"
:field [:client]}] :field [:client]}]
[:input.input {:type "text" [:input.input {:type "text"
:style {:width "20em"} :style {:width "15em"}
:placeholder "Bubblegum" :placeholder "Bubblegum"
:field [:name]}] :field [:name]}]
]}]) ]}])

View File

@@ -9,6 +9,7 @@
[auto-ap.views.components.address :refer [address2-field]] [auto-ap.views.components.address :refer [address2-field]]
[react-signature-canvas] [react-signature-canvas]
[auto-ap.views.components.typeahead :refer [typeahead-v3]] [auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.level :refer [left-stack]]
[auto-ap.views.components.typeahead.vendor [auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]] :refer [search-backed-typeahead]]
[auto-ap.views.utils [auto-ap.views.utils
@@ -350,8 +351,6 @@
(#{:credit ":credit"} type) [:span.icon-credit-card-1] (#{:credit ":credit"} type) [:span.icon-credit-card-1]
:else [:span.icon-accounting-bill])]] :else [:span.icon-accounting-bill])]]
#_[:div.level-item
]
[:div.level-item code ": " name]] [:div.level-item code ": " name]]
[:div.level-right [:div.level-right
[:div.level-item [:div.level-item
@@ -647,7 +646,7 @@
:style { :width "15em"}}] :style { :width "15em"}}]
[:input.input {:field [:location] [:input.input {:field [:location]
:placeholder "DT" :placeholder "DT"
:maxlength 2 :max-length 2
:style { :width "4em"}}]]}]]]) :style { :width "4em"}}]]}]]])
(defn bank-accounts-section [] (defn bank-accounts-section []
@@ -674,41 +673,35 @@
[form-builder/section {:title "Cash Flow"} [form-builder/section {:title "Cash Flow"}
[:label.label (str "Week A (" next-week-a ")")] [:label.label (str "Week A (" next-week-a ")")]
[:div.level [left-stack
[:div.level-left [form-builder/field
[:div.level-item "Regular Credits"
[form-builder/field [:input.input {:type "number"
"Regular Credits" :style {:width "10em"}
[:input.input {:type "number" :placeholder "500.00"
:style {:width "10em"} :field [:week-a-credits]
:placeholder "500.00" :step "0.01"}]]
:field [:week-a-credits] [form-builder/field
:step "0.01"}]]] "Regular Debits"
[:div.level-item [:input.input {:type "number"
[form-builder/field :style {:width "10em"}
"Regular Debits" :placeholder "150.00"
[:input.input {:type "number" :field [:week-a-debits]
:style {:width "10em"} :step "0.01"}]]]
:placeholder "150.00"
:field [:week-a-debits]
:step "0.01"}]]]]]
[:label.label (str "Week B (" next-week-b ")")] [:label.label (str "Week B (" next-week-b ")")]
[:div.level [left-stack
[:div.level-left [form-builder/field "Regular Credits"
[:div.level-item [:input.input {:type "number"
[form-builder/field "Regular Credits" :style {:width "10em"}
[:input.input {:type "number" :placeholder "1000.00"
:style {:width "10em"} :field [:week-b-credits]
:placeholder "1000.00" :step "0.01"}]]
:field [:week-b-credits] [form-builder/field "Regular Debits"
:step "0.01"}]]] [:input.input {:type "number"
[:div.level-item :style {:width "10em"}
[form-builder/field "Regular Debits" :placeholder "250.00"
[:input.input {:type "number" :field [:week-b-debits]
:style {:width "10em"} :step "0.01"}]]]
:placeholder "250.00"
:field [:week-b-debits]
:step "0.01"}]]]]]
[:div.field [:div.field
[:label.label "Forecasted transactions"] [:label.label "Forecasted transactions"]

View File

@@ -148,55 +148,59 @@
value value
(conj value {:key (random-uuid) (conj value {:key (random-uuid)
:new? true}))] :new? true}))]
[:div {:style {:margin-bottom "1em"}} [:div {:style {:margin-bottom "0.25em"}}
(for [[i override] (map vector (range) value) (for [[i override] (map vector (range) value)
:let [is-disabled? (if (= false allow-change?) :let [is-disabled? (if (= false allow-change?)
(not (boolean (:new? override))) (not (boolean (:new? override)))
nil)] nil)]
] ]
^{:key (:key override)} ^{:key (:key override)}
[:div.level [:div.level {:style {:margin-bottom "0.25em"}}
[:div.level-left {:style (when (and (= i (dec (count value))) [:div.level-left {:style {:padding "0.5em 1em"}
(:new? override)) :class (cond
{:background "#EEE" (and (= i (dec (count value)))
:padding "0.25em 1em 0.25em 0em"})} (:new? override))
[:div.level-item "has-background-light"
(if (:new? override)
[:div.icon.is-medium {:class (when (not= i (dec (count value)))
"has-text-info")}
[:i.fa.fa-plus]]
[:div.icon.is-medium])]
[:<> (for [[idx template] (map vector (range ) template)]
^{:key idx}
[:div.level-item (:new? override)
(update template 1 assoc "has-background-info-light"
:value (let [value (get-in override (get-in template [1 :field])) ;; TODO this is really ugly to support maps or strings :else
value (if (map? value) "")}
(dissoc value :key :new?) (let [template (if (fn? template)
value)] (template override)
(if (= value {}) template)]
nil
value)) [:<> (for [[idx template] (map vector (range ) template)]
:disabled (or is-disabled? (get-in template [1 :disabled])) ^{:key idx}
:on-change (fn [e]
(reset! value-repr [:div.level-item
(into [] (update template 1 assoc
(filter (fn [r] :value (let [value (get-in override (get-in template [1 :field])) ;; TODO this is really ugly to support maps or strings
(not= [:key :new?] (keys r))) value (if (map? value)
(assoc-in value (into [i] (get-in template [1 :field])) (dissoc value :key :new?)
(let [this-value (if (and e (.. e -target)) value)]
(.. e -target -value ) (if (= value {})
e)] nil
(if (map? this-value) value))
(update this-value :key (fnil identity (random-uuid))) :disabled (or is-disabled? (get-in template [1 :disabled]))
this-value)) )))) :on-change (fn [e]
(on-change (mapv (reset! value-repr
(fn [v] (into []
(dissoc v :new? :key)) (filter (fn [r]
@value-repr))))]) (not= [:key :new?] (keys r)))
] (assoc-in value
(into [i] (get-in template [1 :field]))
(let [this-value (if (and e (.. e -target))
(.. e -target -value )
e)]
(if (map? this-value)
(update this-value :key (fnil identity (random-uuid)))
this-value)) ))))
(on-change (mapv
(fn [v]
(dissoc v :new? :key))
@value-repr))))])
])
(when-not disable-remove? (when-not disable-remove?
[:div.level-item [:div.level-item
[:a.button.level-item [:a.button.level-item
@@ -537,3 +541,18 @@
(defn account->match-text [x] (defn account->match-text [x]
(str (:numeric-code x) " - " (:name x))) (str (:numeric-code x) " - " (:name x)))
(defn str->int [x]
(cond
(nil? x)
nil
(and (string? x)
(str/blank? x))
nil
(string? x)
(js/parseInt x)
:else
x))

View File

@@ -17,13 +17,15 @@
(t/deftest yodlee->transaction (t/deftest yodlee->transaction
(t/testing "Should parse dates" (t/testing "Should parse dates"
(t/is (= #inst "2021-01-01T00:00:00-08:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-01-01"))))) (t/is (= #inst "2021-01-01T00:00:00-08:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-01-01") false))))
(t/is (= #inst "2021-06-01T00:00:00-07:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-06-01")))))) (t/is (= #inst "2021-06-01T00:00:00-07:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-06-01") false)))))
(t/testing "Should invert amount for debits" (t/testing "Should invert amount for debits"
(t/is (= -12.0 (:transaction/amount (sut/yodlee->transaction (assoc base-transaction (t/is (= -12.0 (:transaction/amount (sut/yodlee->transaction (assoc base-transaction
:amount {:amount 12.0} :amount {:amount 12.0}
:baseType "DEBIT"))))) :baseType "DEBIT")
false))))
(t/is (= 12.0 (:transaction/amount (sut/yodlee->transaction (assoc base-transaction (t/is (= 12.0 (:transaction/amount (sut/yodlee->transaction (assoc base-transaction
:amount {:amount 12.0} :amount {:amount 12.0}
:baseType "CREDIT"))))))) :baseType "CREDIT")
false))))))

View File

@@ -1,4 +1,4 @@
(ns auto-ap.routes.invoices-test (ns auto-ap.routes.invoice-test
(:require (:require
[auto-ap.datomic :refer [uri conn]] [auto-ap.datomic :refer [uri conn]]
[auto-ap.datomic.migrate :as m] [auto-ap.datomic.migrate :as m]

View File

@@ -1,3 +0,0 @@
(ns auto-ap.routes.invoice-test
(:require [clojure.test :as t]))