standardized clients.
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
:headers {"Content-Type" "application/edn"}})
|
||||
(do (log/error "GraphQL error" e)
|
||||
{:status 500
|
||||
:body (pr-str {:errors [(merge {:message (.getMessage e)} (ex-data e))]})
|
||||
:body (pr-str {:errors [(merge {:message (str "Unhandled error:" (str e))} (ex-data e))]})
|
||||
:headers {"Content-Type" "application/edn"}}))))))
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
(re-frame/reg-event-fx
|
||||
::edit
|
||||
(fn [{:keys [db]} [_ client-id]]
|
||||
(println "EDITING")
|
||||
{:dispatch [::events/modal-status :auto-ap.views.pages.admin.clients/edit {:visible? true}]
|
||||
:db (assoc-in db [:admin :client]
|
||||
(get (:clients db) client-id))}))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
(ns auto-ap.views.pages.admin.clients
|
||||
(:require-macros [cljs.core.async.macros :refer [go]]
|
||||
[clojure.string :as str])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
|
||||
[reagent.core :as reagent]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.string :as str]
|
||||
@@ -17,605 +14,81 @@
|
||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
||||
[auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field nf]]
|
||||
[auto-ap.views.components.modal :refer [action-modal]]
|
||||
[auto-ap.views.pages.admin.clients.table :as table]
|
||||
[auto-ap.views.pages.admin.clients.form :as form]
|
||||
[cljs.reader :as edn]
|
||||
[auto-ap.routes :as routes]
|
||||
[bidi.bidi :as bidi]))
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.status :as status]
|
||||
[auto-ap.views.components.grid :as grid]
|
||||
[auto-ap.views.pages.admin.clients.side-bar :as side-bar]
|
||||
[vimsical.re-frame.fx.track :as track]))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::mounted
|
||||
(fn [{:keys [db]} _]
|
||||
{::track/register {:id ::params
|
||||
:subscription [::params]
|
||||
:event-fn (fn [params] [::params-change params])}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::unmounted
|
||||
(fn [{:keys [db]} _]
|
||||
{:db (dissoc db ::table/params ::side-bar/filter-params)
|
||||
::track/dispose {:id ::params}}))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::params
|
||||
:<- [::table/params]
|
||||
:<- [::side-bar/filter-params]
|
||||
(fn [[table-params filter-params]]
|
||||
(cond-> {}
|
||||
(seq filter-params) (merge filter-params)
|
||||
(seq table-params) (merge table-params))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::new
|
||||
(fn [db [_ client-id]]
|
||||
(-> db
|
||||
(forms/start-form ::form {:bank-accounts []}))))
|
||||
|
||||
|
||||
(forms/start-form ::form/form {:bank-accounts []}))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::edit-client-clicked
|
||||
(fn [{:keys [db]} [_ client-id]]
|
||||
{:db (-> db
|
||||
(forms/stop-form ::form)
|
||||
(forms/start-form ::form (get (:clients db) client-id)))}))
|
||||
::params-change
|
||||
(fn [_ [_ params]]
|
||||
{:set-uri-params params}))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::can-submit
|
||||
:<- [::forms/form ::form]
|
||||
(fn [{:keys [data status]} _]
|
||||
(println (s/explain ::entity/client data))
|
||||
(s/valid? ::entity/client data)))
|
||||
::page
|
||||
:<- [::params]
|
||||
:<- [::subs/clients]
|
||||
(fn [[params all-clients]]
|
||||
(let [matching-clients (cond->> all-clients
|
||||
(not-empty (:name params)) (filter #(str/includes? (str/lower-case (or (:name %) ""))
|
||||
(str/lower-case (:name params))))
|
||||
(not-empty (:code params)) (filter #(= (str/lower-case (or (:code %) ""))
|
||||
(str/lower-case (:code params)))))]
|
||||
(assoc (grid/virtual-paginate-controls (:start params ) matching-clients)
|
||||
:data (grid/virtual-paginate (:start params) matching-clients)))))
|
||||
|
||||
|
||||
(re-frame/reg-sub
|
||||
::new-client-request
|
||||
:<- [::forms/form ::form]
|
||||
(fn [{new-client-data :data} _]
|
||||
{:id (:id new-client-data),
|
||||
:name (:name new-client-data)
|
||||
:code (:code new-client-data) ;; TODO add validation can't change
|
||||
:email (:email new-client-data)
|
||||
:locations (:locations new-client-data)
|
||||
:matches (vec (: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 {: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))}
|
||||
:forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}]
|
||||
{:id id
|
||||
:day-of-month day-of-month
|
||||
:identifier identifier
|
||||
:amount amount})
|
||||
(:forecasted-transactions new-client-data))
|
||||
:bank-accounts (map (fn [{:keys [number name check-number include-in-reports type id code bank-name routing bank-code new? sort-order visible yodlee-account-id locations]}]
|
||||
{:number number
|
||||
:name name
|
||||
:check-number check-number
|
||||
:include-in-reports include-in-reports
|
||||
:type type
|
||||
:id id
|
||||
:sort-order sort-order
|
||||
:visible visible
|
||||
:locations (vec locations)
|
||||
:yodlee-account-id (when yodlee-account-id
|
||||
(js/parseInt yodlee-account-id))
|
||||
: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))}))
|
||||
(def admin-clients-content
|
||||
(with-meta
|
||||
(fn []
|
||||
[:div
|
||||
[:div
|
||||
[:h1.title "Clients"]
|
||||
[:div.is-pulled-right
|
||||
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::new])} "New client"]]
|
||||
[table/clients-table {:page @(re-frame/subscribe [::page])
|
||||
:status @(re-frame/subscribe [::status/single ::page])}]]])
|
||||
{:component-did-mount #(re-frame/dispatch [::mounted])
|
||||
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])}))
|
||||
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save-new-client
|
||||
[(forms/in-form ::form)]
|
||||
(fn [{{new-client-data :data :as new-client-form} :db} _]
|
||||
|
||||
(let [new-client-req @(re-frame/subscribe [::new-client-request])
|
||||
user @(re-frame/subscribe [::subs/token])]
|
||||
|
||||
(if (s/valid? ::entity/client new-client-req)
|
||||
|
||||
{:db (-> new-client-form
|
||||
(assoc :status :loading)
|
||||
(assoc :error nil))
|
||||
:graphql
|
||||
{:token user
|
||||
:query-obj {:venia/operation {:operation/type :mutation
|
||||
:operation/name "EditClient"}
|
||||
:venia/queries [{:query/data [:edit-client
|
||||
{:edit-client new-client-req}
|
||||
[:id :name :code :email :locations :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits
|
||||
[:location-matches [:location :match]]
|
||||
[:address [:street1 :street2 :city :state :zip]]
|
||||
[:forecasted-transactions [:id :amount :identifier :day-of-month]]
|
||||
[:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]}
|
||||
:on-success [::save-complete]
|
||||
:on-error [::forms/save-error ::new-client]}}
|
||||
{:db new-client-form}))))
|
||||
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::save-complete
|
||||
(fn [db [_ client]]
|
||||
(-> 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)))))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-location
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :locations conj (:location client))
|
||||
(dissoc :location))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-location-to-bank-account
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ which-account]]
|
||||
(println client which-account)
|
||||
(-> client
|
||||
(update-in [:bank-accounts which-account :locations] #(conj (or % #{}) (get-in client [:bank-accounts which-account :location-select])))
|
||||
(update-in [:bank-accounts which-account] dissoc :location-select))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :matches conj (:match client))
|
||||
(update :matches set)
|
||||
(dissoc :match))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::remove-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ which]]
|
||||
(-> client
|
||||
(update :matches set)
|
||||
(update :matches disj which))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-location-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :location-matches conj (:location-match client))
|
||||
(dissoc :location-match))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-forecasted-transaction
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :forecasted-transactions conj (assoc (:new-forecasted-transaction client) :temp-id (random-uuid)))
|
||||
(dissoc :new-forecasted-transaction))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::remove-forecasted-transaction
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ which]]
|
||||
|
||||
(-> client
|
||||
(update :forecasted-transactions #(transduce
|
||||
(filter (fn [x] (and (not= (:temp-id x) which)
|
||||
(not= (:id x) which))))
|
||||
|
||||
conj
|
||||
[]
|
||||
%)))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::remove-location-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ i]]
|
||||
(-> client
|
||||
(update :location-matches (fn [lm]
|
||||
(->> lm
|
||||
(map vector (range))
|
||||
(filter (fn [[index item]]
|
||||
(not= index i)))
|
||||
(map second)))))))
|
||||
|
||||
(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)))
|
||||
#_(update db :bank-accounts
|
||||
(fn [bas]
|
||||
(filter #(not= (:code %) code) bas)))))
|
||||
|
||||
(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 %)))))
|
||||
|
||||
(defn clients-table []
|
||||
(let [clients (re-frame/subscribe [::subs/clients])
|
||||
editing-client (:client @(re-frame/subscribe [::subs/admin]))]
|
||||
[:table {:class "table", :style {:width "100%"}}
|
||||
[:thead
|
||||
[:tr
|
||||
[:th {:style {:width "50%"}} "Name"]
|
||||
[:th {:style {:width "10%"}} "Name"]
|
||||
[:th {:style {:width "20%"}} "Locations"]
|
||||
[:th {:style {:width "20%"}} "Email"]]]
|
||||
[:tbody (for [{:keys [id name email code locations] :as c} @clients]
|
||||
^{:key (str name "-" id )}
|
||||
[:tr {:on-click (fn [] (re-frame/dispatch [::edit-client-clicked id]))
|
||||
:style {"cursor" "pointer"}}
|
||||
[:td name]
|
||||
[:td code]
|
||||
[:td (str/join ", " locations)]
|
||||
[:td email]])]]))
|
||||
|
||||
|
||||
|
||||
(defn admin-clients-content []
|
||||
[:div
|
||||
(let [clients (re-frame/subscribe [::subs/clients])
|
||||
editing-client (:client @(re-frame/subscribe [::subs/admin]))]
|
||||
[:div
|
||||
[:h1.title "Clients"]
|
||||
[:div.is-pulled-right
|
||||
[:a.button.is-primary.is-large {:on-click (dispatch-event [::new])} "New client"]]
|
||||
[clients-table]])])
|
||||
|
||||
(def client-form
|
||||
(forms/vertical-form {:can-submit [::can-submit]
|
||||
:change-event [::forms/change ::form]
|
||||
:submit-event [::save-new-client ]
|
||||
:id ::form}))
|
||||
|
||||
(defn bank-account-card [new-client {:keys [active? new? type visible code name number check-number id sort-order] :as bank-account} first? last?]
|
||||
(let [{:keys [form field raw-field error-notification submit-button ]} client-form]
|
||||
[:div.card {:style {:margin-bottom "1em"}}
|
||||
[:header.card-header
|
||||
[:p.card-header-title {:style {:text-overflow "ellipsis"}}
|
||||
[: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])]
|
||||
code ": " name]
|
||||
[:p {:style {:padding "0.75em 0.25em"}}
|
||||
[:a.button.is-outlined {:on-click (dispatch-event [::toggle-visible sort-order])} [:span.icon (if visible
|
||||
[:span.fa.fa-eye]
|
||||
[:span.fa.fa-eye-slash]
|
||||
)]]]
|
||||
(when-not last?
|
||||
[:p {:style {:padding "0.75em 0.25em"}}
|
||||
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::sort-swapped sort-order (inc sort-order)])} [:span.icon [:span.fa.fa-sort-down]]]])
|
||||
(when-not first?
|
||||
[:p {:style {:padding "0.75em 0.25em"}}
|
||||
[:a.button.is-primary.is-outlined {: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"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[:div.control
|
||||
[:p.help "Account Code"]
|
||||
(if new?
|
||||
[:div.field.has-addons.is-extended
|
||||
[:p.control [:a.button.is-static (:code new-client) "-" ]]
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "code"
|
||||
:field [:bank-accounts sort-order :code]
|
||||
:spec ::entity/code}]]]]
|
||||
[:div.field [:p.control code]])]
|
||||
|
||||
[field "Nickname"
|
||||
[:input.input {:placeholder "BOA Checking #1"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :name]}]]]
|
||||
(when (#{:check ":check"} type )
|
||||
[:div
|
||||
|
||||
[:label.label "Bank"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Bank Name"
|
||||
[:input.input {:placeholder "Bank of America"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :bank-name]}]]
|
||||
[field "Routing #"
|
||||
[:input.input {:placeholder "104819123"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :routing]}]]
|
||||
[field "Bank code"
|
||||
[:input.input {:placeholder "12/10123"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :bank-code]}]]]
|
||||
|
||||
[:label.label "Checking account"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Account #"
|
||||
[:input.input {:placeholder "123456789"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :number]}]]
|
||||
|
||||
[field "Check Number"
|
||||
[:input.input {:placeholder "10000"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :check-number]}]]]
|
||||
[field "Yodlee Account"
|
||||
[:input.input {:placeholder "Yodlee Account #"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :yodlee-account-id]}]]
|
||||
|
||||
])
|
||||
(when (#{:credit ":credit"} type )
|
||||
[:div
|
||||
|
||||
|
||||
[:label.label "Account"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Bank Name"
|
||||
[:input.input {:placeholder "Bank of America"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :bank-name]}]]]
|
||||
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Account #"
|
||||
[:input.input {:placeholder "123456789"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :number]}]]]
|
||||
|
||||
[field "Yodlee Account"
|
||||
[:input.input {:placeholder "Yodlee Account #"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :yodlee-account-id]}]]])
|
||||
[:div.field
|
||||
[:label.label "Locations"]
|
||||
[:div.control
|
||||
[:p.help "If this account is location-specific, add the valid locations"]
|
||||
[:div.field.has-addons
|
||||
[:p.control
|
||||
[:div.select
|
||||
[raw-field
|
||||
[:select {:type "select"
|
||||
:style {:width "7em"}
|
||||
:field [:bank-accounts sort-order :location-select]
|
||||
:allow-nil? true
|
||||
:spec (set (get-in new-client [:locations]))}
|
||||
(map (fn [l] ^{:key l} [:option {:value l} l]) (get-in new-client [:locations]))]]]]
|
||||
[:p.control {:on-click (dispatch-event [::add-location-to-bank-account sort-order]) } [:a.button "Add"]]]]
|
||||
|
||||
(if-let [locations (seq (get-in new-client [:bank-accounts sort-order :locations]))]
|
||||
[:ul
|
||||
(for [location locations]
|
||||
^{:key location} [:li location ])]
|
||||
[:i "This account applies to all locations"])]
|
||||
[:div.field
|
||||
[:label.checkbox
|
||||
[raw-field
|
||||
[:input {:type "checkbox"
|
||||
:field [:bank-accounts sort-order :include-in-reports]}]]
|
||||
" 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"])])])
|
||||
)
|
||||
|
||||
|
||||
(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)))
|
||||
|
||||
(defn new-client-form []
|
||||
(let [{new-client :data } @(re-frame/subscribe [::forms/form ::form])
|
||||
{:keys [form field raw-field error-notification submit-button ]} client-form
|
||||
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")]
|
||||
|
||||
[side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])}
|
||||
[form {:title "Add client"}
|
||||
[field "Name"
|
||||
[:input.input {:type "text"
|
||||
:field [:name]
|
||||
:spec ::entity/name
|
||||
}]]
|
||||
|
||||
[:div.field
|
||||
[:p.help "Client code"
|
||||
]
|
||||
(if (:id new-client)
|
||||
[:div.control
|
||||
(:code new-client)]
|
||||
[:div.control
|
||||
[raw-field
|
||||
[:input.input {:type "code"
|
||||
:field :code
|
||||
:spec ::entity/code}]]])]
|
||||
|
||||
|
||||
[field "Email"
|
||||
[:input.input {:type "email"
|
||||
:field :email
|
||||
:spec ::entity/email}]]
|
||||
|
||||
[:div.field
|
||||
[:p.help "Matches"]
|
||||
[:div.control
|
||||
[:div.field.has-addons
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:field :match}]]]
|
||||
[:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-match])} "Add"]]]]
|
||||
[:ul
|
||||
(for [match (:matches new-client)]
|
||||
^{:key match} [:li match [:a {:on-click (dispatch-event [::remove-match match])} [:span.icon [:span.fa.fa-times]]]])]]
|
||||
|
||||
|
||||
|
||||
[:div.field
|
||||
[:p.help "Locations"]
|
||||
[:div.control
|
||||
[:div.field.has-addons
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:field :location}]]]
|
||||
[:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location])} "Add"]]]
|
||||
[:ul
|
||||
(for [location (:locations new-client)]
|
||||
^{:key location} [:li location ])]]]
|
||||
|
||||
[:div.field
|
||||
[:p.help "Location matches"]
|
||||
[:div.control
|
||||
[:div.field.has-addons
|
||||
|
||||
[:p.control
|
||||
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:placeholder "San Jose"
|
||||
:field [:location-match :match]}]]]
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:placeholder "DT"
|
||||
:field [:location-match :location]}]]]
|
||||
[:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location-match])} "Add"]]]
|
||||
|
||||
[:ul
|
||||
(for [[index {:keys [location match]}] (map vector (range) (:location-matches new-client))]
|
||||
^{:key index} [:li match "->" location [:a {:on-click (dispatch-event [::remove-location-match index])} [:span.icon
|
||||
[:span.fa.fa-times]]]])]]]
|
||||
|
||||
[:div {:style {:padding-bottom "0.75em" :padding-top "0.75em"}}
|
||||
[:h2.subtitle "Address"]
|
||||
[address-field {:field [:address]
|
||||
:event [::forms/change ::form]
|
||||
:subscription new-client}]]
|
||||
|
||||
[:h2.subtitle "Bank accounts"]
|
||||
(for [bank-account (sort-by :sort-order (:bank-accounts new-client))]
|
||||
^{:key (: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.columns
|
||||
[:div.column.is-third
|
||||
[:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :credit])} "Add Credit Account"]]
|
||||
[:div.column.is-third
|
||||
[:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :check])} "Add Checking Account"]]
|
||||
[:div.column.is-third
|
||||
[:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]]
|
||||
|
||||
[:h2.subtitle "Cash flow"]
|
||||
[:label.label (str "Week A (" next-week-a ")")]
|
||||
[field "Regular Credits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "500.00"
|
||||
:field [:week-a-credits]
|
||||
:step "0.01"}]]
|
||||
[field "Regular Debits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "150.00"
|
||||
:field [:week-a-debits]
|
||||
:step "0.01"}]]
|
||||
[:label.label (str "Week B (" next-week-b ")")]
|
||||
[field "Regular Credits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "1000.00"
|
||||
:field [:week-b-credits]
|
||||
:step "0.01"}]]
|
||||
[field "Regular Debits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "250.00"
|
||||
:field [:week-b-debits]
|
||||
:step "0.01"}]]
|
||||
[:div.field
|
||||
[:label.label "Forecasted transactions"]
|
||||
[:div.control
|
||||
[horizontal-field
|
||||
nil
|
||||
|
||||
[:p.control
|
||||
[:p.help "Identifier"]
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:placeholder "Identifier"
|
||||
:field [:new-forecasted-transaction :identifier]}]]]
|
||||
[:p.control
|
||||
[:p.help "Day of month"]
|
||||
[raw-field
|
||||
[:input.input {:type "number"
|
||||
:placeholder "Day of month"
|
||||
:step "1"
|
||||
:field [:new-forecasted-transaction :day-of-month]}]]]
|
||||
[:p.control
|
||||
[:p.help "Amount"]
|
||||
[raw-field
|
||||
[:input.input {:type "number"
|
||||
:placeholder "250.00"
|
||||
:field [:new-forecasted-transaction :amount]
|
||||
:step "0.01"}]]]
|
||||
[:a.button {:on-click (dispatch-event [::add-forecasted-transaction])} "Add"]]]
|
||||
|
||||
[:ul
|
||||
(for [forecasted-transaction (:forecasted-transactions new-client)]
|
||||
^{:key (or (:id forecasted-transaction)
|
||||
(:temp-id forecasted-transaction))}
|
||||
[:li (:identifier forecasted-transaction) ": " (nf (:amount forecasted-transaction)) " on day " (:day-of-month forecasted-transaction) " of the month"
|
||||
[:a {:on-click (dispatch-event [::remove-forecasted-transaction (or (:id forecasted-transaction)
|
||||
(:temp-id forecasted-transaction))])} [:span.icon [:span.fa.fa-times]]]])]]
|
||||
|
||||
[error-notification]
|
||||
[submit-button "Save"]]]))
|
||||
|
||||
(defn admin-clients-page []
|
||||
(let [{:keys [active?]} @(re-frame/subscribe [::forms/form ::form])]
|
||||
[side-bar-layout {:side-bar [admin-side-bar {}]
|
||||
(let [{:keys [active?]} @(re-frame/subscribe [::forms/form ::form/form])]
|
||||
[side-bar-layout {:side-bar [admin-side-bar {}
|
||||
[side-bar/client-side-bar]]
|
||||
:main [admin-clients-content]
|
||||
:right-side-bar [appearing-side-bar {:visible? active?} [new-client-form]] }]))
|
||||
:right-side-bar [appearing-side-bar {:visible? active?} [form/new-client-form]] }]))
|
||||
|
||||
|
||||
561
src/cljs/auto_ap/views/pages/admin/clients/form.cljs
Normal file
561
src/cljs/auto_ap/views/pages/admin/clients/form.cljs
Normal file
@@ -0,0 +1,561 @@
|
||||
(ns auto-ap.views.pages.admin.clients.form
|
||||
(:require [auto-ap.entities.clients :as entity]
|
||||
[auto-ap.forms :as forms]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.views.components.address :refer [address-field]]
|
||||
[auto-ap.views.components.layouts :refer [side-bar]]
|
||||
[auto-ap.views.utils :refer [dispatch-event horizontal-field nf]]
|
||||
[cljs-time.coerce :as coerce]
|
||||
[cljs-time.core :as t]
|
||||
[clojure.spec.alpha :as s]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::can-submit
|
||||
:<- [::forms/form ::form]
|
||||
(fn [{:keys [data status]} _]
|
||||
(s/valid? ::entity/client data)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::new-client-request
|
||||
:<- [::forms/form ::form]
|
||||
(fn [{new-client-data :data} _]
|
||||
{:id (:id new-client-data),
|
||||
:name (:name new-client-data)
|
||||
:code (:code new-client-data) ;; TODO add validation can't change
|
||||
:email (:email new-client-data)
|
||||
:locations (:locations new-client-data)
|
||||
:matches (vec (: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 {: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))}
|
||||
:forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}]
|
||||
{:id id
|
||||
:day-of-month day-of-month
|
||||
:identifier identifier
|
||||
:amount amount})
|
||||
(:forecasted-transactions new-client-data))
|
||||
:bank-accounts (map (fn [{:keys [number name check-number include-in-reports type id code bank-name routing bank-code new? sort-order visible yodlee-account-id locations]}]
|
||||
{:number number
|
||||
:name name
|
||||
:check-number check-number
|
||||
:include-in-reports include-in-reports
|
||||
:type type
|
||||
:id id
|
||||
:sort-order sort-order
|
||||
:visible visible
|
||||
:locations (vec locations)
|
||||
:yodlee-account-id (when yodlee-account-id
|
||||
(js/parseInt yodlee-account-id))
|
||||
: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
|
||||
::editing
|
||||
(fn [{:keys [db]} [_ client-id]]
|
||||
{:db (-> db
|
||||
(forms/stop-form ::form)
|
||||
(forms/start-form ::form (get (:clients db) client-id)))}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save-new-client
|
||||
[(forms/in-form ::form)]
|
||||
(fn [{{new-client-data :data :as new-client-form} :db} _]
|
||||
|
||||
(let [new-client-req @(re-frame/subscribe [::new-client-request])
|
||||
user @(re-frame/subscribe [::subs/token])]
|
||||
|
||||
(if (s/valid? ::entity/client new-client-req)
|
||||
|
||||
{:db (-> new-client-form
|
||||
(assoc :status :loading)
|
||||
(assoc :error nil))
|
||||
:graphql
|
||||
{:token user
|
||||
:query-obj {:venia/operation {:operation/type :mutation
|
||||
:operation/name "EditClient"}
|
||||
:venia/queries [{:query/data [:edit-client
|
||||
{:edit-client new-client-req}
|
||||
[:id :name :code :email :locations :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits
|
||||
[:location-matches [:location :match]]
|
||||
[:address [:street1 :street2 :city :state :zip]]
|
||||
[:forecasted-transactions [:id :amount :identifier :day-of-month]]
|
||||
[:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]}
|
||||
:on-success [::save-complete]
|
||||
:on-error [::forms/save-error ::new-client]}}
|
||||
{:db new-client-form}))))
|
||||
(re-frame/reg-event-db
|
||||
::save-complete
|
||||
(fn [db [_ client]]
|
||||
(-> 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)))))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-location
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :locations conj (:location client))
|
||||
(dissoc :location))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-location-to-bank-account
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ which-account]]
|
||||
(println client which-account)
|
||||
(-> client
|
||||
(update-in [:bank-accounts which-account :locations] #(conj (or % #{}) (get-in client [:bank-accounts which-account :location-select])))
|
||||
(update-in [:bank-accounts which-account] dissoc :location-select))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :matches conj (:match client))
|
||||
(update :matches set)
|
||||
(dissoc :match))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::remove-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ which]]
|
||||
(-> client
|
||||
(update :matches set)
|
||||
(update :matches disj which))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-new-location-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :location-matches conj (:location-match client))
|
||||
(dissoc :location-match))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::add-forecasted-transaction
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client _]
|
||||
(-> client
|
||||
(update :forecasted-transactions conj (assoc (:new-forecasted-transaction client) :temp-id (random-uuid)))
|
||||
(dissoc :new-forecasted-transaction))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::remove-forecasted-transaction
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ which]]
|
||||
|
||||
(-> client
|
||||
(update :forecasted-transactions #(transduce
|
||||
(filter (fn [x] (and (not= (:temp-id x) which)
|
||||
(not= (:id x) which))))
|
||||
|
||||
conj
|
||||
[]
|
||||
%)))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::remove-location-match
|
||||
[(forms/in-form ::form) (re-frame/path [:data])]
|
||||
(fn [client [_ i]]
|
||||
(-> client
|
||||
(update :location-matches (fn [lm]
|
||||
(->> lm
|
||||
(map vector (range))
|
||||
(filter (fn [[index item]]
|
||||
(not= index i)))
|
||||
(map second)))))))
|
||||
|
||||
(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)))
|
||||
#_(update db :bank-accounts
|
||||
(fn [bas]
|
||||
(filter #(not= (:code %) code) bas)))))
|
||||
|
||||
(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 client-form
|
||||
(forms/vertical-form {:can-submit [::can-submit]
|
||||
:change-event [::forms/change ::form]
|
||||
:submit-event [::save-new-client ]
|
||||
:id ::form}))
|
||||
|
||||
(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)))
|
||||
|
||||
|
||||
(defn bank-account-card [new-client {:keys [active? new? type visible code name number check-number id sort-order] :as bank-account} first? last?]
|
||||
(let [{:keys [form field raw-field error-notification submit-button ]} client-form]
|
||||
[:div.card {:style {:margin-bottom "1em"}}
|
||||
[:header.card-header
|
||||
[:p.card-header-title {:style {:text-overflow "ellipsis"}}
|
||||
[: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])]
|
||||
code ": " name]
|
||||
[:p {:style {:padding "0.75em 0.25em"}}
|
||||
[:a.button.is-outlined {:on-click (dispatch-event [::toggle-visible sort-order])} [:span.icon (if visible
|
||||
[:span.fa.fa-eye]
|
||||
[:span.fa.fa-eye-slash]
|
||||
)]]]
|
||||
(when-not last?
|
||||
[:p {:style {:padding "0.75em 0.25em"}}
|
||||
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::sort-swapped sort-order (inc sort-order)])} [:span.icon [:span.fa.fa-sort-down]]]])
|
||||
(when-not first?
|
||||
[:p {:style {:padding "0.75em 0.25em"}}
|
||||
[:a.button.is-primary.is-outlined {: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"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[:div.control
|
||||
[:p.help "Account Code"]
|
||||
(if new?
|
||||
[:div.field.has-addons.is-extended
|
||||
[:p.control [:a.button.is-static (:code new-client) "-" ]]
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "code"
|
||||
:field [:bank-accounts sort-order :code]
|
||||
:spec ::entity/code}]]]]
|
||||
[:div.field [:p.control code]])]
|
||||
|
||||
[field "Nickname"
|
||||
[:input.input {:placeholder "BOA Checking #1"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :name]}]]]
|
||||
(when (#{:check ":check"} type )
|
||||
[:div
|
||||
|
||||
[:label.label "Bank"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Bank Name"
|
||||
[:input.input {:placeholder "Bank of America"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :bank-name]}]]
|
||||
[field "Routing #"
|
||||
[:input.input {:placeholder "104819123"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :routing]}]]
|
||||
[field "Bank code"
|
||||
[:input.input {:placeholder "12/10123"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :bank-code]}]]]
|
||||
|
||||
[:label.label "Checking account"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Account #"
|
||||
[:input.input {:placeholder "123456789"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :number]}]]
|
||||
|
||||
[field "Check Number"
|
||||
[:input.input {:placeholder "10000"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :check-number]}]]]
|
||||
[field "Yodlee Account"
|
||||
[:input.input {:placeholder "Yodlee Account #"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :yodlee-account-id]}]]
|
||||
|
||||
])
|
||||
(when (#{:credit ":credit"} type )
|
||||
[:div
|
||||
|
||||
|
||||
[:label.label "Account"]
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Bank Name"
|
||||
[:input.input {:placeholder "Bank of America"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :bank-name]}]]]
|
||||
|
||||
[horizontal-field
|
||||
nil
|
||||
[field "Account #"
|
||||
[:input.input {:placeholder "123456789"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :number]}]]]
|
||||
|
||||
[field "Yodlee Account"
|
||||
[:input.input {:placeholder "Yodlee Account #"
|
||||
:type "text"
|
||||
:field [:bank-accounts sort-order :yodlee-account-id]}]]])
|
||||
[:div.field
|
||||
[:label.label "Locations"]
|
||||
[:div.control
|
||||
[:p.help "If this account is location-specific, add the valid locations"]
|
||||
[:div.field.has-addons
|
||||
[:p.control
|
||||
[:div.select
|
||||
[raw-field
|
||||
[:select {:type "select"
|
||||
:style {:width "7em"}
|
||||
:field [:bank-accounts sort-order :location-select]
|
||||
:allow-nil? true
|
||||
:spec (set (get-in new-client [:locations]))}
|
||||
(map (fn [l] ^{:key l} [:option {:value l} l]) (get-in new-client [:locations]))]]]]
|
||||
[:p.control {:on-click (dispatch-event [::add-location-to-bank-account sort-order]) } [:a.button "Add"]]]]
|
||||
|
||||
(if-let [locations (seq (get-in new-client [:bank-accounts sort-order :locations]))]
|
||||
[:ul
|
||||
(for [location locations]
|
||||
^{:key location} [:li location ])]
|
||||
[:i "This account applies to all locations"])]
|
||||
[:div.field
|
||||
[:label.checkbox
|
||||
[raw-field
|
||||
[:input {:type "checkbox"
|
||||
:field [:bank-accounts sort-order :include-in-reports]}]]
|
||||
" 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 new-client-form []
|
||||
(let [{new-client :data } @(re-frame/subscribe [::forms/form ::form])
|
||||
{:keys [form field raw-field error-notification submit-button ]} client-form
|
||||
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")]
|
||||
|
||||
[side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])}
|
||||
[form {:title "Add client"}
|
||||
[field "Name"
|
||||
[:input.input {:type "text"
|
||||
:field [:name]
|
||||
:spec ::entity/name
|
||||
}]]
|
||||
|
||||
[:div.field
|
||||
[:p.help "Client code"
|
||||
]
|
||||
(if (:id new-client)
|
||||
[:div.control
|
||||
(:code new-client)]
|
||||
[:div.control
|
||||
[raw-field
|
||||
[:input.input {:type "code"
|
||||
:field :code
|
||||
:spec ::entity/code}]]])]
|
||||
|
||||
|
||||
[field "Email"
|
||||
[:input.input {:type "email"
|
||||
:field :email
|
||||
:spec ::entity/email}]]
|
||||
|
||||
[:div.field
|
||||
[:p.help "Matches"]
|
||||
[:div.control
|
||||
[:div.field.has-addons
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:field :match}]]]
|
||||
[:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-match])} "Add"]]]]
|
||||
[:ul
|
||||
(for [match (:matches new-client)]
|
||||
^{:key match} [:li match [:a {:on-click (dispatch-event [::remove-match match])} [:span.icon [:span.fa.fa-times]]]])]]
|
||||
|
||||
|
||||
|
||||
[:div.field
|
||||
[:p.help "Locations"]
|
||||
[:div.control
|
||||
[:div.field.has-addons
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:field :location}]]]
|
||||
[:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location])} "Add"]]]
|
||||
[:ul
|
||||
(for [location (:locations new-client)]
|
||||
^{:key location} [:li location ])]]]
|
||||
|
||||
[:div.field
|
||||
[:p.help "Location matches"]
|
||||
[:div.control
|
||||
[:div.field.has-addons
|
||||
|
||||
[:p.control
|
||||
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:placeholder "San Jose"
|
||||
:field [:location-match :match]}]]]
|
||||
[:p.control
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:placeholder "DT"
|
||||
:field [:location-match :location]}]]]
|
||||
[:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location-match])} "Add"]]]
|
||||
|
||||
[:ul
|
||||
(for [[index {:keys [location match]}] (map vector (range) (:location-matches new-client))]
|
||||
^{:key index} [:li match "->" location [:a {:on-click (dispatch-event [::remove-location-match index])} [:span.icon
|
||||
[:span.fa.fa-times]]]])]]]
|
||||
|
||||
[:div {:style {:padding-bottom "0.75em" :padding-top "0.75em"}}
|
||||
[:h2.subtitle "Address"]
|
||||
[address-field {:field [:address]
|
||||
:event [::forms/change ::form]
|
||||
:subscription new-client}]]
|
||||
|
||||
[:h2.subtitle "Bank accounts"]
|
||||
(for [bank-account (sort-by :sort-order (:bank-accounts new-client))]
|
||||
^{:key (: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.columns
|
||||
[:div.column.is-third
|
||||
[:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :credit])} "Add Credit Account"]]
|
||||
[:div.column.is-third
|
||||
[:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :check])} "Add Checking Account"]]
|
||||
[:div.column.is-third
|
||||
[:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]]
|
||||
|
||||
[:h2.subtitle "Cash flow"]
|
||||
[:label.label (str "Week A (" next-week-a ")")]
|
||||
[field "Regular Credits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "500.00"
|
||||
:field [:week-a-credits]
|
||||
:step "0.01"}]]
|
||||
[field "Regular Debits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "150.00"
|
||||
:field [:week-a-debits]
|
||||
:step "0.01"}]]
|
||||
[:label.label (str "Week B (" next-week-b ")")]
|
||||
[field "Regular Credits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "1000.00"
|
||||
:field [:week-b-credits]
|
||||
:step "0.01"}]]
|
||||
[field "Regular Debits"
|
||||
[:input.input {:type "number"
|
||||
:placeholder "250.00"
|
||||
:field [:week-b-debits]
|
||||
:step "0.01"}]]
|
||||
[:div.field
|
||||
[:label.label "Forecasted transactions"]
|
||||
[:div.control
|
||||
[horizontal-field
|
||||
nil
|
||||
|
||||
[:p.control
|
||||
[:p.help "Identifier"]
|
||||
[raw-field
|
||||
[:input.input {:type "text"
|
||||
:placeholder "Identifier"
|
||||
:field [:new-forecasted-transaction :identifier]}]]]
|
||||
[:p.control
|
||||
[:p.help "Day of month"]
|
||||
[raw-field
|
||||
[:input.input {:type "number"
|
||||
:placeholder "Day of month"
|
||||
:step "1"
|
||||
:field [:new-forecasted-transaction :day-of-month]}]]]
|
||||
[:p.control
|
||||
[:p.help "Amount"]
|
||||
[raw-field
|
||||
[:input.input {:type "number"
|
||||
:placeholder "250.00"
|
||||
:field [:new-forecasted-transaction :amount]
|
||||
:step "0.01"}]]]
|
||||
[:a.button {:on-click (dispatch-event [::add-forecasted-transaction])} "Add"]]]
|
||||
|
||||
[:ul
|
||||
(for [forecasted-transaction (:forecasted-transactions new-client)]
|
||||
^{:key (or (:id forecasted-transaction)
|
||||
(:temp-id forecasted-transaction))}
|
||||
[:li (:identifier forecasted-transaction) ": " (nf (:amount forecasted-transaction)) " on day " (:day-of-month forecasted-transaction) " of the month"
|
||||
[:a {:on-click (dispatch-event [::remove-forecasted-transaction (or (:id forecasted-transaction)
|
||||
(:temp-id forecasted-transaction))])} [:span.icon [:span.fa.fa-times]]]])]]
|
||||
|
||||
[error-notification]
|
||||
[submit-button "Save"]]]))
|
||||
79
src/cljs/auto_ap/views/pages/admin/clients/side_bar.cljs
Normal file
79
src/cljs/auto_ap/views/pages/admin/clients/side_bar.cljs
Normal file
@@ -0,0 +1,79 @@
|
||||
(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.subs :as subs]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::specific-filters
|
||||
(fn [db ]
|
||||
(::filters db nil)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::filters
|
||||
:<- [::specific-filters]
|
||||
:<- [::subs/query-params]
|
||||
(fn [[specific-filters vendors-by-id query-params] ]
|
||||
(let [url-filters (-> query-params
|
||||
(select-keys #{:name
|
||||
:code}))
|
||||
url-filters {:name (str (:name url-filters))
|
||||
:code (str (:code url-filters))}]
|
||||
(merge url-filters specific-filters ))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::filter
|
||||
:<- [::filters]
|
||||
(fn [filters [_ which]]
|
||||
(get filters which)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::filter-params
|
||||
:<- [::settled-filters]
|
||||
:<- [::filters]
|
||||
:<- [::subs/active-page]
|
||||
(fn [[settled-filters filters ap ]]
|
||||
(let [filters (or settled-filters filters)]
|
||||
{:name (:name filters)
|
||||
:code (:code filters)})))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::settled-filters
|
||||
(fn [db ]
|
||||
(::settled-filters db)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::filters-settled
|
||||
(fn [{:keys [db]} [_ & params]]
|
||||
{:db (assoc db ::settled-filters @(re-frame/subscribe [::filters]))}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::filter-changed
|
||||
(fn [{:keys [db]} [_ & params]]
|
||||
(let [[a b c] params
|
||||
[which val] (if (= 3 (count params))
|
||||
[(into [a] b) c]
|
||||
[[a] b])]
|
||||
{:db (assoc-in db (into [::filters] which) val)
|
||||
:dispatch-debounce {:event [::filters-settled]
|
||||
:time 800
|
||||
:key ::filters}})))
|
||||
|
||||
|
||||
(defn client-side-bar []
|
||||
[:div
|
||||
[:p.menu-label "Name"]
|
||||
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "Harry's Food Products"
|
||||
:value @(re-frame/subscribe [::filter :name])
|
||||
:on-change (dispatch-value-change [::filter-changed :name])} ]]]
|
||||
|
||||
[:p.menu-label "Code"]
|
||||
|
||||
[:div.field
|
||||
[:div.control [:input.input {:placeholder "CBC"
|
||||
:value @(re-frame/subscribe [::filter :code])
|
||||
:on-change (dispatch-value-change [::filter-changed :code])} ]]]])
|
||||
|
||||
|
||||
53
src/cljs/auto_ap/views/pages/admin/clients/table.cljs
Normal file
53
src/cljs/auto_ap/views/pages/admin/clients/table.cljs
Normal file
@@ -0,0 +1,53 @@
|
||||
(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.pages.admin.clients.form :as form]
|
||||
[auto-ap.views.utils :refer [action-cell-width]]
|
||||
[auto-ap.views.components.grid :as grid]
|
||||
[auto-ap.views.components.buttons :as buttons]))
|
||||
|
||||
(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-sub
|
||||
::params
|
||||
:<- [::specific-params]
|
||||
:<- [::subs/query-params]
|
||||
(fn [[specific-params query-params]]
|
||||
(merge (select-keys query-params #{:start :sort}) specific-params )))
|
||||
|
||||
|
||||
(defn clients-table [{:keys [page status]}]
|
||||
[grid/grid {:on-params-change (fn [p]
|
||||
(re-frame/dispatch [::params-changed p]))
|
||||
:status status
|
||||
:params @(re-frame/subscribe [::params])
|
||||
:column-count 5}
|
||||
[grid/controls page]
|
||||
[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 {} "Email"]
|
||||
[grid/header-cell {:style {:width (action-cell-width 1)}}]]
|
||||
]
|
||||
[grid/body
|
||||
(for [{:keys [id name email code locations] :as c} (:data page)]
|
||||
^{:key (str name "-" id )}
|
||||
[grid/row
|
||||
[grid/cell {} name]
|
||||
[grid/cell {} code]
|
||||
[grid/cell {} (str/join ", " locations)]
|
||||
[grid/cell {} email]
|
||||
[grid/cell {} [buttons/fa-icon {:event [::form/editing id]
|
||||
:icon :fa-pencil}]]])]]])
|
||||
@@ -106,59 +106,58 @@
|
||||
opc (fn [p]
|
||||
(re-frame/dispatch [::params-changed p]))
|
||||
states @(re-frame/subscribe [::status/multi ::run])]
|
||||
[:div
|
||||
[grid/grid {:on-params-change opc
|
||||
:params @(re-frame/subscribe [::table-params])
|
||||
:status status
|
||||
:column-count 6}
|
||||
[grid/controls {:start start :end end :count count :total total}]
|
||||
[grid/table {:fullwidth true }
|
||||
[grid/header
|
||||
[grid/row {}
|
||||
[grid/sortable-header-cell {:sort-key "client"
|
||||
:sort-name "Client"}
|
||||
"Client"]
|
||||
[grid/grid {:on-params-change opc
|
||||
:params @(re-frame/subscribe [::table-params])
|
||||
:status status
|
||||
:column-count 6}
|
||||
[grid/controls {:start start :end end :count count :total total}]
|
||||
[grid/table {:fullwidth true }
|
||||
[grid/header
|
||||
[grid/row {}
|
||||
[grid/sortable-header-cell {:sort-key "client"
|
||||
:sort-name "Client"}
|
||||
"Client"]
|
||||
|
||||
[grid/sortable-header-cell {:sort-key "bank-account"
|
||||
:sort-name "Bank Account"}
|
||||
"Bank Account"]
|
||||
[grid/sortable-header-cell {:sort-key "bank-account"
|
||||
:sort-name "Bank Account"}
|
||||
"Bank Account"]
|
||||
|
||||
[grid/sortable-header-cell {:sort-key "description"
|
||||
:sort-name "Description"}
|
||||
"Description"]
|
||||
[grid/sortable-header-cell {:sort-key "description"
|
||||
:sort-name "Description"}
|
||||
"Description"]
|
||||
|
||||
[grid/header-cell {:style {:width "12em"}} "Amount"]
|
||||
[grid/header-cell {:style {:width "12em"}} "Amount"]
|
||||
|
||||
[grid/sortable-header-cell {:sort-key "note"
|
||||
:sort-name "Note"}
|
||||
"Note"]
|
||||
[grid/sortable-header-cell {:sort-key "note"
|
||||
:sort-name "Note"}
|
||||
"Note"]
|
||||
|
||||
[grid/header-cell {:style {:width (action-cell-width 3)}}]]]
|
||||
[grid/body
|
||||
(for [{:keys [client bank-account description amount-lte amount-gte note id] :as r} transaction-rules]
|
||||
^{:key id}
|
||||
[grid/row {:class (:class r)}
|
||||
[grid/cell {} (:name client)]
|
||||
[grid/cell {} (:name bank-account)]
|
||||
[grid/cell {} description]
|
||||
[grid/cell {:class "has-text-right"}
|
||||
(cond (and amount-gte amount-lte)
|
||||
(str (->$ amount-gte) " - " (->$ amount-lte))
|
||||
[grid/header-cell {:style {:width (action-cell-width 3)}}]]]
|
||||
[grid/body
|
||||
(for [{:keys [client bank-account description amount-lte amount-gte note id] :as r} transaction-rules]
|
||||
^{:key id}
|
||||
[grid/row {:class (:class r)}
|
||||
[grid/cell {} (:name client)]
|
||||
[grid/cell {} (:name bank-account)]
|
||||
[grid/cell {} description]
|
||||
[grid/cell {:class "has-text-right"}
|
||||
(cond (and amount-gte amount-lte)
|
||||
(str (->$ amount-gte) " - " (->$ amount-lte))
|
||||
|
||||
amount-gte
|
||||
(str ">=" (->$ amount-gte))
|
||||
amount-gte
|
||||
(str ">=" (->$ amount-gte))
|
||||
|
||||
amount-lte
|
||||
(str "<=" (->$ amount-lte))
|
||||
amount-lte
|
||||
(str "<=" (->$ amount-lte))
|
||||
|
||||
:else
|
||||
"")]
|
||||
[grid/cell {} note]
|
||||
[grid/cell {}
|
||||
[:div.buttons
|
||||
[buttons/fa-icon {:event [::run-clicked r] :icon :fa-play :class (status/class-for (get states (:id r)))}]
|
||||
[buttons/sl-icon {:event [::request-delete r] :icon :icon-bin-2}]
|
||||
[buttons/fa-icon {:event [::form/editing r] :icon :fa-pencil}]]]])]]]]))
|
||||
:else
|
||||
"")]
|
||||
[grid/cell {} note]
|
||||
[grid/cell {}
|
||||
[:div.buttons
|
||||
[buttons/fa-icon {:event [::run-clicked r] :icon :fa-play :class (status/class-for (get states (:id r)))}]
|
||||
[buttons/sl-icon {:event [::request-delete r] :icon :icon-bin-2}]
|
||||
[buttons/fa-icon {:event [::form/editing r] :icon :fa-pencil}]]]])]]]))
|
||||
|
||||
(defn table [params]
|
||||
(r/create-class {:component-will-unmount (dispatch-event [::unmounted])
|
||||
|
||||
@@ -89,8 +89,7 @@
|
||||
(defn admin-users-content []
|
||||
[:div
|
||||
[:h1.title "Users"]
|
||||
[users-table]
|
||||
[form/form]])
|
||||
[users-table]])
|
||||
|
||||
|
||||
(defn admin-users-page []
|
||||
|
||||
Reference in New Issue
Block a user