Builds client SSR approach, sunsets old cljs.

This commit is contained in:
2024-01-09 21:40:43 -08:00
parent d824cdfff4
commit 8063a8fcbd
74 changed files with 4603 additions and 4047 deletions

View File

@@ -47,7 +47,7 @@
(defn builder [{:keys [value on-change can-submit data-sub error-messages change-event submit-event id fullwidth? schema validation-error-string]}]
(when (and change-event on-change)
(throw "Either the form is to be managed by ::forms, or it should have value and on-change passed in"))
(throw (js/Error. "Either the form is to be managed by ::forms, or it should have value and on-change passed in")))
(let [data-sub (or data-sub [::forms/form id])
change-event (when-not on-change
(or change-event [::forms/change id]))

View File

@@ -14,7 +14,6 @@
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
[auto-ap.views.pages.admin.vendors.common :as common]
[auto-ap.views.utils
:refer [dispatch-event str->int with-is-admin? with-user]]
[malli.core :as m]
@@ -23,9 +22,25 @@
;; Remaining cleanup todos:
;; test minification
(def default-read [:id :name :hidden :terms [:default-account [:name :id :location]]
[:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]]
[:automatically-paid-when-due [:id :name]]
[:terms-overrides [[:client [:id :name]] :id :terms]]
[:schedule-payment-dom [[:client [:id :name]] :id :dom]]
[:usage [:client-id :count]]
[:primary-contact [:name :phone :email :id]]
[:plaid-merchant [:name :id]]
[:secondary-contact [:id :name :phone :email]]
:print-as :invoice-reminder-schedule :code
:legal-entity-name
:legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name
:legal-entity-tin :legal-entity-tin-type
:legal-entity-1099-type
[:address [:id :street1 :street2 :city :state :zip]]])
(def terms-override-schema (m/schema [:map
[:client schema/reference]
[:terms :int]]))
[:client schema/reference]
[:terms :int]]))
(def automatically-paid-schema (m/schema [:map
[:client schema/reference]]))
@@ -35,8 +50,8 @@
[:dom [:int {:max 30}]]]))
(def account-override-schema (m/schema [:map
[:client schema/reference]
[:account schema/reference]]))
[:client schema/reference]
[:account schema/reference]]))
(def schema (m/schema [:map [:name schema/not-empty-string]
[:print-as {:optional true}
@@ -72,73 +87,73 @@
(re-frame/reg-event-fx
::save-complete
[(forms/triggers-stop ::vendor-form)]
(fn [_ [_ _ ]]
{:dispatch [::modal/modal-closed ]}))
(fn [_ [_ _]]
{:dispatch [::modal/modal-closed]}))
(re-frame/reg-event-fx
::save
[with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)]
(fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule plaid-merchant primary-contact automatically-paid-when-due schedule-payment-dom secondary-contact address default-account terms-overrides account-overrides id legal-entity-name legal-entity-tin legal-entity-tin-type legal-entity-first-name legal-entity-last-name legal-entity-middle-name legal-entity-1099-type] :as data} :data} :db} _]
(if (m/validate schema data)
(let [query [:upsert-vendor
{:vendor (cond-> {:id id
:name name
:print-as print-as
:terms (or terms
nil)
:default-account-id (:id default-account)
:address address
:primary-contact primary-contact
:secondary-contact secondary-contact
:invoice-reminder-schedule invoice-reminder-schedule}
is-admin? (assoc :hidden hidden
:terms-overrides (mapv
(fn [{:keys [client terms id]}]
{:id id
:client-id (:id client)
:terms (or (str->int terms) 0)})
terms-overrides)
:account-overrides (mapv
(fn [{:keys [client account id]}]
{:id id
:client-id (:id client)
:account-id (:id account)})
account-overrides)
:schedule-payment-dom (mapv
(fn [{:keys [client dom id]}]
{:id id
:client-id (:id client)
:dom (or (str->int dom)
0)})
schedule-payment-dom)
:automatically-paid-when-due (mapv
(comp :id :client)
automatically-paid-when-due)
:plaid-merchant (:id plaid-merchant)
:legal-entity-name legal-entity-name
:legal-entity-first-name legal-entity-first-name
:legal-entity-middle-name legal-entity-middle-name
:legal-entity-last-name legal-entity-last-name
:legal-entity-tin legal-entity-tin
:legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword)
:legal-entity-1099-type (some-> legal-entity-1099-type clojure.core/name not-empty keyword)))}
common/default-read]]
{ :graphql
{:token user
:owns-state {:single ::vendor-form}
:query-obj {:venia/operation
{:operation/type :mutation
:operation/name "UpsertVendor"} :venia/queries [{:query/data query}]}
:on-success [::save-complete]}})
::save
[with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)]
(fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule plaid-merchant primary-contact automatically-paid-when-due schedule-payment-dom secondary-contact address default-account terms-overrides account-overrides id legal-entity-name legal-entity-tin legal-entity-tin-type legal-entity-first-name legal-entity-last-name legal-entity-middle-name legal-entity-1099-type] :as data} :data} :db} _]
(if (m/validate schema data)
(let [query [:upsert-vendor
{:vendor (cond-> {:id id
:name name
:print-as print-as
:terms (or terms
nil)
:default-account-id (:id default-account)
:address address
:primary-contact primary-contact
:secondary-contact secondary-contact
:invoice-reminder-schedule invoice-reminder-schedule}
is-admin? (assoc :hidden hidden
:terms-overrides (mapv
(fn [{:keys [client terms id]}]
{:id id
:client-id (:id client)
:terms (or (str->int terms) 0)})
terms-overrides)
:account-overrides (mapv
(fn [{:keys [client account id]}]
{:id id
:client-id (:id client)
:account-id (:id account)})
account-overrides)
:schedule-payment-dom (mapv
(fn [{:keys [client dom id]}]
{:id id
:client-id (:id client)
:dom (or (str->int dom)
0)})
schedule-payment-dom)
:automatically-paid-when-due (mapv
(comp :id :client)
automatically-paid-when-due)
:plaid-merchant (:id plaid-merchant)
:legal-entity-name legal-entity-name
:legal-entity-first-name legal-entity-first-name
:legal-entity-middle-name legal-entity-middle-name
:legal-entity-last-name legal-entity-last-name
:legal-entity-tin legal-entity-tin
:legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword)
:legal-entity-1099-type (some-> legal-entity-1099-type clojure.core/name not-empty keyword)))}
default-read]]
{:graphql
{:token user
:owns-state {:single ::vendor-form}
:query-obj {:venia/operation
{:operation/type :mutation
:operation/name "UpsertVendor"} :venia/queries [{:query/data query}]}
:on-success [::save-complete]}})
{:dispatch-n [[::forms/attempted-submit ::vendor-form]
[::status/error ::vendor-form [{:message "Please fix the errors and try again."}]]]})))
{:dispatch-n [[::forms/attempted-submit ::vendor-form]
[::status/error ::vendor-form [{:message "Please fix the errors and try again."}]]]})))
(defn contact-field [{:keys [name field]}]
[form-builder/with-scope {:scope field}
[form-builder/vertical-control
name
[left-stack
[left-stack
[form-builder/vertical-control {:is-small? true}
"Name"
[:div.control.has-icons-left
@@ -196,23 +211,22 @@
[form-builder/section {:title "Terms"}
[form-builder/field-v2 {:field :terms}
"Terms"
[number-input ]]
[number-input]]
(when is-admin?
[form-builder/field-v2 {:field [:terms-overrides]}
"Overrides"
[multi-field-v2 {:template [[form-builder/raw-field-v2 {:field [:client]}
[multi-field-v2 {:template [[form-builder/raw-field-v2 {:field [:client]}
[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "13em"}
:type "typeahead-v3"
}]]
:type "typeahead-v3"}]]
[form-builder/raw-field-v2 {:field :terms}
[number-input]]]
:schema [:sequential terms-override-schema]
:key-fn :id
:next-key (random-uuid)
:new-text "New Terms Override"}]])]
(when is-admin?
[form-builder/section {:title "Schedule payment when due"}
[form-builder/field-v2 {:field [:automatically-paid-when-due]}
@@ -228,7 +242,7 @@
(when is-admin?
[form-builder/section {:title "Schedule payment on day of month"}
[form-builder/field-v2 {:field [:schedule-payment-dom]}
[form-builder/field-v2 {:field [:schedule-payment-dom]}
"Overrides"
[multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :client}
[typeahead-v3 {:entities clients
@@ -249,8 +263,7 @@
{:query i
:allowance :vendor}
[:name :id :warning]])
:style {:width "19em"}}]
]
:style {:width "19em"}}]]
(when (:warning (:default-account vendor))
[:div.notification.is-warning.is-light
(:warning (:default-account vendor))])
@@ -258,23 +271,22 @@
[form-builder/field-v2 {:field [:account-overrides]}
"Overrides"
[multi-field-v2 {:template (fn [entity]
[[form-builder/raw-field-v2 {:field :client}
[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "19em"}
}]]
[form-builder/raw-field-v2 {:field :account}
[search-backed-typeahead {:search-query (fn [i]
[:search_account
{:query i
:client_id (:id (:client entity))
:allowance :vendor}
[:name :id :warning]])
:style {:width "15em"}}]]])
:schema [:sequential account-override-schema]
:key-fn :id
:next-key (random-uuid)
:new-text "Add override"}]])]
[[form-builder/raw-field-v2 {:field :client}
[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "19em"}}]]
[form-builder/raw-field-v2 {:field :account}
[search-backed-typeahead {:search-query (fn [i]
[:search_account
{:query i
:client_id (:id (:client entity))
:allowance :vendor}
[:name :id :warning]])
:style {:width "15em"}}]]])
:schema [:sequential account-override-schema]
:key-fn :id
:next-key (random-uuid)
:new-text "Add override"}]])]
[form-builder/section {:title "Address"}
[:div {:style {:width "30em"}}
@@ -319,8 +331,7 @@
[form-builder/raw-field-v2 {:field :legal-entity-tin}
[:input.input {:type "text"
:placeholder "SSN or EIN"
:size "12"
}]]
:size "12"}]]
[:div.control
[form-builder/raw-field-v2 {:field :legal-entity-tin-type}
@@ -336,25 +347,25 @@
:allow-nil? true}]]])
[form-builder/hidden-submit-button]]))
(defn vendor-dialog [ ]
(defn vendor-dialog []
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form])]
[:div
[form-content {:data data}]]))
(re-frame/reg-event-fx
::vendor-selected
[with-user (forms/in-form ::select-vendor-form)]
(fn [{{:keys [data]} :db :keys [user]} _]
(if (:vendor data)
{:graphql {:token user
:query-obj {:venia/queries [[:vendor-by-id
{:id (:id (:vendor data))}
common/default-read]]}
:owns-state {:single ::select-vendor-form}
:on-success (fn [r]
[::started (:vendor-by-id r)])}}
{:dispatch-n [[::forms/attempted-submit ::select-vendor-form]
[::status/error ::select-vendor-form [{:message "Please select a vendor."}]]]})))
::vendor-selected
[with-user (forms/in-form ::select-vendor-form)]
(fn [{{:keys [data]} :db :keys [user]} _]
(if (:vendor data)
{:graphql {:token user
:query-obj {:venia/queries [[:vendor-by-id
{:id (:id (:vendor data))}
default-read]]}
:owns-state {:single ::select-vendor-form}
:on-success (fn [r]
[::started (:vendor-by-id r)])}}
{:dispatch-n [[::forms/attempted-submit ::select-vendor-form]
[::status/error ::select-vendor-form [{:message "Please select a vendor."}]]]})))
(defn select-vendor-form-content []
[form-builder/builder {:submit-event [::vendor-selected]

View File

@@ -18,9 +18,7 @@
[auto-ap.views.pages.ledger.profit-and-loss-detail :refer [profit-and-loss-detail-page]]
[auto-ap.views.pages.login :refer [login-page]]
[auto-ap.views.pages.payments :refer [payments-page]]
[auto-ap.views.pages.home :refer [home-page]]
[auto-ap.views.pages.admin.clients :refer [admin-clients-page]]
[auto-ap.views.pages.admin.vendors :refer [admin-vendors-page]]))
[auto-ap.views.pages.home :refer [home-page]]))
(defmulti page (fn [active-page] active-page))
(defmethod page :unpaid-invoices [_]
@@ -93,14 +91,6 @@
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
(balance-sheet-page)))
(defmethod page :admin-clients [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
(admin-clients-page)))
(defmethod page :admin-specific-client [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
(admin-clients-page)))
(defmethod page :admin-vendors [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
(admin-vendors-page)))

View File

@@ -1,106 +0,0 @@
(ns auto-ap.views.pages.admin.clients
(:require
[auto-ap.routes :as routes]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.components.grid :as grid]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.pages.admin.clients.form :as form]
[auto-ap.views.pages.admin.clients.side-bar :as side-bar]
[auto-ap.views.pages.admin.clients.table :as table]
[auto-ap.views.pages.page-stack :as page-stack]
[auto-ap.views.utils :refer [with-user]]
[bidi.bidi :as bidi]
[clojure.string :as str]
[clojure.set :as set]
[re-frame.core :as re-frame]
[vimsical.re-frame.fx.track :as track]
[auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.db :as db]))
(re-frame/reg-event-db
::received-intuit-bank-accounts
(fn [db [_ result]]
(assoc db ::subs/intuit-bank-accounts (:intuit-bank-accounts result))))
(re-frame/reg-event-fx
::mounted
[with-user]
(fn [{:keys [user db]} _]
{::track/register [{:id ::params
:subscription [::data-page/params ::page]
:event-fn (fn [params] [::params-change params])}
{:id ::active-route
:subscription [::subs/active-route]
:event-fn (fn [params] [::params-change params])}]
:db (-> db
(forms/stop-form [::form/form]))
:graphql {:token user
:query-obj {:venia/queries [[:intuit_bank_accounts [:external_id :id :name]]]}
:owns-state {:single [::load-intuit-bank-accounts]}
:on-success [::received-intuit-bank-accounts]} }))
(re-frame/reg-event-fx
::unmounted
(fn [{:keys [db]} _]
{:db (dissoc db ::table/params ::side-bar/filter-params)
::track/dispose [{:id ::params}
{:id ::active-route}]}))
(defn data-params->query-params [params]
{:start (:start params 0)
:per-page (:per-page params)
:sort (:sort params)
:name-like (:name-like params)
:code (:code params)})
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user]} [_ params]]
{:graphql {:token user
:owns-state {:single [::data-page/page ::page]}
:query-obj {:venia/queries [[:client-page
{:filters (data-params->query-params params)}
[[:clients (events/client-detail-query user)]
:total
:start
:end]]]}
:on-success (fn [result]
[::data-page/received ::page (set/rename-keys (:client-page result)
{:clients :data})])}}))
(def admin-clients-content
(with-meta
(fn []
[:div
[page-stack/page-stack
{:active @(re-frame/subscribe [::subs/active-route])
:pages [{:key :admin-clients
:breadcrumb "Clients"
:content [:<>
[:div.is-pulled-right
[:a.button.is-primary.is-outlined {:href (bidi/path-for routes/routes :admin-specific-client :id "new")} "New client"]]
[table/clients-table {:data-page ::page
:id :clients}]]}
{:key :admin-specific-client
:breadcrumb [:span [:a {:href (bidi/path-for routes/routes :admin-clients)}
"Clients"]
" / "
(or (:name (:data @(re-frame/subscribe [::forms/form ::form/form])))
[:i "New client"])]
:content [form/new-client-form]}
]}]])
{:component-did-mount #(re-frame/dispatch [::mounted])
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])}))
(defn admin-clients-page []
[side-bar-layout {:side-bar [admin-side-bar {}
[side-bar/client-side-bar {:data-page ::page}]]
:main [admin-clients-content]}])

View File

@@ -1,761 +0,0 @@
(ns auto-ap.views.pages.admin.clients.form
(:require
[auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.routes :as routes]
[auto-ap.subs :as subs]
[auto-ap.views.components.address :refer [address2-field]]
[react-signature-canvas]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.level :refer [left-stack] :as level]
[auto-ap.views.components :as com]
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
[auto-ap.views.utils
:refer [date-picker
with-user
dispatch-event]]
[bidi.bidi :as bidi]
[cljs-time.coerce :as coerce]
[cljs-time.core :as t]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.cofx.inject :as inject]
[auto-ap.schema :as schema]
[malli.core :as m]))
(def signature-canvas (r/adapt-react-class (.-default react-signature-canvas)))
(def location-schema (m/schema [:map
[:location schema/not-empty-string]]))
(def feature-flag-schema (m/schema [:map
[:feature-flag schema/not-empty-string]]))
(def square-location-schema (m/schema [:map
[:square-location schema/reference]
[:client-location schema/not-empty-string]]))
(def ezcater-schema (m/schema [:map
[:caterer schema/reference]
[:client-location schema/not-empty-string]]))
(def name-match-schema (m/schema [:map
[:match schema/not-empty-string]]))
(def location-match-schema (m/schema [:map
[:match schema/not-empty-string]
[:location schema/not-empty-string]]))
(def email-schema [:map
[:email schema/not-empty-string]
[:description schema/not-empty-string]])
(def client-schema [:map
[:name schema/not-empty-string]
[:code schema/code-string]
[:locations [:sequential location-schema]]
[:feature-flags {:optional true} [:maybe [:sequential feature-flag-schema]]]
[:emails {:optional true}
[:maybe [:sequential email-schema]]]
[:matches {:optional true}
[:maybe [:sequential name-match-schema]]]
[:location-matches {:optional true}
[:maybe [:sequential location-match-schema]]]
[:selected-square-locations {:optional true}
[:maybe [:sequential square-location-schema]]]])
(defn upload-replacement-button [{:keys [on-change]} text]
(let [button (atom nil)]
(r/create-class {:display-name "Upload button"
:reagent-render
(fn []
[:<>
[:label.button {:for "upload_replacement_signature"} text]
[:input.button {:type "file" :id "upload_replacement_signature"
:style {:display "none"}
:on-change (fn []
(let [fr (js/FileReader.)]
(.addEventListener fr "load" (fn []
(on-change (.-result fr))))
(.readAsDataURL fr (aget (.-files @button) 0)))
)
:ref (fn [i] (reset! button i))} ]])})))
(defn signature [_]
(let [canvas (atom nil)
edit-mode? (r/atom false)
w (* 1.5 464)
h (* 1.5 174)]
(fn [{:keys [signature-file signature-data on-change]}]
[:div
(if @edit-mode?
[:div
[signature-canvas {"canvasProps" {"width" w
"height" h
"style" #js {"border" "1px solid #CCC"
"border-radius" "10px"}}
"backgroundColor" "#FFF"
:ref (fn [el]
(reset! canvas el))}]
[:div.buttons
[:a.button.is-primary.is-outlined {:on-click (fn []
(on-change (.toDataURL @canvas "image/jpeg"))
(reset! edit-mode? false))}
"Accept"]
[:a.button.is-warning.is-outlined {:on-click (fn []
(.clear @canvas)
(reset! edit-mode? false))}
"Cancel"]]]
(if (or signature-data signature-file)
[:div
[:img {:src (or signature-data signature-file)
:style {:width w
:height h
:border "1px solid #CCC"
:border-radius "10px"}}]
[:div.buttons
[:a.button {:on-click (fn []
(reset! edit-mode? true))}
"Replace Signature"]
[upload-replacement-button {:on-change on-change} "Upload replacement"]]]
[:div
[:div.has-text-centered.is-vcentered {:style {:width w
:height h
:margin-bottom "8px"
:border "1px solid #CCC"
:border-radius "10px"
:background "#EEE"
}}
"No signature"]
[:div.buttons
[:a.button.is-primary.is-outlined {:on-click (fn []
(reset! edit-mode? true))}
"New Signature"]
[upload-replacement-button {:on-change on-change} "Upload signature"]]]))
])))
(re-frame/reg-sub
::new-client-request
:<- [::forms/form ::form]
(fn [{new-client-data :data} _]
(cond->
{:id (:id new-client-data),
:name (:name new-client-data)
:code (:code new-client-data) ;; TODO add validation can't change
:emails (map #(select-keys % [:id :email :description])
(:emails new-client-data))
:square-auth-token (:square-auth-token new-client-data)
:square-locations (map
(fn [x]
{:id (:id (:square-location x))
:client-location (:client-location x)})
(:selected-square-locations new-client-data))
:ezcater-locations (map
(fn [x]
{:id (:id x)
:caterer (:id (:caterer x))
:location (:location x)})
(:ezcater-locations new-client-data))
:locked-until (:locked-until new-client-data)
:locations (mapv :location (:locations new-client-data))
:feature-flags (mapv :feature-flag (:feature-flags new-client-data))
:matches (mapv :match (:matches new-client-data))
:location-matches (:location-matches new-client-data)
:week-a-credits (:week-a-credits new-client-data)
:week-a-debits (:week-a-debits new-client-data)
:week-b-credits (:week-b-credits new-client-data)
:week-b-debits (:week-b-debits new-client-data)
:address {:id (:id (:address new-client-data))
:street1 (:street1 (:address new-client-data))
:street2 (:street2 (:address new-client-data)),
:city (:city (:address new-client-data))
:state (:state (:address new-client-data))
:zip (:zip (:address new-client-data))}
:signature-data (:signature-data new-client-data)
:forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}]
{:id id
:day-of-month (js/parseInt day-of-month)
:identifier identifier
:amount amount})
(:forecasted-transactions new-client-data))
:bank-accounts (map-indexed (fn [i {:keys [number name check-number plaid-account intuit-bank-account include-in-reports type id code numeric-code start-date bank-name routing bank-code new? visible locations yodlee-account use-date-instead-of-post-date]}]
{:number number
:name name
:check-number check-number
:numeric-code numeric-code
:include-in-reports include-in-reports
:start-date start-date
:type type
:id id
:sort-order i
:visible visible
:locations (mapv :location locations)
:use-date-instead-of-post-date use-date-instead-of-post-date
:yodlee-account (:id yodlee-account)
:plaid-account (:id plaid-account)
:intuit-bank-account (:id intuit-bank-account)
:code (if new?
(str (:code new-client-data) "-" code)
code)
:bank-name bank-name
:routing routing
:bank-code bank-code})
(:bank-accounts new-client-data))})))
(re-frame/reg-event-fx
::mounted
[with-user (re-frame/inject-cofx ::inject/sub [::subs/route-params])]
(fn [{:keys [user db] ::subs/keys [route-params]} _]
(when-let [id (some-> (:id route-params) (js/parseInt ) (#(if (js/Number.isNaN %) nil %)))]
{:graphql {:token user
:query-obj {:venia/queries [[:admin-client
{:id id}
(events/client-detail-query user)]]}
:on-success (fn [result]
[::received (:admin-client result)])}
:db (-> db
(forms/stop-form ::form))})))
(re-frame/reg-event-db
::received
(fn [db [_ client]]
(-> db
(forms/stop-form ::form)
(forms/start-form ::form (-> client
(assoc :selected-square-locations (->> (:square-locations client)
(filter :client-location )
(mapv (fn [sl]
{:id (:id sl)
:square-location sl
:client-location (:client-location sl)}))))
(update :locations #(mapv (fn [l] {:location l
:id (random-uuid)}) %))
(update :feature-flags #(mapv (fn [l] {:feature-flag l
:id (random-uuid)}) %))
(update :matches #(mapv (fn [l] {:match l
:id (random-uuid)}) %))
(update :bank-accounts
(fn [bas]
(mapv (fn [ba]
(update ba :locations (fn [ls]
(map (fn [l] {:location l
:id (random-uuid)})
ls))))
bas))))))))
(re-frame/reg-event-fx
::save-new-client
[(forms/in-form ::form)]
(fn [_ _]
(let [new-client-req @(re-frame/subscribe [::new-client-request])
user @(re-frame/subscribe [::subs/token])]
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "EditClient"}
:venia/queries [{:query/data [:edit-client
{:edit-client new-client-req}
(events/client-detail-query user)]}]}
:on-success [::save-complete]
:on-error [::forms/save-error ::form]}})))
(re-frame/reg-event-fx
::save-complete
(fn [{:keys [db]} [_ client]]
{:db
(-> db
#_(forms/stop-form ::form)
(assoc-in [:clients (:id (:edit-client client))] (update (:edit-client client) :bank-accounts (fn [bas] (->> bas (sort-by :sort-order) vec)))))
:redirect (bidi/path-for routes/routes :admin-clients)}))
(re-frame/reg-event-db
::add-new-bank-account
[(forms/in-form ::form) (re-frame/path [:data])]
(fn [client [_ type]]
(update client :bank-accounts conj {:type type :active? true :new? true :visible true :sort-order (count (:bank-accounts client))})))
(re-frame/reg-event-db
::bank-account-activated
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
(fn [bank-accounts [_ index]]
(update (vec (sort-by :sort-order bank-accounts)) index assoc :active? true)))
(re-frame/reg-event-db
::bank-account-deactivated
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
(fn [bank-accounts [_ index]]
(update (vec (sort-by :sort-order bank-accounts)) index assoc :active? false)))
(re-frame/reg-event-db
::bank-account-removed
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
(fn [bank-accounts [_ index]]
(vec (concat (take index bank-accounts)
(drop (inc index) bank-accounts)))))
(re-frame/reg-event-db
::sort-swapped
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
(fn [bank-accounts [_ source dest]]
(->> (-> bank-accounts
(assoc-in [source :sort-order] (get-in bank-accounts [dest :sort-order]))
(assoc-in [dest :sort-order] (get-in bank-accounts [source :sort-order]))
)
(sort-by :sort-order)
vec)))
(re-frame/reg-event-db
::toggle-visible
[(forms/in-form ::form) (re-frame/path [:data :bank-accounts])]
(fn [bank-accounts [_ account]]
(-> (->> bank-accounts
(sort-by :sort-order)
vec)
(update-in [account :visible] #(not %)))))
(def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00"))
(defn is-week-a? [d]
(= 0 (mod (t/in-weeks (t/interval first-week-a d)) 2)))
(re-frame/reg-sub
::yodlee-accounts
:<- [::subs/clients-by-id]
(fn [clients [_ id]]
(if id
(mapcat :accounts (:yodlee-provider-accounts (get clients id) ))
[])))
(re-frame/reg-sub
::plaid-accounts
:<- [::subs/clients-by-id]
(fn [clients [_ id]]
(if id
(mapcat :accounts (:plaid-items (get clients id) ))
[])))
(defn bank-account-card [new-client {:keys [active? new? type visible code name sort-order]} first? last?]
[:div.card {:style {:margin-bottom "1em"
:width "600px"}}
[:header.card-header.has-background-primary-light
[:div.card-header-title {:style {:text-overflow "ellipsis"}}
[:div.level {:style {:width "100%"}}
[:div.level-left
[:div.level-item
[:span.icon.inline
(cond
(#{:check ":check"} type) [:span.icon-check-payment-sign]
(#{:credit ":credit"} type) [:span.icon-credit-card-1]
:else [:span.icon-accounting-bill])]]
[:div.level-item code ": " name]]
[:div.level-right
[:div.level-item
[:div.buttons
[:a.button {:on-click (dispatch-event [::toggle-visible sort-order])} [:span.icon (if visible
[:span.fa.fa-eye]
[:span.fa.fa-eye-slash]
)]]
(when-not last?
[:a.button {:on-click (dispatch-event [::sort-swapped sort-order (inc sort-order)])} [:span.icon [:span.fa.fa-sort-down]]])
(when-not first?
[:a.button {:on-click (dispatch-event [::sort-swapped sort-order (dec sort-order)])} [:span.icon [:span.fa.fa-sort-up]]])]]]]]
(if active?
[:a.card-header-icon
{:on-click (dispatch-event [::bank-account-deactivated sort-order])}
[:span.icon
[:span.fa.fa-angle-up]]]
[:a.card-header-icon
{:on-click (dispatch-event [::bank-account-activated sort-order])}
[:span.icon
[:span.fa.fa-angle-down]]])]
(when active?
[:div.card-content
[:label.label "General"]
[level/left-stack
[:div.control
[:p.help "Account Code"]
(if new?
[:div.field.has-addons
[:p.control [:a.button.is-static (:code new-client) "-" ]]
[:p.control
[form-builder/raw-field-v2 {:field :code}
[:input.input {:type "text"}]]]]
[:div.field [:p.control code]])]
[form-builder/field-v2 {:field :name}
"Nickname"
[:input.input {:placeholder "BOA Checking #1"
:type "text"}]]
[form-builder/field-v2 {:field :numeric-code}
"Numeric Code"
[com/number-input {:placeholder "20101"
:style {:width "8em"}}]]
[form-builder/field-v2 {:field :start-date}
"Start date"
[date-picker {:output :cljs-date}]]]
(when (#{:check ":check"} type )
[:div
[:label.label "Bank"]
[level/left-stack
[form-builder/field-v2 {:field :bank-name}
"Bank Name"
[:input.input {:placeholder "Bank of America"
:type "text"}]]
[form-builder/field-v2 {:field [:routing]}
"Routing #"
[:input.input {:placeholder "104819123"
:style {:width "9em"}
:type "text"}]]
[form-builder/field-v2 {:field :bank-code}
"Bank code"
[:input.input {:placeholder "12/10123"
:type "text"}]]]
[level/left-stack
[form-builder/field-v2 {:field :number}
"Account #"
[:input.input {:placeholder "123456789"
:type "text"
:style {:width "20em"}}]]
[form-builder/field-v2 {:field :check-number}
"Check Number"
[com/number-input {:style {:width "8em"}
:placeholder "10000"}]]]
[form-builder/field-v2 {:field :yodlee-account}
"Yodlee Account (new)"
[typeahead-v3 {:entities (mapcat :accounts (:yodlee-provider-accounts new-client ))
:entity->text (fn [m] (str (:name m) " - " (:number m)))}]]
[form-builder/raw-field-v2 {:field :use-date-instead-of-post-date}
[com/checkbox {:label " (Yodlee only) Use 'date' instead of 'postDate'"}]]
[form-builder/field-v2 {:field :intuit-bank-account}
"Intuit Bank Account"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
:entity->text (fn [m] (str (:name m)))}]]
[form-builder/field-v2 {:field :plaid-account}
"Plaid Account"
[typeahead-v3 {:entities (mapcat :accounts (:plaid-items new-client ))
:entity->text (fn [m] (str (:name m)))}]]])
(when (#{:credit ":credit"} type )
[:div
[:label.label "Account"]
[form-builder/field-v2 {:field :bank-name}
"Bank Name"
[:input.input {:placeholder "Bank of America"
:type "text"}]]
[form-builder/field-v2 {:field :number}
"Account #"
[:input.input {:placeholder "123456789"
:type "text"
:style {:width "20em"}}]]
[form-builder/field-v2 {:field :yodlee-account}
"Yodlee Account (new)"
[typeahead-v3 {:entities (mapcat :accounts (:yodlee-provider-accounts new-client ))
:entity->text (fn [m] (str (:name m) " - " (:number m)))}]]
[form-builder/raw-field-v2 {:field :use-date-instead-of-post-date}
[com/checkbox {:label "(Yodlee only) Use 'date' instead of 'postDate'"}]
[:input {:type "checkbox"
:field [:use-date-instead-of-post-date]}]]
[form-builder/field-v2 {:field :intuit-bank-account}
"Intuit Bank Account"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
:entity->text (fn [m] (str (:name m)))}]]
[form-builder/field-v2 {:field :plaid-account}
"Plaid Account"
[typeahead-v3 {:entities (mapcat :accounts (:plaid-items new-client ))
:entity->text (fn [m] (str (:name m)))}]]])
[:div.field
[:label.label "Locations"]
[:div.control
[:p.help "If this account is location-specific, add the valid locations"]
[form-builder/raw-field-v2 {:field :locations}
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :location}
[com/select-field {:options (map (fn [l]
[(:location l) (:location l)])
(get-in new-client [:locations]))
:allow-nil? true
:style {:width "7em"}
}]]]
:schema [:sequential location-schema]
:key-fn :id}]]]]
[form-builder/raw-field-v2 {:field :include-in-reports}
[com/checkbox {:label "Include in reports"}]
]
])
(when active?
[:footer.card-footer
[:a.card-footer-item {:href "#" :on-click (dispatch-event [::bank-account-deactivated sort-order])} "Done"]
(when new?
[:a.card-footer-item.is-warning {:href "#" :on-click (dispatch-event [::bank-account-removed sort-order])} "Remove"])])])
(defn general-section []
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "General"}
[form-builder/field-v2 {:field :name}
"Name"
[:input.input {:type "text"
:style {:width "20em"}}]]
[form-builder/field-v2 {:field :code}
"Client code"
[:input.input {:type "code"
:style {:width "5em"}
:disabled (boolean (:id new-client))}]]
[:div.field
[:label.label "Feature Flags"]
[:div.control
[:p.help "These are specific new features that can be enabled or disabled on a per-client basis"]
[form-builder/raw-field-v2 {:field :feature-flags}
[com/multi-field-v2 {:allow-change? true
:template [[form-builder/raw-field-v2 {:field :feature-flag}
[com/select-field {:options [[nil nil]
["new-square" "New Square+Ezcater (no effect)"]
["manually-pay-cintas" "Manually Pay Cintas"]
["include-in-ntg-corp-reports" "Include in NTG Corporate reports"]]
:allow-nil? false
:style {:width "18em"}}]]]
:key-fn :id
:schema [:sequential feature-flag-schema]
:next-key (random-uuid)}]]]]
[form-builder/field-v2 {:field :locations}
"Locations"
[com/multi-field-v2 {:allow-change? false
:template [[form-builder/raw-field-v2 {:field :location}
[:input.input {:max-length 2
:style {:width "4em"}}]]]
:disable-remove? true
:key-fn :id
:schema [:sequential location-schema]
:next-key (random-uuid)}]]
[form-builder/vertical-control
"Signature"
[signature {:signature-file (:signature-file new-client)
:signature-data (:signature-data new-client)
:on-change (fn [uri]
(re-frame/dispatch [::forms/change ::form [:signature-data] uri]))}]]
[form-builder/field-v2 {:field :locked-until}
"Locked Until"
[date-picker {:output :cljs-date
:style {:width "15em"}}]]]))
(defn contacts-section []
[form-builder/section {:title "Contacts"}
[form-builder/field-v2 {:field :emails}
"Emails (address/description)"
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :email}
[:input.input {:type "email"
:placeholder "tom@myspace.com"}]]
[form-builder/raw-field-v2 {:field :description}
[:input.input {:type "text"
:placeholder "Manager"}]]]
:key-fn :id
:schema [:sequential email-schema]
:next-key (random-uuid)}]]
[form-builder/vertical-control
"Address"
[:div {:style {:width "30em"}}
[form-builder/raw-field-v2 {:field :address}
[address2-field]]]]])
;; TODO Name matches, locations, bank account locations are all "single field multis", and require weird mounting and
;; unmounting. A new field could sort that out easily
(defn matching-section []
[form-builder/section {:title "Matching"}
[form-builder/field-v2 {:field :matches}
"Name matches"
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field [:match]}
[:input.input {:placeholder "Harry's burger joint"
:style { :width "15em"}}]]]
:key-fn :id
:next-key (random-uuid)
:schema [:sequential name-match-schema]}]]
[form-builder/field-v2 {:field :location-matches}
"Location Matches"
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :match}
[:input.input {:placeholder "Downtown"
:style { :width "15em"}}]]
[form-builder/raw-field-v2 {:field :location}
[:input.input {:placeholder "DT"
:max-length 2
:style { :width "4em"}}]]]
:schema [:sequential location-match-schema]
:next-key (random-uuid)
:key-fn :id}]]])
(defn bank-accounts-section []
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "Bank Accounts"}
(for [bank-account (sort-by :sort-order (:bank-accounts new-client))]
^{:key (:sort-order bank-account)}
[form-builder/with-scope {:scope [:bank-accounts (:sort-order bank-account)]}
[bank-account-card new-client bank-account (= 0 (:sort-order bank-account)) (= (:sort-order bank-account) (dec (count (:bank-accounts new-client))))]])
[:div.buttons
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :credit])} "Add Credit Account"]
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :check])} "Add Checking Account"]
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]]))
(defn cash-flow-section []
(let [next-week-a (if (is-week-a? (t/now))
"This week"
"Next week")
next-week-b (if (is-week-a? (t/now))
"Next week"
"This week")]
[form-builder/section {:title "Cash Flow"}
[:label.label (str "Week A (" next-week-a ")")]
[left-stack
[form-builder/field-v2 {:field :week-a-credits}
"Regular Credits"
[com/money-input]]
[form-builder/field-v2 {:field :week-a-debits}
"Regular Debits"
[com/money-input]]]
[:label.label (str "Week B (" next-week-b ")")]
[left-stack
[form-builder/field-v2 {:field :week-b-credits}
"Regular Credits"
[com/money-input]]
[form-builder/field-v2 {:field :week-b-debits}
"Regular Debits"
[com/money-input]]]
[form-builder/field-v2 {:field :forecasted-transactions}
"Forecasted transactions"
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :identifier}
[:input.input {:type "text"
:placeholder "Identifier"
:style {:width "10em"}}]]
[form-builder/raw-field-v2 {:field :day-of-month}
[com/number-input {:placeholder "DOM"}]]
[form-builder/raw-field-v2 {:field :amount
:placeholder "AMT"}
[com/money-input]]]
:key-fn :id}]]]))
(defn square-section []
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "Square Integration"}
[form-builder/field-v2 {:field :square-auth-token}
"Square Authentication Token"
[:input.input {:type "text"
:style {:width "40em"}}]]
[form-builder/field-v2 {:field :selected-square-locations}
"Square Locations"
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :square-location}
[typeahead-v3 {:entities (:square-locations new-client)
:entity->text :name
:style {:width "15em"}}]]
[form-builder/raw-field-v2 {:field :client-location}
[com/select-field {:options (map (fn [l]
[(:location l) (:location l)])
(get-in new-client [:locations]))
:allow-nil? true
:style {:width "7em"}
}]]]
:disable-remove? true
:key-fn :id
:schema [:sequential square-location-schema]}]]]))
(defn ezcater-section []
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "EZCater integration"}
[form-builder/field-v2 {:field :ezcater-locations}
"EZCater Locations"
[com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :caterer}
[search-backed-typeahead {:search-query (fn [i]
[:search_ezcater_caterer
{:query i}
[:name :id]])
:entity->text :name
:style {:width "20em"}}]]
[form-builder/raw-field-v2 {:field [:location]}
[com/select-field {:options (map (fn [l]
[(:location l) (:location l)])
(get-in new-client [:locations]))
:allow-nil? true
:style {:width "7em"}}]]]
:key-fn :id
:schema [:sequential ezcater-schema]
:disable-remove? true}]]]))
(defn form-content []
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
^{:key (or (:id new-client)
"new")}
[form-builder/builder {:submit-event [::save-new-client ]
:id ::form
:fullwidth? false
:schema client-schema}
[general-section]
[contacts-section]
[matching-section]
[bank-accounts-section]
[cash-flow-section]
[square-section]
[ezcater-section]
[form-builder/error-notification]
[form-builder/submit-button "Save"]]))
(def new-client-form
(with-meta
(fn []
(let [_ @(re-frame/subscribe [::subs/route-params])]
[form-content]))
{:component-did-mount #(re-frame/dispatch [::mounted])}))

View File

@@ -1,23 +0,0 @@
(ns auto-ap.views.pages.admin.clients.side-bar
(:require
[re-frame.core :as re-frame]
[auto-ap.views.utils :refer [dispatch-value-change]]
[auto-ap.views.pages.data-page :as data-page]))
(defn client-side-bar [{:keys [data-page]}]
[:div
[:p.menu-label "Name"]
[:div.field
[:div.control [:input.input {:placeholder "Harry's Food Products"
:value @(re-frame/subscribe [::data-page/filter data-page :name-like])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :name-like])} ]]]
[:p.menu-label "Code"]
[:div.field
[:div.control [:input.input {:placeholder "CBC"
:value @(re-frame/subscribe [::data-page/filter data-page :code])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :code])} ]]]])

View File

@@ -1,123 +0,0 @@
(ns auto-ap.views.pages.admin.clients.table
(:require [auto-ap.subs :as subs]
[clojure.string :as str]
[re-frame.core :as re-frame]
[auto-ap.views.utils :refer [action-cell-width date->str with-user]]
[auto-ap.views.components.grid :as grid]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.status :as status]
[bidi.bidi :as bidi]
[auto-ap.routes :as routes]
[auto-ap.views.pages.data-page :as data-page]))
(re-frame/reg-sub
::specific-params
(fn [db]
(::params db)))
(re-frame/reg-event-fx
::params-changed
(fn [{:keys [db]} [_ p]]
{:db (assoc db ::params p)}))
(re-frame/reg-event-fx
::sales-queries-setup
(fn [_ [_ results]]
{:dispatch [::modal/modal-requested {:title "Sales Queries"
:body [:div [:pre (:message (:setup-sales-queries results))]]}]}))
(re-frame/reg-event-fx
::setup-sales-queries
[with-user]
(fn [{:keys [user]} [_ client-id]]
{:graphql
{:token user
:owns-state {:multi ::setup-sales-queries
:which client-id}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "SetupSalesQueries"}
:venia/queries [{:query/data [:setup-sales-queries
{:client-id client-id}
[:message]]}]}
:on-success [::sales-queries-setup]}}
))
(re-frame/reg-sub
::params
:<- [::specific-params]
:<- [::subs/query-params]
(fn [[specific-params query-params]]
(merge (select-keys query-params #{:start :sort}) specific-params )))
(defn integration-status-badge [name status]
(condp = (:state status)
:success
[:div.tag.has-tooltip-right.has-tooltip-arrow {:data-tooltip (str "Last updated:" (date->str (:last-updated status))
"\n"
"Last Attempted:" (date->str (:last-attempt status)))} [:span.icon [:i.has-text-success.fa.fa-check]] [:span name]]
:failed
[:div.tag.is-danger.is-light.has-tooltip-right.has-tooltip-arrow {:data-tooltip (str "Last updated:" (date->str (:last-updated status))
"\n"
"Last Attempted:" (date->str (:last-attempt status))
"\n"
(:message status))
} [:span.icon [:i.has-text-danger.fa.fa-warning]] [:span name]]
:unauthorized
[:div.tag.is-danger.is-light.has-tooltip-right.has-tooltip-arrow {:data-tooltip (str "Last updated:" (date->str (:last-updated status))
"\n"
"Last Attempted:" (date->str (:last-attempt status))
"\n"
"Your user is unauthorized. Detail:\n"
(:message status))
} [:span.icon [:i.has-text-danger.fa.fa-warning]] [:span name]]
nil
))
(defn clients-table [{:keys [data-page status]}]
(let [states @(re-frame/subscribe [::status/multi ::setup-sales-queries])
{:keys [data]} @(re-frame/subscribe [::data-page/page data-page])]
[grid/grid {:on-params-change (fn [p]
(re-frame/dispatch [::params-changed p]))
:data-page data-page
:status status
:params @(re-frame/subscribe [::params])
:column-count 5}
[grid/controls data]
[grid/table {:fullwidth true}
[grid/header
[grid/row {}
[grid/header-cell {} "Name"]
[grid/header-cell {:style {:width "20em"}} "Code"]
[grid/header-cell {} "Locations"]
[grid/header-cell {} "Status"]
[grid/header-cell {} "Email"]
[grid/header-cell {:style {:width (action-cell-width 2)}}]]]
[grid/body
(for [{:keys [id name email square-integration-status locked-until code locations bank-accounts]} (:data data)]
^{:key (str name "-" id)}
[grid/row {:id id}
[grid/cell {} name]
[grid/cell {} code]
[grid/cell {} (str/join ", " locations)]
[grid/cell {:class "expandable"} [:div.tags
[:div.tag (or (some-> locked-until date->str (#(str "Locked " %))) "Not locked")]
[integration-status-badge "Square" square-integration-status]
[:<>
(for [bank-account bank-accounts
:let [code (:code bank-account)
integration-status (:integration-status bank-account)]
:when (:id integration-status)]
^{:key (:id integration-status)}
[integration-status-badge code integration-status])]]]
[grid/cell {} email]
[grid/cell {} [:div.buttons [buttons/fa-icon {:event [::setup-sales-queries id]
:class (status/class-for (get states id))
:icon :fa-dollar}]
[buttons/fa-icon {:href (bidi/path-for routes/routes :admin-specific-client :id id)
:icon :fa-pencil}]]]])]]
[grid/bottom-paginator data]]))

View File

@@ -1,94 +0,0 @@
(ns auto-ap.views.pages.admin.vendors
(:require
[auto-ap.effects.forward :as forward]
[auto-ap.subs :as subs]
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.pages.admin.vendors.merge-dialog :as merge-dialog]
[auto-ap.views.pages.admin.vendors.side-bar :as side-bar]
[auto-ap.views.pages.admin.vendors.table :as table]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.utils :refer [dispatch-event with-user]]
[clojure.set :as set]
[re-frame.core :as re-frame]
[vimsical.re-frame.fx.track :as track]
[auto-ap.views.components.vendor-dialog :as vendor-dialog]))
(def default-read [:id :name :hidden :terms [:default-account [:name :id :location]]
[:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]]
[:automatically-paid-when-due [:id :name]]
[:terms-overrides [[:client [:id :name]] :id :terms]]
[:schedule-payment-dom [[:client [:id :name]] :id :dom]]
[:usage [:client-id :count]]
[:primary-contact [:name :phone :email :id]]
[:secondary-contact [:id :name :phone :email]]
[:plaid-merchant [:id :name]]
:print-as :invoice-reminder-schedule :code
:legal-entity-name
:legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name
:legal-entity-tin :legal-entity-tin-type
:legal-entity-1099-type
[:address [:id :street1 :street2 :city :state :zip]]])
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user]} [_ params]]
{:graphql {:token user
:owns-state {:single [::data-page/page ::page]}
:query-obj {:venia/queries [{:query/data [:vendor
{:sort (:sort params)
:start (:start params 0)
:per-page (:per-page params)
:name-like (:name-like params)}
[[:vendors default-read]
:total
:start
:end]]
:query/alias :result}]}
:on-success (fn [result]
[::data-page/received ::page
(set/rename-keys (:result result)
{:vendors :data})])}}))
(re-frame/reg-event-fx
::mounted
(fn [_ _]
{::forward/register [{:id ::merge-complete
:events #{::merge-dialog/complete}
:event-fn (fn [_]
[::params-change {}])}
{:id ::save-complete
:events #{::vendor-dialog/save-complete}
:event-fn (fn [_]
[::params-change {}])}]
::track/register {:id ::params
:subscription [::data-page/params ::page]
:event-fn (fn [params]
[::params-change params])}}))
(re-frame/reg-event-fx
::unmounted
(fn [_ _]
{:dispatch [::data-page/dispose ::page]
::forward/dispose [{:id ::merge-complete} {:id ::save-complete}]
::track/dispose {:id ::params}}))
(defn admin-vendors-content []
[(with-meta
(fn []
[:div.inbox-messages
(when-let [banner (:banner @(re-frame/subscribe [::subs/admin]))]
[:div.notification banner])
[:div
[:h1.title "Vendors"]
[:div.is-pulled-right [:a.button.is-primary.is-outlined {:on-click (dispatch-event [::merge-dialog/show])} "Merge vendors"]]
[table/vendors-table {:id :vendors
:data-page ::page}]]])
{:component-did-mount #(re-frame/dispatch [::mounted])
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])})])
(defn admin-vendors-page []
[side-bar-layout {:side-bar [admin-side-bar {}
[side-bar/vendor-side-bar {:data-page ::page}]]
:main [admin-vendors-content]}])

View File

@@ -1,17 +0,0 @@
(ns auto-ap.views.pages.admin.vendors.common)
(def default-read [:id :name :hidden :terms [:default-account [:name :id :location]]
[:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]]
[:automatically-paid-when-due [:id :name]]
[:terms-overrides [[:client [:id :name]] :id :terms]]
[:schedule-payment-dom [[:client [:id :name]] :id :dom]]
[:usage [:client-id :count]]
[:primary-contact [:name :phone :email :id]]
[:plaid-merchant [:name :id]]
[:secondary-contact [:id :name :phone :email]]
:print-as :invoice-reminder-schedule :code
:legal-entity-name
:legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name
:legal-entity-tin :legal-entity-tin-type
:legal-entity-1099-type
[:address [:id :street1 :street2 :city :state :zip]]])

View File

@@ -1,80 +0,0 @@
(ns auto-ap.views.pages.admin.vendors.merge-dialog
(:require
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.schema :as schema]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components :as com]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.utils :refer [dispatch-event]]
[malli.core :as m]
[re-frame.core :as re-frame]))
(def merge-schema
(m/schema [:map
[:from schema/reference]
[:to schema/reference]]))
(defn form []
[form-builder/builder {:submit-event [::try-save]
:id ::form
:schema merge-schema}
[form-builder/field-v2 {:field :from}
"Form Vendor (will be deleted)"
[com/search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:auto-focus true}]]
[form-builder/field-v2 {:field :to}
"To Vendor"
[com/search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])}]]
[form-builder/hidden-submit-button]])
(re-frame/reg-event-fx
::show
(fn [{:keys [db]} _]
{:dispatch [::modal/modal-requested {:title "Merge Vendors"
:body [form]
:confirm {:value "Merge"
:status-from [::status/single ::form]
:class "is-primary"
:on-click (dispatch-event [::try-save])
:close-event [::status/completed ::form]}}]
:db (forms/start-form db ::form {})}))
(re-frame/reg-event-fx
::complete
(fn [{:keys [db]} _]
{:db (forms/stop-form db ::form)
:dispatch [::modal/modal-closed ]}))
(re-frame/reg-event-fx
::save
[(forms/in-form ::form)]
(fn [{{{:keys [from to]} :data} :db} _]
(let [user @(re-frame/subscribe [::subs/token])]
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "MergeVendors"}
:venia/queries [{:query/data [:merge-vendors
{:from (:id from) :to (:id to)} []]}]}
:on-success [::complete]}})))
(re-frame/reg-event-fx
::try-save
[(forms/in-form ::form)]
(fn [{:keys [db]}]
(if (not (m/validate merge-schema (:data db)))
{:dispatch-n [[::status/error ::form [{:message "Please correct any errors and try again"}]]
[::forms/attempted-submit ::form]]}
{:dispatch [::save]})))

View File

@@ -1,16 +0,0 @@
(ns auto-ap.views.pages.admin.vendors.side-bar
(:require
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.utils :refer [dispatch-value-change]]
[re-frame.core :as re-frame]))
(defn vendor-side-bar [{:keys [data-page]}]
[:div
[:p.menu-label "Name"]
[:div
[:div.field
[:div.control [:input.input {:placeholder "HOME DEPOT"
:value @(re-frame/subscribe [::data-page/filter data-page :name-like])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :name-like])} ]]]]])

View File

@@ -1,36 +0,0 @@
(ns auto-ap.views.pages.admin.vendors.table
(:require
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.components.grid :as grid]
[auto-ap.views.components.vendor-dialog :as vendor-dialog]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.utils :refer [action-cell-width]]
[re-frame.core :as re-frame]))
(defn vendors-table [{:keys [data-page]}]
(let [{:keys [data]} @(re-frame/subscribe [::data-page/page data-page])]
[grid/grid {:data-page data-page
:column-count 4}
[grid/controls data]
[grid/table {:fullwidth true}
[grid/header
[grid/row {}
[grid/header-cell {} "Name"]
[grid/header-cell {} "Email"]
[grid/header-cell {} "Default Account"]
[grid/header-cell {:style {:width (action-cell-width 1)}}]]]
[grid/body
(for [v (:data data)]
^{:key (str (:id v))}
[grid/row {:class (:class v) :id (:id v)}
[grid/cell {} (:name v)
(let [total-usage (reduce + 0 (map :count (:usage v)))]
(if (> total-usage 0)
[:div.mx-2.tag.is-info.is-light total-usage " usages, " (count (:usage v)) " clients"]
[:div.mx-2.tag.is-warning.is-light "Unused"]))]
[grid/cell {} (:email (:primary-contact v))]
[grid/cell {} (-> v :default-account :name)]
[grid/cell {}
[buttons/fa-icon {:event [::vendor-dialog/started v]
:icon "fa-pencil"}]]])]]
[grid/bottom-paginator data]]))