Form builder as a way to simplify some things

This commit is contained in:
2022-07-14 22:08:03 -07:00
parent 99fe24ddae
commit 586978823e
6 changed files with 760 additions and 640 deletions

View File

@@ -17,14 +17,6 @@
:data :data
(get-in f)))) (get-in f))))
(re-frame/reg-sub
::is-loading?
(fn [db [_ x]]
(if (#{"loading" :loading} (get-in db [::forms x :status]) )
true
false)))
(re-frame/reg-sub (re-frame/reg-sub
::loading-class ::loading-class
(fn [db [_ x]] (fn [db [_ x]]
@@ -43,10 +35,6 @@
:data data :data data
:complete-listener complete-listener}))) :complete-listener complete-listener})))
(defn ^:depracated saved-form [db form data]
(update-in db [::forms form]
assoc :error nil :status nil :data data))
(defn triggers-saved [form data-key] (defn triggers-saved [form data-key]
(i/->interceptor (i/->interceptor
:id :triggers-saved :id :triggers-saved
@@ -55,7 +43,6 @@
:after (fn [context] :after (fn [context]
(let [db (i/get-coeffect context :db) (let [db (i/get-coeffect context :db)
result (get-in (i/get-coeffect context :event) [1 data-key])] result (get-in (i/get-coeffect context :event) [1 data-key])]
(cond-> context (cond-> context
true true
(i/assoc-effect :db (update-in db (i/assoc-effect :db (update-in db

View File

@@ -0,0 +1,106 @@
(ns auto-ap.forms.builder
(:require
[auto-ap.views.utils :refer [bind-field]]
[re-frame.core :as re-frame]
[react :as react]
[reagent.core :as r]
[auto-ap.forms :as forms]
[auto-ap.status :as status]))
(defonce ^js/React.Context form-context (react/createContext "default"))
(def ^js/React.Provider Provider (. form-context -Provider))
(def ^js/React.Consumer Consumer (. form-context -Consumer))
(defonce ^js/React.Context form-scope-context (react/createContext []))
(def ^js/React.Provider FormScopeProvider (. form-scope-context -Provider))
(def ^js/React.Consumer FormScopeConsumer (. form-scope-context -Consumer))
(defn builder [{:keys [can-submit data-sub change-event submit-event id fullwidth?] :as z}]
(let [data-sub (or data-sub [::forms/form id])
change-event (or change-event [::forms/change id])
{:keys [data error]} @(re-frame/subscribe data-sub)]
(r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit)
:change-event change-event
:submit-event submit-event
:error error
:status @(re-frame/subscribe [::status/single id])
:id id
:data data
:fullwidth? fullwidth?}}
(r/as-element
(into [:form {:on-submit (fn [e]
(when (.-stopPropagation e)
(.stopPropagation e)
(.preventDefault e))
(when can-submit
(re-frame/dispatch-sync (vec (conj submit-event {})))))}]
(r/children (r/current-component)))))))
(defn raw-field []
(let [[child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume-form]
(r/as-element
[:> FormScopeConsumer {}
(fn [form-scope]
(r/as-element
[bind-field (-> child
(update-in [1 :field] (fn [f]
(cond
(sequential? f)
(into form-scope f)
f
(conj form-scope f)
:else
nil)))
(assoc-in [1 :subscription] (aget consume-form "data"))
(assoc-in [1 :event] (aget consume-form "change-event")))]))]))]))
(defn with-scope [{:keys [scope]}]
(r/create-element FormScopeProvider #js {:value scope}
(r/as-element (into [:<>]
(r/children (r/current-component))))))
(defn field []
(let [[label child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(r/as-element
[:div.field
(when label (if (aget consume "fullwidth?") [:p.help label]
[:label.label label]))
[:div.control [raw-field {} child]]]))]))
(defn section [{:keys [title]}]
[:<>
[:h4.is-4.title title]
[:hr]
(into [:div {:style {:margin-bottom "5em"}}]
(r/children (r/current-component)))])
(defn submit-button []
(let [[child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(let [status (aget consume "status")
can-submit (aget consume "can-submit")
fullwidth? (aget consume "fullwidth?")]
(r/as-element
[:button.button.is-medium.is-primary {:disabled (or (status/disabled-for status)
(not can-submit))
:class (cond-> (status/class-for status)
fullwidth? (conj "is-fullwidth")) }
child])))]))
(defn error-notification []
(let [[child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(r/as-element
(when-let [error (aget consume "error")]
^{:key error}
[:div.has-text-danger.animated.fadeInUp {} error])))]))

View File

@@ -1,6 +1,7 @@
(ns auto-ap.views.components.address (ns auto-ap.views.components.address
(:require [auto-ap.entities.address :as address] (:require [auto-ap.entities.address :as address]
[auto-ap.views.utils :refer [dispatch-value-change dispatch-event bind-field horizontal-field]])) [auto-ap.views.utils :refer [dispatch-value-change dispatch-event bind-field horizontal-field]]
[auto-ap.forms.builder :as form-builder]))
(defn address-field [{:keys [event field subscription]}] (defn address-field [{:keys [event field subscription]}]
[:span [:span
@@ -58,3 +59,50 @@
:event event :event event
:subscription subscription :subscription subscription
:placeholder "95014"}]]]]]) :placeholder "95014"}]]]]])
(defn address2-field []
[:span
[horizontal-field
nil
[:div.control
[:p.help "Address"]
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:placeholder "1700 Pennsylvania Ave"
:field [:street1]
:spec ::address/street1}]]]]
[horizontal-field
nil
[:div.control
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:placeholder "Suite 400"
:field [:street2]
:spec ::address/street2}]]]]
[horizontal-field
nil
[:div.control
[:p.help "City"]
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:placeholder "Cupertino"
:field [:city]
:spec ::address/city}]]]
[:div.control
[:p.help "State"]
[form-builder/raw-field
[:input.input {:type "text"
:placeholder "CA"
:field [:state]
:spec ::address/state
:size 2
:max-length "2"}]]]
[:div.control
[:p.help "Zip"]
[form-builder/raw-field
[:input.input {:type "text"
:field [:zip]
:spec ::address/zip
:placeholder "95014"}]]]]])

View File

@@ -1,21 +1,20 @@
(ns auto-ap.views.pages.admin.clients (ns auto-ap.views.pages.admin.clients
(:require (:require
[auto-ap.forms :as forms] [auto-ap.routes :as routes]
[auto-ap.subs :as subs]
[auto-ap.status :as status] [auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]] [auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.components.grid :as grid] [auto-ap.views.components.grid :as grid]
[auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.pages.admin.clients.form :as form] [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.side-bar :as side-bar]
[auto-ap.views.pages.admin.clients.table :as table] [auto-ap.views.pages.admin.clients.table :as table]
[auto-ap.views.utils :refer [dispatch-event transition switch-transition with-user transition-group]] [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.string :as str]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as r] [vimsical.re-frame.fx.track :as track]))
[vimsical.re-frame.fx.track :as track]
[bidi.bidi :as bidi]
[auto-ap.routes :as routes]))
(re-frame/reg-event-db (re-frame/reg-event-db
::received-intuit-bank-accounts ::received-intuit-bank-accounts
@@ -25,7 +24,7 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::mounted ::mounted
[with-user] [with-user]
(fn [{:keys [db user]} _] (fn [{:keys [user]} _]
{::track/register {:id ::params {::track/register {:id ::params
:subscription [::params] :subscription [::params]
:event-fn (fn [params] [::params-change params])} :event-fn (fn [params] [::params-change params])}
@@ -49,14 +48,6 @@
(seq filter-params) (merge filter-params) (seq filter-params) (merge filter-params)
(seq table-params) (merge table-params)))) (seq table-params) (merge table-params))))
(re-frame/reg-event-db
::new
(fn [db [_ client-id]]
(-> db
(forms/start-form ::form/form {:bank-accounts []}))))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::params-change ::params-change
(fn [_ [_ params]] (fn [_ [_ params]]
@@ -75,125 +66,28 @@
(assoc (grid/virtual-paginate-controls (:start params ) (:per-page params) matching-clients) (assoc (grid/virtual-paginate-controls (:start params ) (:per-page params) matching-clients)
:data (grid/virtual-paginate (:start params) (:per-page params) matching-clients))))) :data (grid/virtual-paginate (:start params) (:per-page params) matching-clients)))))
(defn stacked-page [& children]
[:div
(->> children
(reverse)
(filter identity)
first)])
(defn hierachy []
(let [last-stack-size (r/atom (->> (r/current-component)
r/children
(filter identity) count))
forward? (r/atom false)]
(fn []
(let [stack (filter identity (r/children (r/current-component)))
current-stack-size (count stack)
current-child (last stack)]
(when (not= current-stack-size @last-stack-size)
(reset! forward? (> current-stack-size @last-stack-size))
(reset! last-stack-size current-stack-size))
[:div
[switch-transition {:mode "in-out"}
^{:key current-stack-size}
[transition
{:timeout 100
:exit true
:in true #_(= current-stack- (:key (meta child)))
:appear (> current-stack-size 1)}
(clj->js (fn [state]
(r/as-element
[:div {:style {
:position (cond
(= "entered" state)
""
(= "entering" state)
"absolute"
(= "exiting" state)
"absolute"
(= "exited" state)
"")
:transition "opacity 300ms ease-in-out, transform 300ms ease-in-out"
:opacity (cond
(= "entered" state)
""
(= "entering" state)
0.0
(= "exiting" state)
1.0
(= "exited" state)
0.0)
:transform (if @forward?
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(100%)"
(= "exiting" state)
"translateX(-100%)"
(= "exited" state)
"translateX(0%)")
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(-100%)"
(= "exiting" state)
"translateX(100%)"
(= "exited" state)
"translateX(0%)"))}}
current-child])))]]
]))))
(defn test-appear []
[:div
[hierachy
[table/clients-table {:page @(re-frame/subscribe [::page])
:status @(re-frame/subscribe [::status/single ::page])}]
(when (#{:admin-specific-bank-account :admin-specific-client} @(re-frame/subscribe [::subs/active-route]))
[form/new-client-form])
(when (= :admin-specific-bank-account @(re-frame/subscribe [::subs/active-route]))
[:div "This is a bank account!"])]])
(defn breadcrumbs []
[:h1.title.is-1
(cond (= :admin-clients @(re-frame/subscribe [::subs/active-route]))
"Clients"
:else
[:span [:a {:href (bidi/path-for routes/routes :admin-clients)}
"Clients"]
" / "
(or (:name (:data @(re-frame/subscribe [::forms/form ::form/form])))
[:i "New client"])])
])
(def admin-clients-content (def admin-clients-content
(with-meta (with-meta
(fn [] (fn []
[:div [:div
[breadcrumbs] [page-stack/page-stack
{:active @(re-frame/subscribe [::subs/active-route])
:pages [{:key :admin-clients
:breadcrumb "Clients"
:content [:<>
[:div.is-pulled-right [:div.is-pulled-right
(when (= :admin-clients @(re-frame/subscribe [::subs/active-route])) [:a.button.is-primary.is-outlined {:href (bidi/path-for routes/routes :admin-specific-client :id "new")} "New client"]]
[:a.button.is-primary.is-outlined {:href (bidi/path-for routes/routes :admin-specific-client :id "new")} "New client"])] [table/clients-table {:page @(re-frame/subscribe [::page])
[test-appear] :status @(re-frame/subscribe [::status/single ::page])}]]}
])
{:key :admin-specific-client
:breadcrumb [:span [:a {:href (bidi/path-for routes/routes :admin-clients)}
"Clients"]
" / "
(or (:name @(re-frame/subscribe [::form/client]))
[:i "New client"])]
:content [form/new-client-form]}
]}]])
{:component-did-mount #(re-frame/dispatch [::mounted]) {:component-did-mount #(re-frame/dispatch [::mounted])
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])})) :component-will-unmount #(re-frame/dispatch-sync [::unmounted])}))

View File

@@ -1,30 +1,32 @@
(ns auto-ap.views.pages.admin.clients.form (ns auto-ap.views.pages.admin.clients.form
(:require [auto-ap.entities.clients :as entity] (:require
[auto-ap.forms :as forms] [auto-ap.entities.clients :as entity]
[auto-ap.subs :as subs]
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.views.components.address :refer [address-field]] [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.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.typeahead.vendor [auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]] :refer [search-backed-typeahead]]
[auto-ap.views.components.layouts :refer [side-bar]]
[auto-ap.views.utils [auto-ap.views.utils
:refer :refer [date->str
[date->str
date-picker date-picker
date-picker-friendly
dispatch-event dispatch-event
horizontal-field horizontal-field
multi-field multi-field
standard]] standard]]
[bidi.bidi :as bidi]
[cljs-time.coerce :as coerce] [cljs-time.coerce :as coerce]
[cljs-time.core :as t] [cljs-time.core :as t]
[clojure.string :as str] [clojure.string :as str]
[vimsical.re-frame.cofx.inject :as inject]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as r] [reagent.core :as r]
[react-signature-canvas] [react-signature-canvas]
[bidi.bidi :as bidi] [vimsical.re-frame.cofx.inject :as inject]))
[auto-ap.routes :as routes]))
(def signature-canvas (r/adapt-react-class (.-default react-signature-canvas))) (def signature-canvas (r/adapt-react-class (.-default react-signature-canvas)))
@@ -196,13 +198,18 @@
:bank-code bank-code}) :bank-code bank-code})
(:bank-accounts new-client-data))}))) (:bank-accounts new-client-data))})))
(re-frame/reg-sub
::client
:<- [::subs/route-params]
:<- [::subs/clients-by-id]
(fn [[rp clients-by-id]]
(or (clients-by-id (:id rp))
{})))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::mounted ::mounted
[(re-frame/inject-cofx ::inject/sub [::subs/route-params]) [(re-frame/inject-cofx ::inject/sub [::client])]
(re-frame/inject-cofx ::inject/sub [::subs/clients-by-id])] (fn [{:keys [db] ::keys [client]} _]
(fn [{:keys [db]
::subs/keys [clients-by-id route-params]} [_ client-id]]
(let [client (get clients-by-id (:id route-params))]
{:db (-> db {:db (-> db
(forms/stop-form ::form) (forms/stop-form ::form)
(forms/start-form ::form (-> client (forms/start-form ::form (-> client
@@ -220,7 +227,7 @@
(update ba :locations (fn [ls] (update ba :locations (fn [ls]
(map (fn [l] {:location l}) (map (fn [l] {:location l})
ls)))) ls))))
bas))))))}))) bas))))))}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::save-new-client ::save-new-client
@@ -302,12 +309,6 @@
(def client-form
(forms/vertical-form {:can-submit [::can-submit]
:change-event [::forms/change ::form]
:submit-event [::save-new-client ]
:id ::form
:fullwidth? false}))
(def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00")) (def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00"))
@@ -335,7 +336,6 @@
(defn bank-account-card [new-client {:keys [active? new? type visible code name sort-order]} first? last?] (defn bank-account-card [new-client {:keys [active? new? type visible code name sort-order]} first? last?]
(let [{:keys [field raw-field]} client-form]
[:div.card {:style {:margin-bottom "1em" [:div.card {:style {:margin-bottom "1em"
:width "600px"}} :width "600px"}}
[:header.card-header.has-background-primary-light [:header.card-header.has-background-primary-light
@@ -384,25 +384,28 @@
[:div.field.has-addons.is-extended [:div.field.has-addons.is-extended
[:p.control [:a.button.is-static (:code new-client) "-" ]] [:p.control [:a.button.is-static (:code new-client) "-" ]]
[:p.control [:p.control
[raw-field [form-builder/raw-field
[:input.input {:type "code" [:input.input {:type "code"
:field [:bank-accounts sort-order :code] :field [:code]
:spec ::entity/code}]]]] :spec ::entity/code}]]]]
[:div.field [:p.control code]])] [:div.field [:p.control code]])]
[field "Nickname" [form-builder/field
"Nickname"
[:input.input {:placeholder "BOA Checking #1" [:input.input {:placeholder "BOA Checking #1"
:type "text" :type "text"
:field [:bank-accounts sort-order :name]}]] :field [:name]}]]
[horizontal-field [horizontal-field
nil nil
[field "Numeric Code" [form-builder/field
"Numeric Code"
[:input.input {:placeholder "20101" [:input.input {:placeholder "20101"
:type "text" :type "text"
:field [:bank-accounts sort-order :numeric-code]}]]] :field [:numeric-code]}]]]
[field "Start date" [form-builder/field
"Start date"
[date-picker {:class-name "input" [date-picker {:class-name "input"
:class "input" :class "input"
:format-week-number (fn [] "") :format-week-number (fn [] "")
@@ -411,65 +414,75 @@
:next-month-button-label "" :next-month-button-label ""
:next-month-label "" :next-month-label ""
:type "date" :type "date"
:field [:bank-accounts sort-order :start-date]}]]] :field [:start-date]}]]]
(when (#{:check ":check"} type ) (when (#{:check ":check"} type )
[:div [:div
[:label.label "Bank"] [:label.label "Bank"]
[horizontal-field [horizontal-field
nil nil
[field "Bank Name" [form-builder/field
"Bank Name"
[:input.input {:placeholder "Bank of America" [:input.input {:placeholder "Bank of America"
:type "text" :type "text"
:field [:bank-accounts sort-order :bank-name]}]] :field [:bank-name]}]]
[field "Routing #" [form-builder/field
"Routing #"
[:input.input {:placeholder "104819123" [:input.input {:placeholder "104819123"
:style {:width "9em"} :style {:width "9em"}
:type "text" :type "text"
:field [:bank-accounts sort-order :routing]}]] :field [:routing]}]]
[field "Bank code" [form-builder/field
"Bank code"
[:input.input {:placeholder "12/10123" [:input.input {:placeholder "12/10123"
:type "text" :type "text"
:field [:bank-accounts sort-order :bank-code]}]]] :field [:bank-code]}]]]
[horizontal-field [horizontal-field
nil nil
[field "Account #" [form-builder/field
"Account #"
[:input.input {:placeholder "123456789" [:input.input {:placeholder "123456789"
:type "text" :type "text"
:style {:width "20em"} :style {:width "20em"}
:field [:bank-accounts sort-order :number]}]] :field [:number]}]]
[field "Check Number" [form-builder/field
"Check Number"
[:input.input {:placeholder "10000" [:input.input {:placeholder "10000"
:style {:width "6em"} :style {:width "6em"}
:type "text" :type "text"
:field [:bank-accounts sort-order :check-number]}]]] :field [:check-number]}]]]
[field "Yodlee Account" [form-builder/field
"Yodlee Account"
[:input.input {:placeholder "Yodlee Account #" [:input.input {:placeholder "Yodlee Account #"
:type "text" :type "text"
:field [:bank-accounts sort-order :yodlee-account-id]}]] :field [:yodlee-account-id]}]]
[field "Yodlee Account (new)" [form-builder/field
"Yodlee Account (new)"
[typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)]) [typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)])
:entity->text (fn [m] (str (:name m) " - " (:number m))) :entity->text (fn [m] (str (:name m) " - " (:number m)))
:type "typeahead-v3" :type "typeahead-v3"
:field [:bank-accounts sort-order :yodlee-account]}]] :field [:yodlee-account]}]]
[:div.field [:div.field
[:label.checkbox [:label.checkbox
[raw-field [form-builder/raw-field
[:input {:type "checkbox" [:input {:type "checkbox"
:field [:bank-accounts sort-order :use-date-instead-of-post-date]}]] :field [:use-date-instead-of-post-date]}]]
" (Yodlee only) Use 'date' instead of 'postDate'"]] " (Yodlee only) Use 'date' instead of 'postDate'"]]
[field "Intuit Bank Account"
[form-builder/field
"Intuit Bank Account"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
:entity->text (fn [m] (str (:name m))) :entity->text (fn [m] (str (:name m)))
:type "typeahead-v3" :type "typeahead-v3"
:field [:bank-accounts sort-order :intuit-bank-account]}]] :field [:intuit-bank-account]}]]
[field "Plaid Account" [form-builder/field
"Plaid Account"
[typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)])
:entity->text (fn [m] (str (:name m))) :entity->text (fn [m] (str (:name m)))
:type "typeahead-v3" :type "typeahead-v3"
:field [:bank-accounts sort-order :plaid-account]}]]]) :field [:plaid-account]}]]])
(when (#{:credit ":credit"} type ) (when (#{:credit ":credit"} type )
[:div [:div
@@ -478,52 +491,57 @@
[:label.label "Account"] [:label.label "Account"]
[horizontal-field [horizontal-field
nil nil
[field "Bank Name" [form-builder/field
"Bank Name"
[:input.input {:placeholder "Bank of America" [:input.input {:placeholder "Bank of America"
:type "text" :type "text"
:field [:bank-accounts sort-order :bank-name]}]]] :field [:bank-name]}]]]
[horizontal-field [horizontal-field
nil nil
[field "Account #" [form-builder/field
"Account #"
[:input.input {:placeholder "123456789" [:input.input {:placeholder "123456789"
:type "text" :type "text"
:field [:bank-accounts sort-order :number]}]]] :field [:number]}]]]
[field "Yodlee Account" [form-builder/field
"Yodlee Account"
[:input.input {:placeholder "Yodlee Account #" [:input.input {:placeholder "Yodlee Account #"
:type "text" :type "text"
:field [:bank-accounts sort-order :yodlee-account-id]}]] :field [:yodlee-account-id]}]]
[field "Yodlee Account (new)" [form-builder/field
"Yodlee Account (new)"
[typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)]) [typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)])
:entity->text (fn [m] (str (:name m) " - " (:number m))) :entity->text (fn [m] (str (:name m) " - " (:number m)))
:type "typeahead-v3" :type "typeahead-v3"
:field [:bank-accounts sort-order :yodlee-account]}]] :field [:yodlee-account]}]]
[:div.field [:div.field
[:label.checkbox [:label.checkbox
[raw-field [form-builder/raw-field
[:input {:type "checkbox" [:input {:type "checkbox"
:field [:bank-accounts sort-order :use-date-instead-of-post-date]}]] :field [:use-date-instead-of-post-date]}]]
" (Yodlee only) Use 'date' instead of 'postDate'"]] " (Yodlee only) Use 'date' instead of 'postDate'"]]
[field "Intuit Bank Account" [form-builder/field
"Intuit Bank Account"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
:entity->text (fn [m] (str (:name m))) :entity->text (fn [m] (str (:name m)))
:type "typeahead-v3" :type "typeahead-v3"
:field [:bank-accounts sort-order :intuit-bank-account]}]] :field [:intuit-bank-account]}]]
[field "Plaid Account" [form-builder/field
"Plaid Account"
[typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)])
:entity->text (fn [m] (str (:name m))) :entity->text (fn [m] (str (:name m)))
:type "typeahead-v3" :type "typeahead-v3"
:field [:bank-accounts sort-order :plaid-account]}]] :field [:plaid-account]}]]])
])
[:div.field [:div.field
[:label.label "Locations"] [:label.label "Locations"]
[:div.control [:div.control
[:p.help "If this account is location-specific, add the valid locations"] [:p.help "If this account is location-specific, add the valid locations"]
[raw-field [form-builder/raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field [:bank-accounts sort-order :locations] :field [:locations]
:template [[:select.select {:type "select" :template [[:select.select {:type "select"
:style {:width "7em"} :style {:width "7em"}
:allow-nil? true :allow-nil? true
@@ -536,77 +554,43 @@
(get-in new-client [:locations]))]]]]}]]]] (get-in new-client [:locations]))]]]]}]]]]
[:div.field [:div.field
[:label.checkbox [:label.checkbox
[raw-field [form-builder/raw-field
[:input {:type "checkbox" [:input {:type "checkbox"
:field [:bank-accounts sort-order :include-in-reports]}]] :field [:include-in-reports]}]]
" Include in reports"]]]) " Include in reports"]]])
(when active? (when active?
[:footer.card-footer [:footer.card-footer
[:a.card-footer-item {:href "#" :on-click (dispatch-event [::bank-account-deactivated sort-order])} "Done"] [:a.card-footer-item {:href "#" :on-click (dispatch-event [::bank-account-deactivated sort-order])} "Done"]
(when new? (when new?
[:a.card-footer-item.is-warning {:href "#" :on-click (dispatch-event [::bank-account-removed sort-order])} "Remove"])])])) [:a.card-footer-item.is-warning {:href "#" :on-click (dispatch-event [::bank-account-removed sort-order])} "Remove"])])])
(defn form-section [{:keys [title]}]
[:<>
[:h4.is-4.title title]
[:hr]
(into [:div {:style {:margin-bottom "5em"}}]
(r/children (r/current-component)))])
(defn form-content []
(let [{new-client :data :as f} @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline 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")]
^{:key (or (:id new-client)
"new")}
[:div ;; div is important for actually unmounting for now
(form-inline {:title ""} (defn general-section []
[:<> (let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "General"}
[form-builder/field
[form-section {:title "General"} "Name"
(field "Name"
[:input.input {:type "text" [:input.input {:type "text"
:style {:width "20em"} :style {:width "20em"}
:field [:name] :field [:name]
:spec ::entity/name :spec ::entity/name}]]
}]) [form-builder/field
[:div.field "Client code"
[:label.label "Client code"]
(if (:id new-client)
[:div.control
(raw-field
[:input.input {:type "code" [:input.input {:type "code"
:style {:width "5em"} :style {:width "5em"}
:field :code :field :code
:spec ::entity/code :disabled (boolean (:id new-client))
:disabled true}]) :spec ::entity/code}]]
]
[:div.control
(raw-field
[:input.input {:type "code"
:style {:width "5em"}
:field :code
:spec ::entity/code}])])]
[:div.field [form-builder/field
[:label.label "Locations"] "Locations"
[:div.control
(raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :locations :field :locations
:allow-change? false :allow-change? false
:template [[:input.input {:field [:location] :template [[:input.input {:field [:location]
:maxlength 2 :max-length 2
:style { :width "4em"}}]]}])]] :style { :width "4em"}}]]}]]
[:div.field [:div.field
[:label.label "Signature"] [:label.label "Signature"]
@@ -615,26 +599,17 @@
:on-change (fn [uri] :on-change (fn [uri]
(re-frame/dispatch [::forms/change ::form [:signature-data] uri]))}]] (re-frame/dispatch [::forms/change ::form [:signature-data] uri]))}]]
[form-builder/field
"Locked Until"
(field "Locked Until" [date-picker-friendly {:type "date"
[date-picker {:class-name "input"
:class "input"
:format-week-number (fn [] "")
:previous-month-button-label ""
:placeholder "mm/dd/yyyy"
:next-month-button-label ""
:next-month-label ""
:type "date"
:field [:locked-until] :field [:locked-until]
:style {:width "15em"}}])] :style {:width "15em"}}]]]))
[form-section {:title "Contacts"} (defn contacts-section []
[form-builder/section {:title "Contacts"}
[:div.field [form-builder/field
[:label.label "Emails (address/description)"] "Emails (address/description)"
[:div.control
(raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :emails :field :emails
:template [[:input.input {:type "email" :template [[:input.input {:type "email"
@@ -643,30 +618,28 @@
:spec ::entity/email}] :spec ::entity/email}]
[:input.input {:type "text" [:input.input {:type "text"
:placeholder "Manager" :placeholder "Manager"
:field [:description]}]]}])]] :field [:description]}]]}]]
[:label.label "Address"]
[:div {:style {:width "30em"}}
[address-field {:field [:address]
:event [::forms/change ::form]
:subscription new-client}]]]
[form-builder/with-scope {:scope [:address ]}
[form-section {:title "Matching"}
[:div.field [:div.field
[:label.label "Name matches"] [:label.label "Address"]
[:div.control [:div.control
(raw-field [:div {:style {:width "30em"}}
[address2-field]]]]]])
(defn matching-section []
[form-builder/section {:title "Matching"}
[form-builder/field
"Name matches"
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :matches :field :matches
:template [[:input.input {:field [:match] :template [[:input.input {:field [:match]
:placeholder "Harry's burger joint" :placeholder "Harry's burger joint"
:style { :width "15em"}}]]}])]] :style { :width "15em"}}]]}]]
[:div.field [form-builder/field
[:label.label "Location Matches"] "Location Matches"
[:div.control
(raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :location-matches :field :location-matches
:template [[:input.input {:field [:match] :template [[:input.input {:field [:match]
@@ -675,34 +648,45 @@
[:input.input {:field [:location] [:input.input {:field [:location]
:placeholder "DT" :placeholder "DT"
:maxlength 2 :maxlength 2
:style { :width "4em"}}]]}])]]] :style { :width "4em"}}]]}]]])
(defn bank-accounts-section []
(let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "Bank Accounts"}
[form-section {:title "Bank Accounts"}
(for [bank-account (sort-by :sort-order (:bank-accounts new-client))] (for [bank-account (sort-by :sort-order (:bank-accounts new-client))]
^{:key (:sort-order bank-account)} ^{: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))))]) [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 [: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 :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 :check])} "Add Checking Account"]
[:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]] [:a.button.is-primary.is-outlined {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]]))
[form-section {:title "Cash flow"} (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 ")")] [:label.label (str "Week A (" next-week-a ")")]
[:div.level [:div.level
[:div.level-left [:div.level-left
[:div.level-item [:div.level-item
[field "Regular Credits" [form-builder/field
"Regular Credits"
[:input.input {:type "number" [:input.input {:type "number"
:style {:width "10em"} :style {:width "10em"}
:placeholder "500.00" :placeholder "500.00"
:field [:week-a-credits] :field [:week-a-credits]
:step "0.01"}]]] :step "0.01"}]]]
[:div.level-item [:div.level-item
[field "Regular Debits" [form-builder/field
"Regular Debits"
[:input.input {:type "number" [:input.input {:type "number"
:style {:width "10em"} :style {:width "10em"}
:placeholder "150.00" :placeholder "150.00"
@@ -712,14 +696,14 @@
[:div.level [:div.level
[:div.level-left [:div.level-left
[:div.level-item [:div.level-item
[field "Regular Credits" [form-builder/field "Regular Credits"
[:input.input {:type "number" [:input.input {:type "number"
:style {:width "10em"} :style {:width "10em"}
:placeholder "1000.00" :placeholder "1000.00"
:field [:week-b-credits] :field [:week-b-credits]
:step "0.01"}]]] :step "0.01"}]]]
[:div.level-item [:div.level-item
[field "Regular Debits" [form-builder/field "Regular Debits"
[:input.input {:type "number" [:input.input {:type "number"
:style {:width "10em"} :style {:width "10em"}
:placeholder "250.00" :placeholder "250.00"
@@ -729,7 +713,7 @@
[:label.label "Forecasted transactions"] [:label.label "Forecasted transactions"]
[:div.control [:div.control
(raw-field [form-builder/raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :forecasted-transactions :field :forecasted-transactions
:template [[:input.input {:type "text" :template [[:input.input {:type "text"
@@ -746,17 +730,17 @@
:class "has-text-right" :class "has-text-right"
:style {:width "7em"} :style {:width "7em"}
:field [:amount] :field [:amount]
:step "0.01"}]]}])]]] :step "0.01"}]]}]]]]]))
[form-section {:title "Square Integration"} (defn square-section []
(field "Square Authentication Token" (let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/section {:title "Square Integration"}
[form-builder/field "Square Authentication Token"
[:input.input {:type "text" [:input.input {:type "text"
:style {:width "40em"} :style {:width "40em"}
:field [:square-auth-token]}]) :field [:square-auth-token]}]]
[:div.field [form-builder/field
[:label.label "Square Locations"] "Square Locations"
[:div.control
(raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :selected-square-locations :field :selected-square-locations
:template [[typeahead-v3 {:entities (:square-locations new-client) :template [[typeahead-v3 {:entities (:square-locations new-client)
@@ -768,18 +752,16 @@
:style {:width "4em"} :style {:width "4em"}
:field [:client-location] :field [:client-location]
:step "0.01"}]] :step "0.01"}]]
:disable-remove? true}])]]] :disable-remove? true}]]]))
[form-section {:title "EZCater integration"} (defn ezcater-section []
[form-builder/section {:title "EZCater integration"}
[:div.field [form-builder/field
[:label.label "EZCater Locations"] "EZCater Locations"
[:div.control
(raw-field
[multi-field {:type "multi-field" [multi-field {:type "multi-field"
:field :ezcater-locations :field :ezcater-locations
:template [ :template [[search-backed-typeahead {:search-query (fn [i]
[search-backed-typeahead {:search-query (fn [i]
[:search_ezcater_caterer [:search_ezcater_caterer
{:query i} {:query i}
[:name :id]]) [:name :id]])
@@ -791,10 +773,28 @@
:style {:width "4em"} :style {:width "4em"}
:field [:location] :field [:location]
:step "0.01"}]] :step "0.01"}]]
:disable-remove? true}])]]] :disable-remove? true}]]])
(error-notification) (defn form-content []
(submit-button "Save")])])) (let [_ @(re-frame/subscribe [::client])
{new-client :data} @(re-frame/subscribe [::forms/form ::form])]
^{:key (or (:id new-client)
"new")}
[form-builder/builder {:can-submit [::can-submit]
:submit-event [::save-new-client ]
:id ::form
:fullwidth? false}
[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 (def new-client-form
(with-meta (with-meta

View File

@@ -0,0 +1,85 @@
(ns auto-ap.views.pages.page-stack
(:require
[auto-ap.views.utils :refer [switch-transition transition]]
[reagent.core :as r]))
(defn page-stack []
(let [last-stack-size (r/atom (->> (r/current-component)
r/children
(filter identity) count))
forward? (r/atom false)]
(fn [{:keys [pages active]}]
(let [current-stack-size (->> pages
(take-while #(not= (:key %)
active))
count)
current-child (->> pages
(filter #(= (:key %)
active))
first)]
(when (not= current-stack-size @last-stack-size)
(reset! forward? (> current-stack-size @last-stack-size))
(reset! last-stack-size current-stack-size))
[:div
[:h1.title (:breadcrumb current-child)]
[switch-transition {:mode "in-out"}
^{:key current-stack-size}
[transition
{:timeout 100
:exit true
:in true #_(= current-stack- (:key (meta child)))
:appear (> current-stack-size 1)}
(clj->js (fn [state]
(r/as-element
[:div {:style {
:position (cond
(= "entered" state)
""
(= "entering" state)
"absolute"
(= "exiting" state)
"absolute"
(= "exited" state)
"")
:transition "opacity 300ms ease-in-out, transform 300ms ease-in-out"
:opacity (cond
(= "entered" state)
""
(= "entering" state)
0.0
(= "exiting" state)
1.0
(= "exited" state)
0.0)
:transform (if @forward?
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(100%)"
(= "exiting" state)
"translateX(-100%)"
(= "exited" state)
"translateX(0%)")
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(-100%)"
(= "exiting" state)
"translateX(100%)"
(= "exited" state)
"translateX(0%)"))}}
(:content current-child)])))]]]))))