diff --git a/src/cljs/auto_ap/forms.cljs b/src/cljs/auto_ap/forms.cljs index 344305f2..0c1f40c5 100644 --- a/src/cljs/auto_ap/forms.cljs +++ b/src/cljs/auto_ap/forms.cljs @@ -17,15 +17,7 @@ :data (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 (fn [db [_ x]] (if (#{"loading" :loading} (get-in db [::forms x :status]) ) @@ -43,10 +35,6 @@ :data data :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] (i/->interceptor :id :triggers-saved @@ -55,7 +43,6 @@ :after (fn [context] (let [db (i/get-coeffect context :db) result (get-in (i/get-coeffect context :event) [1 data-key])] - (cond-> context true (i/assoc-effect :db (update-in db diff --git a/src/cljs/auto_ap/forms/builder.cljs b/src/cljs/auto_ap/forms/builder.cljs new file mode 100644 index 00000000..54719c51 --- /dev/null +++ b/src/cljs/auto_ap/forms/builder.cljs @@ -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])))])) + + diff --git a/src/cljs/auto_ap/views/components/address.cljs b/src/cljs/auto_ap/views/components/address.cljs index 5d2c4e2f..4b33a58c 100644 --- a/src/cljs/auto_ap/views/components/address.cljs +++ b/src/cljs/auto_ap/views/components/address.cljs @@ -1,6 +1,7 @@ (ns auto-ap.views.components.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]}] [:span @@ -58,3 +59,50 @@ :event event :subscription subscription :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"}]]]]]) diff --git a/src/cljs/auto_ap/views/pages/admin/clients.cljs b/src/cljs/auto_ap/views/pages/admin/clients.cljs index 68374b3b..068ef2d6 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients.cljs @@ -1,21 +1,20 @@ (ns auto-ap.views.pages.admin.clients (:require - [auto-ap.forms :as forms] - [auto-ap.subs :as subs] + [auto-ap.routes :as routes] [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.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.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] [re-frame.core :as re-frame] - [reagent.core :as r] - [vimsical.re-frame.fx.track :as track] - [bidi.bidi :as bidi] - [auto-ap.routes :as routes])) + [vimsical.re-frame.fx.track :as track])) (re-frame/reg-event-db ::received-intuit-bank-accounts @@ -25,7 +24,7 @@ (re-frame/reg-event-fx ::mounted [with-user] - (fn [{:keys [db user]} _] + (fn [{:keys [user]} _] {::track/register {:id ::params :subscription [::params] :event-fn (fn [params] [::params-change params])} @@ -49,14 +48,6 @@ (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/form {:bank-accounts []})))) - (re-frame/reg-event-fx ::params-change (fn [_ [_ params]] @@ -75,125 +66,28 @@ (assoc (grid/virtual-paginate-controls (: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 (with-meta (fn [] [:div - [breadcrumbs] - [: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"])] - [test-appear] - ]) + [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 {:page @(re-frame/subscribe [::page]) + :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-will-unmount #(re-frame/dispatch-sync [::unmounted])})) diff --git a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs index 498c1831..3a0f6fe7 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -1,30 +1,32 @@ (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.events :as events] - [auto-ap.views.components.address :refer [address-field]] - [auto-ap.views.components.typeahead :refer [typeahead-v3]] - [auto-ap.views.components.typeahead.vendor - :refer [search-backed-typeahead]] - [auto-ap.views.components.layouts :refer [side-bar]] - [auto-ap.views.utils - :refer - [date->str - date-picker - dispatch-event - horizontal-field - multi-field - standard]] - [cljs-time.coerce :as coerce] - [cljs-time.core :as t] - [clojure.string :as str] - [vimsical.re-frame.cofx.inject :as inject] - [re-frame.core :as re-frame] - [reagent.core :as r] - [react-signature-canvas] - [bidi.bidi :as bidi] - [auto-ap.routes :as routes])) + (:require + [auto-ap.entities.clients :as entity] + [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.typeahead.vendor + :refer [search-backed-typeahead]] + [auto-ap.views.utils + :refer [date->str + date-picker + date-picker-friendly + dispatch-event + horizontal-field + multi-field + standard]] + [bidi.bidi :as bidi] + [cljs-time.coerce :as coerce] + [cljs-time.core :as t] + [clojure.string :as str] + [re-frame.core :as re-frame] + [reagent.core :as r] + [react-signature-canvas] + [vimsical.re-frame.cofx.inject :as inject])) (def signature-canvas (r/adapt-react-class (.-default react-signature-canvas))) @@ -196,31 +198,36 @@ :bank-code bank-code}) (: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 ::mounted - [(re-frame/inject-cofx ::inject/sub [::subs/route-params]) - (re-frame/inject-cofx ::inject/sub [::subs/clients-by-id])] - (fn [{:keys [db] - ::subs/keys [clients-by-id route-params]} [_ client-id]] - (let [client (get clients-by-id (:id route-params))] - {:db (-> 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}) %)) - (update :matches #(mapv (fn [l] {:match l}) %)) - (update :bank-accounts - (fn [bas] - (mapv (fn [ba] - (update ba :locations (fn [ls] - (map (fn [l] {:location l}) - ls)))) - bas))))))}))) + [(re-frame/inject-cofx ::inject/sub [::client])] + (fn [{:keys [db] ::keys [client]} _] + {:db (-> 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}) %)) + (update :matches #(mapv (fn [l] {:match l}) %)) + (update :bank-accounts + (fn [bas] + (mapv (fn [ba] + (update ba :locations (fn [ls] + (map (fn [l] {:location l}) + ls)))) + bas))))))})) (re-frame/reg-event-fx ::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")) @@ -335,466 +336,465 @@ (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" - :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] + [: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] + (#{:credit ":credit"} type) [:span.icon-credit-card-1] - :else [:span.icon-accounting-bill])]] - #_[:div.level-item + :else [:span.icon-accounting-bill])]] + #_[:div.level-item ] - [: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"] - [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]])] + [: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"] + [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 + [form-builder/raw-field + [:input.input {:type "code" + :field [: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]}]] + [form-builder/field + "Nickname" + [:input.input {:placeholder "BOA Checking #1" + :type "text" + :field [:name]}]] + [horizontal-field + nil + [form-builder/field + "Numeric Code" + [:input.input {:placeholder "20101" + :type "text" + :field [:numeric-code]}]]] + [form-builder/field + "Start 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 [:start-date]}]]] + (when (#{:check ":check"} type ) + [:div + + [:label.label "Bank"] [horizontal-field nil - [field "Numeric Code" - [:input.input {:placeholder "20101" - :type "text" - :field [:bank-accounts sort-order :numeric-code]}]]] - [field "Start 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 [:bank-accounts sort-order :start-date]}]]] - (when (#{:check ":check"} type ) - [:div + [form-builder/field + "Bank Name" + [:input.input {:placeholder "Bank of America" + :type "text" + :field [:bank-name]}]] + [form-builder/field + "Routing #" + [:input.input {:placeholder "104819123" + :style {:width "9em"} + :type "text" + :field [:routing]}]] + [form-builder/field + "Bank code" + [:input.input {:placeholder "12/10123" + :type "text" + :field [:bank-code]}]]] - [: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" - :style {:width "9em"} - :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]}]]] + [horizontal-field + nil + [form-builder/field + "Account #" + [:input.input {:placeholder "123456789" + :type "text" + :style {:width "20em"} + :field [:number]}]] - [horizontal-field - nil - [field "Account #" - [:input.input {:placeholder "123456789" - :type "text" - :style {:width "20em"} - :field [:bank-accounts sort-order :number]}]] + [form-builder/field + "Check Number" + [:input.input {:placeholder "10000" + :style {:width "6em"} + :type "text" + :field [:check-number]}]]] + [form-builder/field + "Yodlee Account" + [:input.input {:placeholder "Yodlee Account #" + :type "text" + :field [:yodlee-account-id]}]] + [form-builder/field + "Yodlee Account (new)" + [typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)]) + :entity->text (fn [m] (str (:name m) " - " (:number m))) + :type "typeahead-v3" + :field [:yodlee-account]}]] + [:div.field + [:label.checkbox + [form-builder/raw-field + [:input {:type "checkbox" + :field [:use-date-instead-of-post-date]}]] + " (Yodlee only) Use 'date' instead of 'postDate'"]] - [field "Check Number" - [:input.input {:placeholder "10000" - :style {:width "6em"} - :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]}]] - [field "Yodlee Account (new)" - [typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)]) - :entity->text (fn [m] (str (:name m) " - " (:number m))) - :type "typeahead-v3" - :field [:bank-accounts sort-order :yodlee-account]}]] - [:div.field - [:label.checkbox - [raw-field - [:input {:type "checkbox" - :field [:bank-accounts sort-order :use-date-instead-of-post-date]}]] - " (Yodlee only) Use 'date' instead of 'postDate'"]] - [field "Intuit Bank Account" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) - :entity->text (fn [m] (str (:name m))) - :type "typeahead-v3" - :field [:bank-accounts sort-order :intuit-bank-account]}]] - [field "Plaid Account" - [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) - :entity->text (fn [m] (str (:name m))) - :type "typeahead-v3" - :field [:bank-accounts sort-order :plaid-account]}]]]) + [form-builder/field + "Intuit Bank Account" + [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) + :entity->text (fn [m] (str (:name m))) + :type "typeahead-v3" + :field [:intuit-bank-account]}]] + [form-builder/field + "Plaid Account" + [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) + :entity->text (fn [m] (str (:name m))) + :type "typeahead-v3" + :field [:plaid-account]}]]]) - (when (#{:credit ":credit"} type ) - [:div + (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]}]]] - + + [:label.label "Account"] + [horizontal-field + nil + [form-builder/field + "Bank Name" + [:input.input {:placeholder "Bank of America" + :type "text" + :field [:bank-name]}]]] + - [horizontal-field - nil - [field "Account #" - [:input.input {:placeholder "123456789" - :type "text" - :field [:bank-accounts sort-order :number]}]]] + [horizontal-field + nil + [form-builder/field + "Account #" + [:input.input {:placeholder "123456789" + :type "text" + :field [:number]}]]] - [field "Yodlee Account" - [:input.input {:placeholder "Yodlee Account #" - :type "text" - :field [:bank-accounts sort-order :yodlee-account-id]}]] - [field "Yodlee Account (new)" - [typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)]) - :entity->text (fn [m] (str (:name m) " - " (:number m))) - :type "typeahead-v3" - :field [:bank-accounts sort-order :yodlee-account]}]] - [:div.field - [:label.checkbox - [raw-field - [:input {:type "checkbox" - :field [:bank-accounts sort-order :use-date-instead-of-post-date]}]] - " (Yodlee only) Use 'date' instead of 'postDate'"]] - [field "Intuit Bank Account" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) - :entity->text (fn [m] (str (:name m))) - :type "typeahead-v3" - :field [:bank-accounts sort-order :intuit-bank-account]}]] - [field "Plaid Account" - [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) - :entity->text (fn [m] (str (:name m))) - :type "typeahead-v3" - :field [:bank-accounts sort-order :plaid-account]}]] - ]) - [:div.field - [:label.label "Locations"] - [:div.control - [:p.help "If this account is location-specific, add the valid locations"] - [raw-field - [multi-field {:type "multi-field" - :field [:bank-accounts sort-order :locations] - :template [[:select.select {:type "select" - :style {:width "7em"} - :allow-nil? true - :field [:location] - :spec (set (map :location (get-in new-client [:locations])))} - [:<> - [:option ""] - [:<> (map (fn [l] ^{:key (:location l)} - [:option {:value (:location l)} (:location l)]) - (get-in new-client [:locations]))]]]]}]]]] - [:div.field - [:label.checkbox - [raw-field - [:input {:type "checkbox" - :field [:bank-accounts sort-order :include-in-reports]}]] - " Include in reports"]]]) + [form-builder/field + "Yodlee Account" + [:input.input {:placeholder "Yodlee Account #" + :type "text" + :field [:yodlee-account-id]}]] + [form-builder/field + "Yodlee Account (new)" + [typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)]) + :entity->text (fn [m] (str (:name m) " - " (:number m))) + :type "typeahead-v3" + :field [:yodlee-account]}]] + [:div.field + [:label.checkbox + [form-builder/raw-field + [:input {:type "checkbox" + :field [:use-date-instead-of-post-date]}]] + " (Yodlee only) Use 'date' instead of 'postDate'"]] + [form-builder/field + "Intuit Bank Account" + [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) + :entity->text (fn [m] (str (:name m))) + :type "typeahead-v3" + :field [:intuit-bank-account]}]] + [form-builder/field + "Plaid Account" + [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) + :entity->text (fn [m] (str (:name m))) + :type "typeahead-v3" + :field [:plaid-account]}]]]) + [:div.field + [:label.label "Locations"] + [:div.control + [:p.help "If this account is location-specific, add the valid locations"] + [form-builder/raw-field + [multi-field {:type "multi-field" + :field [:locations] + :template [[:select.select {:type "select" + :style {:width "7em"} + :allow-nil? true + :field [:location] + :spec (set (map :location (get-in new-client [:locations])))} + [:<> + [:option ""] + [:<> (map (fn [l] ^{:key (:location l)} + [:option {:value (:location l)} (:location l)]) + (get-in new-client [:locations]))]]]]}]]]] + [:div.field + [:label.checkbox + [form-builder/raw-field + [:input {:type "checkbox" + :field [: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"])])])) + (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 form-section [{:keys [title]}] - [:<> - [:h4.is-4.title title] - [:hr] - (into [:div {:style {:margin-bottom "5em"}}] - (r/children (r/current-component)))]) + +(defn general-section [] + (let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])] + [form-builder/section {:title "General"} + [form-builder/field + "Name" + [:input.input {:type "text" + :style {:width "20em"} + :field [:name] + :spec ::entity/name}]] + [form-builder/field + "Client code" + [:input.input {:type "code" + :style {:width "5em"} + :field :code + :disabled (boolean (:id new-client)) + :spec ::entity/code}]] + + [form-builder/field + "Locations" + [multi-field {:type "multi-field" + :field :locations + :allow-change? false + :template [[:input.input {:field [:location] + :max-length 2 + :style { :width "4em"}}]]}]] + + [:div.field + [:label.label "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 + "Locked Until" + [date-picker-friendly {:type "date" + :field [:locked-until] + :style {:width "15em"}}]]])) + +(defn contacts-section [] + [form-builder/section {:title "Contacts"} + + [form-builder/field + "Emails (address/description)" + [multi-field {:type "multi-field" + :field :emails + :template [[:input.input {:type "email" + :field [:email] + :placeholder "tom@myspace.com" + :spec ::entity/email}] + [:input.input {:type "text" + :placeholder "Manager" + :field [:description]}]]}]] + + [form-builder/with-scope {:scope [:address ]} + [:div.field + [:label.label "Address"] + [:div.control + [:div {:style {:width "30em"}} + [address2-field]]]]]]) + +(defn matching-section [] + [form-builder/section {:title "Matching"} + [form-builder/field + "Name matches" + [multi-field {:type "multi-field" + :field :matches + :template [[:input.input {:field [:match] + :placeholder "Harry's burger joint" + :style { :width "15em"}}]]}]] + + + [form-builder/field + "Location Matches" + [multi-field {:type "multi-field" + :field :location-matches + :template [[:input.input {:field [:match] + :placeholder "Downtown" + :style { :width "15em"}}] + [:input.input {:field [:location] + :placeholder "DT" + :maxlength 2 + :style { :width "4em"}}]]}]]]) + +(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 ")")] + [:div.level + [:div.level-left + [:div.level-item + [form-builder/field + "Regular Credits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "500.00" + :field [:week-a-credits] + :step "0.01"}]]] + [:div.level-item + [form-builder/field + "Regular Debits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "150.00" + :field [:week-a-debits] + :step "0.01"}]]]]] + [:label.label (str "Week B (" next-week-b ")")] + [:div.level + [:div.level-left + [:div.level-item + [form-builder/field "Regular Credits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "1000.00" + :field [:week-b-credits] + :step "0.01"}]]] + [:div.level-item + [form-builder/field "Regular Debits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "250.00" + :field [:week-b-debits] + :step "0.01"}]]]]] + [:div.field + [:label.label "Forecasted transactions"] + + [:div.control + [form-builder/raw-field + [multi-field {:type "multi-field" + :field :forecasted-transactions + :template [[:input.input {:type "text" + :placeholder "Identifier" + :style {:width "10em"} + :field [ :identifier]}] + [:input.input {:type "number" + :style {:width "8em"} + :placeholder "DOM" + :step "1" + :field [:day-of-month]}] + [:input.input {:type "number" + :placeholder "250.00" + :class "has-text-right" + :style {:width "7em"} + :field [:amount] + :step "0.01"}]]}]]]]])) + +(defn square-section [] + (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" + :style {:width "40em"} + :field [:square-auth-token]}]] + [form-builder/field + "Square Locations" + [multi-field {:type "multi-field" + :field :selected-square-locations + :template [[typeahead-v3 {:entities (:square-locations new-client) + :entity->text :name + :type "typeahead-v3" + :style {:width "15em"} + :field [:square-location]}] + [:input.input {:type "text" + :style {:width "4em"} + :field [:client-location] + :step "0.01"}]] + :disable-remove? true}]]])) + +(defn ezcater-section [] + [form-builder/section {:title "EZCater integration"} + + [form-builder/field + "EZCater Locations" + [multi-field {:type "multi-field" + :field :ezcater-locations + :template [[search-backed-typeahead {:search-query (fn [i] + [:search_ezcater_caterer + {:query i} + [:name :id]]) + :entity->text :name + :type "typeahead-v3" + :field [:caterer] + :style {:width "20em"}}] + [:input.input {:type "text" + :style {:width "4em"} + :field [:location] + :step "0.01"}]] + :disable-remove? true}]]]) (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")] + (let [_ @(re-frame/subscribe [::client]) + {new-client :data} @(re-frame/subscribe [::forms/form ::form])] + ^{:key (or (:id new-client) "new")} - [:div ;; div is important for actually unmounting for now - + [form-builder/builder {:can-submit [::can-submit] + :submit-event [::save-new-client ] + :id ::form + :fullwidth? false} - (form-inline {:title ""} - [:<> - - - [form-section {:title "General"} - (field "Name" - [:input.input {:type "text" - :style {:width "20em"} - :field [:name] - :spec ::entity/name - }]) - [:div.field - [:label.label "Client code"] - (if (:id new-client) - [:div.control - (raw-field - [:input.input {:type "code" - :style {:width "5em"} - :field :code - :spec ::entity/code - :disabled true}]) - ] - [:div.control - (raw-field - [:input.input {:type "code" - :style {:width "5em"} - :field :code - :spec ::entity/code}])])] - - [:div.field - [:label.label "Locations"] - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :locations - :allow-change? false - :template [[:input.input {:field [:location] - :maxlength 2 - :style { :width "4em"}}]]}])]] - - [:div.field - [:label.label "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]))}]] - - - - (field "Locked Until" - [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] - :style {:width "15em"}}])] - - [form-section {:title "Contacts"} - - [:div.field - [:label.label "Emails (address/description)"] - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :emails - :template [[:input.input {:type "email" - :field [:email] - :placeholder "tom@myspace.com" - :spec ::entity/email}] - [:input.input {:type "text" - :placeholder "Manager" - :field [:description]}]]}])]] - [:label.label "Address"] - [:div {:style {:width "30em"}} - [address-field {:field [:address] - :event [::forms/change ::form] - :subscription new-client}]]] - - - [form-section {:title "Matching"} - [:div.field - [:label.label "Name matches"] - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :matches - :template [[:input.input {:field [:match] - :placeholder "Harry's burger joint" - :style { :width "15em"}}]]}])]] - - - [:div.field - [:label.label "Location Matches"] - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :location-matches - :template [[:input.input {:field [:match] - :placeholder "Downtown" - :style { :width "15em"}}] - [:input.input {:field [:location] - :placeholder "DT" - :maxlength 2 - :style { :width "4em"}}]]}])]]] - - - - - [form-section {:title "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.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"]]] - - [form-section {:title "Cash flow"} - [:label.label (str "Week A (" next-week-a ")")] - [:div.level - [:div.level-left - [:div.level-item - [field "Regular Credits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "500.00" - :field [:week-a-credits] - :step "0.01"}]]] - [:div.level-item - [field "Regular Debits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "150.00" - :field [:week-a-debits] - :step "0.01"}]]]]] - [:label.label (str "Week B (" next-week-b ")")] - [:div.level - [:div.level-left - [:div.level-item - [field "Regular Credits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "1000.00" - :field [:week-b-credits] - :step "0.01"}]]] - [:div.level-item - [field "Regular Debits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "250.00" - :field [:week-b-debits] - :step "0.01"}]]]]] - [:div.field - [:label.label "Forecasted transactions"] - - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :forecasted-transactions - :template [[:input.input {:type "text" - :placeholder "Identifier" - :style {:width "10em"} - :field [ :identifier]}] - [:input.input {:type "number" - :style {:width "8em"} - :placeholder "DOM" - :step "1" - :field [:day-of-month]}] - [:input.input {:type "number" - :placeholder "250.00" - :class "has-text-right" - :style {:width "7em"} - :field [:amount] - :step "0.01"}]]}])]]] - - [form-section {:title "Square Integration"} - (field "Square Authentication Token" - [:input.input {:type "text" - :style {:width "40em"} - :field [:square-auth-token]}]) - [:div.field - [:label.label "Square Locations"] - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :selected-square-locations - :template [[typeahead-v3 {:entities (:square-locations new-client) - :entity->text :name - :type "typeahead-v3" - :style {:width "15em"} - :field [:square-location]}] - [:input.input {:type "text" - :style {:width "4em"} - :field [:client-location] - :step "0.01"}]] - :disable-remove? true}])]]] - - [form-section {:title "EZCater integration"} - - [:div.field - [:label.label "EZCater Locations"] - [:div.control - (raw-field - [multi-field {:type "multi-field" - :field :ezcater-locations - :template [ - [search-backed-typeahead {:search-query (fn [i] - [:search_ezcater_caterer - {:query i} - [:name :id]]) - :entity->text :name - :type "typeahead-v3" - :field [:caterer] - :style {:width "20em"}}] - [:input.input {:type "text" - :style {:width "4em"} - :field [:location] - :step "0.01"}]] - :disable-remove? true}])]]] - - (error-notification) - (submit-button "Save")])])) + [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 diff --git a/src/cljs/auto_ap/views/pages/page_stack.cljs b/src/cljs/auto_ap/views/pages/page_stack.cljs new file mode 100644 index 00000000..188375f0 --- /dev/null +++ b/src/cljs/auto_ap/views/pages/page_stack.cljs @@ -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)])))]]]))))