diff --git a/src/cljs/auto_ap/forms.cljs b/src/cljs/auto_ap/forms.cljs index 0c1f40c5..50408e7a 100644 --- a/src/cljs/auto_ap/forms.cljs +++ b/src/cljs/auto_ap/forms.cljs @@ -138,68 +138,3 @@ (assoc-in [::forms id :error] nil))) -(defn vertical-form [{:keys [can-submit id change-event submit-event fullwidth?] :or {fullwidth? true}}] - {:form - (fn [{:keys [title] :as params} & children] - (let [{:keys [data active? error]} @(re-frame/subscribe [::form id]) - can-submit @(re-frame/subscribe can-submit)] - - [:form { :on-submit (fn [e] - (when (.-stopPropagation e) - (.stopPropagation e) - (.preventDefault e)) - (when can-submit - (re-frame/dispatch-sync (vec (conj submit-event params)))))} - [:h1.title.is-2 title] - [:<> - children]])) - - :form-inline - (fn [{:keys [title] :as params} children] - (let [{:keys [data active? error]} @(re-frame/subscribe [::form id]) - can-submit @(re-frame/subscribe can-submit)] - - [:form { :on-submit (fn [e] - (when (.-stopPropagation e) - (.stopPropagation e) - (.preventDefault e)) - (when can-submit - (re-frame/dispatch-sync (vec (conj submit-event params)))))} - (when title - [:h1.title.is-2 title]) - children])) - :raw-field (fn [control] - (let [{:keys [data]} @(re-frame/subscribe [::form id])] - [bind-field (-> control - (assoc-in [1 :subscription] data) - (assoc-in [1 :event] change-event))])) - :field-holder (fn [label control] - [:div.field - (when label (if fullwidth? [:p.help label] - [:label.label label])) - [:div.control control]]) - :field ^{:key "field"} - (fn [label control] - (let [{:keys [data]} @(re-frame/subscribe [::form id])] - [:div.field - (when label (if fullwidth? [:p.help label] - [:label.label label])) - [:div.control [bind-field (-> control - (assoc-in [1 :subscription] data) - (assoc-in [1 :event] change-event))]]])) - - :error-notification - (fn [] - (when-let [error (:error @(re-frame/subscribe [::form id]))] - ^{:key error} - [:div.has-text-danger.animated.fadeInUp {} error])) - :submit-button (fn [child] - (let [error (:error @(re-frame/subscribe [::form id])) - status @(re-frame/subscribe [::status/single id]) - can-submit @(re-frame/subscribe can-submit)] - [: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]))}) - diff --git a/src/cljs/auto_ap/forms/builder.cljs b/src/cljs/auto_ap/forms/builder.cljs index 47cf5be3..744273e2 100644 --- a/src/cljs/auto_ap/forms/builder.cljs +++ b/src/cljs/auto_ap/forms/builder.cljs @@ -19,8 +19,10 @@ (let [data-sub (or data-sub [::forms/form id]) change-event (or change-event [::forms/change id]) {:keys [data error] form-key :id} @(re-frame/subscribe data-sub) - status @(re-frame/subscribe [::status/single id])] - (r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit) + status @(re-frame/subscribe [::status/single id]) + can-submit (if can-submit @(re-frame/subscribe can-submit) + true)] + (r/create-element Provider #js {:value #js {:can-submit can-submit :change-event change-event :submit-event submit-event :error error diff --git a/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs b/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs index 88d1b3ed..7a426894 100644 --- a/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs +++ b/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs @@ -2,7 +2,6 @@ (:require [auto-ap.forms :as forms] [auto-ap.status :as status] - [auto-ap.subs :as subs] [auto-ap.utils :refer [by]] [auto-ap.views.components.modal :as modal] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] @@ -10,17 +9,13 @@ [auto-ap.views.utils :refer [dispatch-event with-user]] [clojure.string :as str] [goog.string :as gstring] - [re-frame.core :as re-frame])) - -(re-frame/reg-sub - ::can-submit - (fn [db] - true)) + [re-frame.core :as re-frame] + [auto-ap.forms.builder :as form-builder])) (re-frame/reg-event-fx ::try-save [(forms/in-form ::form)] - (fn [{:keys [db]} [_ id ]] + (fn [{:keys [db]} _] (let [{{:keys [ total]} :invoice :keys [expense-accounts]} (:data db) expense-accounts (vals expense-accounts) @@ -44,7 +39,7 @@ (re-frame/reg-event-fx ::save [with-user (forms/in-form ::form)] - (fn [{:keys [db user] } [_ id]] + (fn [{:keys [db user] } _] (let [{{:keys [id]} :invoice :keys [expense-accounts]} (:data db) expense-accounts (vals expense-accounts)] @@ -85,16 +80,10 @@ (fn [db [_ x]] (update-in db [:data :expense-accounts] dissoc x))) -(def change-expense-accounts-form (forms/vertical-form {:submit-event [::try-save] - :change-event [::forms/change ::form] - :can-submit [::can-submit] - :id ::form})) - (defn form [] - (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) expense-accounts (:expense-accounts data) {:keys [total] :or {total 0} {:keys [locations] :as client} :client} (:invoice data) - {:keys [form-inline horizontal-field field raw-field error-notification submit-button]} change-expense-accounts-form multi-location? (> (count locations) 1) expense-accounts-total (->> expense-accounts vals @@ -108,72 +97,75 @@ [:div [:div [:a.button.is-outlined {:on-click (dispatch-event [::add-split])} "Add split"]] - (form-inline {} - [:table.table - [:thead - [:tr - [:th {:style {:width "500px"}} "Expense Account"] - (when multi-location? - [:th {:style {:width "200px"}} "Location"]) - [:th {:style {:width "200px"}} "Original Amount"] - [:th {:style {:width "300px"}} "Amount"] - [:th {:style {:width "5em"}}]]] - [:tbody - (doall (for [[id expense-account] expense-accounts] - ^{:key id} - [:tr - [:td.expandable [:div.control - (raw-field - [search-backed-typeahead {:search-query (fn [i] - [:search_account - {:query i - :client-id (:id client)} - [:name :id :location]]) - :type "typeahead-v3" - :field [:expense-accounts id :account]}])]] + [form-builder/builder {:submit-event [::try-save] + :id ::form} + [:table.table + [:thead + [:tr + [:th {:style {:width "500px"}} "Expense Account"] + (when multi-location? + [:th {:style {:width "200px"}} "Location"]) + [:th {:style {:width "200px"}} "Original Amount"] + [:th {:style {:width "300px"}} "Amount"] + [:th {:style {:width "5em"}}]]] + [:tbody + (doall (for [[id _] expense-accounts] + ^{:key id} + [:tr + [:td.expandable [:div.control + [form-builder/raw-field + [search-backed-typeahead {:search-query (fn [i] + [:search_account + {:query i + :client-id (:id client)} + [:name :id :location]]) + :type "typeahead-v3" + :field [:expense-accounts id :account]}]]]] - (when multi-location? - [:td - (if-let [forced-location (get-in expense-accounts [id :account :location])] - [:div.select - [:select {:disabled "disabled" :value forced-location} [:option {:value forced-location} forced-location]]] - [:div.select - (raw-field - [:select {:type "select" - :field [:expense-accounts id :location] - :spec (set locations)} - (map (fn [l] ^{:key l} [:option {:value l} l]) locations)])])]) - - [:td - (str "$" (get-in expense-accounts [id :amount]))] - [:td - [:div.control - [:div.field.has-addons.is-extended - [:p.control [:a.button.is-static "$"]] - [:p.control - (raw-field - [:input.input {:type "number" - :field [:expense-accounts id :new-amount-temp] - :style {:text-align "right"} - :on-blur (dispatch-event [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])]) - :on-key-down (fn [e ] - (if (= 13 (.-keyCode e)) - (do - (re-frame/dispatch [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])]) - true) - false)) - :max (:total data) - :step "0.01"}])]]]] - [:td [:a.button {:on-click (dispatch-event [::remove-expense-account-split id])} [:i.fa.fa-times]]]])) - [:tr - [:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Invoice total: "] - [:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" total ) )]] - [:tr - [:td { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Account total: "] - [:td { :style { :text-align "right"} } (str (gstring/format "$%.2f" expense-accounts-total ) )]] - [:tr - [:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Difference: "] - [:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" (- total expense-accounts-total) ) )]]]])])) + (when multi-location? + [:td + (if-let [forced-location (get-in expense-accounts [id :account :location])] + [:div.select + [:select {:disabled "disabled" :value forced-location} [:option {:value forced-location} forced-location]]] + [:div.select + + [form-builder/raw-field + [:select {:type "select" + :field [:expense-accounts id :location] + :spec (set locations)} + (map (fn [l] ^{:key l} [:option {:value l} l]) locations)]]])]) + + [:td + (str "$" (get-in expense-accounts [id :amount]))] + [:td + [:div.control + [:div.field.has-addons.is-extended + [:p.control [:a.button.is-static "$"]] + [:p.control + [form-builder/raw-field + [:input.input {:type "number" + :field [:expense-accounts id :new-amount-temp] + :style {:text-align "right"} + :on-blur (dispatch-event [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])]) + :on-key-down (fn [e ] + (if (= 13 (.-keyCode e)) + (do + (re-frame/dispatch [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])]) + true) + false)) + :max (:total data) + :step "0.01"}]]]]]] + [:td [:a.button {:on-click (dispatch-event [::remove-expense-account-split id])} [:i.fa.fa-times]]]])) + [:tr + [:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Invoice total: "] + [:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" total ) )]] + [:tr + [:td { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Account total: "] + [:td { :style { :text-align "right"} } (str (gstring/format "$%.2f" expense-accounts-total ) )]] + [:tr + [:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Difference: "] + [:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" (- total expense-accounts-total) ) )]]]] + [form-builder/hidden-submit-button]]])) (re-frame/reg-event-fx ::show @@ -184,11 +176,10 @@ :status-from [::status/single ::form] :class "is-primary" :on-click (dispatch-event [::try-save]) - :can-submit [::can-submit] :close-event [::status/completed ::form]}}] :db (-> db - (forms/start-form ::form + (forms/start-form ::form - {:expense-accounts (by :id - (:expense-accounts i)) - :invoice i}))})) + {:expense-accounts (by :id + (:expense-accounts i)) + :invoice i}))})) diff --git a/src/cljs/auto_ap/views/components/money_field.cljs b/src/cljs/auto_ap/views/components/money_field.cljs index 954371df..62f8cbd3 100644 --- a/src/cljs/auto_ap/views/components/money_field.cljs +++ b/src/cljs/auto_ap/views/components/money_field.cljs @@ -5,7 +5,7 @@ [react :as react])) (def good-$ #"^\-?[0-9]+(\.[0-9][0-9])?$") -(defn -money-field [{:keys [min max disabled on-change value class style]}] +(defn -money-field [{:keys [min max disabled on-change value class style placeholder]}] (let [[ parsed-amount set-parsed-amount] (react/useState {:parsed value :raw (cond (str/blank? value) @@ -40,6 +40,7 @@ [:div.control.has-icons-left [:input.input {:type "text" :disabled disabled + :placeholder placeholder :class class :on-change (fn [e] (let [raw (.. e -target -value) diff --git a/src/cljs/auto_ap/views/components/typeahead/vendor.cljs b/src/cljs/auto_ap/views/components/typeahead/vendor.cljs index 3768e867..a8626dbd 100644 --- a/src/cljs/auto_ap/views/components/typeahead/vendor.cljs +++ b/src/cljs/auto_ap/views/components/typeahead/vendor.cljs @@ -119,7 +119,9 @@ [:div.level-item [:div.control [:div.tags.has-addons - [:span.tag (:name selectedItem)] + [:span.tag (if entity->text + (entity->text selectedItem) + (:name selectedItem))] (when name [:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}]) (when-not disabled diff --git a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs index e83eb8ad..33871559 100644 --- a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs @@ -4,16 +4,16 @@ [auto-ap.subs :as subs] [auto-ap.views.components.layouts :refer [side-bar]] [auto-ap.views.components.typeahead :refer [typeahead-v3]] - [auto-ap.views.utils :refer [dispatch-event multi-field]] + [auto-ap.views.utils :refer [dispatch-event multi-field with-user]] [clojure.spec.alpha :as s] [clojure.string :as str] - [re-frame.core :as re-frame])) + [re-frame.core :as re-frame] + [auto-ap.forms.builder :as form-builder] + [vimsical.re-frame.cofx.inject :as inject])) (def types [:dividend :expense :asset :liability :equity :revenue]) (def applicabilities [:global :optional :customized]) - - (re-frame/reg-sub ::request :<- [::forms/form ::form] @@ -51,103 +51,99 @@ (re-frame/reg-event-fx ::edited [(forms/triggers-saved ::form :upsert-account)] - (fn [{:keys [db]} [_ {:keys [upsert-account]}]])) + (fn [_ [_ _]])) -(re-frame/reg-event-db - ::add-client-override - [(forms/in-form ::form)] - (fn [form] - (update form :data (fn [data] - (-> data - (update :client-overrides conj (:new-client-override data)) - (dissoc :new-client-override)))))) (re-frame/reg-event-fx ::saving - (fn [{:keys [db]} _] + [with-user (re-frame/inject-cofx ::inject/sub [::request]) ] + (fn [{:keys [user] ::keys [request]} _] (when @(re-frame/subscribe [::can-submit]) - (let [{{:keys [id type name numeric-code account-set]} :data :as data} @(re-frame/subscribe [::forms/form ::form])] - {:db (forms/loading db ::form ) - :graphql - {:token (-> db :user) + (let [_ @(re-frame/subscribe [::forms/form ::form])] + {:graphql + {:owns-state {:single ::form} + :token user :query-obj {:venia/operation {:operation/type :mutation :operation/name "UpsertAccount"} :venia/queries [{:query/data [:upsert-account - {:account @(re-frame/subscribe [::request])} + {:account request} [:id :type :name :account-set :numeric-code :location :applicability [:client-overrides [:name :id [:client [:id :name]]]]]]}]} :on-success [::edited] :on-error [::forms/save-error ::form]}})))) -(def account-form (forms/vertical-form {:can-submit [::can-submit] - :change-event [::forms/change ::form] - :submit-event [::saving] - :id ::form})) (defn form [_] - (let [{error :error account :data } @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline field field-holder raw-field error-notification submit-button]} account-form] - - ^{:key (:id account)} + (let [{account :data } @(re-frame/subscribe [::forms/form ::form])] [side-bar {:on-close (dispatch-event [::forms/form-closing ::form])} - (form-inline {:title (if (:id account) - "Edit account" - "Add account")} - [:<> + [form-builder/builder {:can-submit [::can-submit] + :change-event [::forms/change ::form] + :submit-event [::saving] + :id ::form} + [form-builder/section {:title (if (:id account) + "Edit account" + "Add account")} + [form-builder/field + "Account Set" + [:input.input {:type "text" + :field :account-set + :disabled (boolean (:id account)) + :spec ::entity/account-set}]] - (field "Account Set" - [:input.input {:type "text" - :field :account-set - :disabled (boolean (:id account)) - :spec ::entity/account-set}]) + [form-builder/field + "Code" + [:input.input {:type "text" + :field :numeric-code + :disabled (boolean (:id account)) + :spec ::entity/numeric-code}]] - (field "Code" - [:input.input {:type "text" - :field :numeric-code - :disabled (boolean (:id account)) - :spec ::entity/numeric-code}]) + [form-builder/field + "Name" + [:input.input {:type "text" + :field :name + :spec ::entity/name}]] - (field "Name" - [:input.input {:type "text" - :field :name - :spec ::entity/name}]) - - (field-holder "Account Type" - [:div.select - (raw-field - [:select {:type "select" - :field :type - :spec (set types)} - (map (fn [l] - [:option {:value (name l)} (str/capitalize (name l))]) types)])]) + [form-builder/vertical-control + "Account Type" + [:div.select + [form-builder/raw-field + [:select {:type "select" + :field :type + :spec (set types)} + (map (fn [l] + [:option {:value (name l)} (str/capitalize (name l))]) types)]]]] - (field "Location" - [:input.input.known-field.location {:type "text" - :field :location - :spec ::entity/location}]) + [form-builder/field + "Location" + [:input.input.known-field.location {:type "text" + :field :location + :spec ::entity/location}]] - [:h2.subtitle "Client"] - (field-holder "Applicability" - [:div.select - (raw-field - [:select {:type "select" - :field :applicability - :spec (set applicabilities)} - (map (fn [l] - [:option {:value (name l)} (str/capitalize (name l))]) applicabilities)])]) - (field "Customizations" - [multi-field {:type "multi-field" - :field [:client-overrides] - :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) - :style {:width "13em"} - :entity->text :name - :type "typeahead-v3" - :field [:client]}] - [:input.input {:type "text" - :style {:width "15em"} - :placeholder "Bubblegum" - :field [:name]}] - ]}]) - (error-notification) + [form-builder/section {:title "Client"} + [:h2.subtitle "Client"] + [form-builder/vertical-control + "Applicability" + [:div.select + [form-builder/raw-field + [:select {:type "select" + :field :applicability + :spec (set applicabilities)} + (map (fn [l] + [:option {:value (name l)} (str/capitalize (name l))]) applicabilities)]]]] + [form-builder/field + "Customizations" + [multi-field {:type "multi-field" + :field [:client-overrides] + :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) + :style {:width "13em"} + :entity->text :name + :type "typeahead-v3" + :field [:client]}] + [:input.input {:type "text" + :style {:width "15em"} + :placeholder "Bubblegum" + :field [:name]}] + ]}]]] + [form-builder/error-notification] - (submit-button "Save")])])) + [form-builder/submit-button "Save"]]]])) diff --git a/src/cljs/auto_ap/views/pages/admin/rules/form.cljs b/src/cljs/auto_ap/views/pages/admin/rules/form.cljs index bf967e9c..14bd8639 100644 --- a/src/cljs/auto_ap/views/pages/admin/rules/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/rules/form.cljs @@ -3,13 +3,16 @@ [auto-ap.entities.transaction-rule :as entity] [auto-ap.events :as events] [auto-ap.forms :as forms] + [auto-ap.forms.builder :as form-builder] [auto-ap.status :as status] [auto-ap.subs :as subs] + [auto-ap.views.components.level :refer [left-stack]] [auto-ap.views.components.button-radio :refer [button-radio]] [auto-ap.views.components.expense-accounts-field :as expense-accounts-field :refer [expense-accounts-field]] [auto-ap.views.components.layouts :as layouts] + [auto-ap.views.components.money-field :refer [money-field]] [auto-ap.views.components.typeahead :refer [typeahead-v3]] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] @@ -29,7 +32,6 @@ ::default-note :<- [::forms/form ::form] (fn [{{:keys [client description amount-lte amount-gte dom-lte dom-gte]} :data}] - (str/join " - " (filter (complement str/blank?) [(:code client) description @@ -185,18 +187,21 @@ :on-success [::changed [:vendor-preferences]]}]}))) (re-frame/reg-event-db - ::changed - (forms/change-handler ::form - (fn [data field value] - (cond (and (= [:vendor-preferences] field) - value - (expense-accounts-field/can-replace-with-default? (:accounts data))) - [[:accounts] (expense-accounts-field/default-account (:accounts data) - (:default-account value) - (:total data) - [])] - :else - [])))) + ::changed + (forms/change-handler ::form + (fn [data field value] + (cond (and (= [:vendor-preferences] field) + value + (expense-accounts-field/can-replace-with-default? (:accounts data))) + [[:accounts] (expense-accounts-field/default-account (:accounts data) + (:default-account value) + (:total data) + [])] + + (= [:client] field) + [[:bank-account] nil] + :else + [])))) (re-frame/reg-event-fx ::saving @@ -223,9 +228,8 @@ (re-frame/reg-event-fx ::updated [(forms/triggers-stop ::form)] - (fn [{:keys [db]} [_ {:keys [rule-saved]} result]] - {:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])}) - :dispatch (conj rule-saved (:upsert-transaction-rule result))})) + (fn [{:keys [db]} [_ {:keys [rule-saved]} _]] + {:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])})})) (re-frame/reg-event-fx ::succeeded-test @@ -238,10 +242,6 @@ ;; VIEWS -(def rule-form (forms/vertical-form {:can-submit [::can-submit] - :change-event [::changed] - :submit-event [::saving ] - :id ::form})) (re-frame/reg-event-fx ::mounted @@ -263,131 +263,127 @@ (defn form-contents [params] [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])} - (let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline field raw-field error-notification submit-button ]} rule-form - default-note @(re-frame/subscribe [::default-note]) - test-state @(re-frame/subscribe [::status/single ::test])] - ^{:key id} - (form-inline (assoc params :title "New Transaction Rule") - [:<> - + (let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::form]) + default-note @(re-frame/subscribe [::default-note]) + test-state @(re-frame/subscribe [::status/single ::test])] + [form-builder/builder {:can-submit [::can-submit] + :change-event [::changed] + :submit-event [::saving ] + :id ::form} + [form-builder/section {:title "Transaction Rule"} + [form-builder/field {:required? true} + "Client" + [typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) + :auto-focus true + :entity->text :name + :type "typeahead-v3" + :field [:client] + :spec ::entity/client}]] - (field "Client" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) - :auto-focus true - :entity->text :name - :type "typeahead-v3" - :field [:client] - :spec ::entity/client}]) + + [form-builder/field + "Bank account" + [typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)]) + :entity->text :name + :type "typeahead-v3" + :field [:bank-account] + :spec ::entity/bank-account}]] - - (with-meta - (field "Bank account" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)]) - :entity->text :name - :type "typeahead-v3" - :field [:bank-account] - :spec ::entity/bank-account}]) - ;; TODO this forces unmounting when client changes, since it is an "uncontorlled" input - {:key (str "client-" (:id (:client data)))}) + #_[form-builder/field + "Yodlee Merchant" + [typeahead-v3 {:entities @(re-frame/subscribe [::subs/yodlee-merchants]) + :entity->text #(str (:name %) " - " (:yodlee-id %)) + :type "typeahead-v3" + :field [:yodlee-merchant]}]] - (field "Yodlee Merchant" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/yodlee-merchants]) - :entity->text #(str (:name %) " - " (:yodlee-id %)) - :type "typeahead-v3" - :field [:yodlee-merchant]}]) + [form-builder/field + [:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ] + [:input.input {:type "text" + :field [:description] + :spec ::entity/description}]] - (field [:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ] - [:input.input {:type "text" - :field [:description] - :spec ::entity/description}]) + [:div.field + [:p.help "Amount"] + [left-stack + [form-builder/raw-field + [money-field {:type "money" + :placeholder ">=" + :field [:amount-gte] + :spec ::entity/amount-gte}]] + "-" + [form-builder/raw-field + [money-field {:type "money" + :placeholder "<=" + :field [:amount-lte] + :spec ::entity/amount-lte}]]]] - [:div.field - [:p.help "Amount"] - [:div.control - [:div.columns - [:div.column - (raw-field - [:input.input {:type "number" - :placeholder ">=" - :field [:amount-gte] - :spec ::entity/amount-gte - :step "0.01"}])] - [:div.column - (raw-field - [:input.input {:type "number" - :placeholder "<=" - :field [:amount-lte] - :spec ::entity/amount-lte - :step "0.01"}])]]]] + [:div.field + [:p.help "Day of Month"] + [:div.control + [:div.columns + [:div.column + [form-builder/raw-field + [:input.input {:type "number" + :placeholder ">=" + :field [:dom-gte] + :spec ::entity/dom-gte + :precision 0 + :step "1"}]]] + [:div.column + [form-builder/raw-field + [:input.input {:type "number" + :placeholder "<=" + :field [:dom-lte] + :spec ::entity/dom-lte + :precision 0 + :step "1"}]]]]]] - [:div.field - [:p.help "Day of Month"] - [:div.control - [:div.columns - [:div.column - (raw-field - [:input.input {:type "number" - :placeholder ">=" - :field [:dom-gte] - :spec ::entity/dom-gte - :precision 0 - :step "1"}])] - [:div.column - (raw-field - [:input.input {:type "number" - :placeholder "<=" - :field [:dom-lte] - :spec ::entity/dom-lte - :precision 0 - :step "1"}])]]]] + [:h2.title.is-4 "Outcomes"] - [:h2.title.is-4 "Outcomes"] + [form-builder/field "Assign Vendor" + [search-backed-typeahead {:search-query (fn [i] + [:search_vendor + {:query i} + [:name :id]]) + :type "typeahead-v3" + :field [:vendor]}]] - (field "Assign Vendor" - [search-backed-typeahead {:search-query (fn [i] - [:search_vendor - {:query i} - [:name :id]]) - :type "typeahead-v3" - :field [:vendor]}]) + [form-builder/raw-field + [expense-accounts-field {:type "expense-accounts" + :descriptor "account asssignment" + :percentage-only? true + :client (:client data) + :locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client-or-bank-account (:id (:client data)) (:id (:bank-account data))])) + :max 100 + :field [:accounts]}]] - (with-meta - (field nil - [expense-accounts-field {:type "expense-accounts" - :descriptor "account asssignment" - :percentage-only? true - :client (:client data) - :locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client-or-bank-account (:id (:client data)) (:id (:bank-account data))])) - :max 100 - :field [:accounts]}]) - {:key (str (some-> data :vendor :id str) "-" (some-> data :client :id str))}) + [form-builder/field + "Approval Status" + [button-radio + {:type "button-radio" + :field [:transaction-approval-status] + :options [[:unapproved "Unapproved"] + [:requires-feedback "Client Review"] + [:approved "Approved"] + [:excluded "Excluded from Ledger"]]}]] - (field "Approval Status" - [button-radio - {:type "button-radio" - :field [:transaction-approval-status] - :options [[:unapproved "Unapproved"] - [:requires-feedback "Client Review"] - [:approved "Approved"] - [:excluded "Excluded from Ledger"]]}]) + [form-builder/field "Note" + [:input.input {:type "text" + :field [:note] + :placeholder default-note + :spec (s/nilable ::entity/note)}]] - (field "Note" - [:input.input {:type "text" - :field [:note] - :placeholder default-note - :spec (s/nilable ::entity/note)}]) - - [:div.is-divider] - (error-notification) - [:div.columns - [:div.column - [:a.button.is-medium.is-fullwidth.is-outlined {:on-click (dispatch-event [::test-clicked]) - :disabled (status/disabled-for test-state) - :class (status/class-for test-state)} - "Test Rule"]] - [:div.column - (submit-button "Save")]]]))]) + [:div.is-divider] + [form-builder/error-notification] + [:div.columns + [:div.column + [:a.button.is-medium.is-fullwidth.is-outlined {:on-click (dispatch-event [::test-clicked]) + :disabled (status/disabled-for test-state) + :class (status/class-for test-state)} + "Test Rule"]] + [:div.column + [form-builder/submit-button {:class ["is-fullwidth"]} + "Save"]]]]])]) (defn form [_] (r/create-class diff --git a/src/cljs/auto_ap/views/pages/admin/users/form.cljs b/src/cljs/auto_ap/views/pages/admin/users/form.cljs index 3942de13..8bdd3a47 100644 --- a/src/cljs/auto_ap/views/pages/admin/users/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/users/form.cljs @@ -1,49 +1,14 @@ (ns auto-ap.views.pages.admin.users.form - (:require [re-frame.core :as re-frame] - [reagent.core :as reagent] - [clojure.string :as str] - [auto-ap.subs :as subs] - [auto-ap.events :as events] - [auto-ap.entities.clients :as entity] - [auto-ap.views.components.address :refer [address-field]] - [auto-ap.views.components.admin.side-bar :refer [admin-side-bar]] - [auto-ap.views.components.layouts :refer [side-bar-layout]] - [auto-ap.views.utils :refer [login-url dispatch-value-change bind-field horizontal-field dispatch-event with-user]] - [auto-ap.views.components.grid :as grid] - [auto-ap.utils :refer [by replace-if]] - [cljs.reader :as edn] - [auto-ap.routes :as routes] - [bidi.bidi :as bidi] - [auto-ap.status :as status] - [auto-ap.forms :as forms] - [auto-ap.views.components.modal :as modal])) - -(re-frame/reg-sub - ::can-submit - (fn [db] - true)) - -(re-frame/reg-event-db - ::changed - (forms/change-handler ::form - (fn [data field value] - []))) - - - -(re-frame/reg-event-db - ::add-client - [(forms/in-form ::form)] - (fn [form [_ d]] - (let [client (get @(re-frame/subscribe [::subs/clients-by-id]) - (get-in form [:data :adding-client]))] - (update-in form [:data :clients] conj client )))) - -(re-frame/reg-event-db - ::remove-client - [(forms/in-form ::form)] - (fn [form [_ d]] - (update-in form [:data :clients] #(filter (fn [c] (not= (:id c) d)) %)))) + (:require + [auto-ap.entities.clients :as entity] + [auto-ap.forms :as forms] + [auto-ap.forms.builder :as form-builder] + [auto-ap.status :as status] + [auto-ap.subs :as subs] + [auto-ap.views.components.modal :as modal] + [auto-ap.views.components.typeahead :refer [typeahead-v3]] + [auto-ap.views.utils :refer [dispatch-event multi-field with-user]] + [re-frame.core :as re-frame])) (re-frame/reg-event-fx ::saving @@ -56,7 +21,7 @@ :operation/name "EditUser"} :venia/queries [{:query/data [:edit-user {:edit-user (-> (:data db) - (update :clients #(map :id %)) + (update :clients #(map (comp :id :client) %)) (select-keys #{:id :name :clients :role}))} [:id :name :role [:clients [:id :name]]]]}]} :on-success [::saved]}})) @@ -64,66 +29,54 @@ (re-frame/reg-event-fx ::saved (forms/triggers-stop ::form) - (fn [{:keys [db]} [_ {:keys [edit-user]}]] + (fn [_ _] {:dispatch [::modal/modal-closed]})) -(def user-form (forms/vertical-form {:submit-event [::saving] - :change-event [::changed] - :can-submit [::can-submit] - :id ::form})) (defn form [] - (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline field raw-field error-notification submit-button]} user-form] - (form-inline {} - [:<> - (field "Name" - [:input.input {:type "text" - :field [:name] - :spec ::entity/name}]) - [:div.field - [:p.help "Role"] - [:div.control - [:div.select - [raw-field - [:select {:type "select" - :field [:role]} - [:option {:value ":none"} "None"] - [:option {:value ":user"} "User"] - [:option {:value ":manager"} "Manager"] - [:option {:value ":power_user"} "Power User"] - [:option {:value ":admin"} "Admin"]]]]]] - (when (#{":user" ":manager" ":power_user"} (:role data)) - [:div.field - [:p.help "Clients"] - [:div.control - [:div.field.has-addons - [:div.control - [:div.select - [raw-field - [:select {:type "select" - :field [:adding-client]} - [:option] - (let [used-clients (set (map :id (:clients data)))] - (for [{:keys [id name] :as client} @(re-frame/subscribe [::subs/clients]) - :when (not (used-clients id))] - ^{:key id} [:option {:value id} name]))]]]] - [:p.control - [:button.button.is-primary {:on-click (dispatch-event [::add-client])} "Add"]]] + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) + clients @(re-frame/subscribe [::subs/clients])] + [form-builder/builder {:submit-event [::saving] + :id ::form} + [form-builder/field + "Name" + [:input.input {:type "text" + :field [:name] + :spec ::entity/name}]] + [:div.field + [:p.help "Role"] + [:div.control + [:div.select + [form-builder/raw-field + [:select {:type "select" + :field [:role]} + [:option {:value ":none"} "None"] + [:option {:value ":user"} "User"] + [:option {:value ":manager"} "Manager"] + [:option {:value ":power_user"} "Power User"] + [:option {:value ":admin"} "Admin"]]]]]] + (when (#{":user" ":manager" ":power_user"} (:role data)) + [form-builder/field + "Client" + [multi-field {:type "multi-field" + :field [:clients] + :template [[typeahead-v3 {:entities clients + :entity->text :name + :style {:width "13em"} + :type "typeahead-v3" + :field [:client]}]]}]]) + [form-builder/hidden-submit-button]])) - [:ul - (for [{:keys [id name]} (:clients data)] - ^{:key id} [:li name [:a.icon {:on-click (dispatch-event [::remove-client id])} [:i.fa.fa-times ]]])]]])]))) (re-frame/reg-event-fx ::editing (fn [{:keys [db]} [_ d]] - {:db (-> db - (forms/start-form ::form d)) - :dispatch [::modal/modal-requested {:title (str "Edit user " (:name d)) - :body [form] - :cancel? false - :confirm {:value "Save" + {:db (-> db + (forms/start-form ::form (update d :clients #(map (fn [x] {:client x}) %)))) + :dispatch [::modal/modal-requested {:title (str "Edit user " (:name d)) + :body [form] + :cancel? false + :confirm {:value "Save" :status-from [::status/single ::form] - :class "is-primary" - :on-click (dispatch-event [::saving]) + :class "is-primary" + :on-click (dispatch-event [::saving]) :close-event [::status/completed ::form]}}]})) diff --git a/src/cljs/auto_ap/views/pages/admin/vendors/merge_dialog.cljs b/src/cljs/auto_ap/views/pages/admin/vendors/merge_dialog.cljs index 65182f73..cb33c74c 100644 --- a/src/cljs/auto_ap/views/pages/admin/vendors/merge_dialog.cljs +++ b/src/cljs/auto_ap/views/pages/admin/vendors/merge_dialog.cljs @@ -7,59 +7,51 @@ [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] [auto-ap.views.utils :refer [dispatch-event]] - [re-frame.core :as re-frame])) + [re-frame.core :as re-frame] + [auto-ap.forms.builder :as form-builder])) (re-frame/reg-sub ::can-submit :<- [::forms/form ::form] (fn [{:keys [data]}] - (println data) (and (:from data) (:to data)))) -(def merge-form (forms/vertical-form {:submit-event [::save] - :change-event [::forms/change ::form] - :can-submit [::can-submit] - :id ::form})) - (defn form [] - (let [_ @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline field]} merge-form] - - - (form-inline {} - [:<> - (field "Form Vendor (will be deleted)" - [search-backed-typeahead {:search-query (fn [i] - [:search_vendor - {:query i} - [:name :id]]) - :type "typeahead-v3" - :auto-focus true - :field [:from]}]) + [form-builder/builder {:submit-event [::save] + :can-submit [::can-submit] + :id ::form} + [form-builder/field "Form Vendor (will be deleted)" + [search-backed-typeahead {:search-query (fn [i] + [:search_vendor + {:query i} + [:name :id]]) + :type "typeahead-v3" + :auto-focus true + :field [:from]}]] - (field "To Vendor" - [search-backed-typeahead {:search-query (fn [i] - [:search_vendor - {:query i} - [:name :id]]) - :type "typeahead-v3" - :field [:to]}])]))) + [form-builder/field "To Vendor" + [search-backed-typeahead {:search-query (fn [i] + [:search_vendor + {:query i} + [:name :id]]) + :type "typeahead-v3" + :field [:to]}]] + [form-builder/hidden-submit-button]]) (re-frame/reg-event-fx ::show (fn [{:keys [db]} _] - {:dispatch [::modal/modal-requested {:title "Merge Vendors" - :body [form] + {:dispatch [::modal/modal-requested {:title "Merge Vendors" + :body [form] :confirm {:value "Merge" :status-from [::status/single ::form] :class "is-primary" :on-click (dispatch-event [::save]) :can-submit [::can-submit] :close-event [::status/completed ::form]}}] - :db (forms/start-form db ::form {})} - )) + :db (forms/start-form db ::form {})})) (re-frame/reg-event-fx ::complete diff --git a/src/cljs/auto_ap/views/pages/admin/yodlee.cljs b/src/cljs/auto_ap/views/pages/admin/yodlee.cljs deleted file mode 100644 index 6d159d5d..00000000 --- a/src/cljs/auto_ap/views/pages/admin/yodlee.cljs +++ /dev/null @@ -1,415 +0,0 @@ -(ns auto-ap.views.pages.admin.yodlee - (:require [re-frame.core :as re-frame] - [auto-ap.forms :as forms] - [reagent.core :as reagent] - [clojure.string :as str] - [cljs-time.format :as f] - [cljs-time.core :as time] - [auto-ap.subs :as subs] - [auto-ap.events :as events] - [auto-ap.entities.clients :as entity] - [auto-ap.views.components.layouts :refer [side-bar-layout]] - [auto-ap.views.components.admin.side-bar :refer [admin-side-bar]] - [auto-ap.views.components.address :refer [address-field]] - [auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field str->date date->str with-user]] - [auto-ap.views.components.modal :as modal] - [auto-ap.status :as status] - [cljs.reader :as edn] - [auto-ap.routes :as routes] - [bidi.bidi :as bidi])) - - - -(re-frame/reg-sub - ::authentication - (fn [db] - (-> db ::yodlee :authentication))) - -(re-frame/reg-sub - ::can-submit - (fn [db] - true)) - -(re-frame/reg-sub - ::loading? - (fn [db] - (-> db ::yodlee :loading?))) - -(re-frame/reg-sub - ::accounts - (fn [db] - (-> db ::yodlee :accounts))) - -(re-frame/reg-sub - ::accounts-loading? - (fn [db] - (-> db ::yodlee :accounts-loading?))) - -(re-frame/reg-sub - ::provider-accounts-loading? - (fn [db] - (-> db ::provider-accounts-loading?))) - -(re-frame/reg-sub - ::provider-accounts - (fn [db] - (-> db ::provider-accounts))) - -(re-frame/reg-event-fx - ::authenticate-with-yodlee - (fn [{:keys [db]} _] - {:db (assoc-in db [::yodlee :loading?] true) - :http {:token (:user db) - :method :get - :headers {"Content-Type" "application/edn"} - :uri (str "/api/yodlee/fastlink") - :on-success [::authenticated] - :on-error [::save-error]}})) - -(re-frame/reg-event-fx - ::mounted - (fn [{:keys [db]} _] - {:db (-> db - (assoc ::yodlee {:provider-accounts-loading? true}) - (assoc ::save-error nil) - (assoc ::provider-accounts []) - (assoc ::provider-accounts-loading? true)) - :http {:token (:user db) - :method :get - :headers {"Content-Type" "application/edn"} - :uri (str "/api/yodlee/provider-accounts") - :on-success [::got-provider-accounts] - :on-error [::save-error]}})) - -(re-frame/reg-event-fx - ::kicked - (fn [{:keys [db]} [_ id state]] - {:dispatch [::mounted]})) - -(re-frame/reg-event-fx - ::kicked - (fn [{:keys [db]} [_ id state]] - {:dispatch [::mounted]})) - -(re-frame/reg-event-fx - ::kick - (fn [{:keys [db]} [_ id]] - {:http {:token (:user db) - :method :post - :headers {"Content-Type" "application/edn"} - :uri (str "/api/yodlee/provider-accounts/" id) - :on-success [::kicked id :kicked] - :on-error [::kicked id :errored]}})) - -(re-frame/reg-event-fx - ::got-accounts - (fn [{:keys [db]} [_ accounts]] - {:db (-> db - (assoc-in [::yodlee :accounts] accounts) - (assoc-in [::yodlee :accounts-loading?] false))})) - -(re-frame/reg-event-fx - ::got-provider-accounts - (fn [{:keys [db]} [_ accounts]] - {:db (-> db - (assoc-in [::provider-accounts] accounts) - (assoc-in [::provider-accounts-loading?] false))})) - -(re-frame/reg-event-fx - ::authenticated - (fn [{:keys [db]} [_ authentication]] - {:db (-> db - (assoc-in [::yodlee :authentication] authentication) - (assoc-in [::yodlee :loading?] false))})) - -(re-frame/reg-event-fx - ::authenticated-mfa - (fn [{:keys [db]} [_ provider-account-id authentication]] - {:db (-> db - (assoc-in [::yodlee :authentication] authentication) - (assoc-in [::yodlee :loading?] false) - (forms/stop-form [::mfa-form provider-account-id]))})) - -(re-frame/reg-event-fx - ::save-error - (fn [{:keys [db]} [_ authentication]] - {:db (assoc db ::load-error "error")})) - -(defn yodlee-link-button [] - [:div - (let [authentication @(re-frame/subscribe [::authentication]) - loading? @(re-frame/subscribe [::loading?])] - - (if authentication - [:div - "Authentication successful!" - [:form {:action (:url authentication) :method "POST"} - [:input {:type "hidden" - :name "rsession" - :value (:session authentication)}] - [:input {:type "hidden" - :name "token" - :value (:token authentication)}] - [:input {:type "hidden" - :name "app" - :value (:app authentication)}] - - [:input {:type "hidden" - :name "redirectReq" - :value "true"}] - [:button.button.is-primary [:span [:span.icon [:i.fa.fa-external-link]] " Go to yodlee"]]]] - - [:button.button.is-primary {:class (if loading? "is-loading" "") :on-click (dispatch-event [::authenticate-with-yodlee])} "Authenticate with Yodlee"]))]) - -(defn yodlee-date->date [d] - (try - (some-> d - (str->date (:date-time-no-ms f/formatters)) - ) - (catch js/Error e - nil))) - -(defn yodlee-date->str [d] - (try - (or (some-> d - (str->date (:date-time-no-ms f/formatters)) - date->str) - "N/A") - (catch js/Error e - "N/A"))) - -(defn yodlee-accounts-table [accounts] - (let [bank-accounts @(re-frame/subscribe [::bank-accounts-by-yodlee-account-id])] - [:div - [:table.table - [:thead - [:tr - [:th "Account Name"] - [:th "Account Number"] - [:th "Yodlee Account Number"] - [:th "Balance"] - [:th "Yodlee Status"] - [:th "Usage"]]] - [:tbody - - (for [account accounts] - ^{:key (:id account)} [:tr - [:td (:accountName account)] - [:td (:accountNumber account)] - [:td (:id account)] - [:td.has-text-right (:amount (:balance account))] - [:td (str/join ", " (map :additionalStatus (:dataset account)))] - [:td - (when-let [bank-accounts (get bank-accounts (:id account))] - [:div.tags - (for [bank-account bank-accounts] - ^{:key (:id bank-account)} - [:div.tag (:name bank-account) " (" (:code bank-account) ")"])])] - ])]]])) - -(re-frame/reg-event-fx - ::reauthenticate-mfa - [with-user ] - (fn [{:keys [user db]} [_ provider-account-id ]] - {:db (forms/loading db [::mfa-form provider-account-id]) - :http {:token user - :method :post - :headers {"Content-Type" "application/edn"} - :uri (str "/api/yodlee/reauthenticate/" provider-account-id ) - :body {"loginForm" - {"row" - (->> (get-in db [::forms/forms [::mfa-form provider-account-id]]) - :data - :login - (sort-by (fn [[k v]] k)) - (map second) - (map (fn [row] - {"field" - (mapv (fn [[k v]] - {"id" k - "value" v}) - row)})))} - "field" - (mapv (fn [[k v]] - {"id" k - "value" v}) - (:mfa (:data (get-in db [::forms/forms [::mfa-form provider-account-id]]))))} - - :on-success [::authenticated-mfa provider-account-id] - :on-error [::forms/save-error [::mfa-form provider-account-id] ]}})) - -(re-frame/reg-event-fx - ::provider-account-refreshed - (fn [{:keys [db]} [_ i result]] - - {:db (assoc-in db [::provider-accounts] result) - :dispatch [::forms/form-closing [::refresh-provider-account i]]})) - -(re-frame/reg-event-fx - ::refresh-provider-account - [with-user ] - (fn [{:keys [user db]} [_ provider-account-id ]] - {:db (forms/loading db [::refresh-provider-account provider-account-id]) - :http {:token user - :method :post - :headers {"Content-Type" "application/edn"} - :uri (str "/api/yodlee/provider-accounts/refresh/" provider-account-id ) - :body {} - :on-success [::provider-account-refreshed provider-account-id] - :on-error [::forms/save-error [::refresh-provider-account provider-account-id] ]}})) - -(re-frame/reg-event-fx - ::provider-account-deleted - (fn [{:keys [db]} [_ i result]] - {:db (assoc-in db [::provider-accounts] result) - :dispatch-n [[::forms/form-closing [::refresh-provider-account i]] - [::modal/modal-closed ]]})) - -(re-frame/reg-event-fx - ::delete-provider-account - [with-user ] - (fn [{:keys [user db]} [_ provider-account-id ]] - {:http {:token user - :method :post - :owns-state {:single ::delete-provider-account} - :headers {"Content-Type" "application/edn"} - :uri (str "/api/yodlee/provider-accounts/delete/" provider-account-id ) - :body {} - :on-success [::provider-account-deleted provider-account-id] - :on-error [::forms/save-error [::delete-provider-account provider-account-id] ]}})) - - - -(re-frame/reg-event-fx - ::delete-requested - [with-user] - (fn [{:keys [user db]} [_ account-id]] - {:dispatch - [::modal/modal-requested {:title "Delete Provider account " - :body [:div "Are you sure you want to delete provider account " account-id "?"] - :confirm {:value "Delete provider account" - :status-from [::status/single ::delete-provider-account] - :class "is-danger" - :on-click (dispatch-event [::delete-provider-account account-id]) - :close-event [::status/completed ::delete-provider-account]} - :cancel? true}]})) - - -(defn delete-button [account-id] - [:button.button - {:on-click (dispatch-event [::delete-requested account-id])} - [:span.icon [:i.fa.fa-times]]]) - -(re-frame/reg-sub - ::bank-accounts-by-yodlee-account-id - :<- [::subs/bank-accounts] - (fn [bank-accounts] - (group-by :yodlee-account-id bank-accounts))) - -(defn yodlee-provider-accounts-table [] - (let [bank-accounts @(re-frame/subscribe [::bank-accounts-by-yodlee-account-id])] - - (if @(re-frame/subscribe [::provider-accounts-loading?]) - [:div "Loading..."] - [:div.columns - [:div.column.is-half - (doall - (for [account @(re-frame/subscribe [::provider-accounts]) - :let [{:keys [error status] :as g} @(re-frame/subscribe [::forms/form [::refresh-provider-account (:id account)]]) - total-usages (mapcat (comp bank-accounts :id) (:accounts account))]] - - ^{:key (:id account)} - [:div.card {:style {:margin-bottom "1em"}} - [:div.card-header - [:div.card-header-title "Provider account " (:id account)] - [:div.card-header-icon - (when (seq total-usages) - [:div.tags - [:div.tag.is-primary (count total-usages) " usages"]])] - [:div.card-header-icon - [delete-button (:id account)]] - [:div.card-header-icon - (cond - (= :loading status) [:button.button.is-disabled.is-loading [:i.fa.fa-refresh]] - error [:button.button.is-disabled [:span.icon [:i.fa.fa-exclamation-triangle]]] - :else - [:button.button - {:on-click (dispatch-event [::refresh-provider-account (:id account)])} - [:span.icon [:i.fa.fa-refresh]]])]] - [:div.card-content - - (if (> (some-> (-> account :dataset first :lastUpdated) - (yodlee-date->date ) - (time/interval (time/now)) - (time/in-days )) - 1) - [:div.notification.is-info.is-light - [:div.level - [:div.level-left - [:div.level-item - [:p - "This account was last updated on " - (yodlee-date->str (-> account :dataset first :lastUpdated)) - ", and last attempted " - (yodlee-date->str (-> account :dataset first :lastUpdateAttempt)) - "."]]] - [:div.level-right [:button.button.is-success {:on-click (dispatch-event [::kick (:id account)] )} "Sync yodlee with bank" ]]] - - ]) - - - [yodlee-accounts-table (:accounts account)] - (if (not= (-> account :dataset first :additionalStatus) - "AVAILABLE_DATA_RETRIEVED") - [:div - [:div.notification.is-info.is-warning - [:div.level - [:div.level-left - [:div.level-item - "This provider account's status is '" - (-> account :dataset first :additionalStatus) - "'. If this is in error, it might help to try reauthenticating by filling out the form below."]]]] - (let [{error :error account-data :data } @(re-frame/subscribe [::forms/form [::mfa-form (:id account)]]) - change-event [::forms/change [::mfa-form (:id account)]] - {:keys [form-inline field field-holder raw-field error-notification submit-button]} (forms/vertical-form {:can-submit [::can-submit] - :change-event change-event - :submit-event [::reauthenticate-mfa (:id account)] - :id [::mfa-form (:id account)]} )] - (form-inline {:title "Reauthenticate"} - [:<> - (error-notification) - (doall - (for [[row i] (map vector (-> account :loginForm last :row) (range)) - f (:field row) - :let [options (map :optionValue (:option f))]] - ^{:key (:id f)} - [:div - (field (:label row) - [:input.input {:type "text" :field [:login i (:id f)]}]) - (if (seq options) - [:ul - (for [o options] - ^{:key o} - [:li [:pre o]])])])) - (doall - (for [f (-> account :field)] - ^{:key (:id f)} - (field (:label f) - [:input.input {:type "text" :mfa [:form (:id f)] :value (-> f :field first :value)}]))) - (submit-button "Reauthenticate")]))])]]))]]))) - - -(defn admin-yodlee-content [] - [(with-meta - (fn [] - [:div - [:h1.title "Yodlee provider accounts"] - - [yodlee-provider-accounts-table] - [yodlee-link-button]]) - {:component-did-mount (fn [] - (re-frame/dispatch [::mounted]))})]) - -#_(defn admin-yodlee-page [] - [side-bar-layout {:side-bar [admin-side-bar {}] - :main [admin-yodlee-content]}]) diff --git a/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs b/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs index a397c3b5..4856a1ee 100644 --- a/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs +++ b/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs @@ -1,19 +1,29 @@ (ns auto-ap.views.pages.invoices.advanced-print-checks - (:require [auto-ap.forms :as forms] - [auto-ap.status :as status] - [auto-ap.subs :as subs] - [auto-ap.utils :refer [by]] - [auto-ap.views.components.modal :as modal] - [auto-ap.views.pages.invoices.common :refer [invoice-read does-amount-exceed-outstanding?]] - [auto-ap.views.pages.invoices.form :as form] - [auto-ap.views.utils :refer [dispatch-event horizontal-field with-user]] - [re-frame.core :as re-frame])) + (:require + [auto-ap.forms :as forms] + [auto-ap.forms.builder :as form-builder] + [auto-ap.status :as status] + [auto-ap.subs :as subs] + [auto-ap.views.components.modal :as modal] + [auto-ap.views.pages.invoices.common + :refer [does-amount-exceed-outstanding? invoice-read]] + [auto-ap.views.components.money-field :refer [money-field]] + [auto-ap.views.utils :refer [dispatch-event horizontal-field with-user]] + [re-frame.core :as re-frame])) (re-frame/reg-sub ::can-submit :<- [::forms/form ::form] (fn [{ {:keys [invoices invoice-amounts]} :data}] - (cond (seq (filter + (cond + (->> invoice-amounts + vals + (map :amount) + (filter nil?) + seq) + false + + (seq (filter (fn [{:keys [id outstanding-balance]}] (does-amount-exceed-outstanding? (get-in invoice-amounts [id :amount]) outstanding-balance )) invoices)) @@ -22,63 +32,55 @@ :else true))) -(def advanced-print-checks-form (forms/vertical-form {:submit-event [::save] - :change-event [::forms/change ::form] - :can-submit [::can-submit] - :id ::form})) - - (defn form [] (let [real-bank-accounts @(re-frame/subscribe [::subs/real-bank-accounts]) - {:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline horizontal-field field raw-field error-notification submit-button]} advanced-print-checks-form] + {:keys [data]} @(re-frame/subscribe [::forms/form ::form])] - (form-inline {} - [:<> - [:div.field - [:label.label "Pay using"] - [:div.control - [:span.select - [raw-field - [:select {:type "select" - :field :bank-account-id} - (for [{:keys [id number name]} real-bank-accounts] - ^{:key id} [:option {:value id} name])]]]]] + [form-builder/builder {:submit-event [::save] + :can-submit [::can-submit] + :id ::form} + [:div.field + [:label.label "Pay using"] + [:div.control + [:span.select + [form-builder/raw-field + [:select {:type "select" + :field :bank-account-id} + (for [{:keys [id name]} real-bank-accounts] + ^{:key id} [:option {:value id} name])]]]]] - [:table.table.is-fullwidth - [:thead - [:tr - [:th "Vendor"] - [:th "Invoice ID"] - [:th {:style {"width" "10em"}} "Payment"]]] - [:tbody - (doall - (for [{:keys [vendor payment outstanding-balance invoice-number id] :as i} (:invoices data)] - ^{:key id} - [:tr - [:td (:name vendor)] - [:td invoice-number] - - [:td [:div.field.has-addons.is-extended - [:p.control [:a.button.is-static "$"]] - [:p.control - (raw-field - [:input.input.has-text-right {:type "number" - :field [:invoice-amounts id :amount] - :step "0.01"}])]]]]))]]]))) + [:table.table.is-fullwidth + [:thead + [:tr + [:th "Vendor"] + [:th "Invoice ID"] + [:th {:style {"width" "10em"}} "Payment"]]] + [:tbody + (doall + (for [{:keys [vendor invoice-number id] :as i} (:invoices data)] + ^{:key id} + [:tr + [:td (:name vendor)] + [:td invoice-number] + + [:td + [form-builder/raw-field + [money-field {:type "money" + :field [:invoice-amounts id :amount] + :step "0.01"}]]]]))]]])) (re-frame/reg-event-fx ::show (fn [{:keys [db]} [_ invoices]] - {:dispatch [::modal/modal-requested {:title "Print Checks" - :body [form] - :confirm {:value "Print checks" + {:dispatch [::modal/modal-requested {:title "Print Checks" + :body [form] + :confirm {:value "Print checks" :status-from [::status/single ::form] - :class "is-primary" - :on-click (dispatch-event [::save]) - :can-submit [::can-submit] + :class "is-primary" + :on-click (dispatch-event [::save]) + :can-submit [::can-submit] :close-event [::status/completed ::form]}}] - :db (-> db + :db (-> db (forms/start-form ::form {:bank-account-id (:id (first @(re-frame/subscribe [::subs/real-bank-accounts]))) :invoices invoices diff --git a/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs b/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs index e856d0a9..7aa28851 100644 --- a/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs @@ -23,7 +23,8 @@ [reagent.core :as reagent] [vimsical.re-frame.fx.track :as track] [vimsical.re-frame.cofx.inject :as inject] - [auto-ap.views.pages.ledger.report-table :as rtable])) + [auto-ap.views.pages.ledger.report-table :as rtable] + [auto-ap.forms.builder :as form-builder])) (defn data-params->query-params [params] (when params @@ -36,11 +37,6 @@ :to-numeric-code (:to-numeric-code params) :date-range (:date-range params)})) -(re-frame/reg-sub - ::can-submit - (fn [_] - true)) - (re-frame/reg-sub ::ledger-list-active? @@ -183,53 +179,46 @@ NOTE: Please review the transactions we may have question for you here: https:// :event-fn (fn [params] [::ledger-params-change params])}})) -(def balance-sheet-form (forms/vertical-form {:can-submit [::can-submit] - :change-event [::change] - :submit-event [::report-requested] - :id ::form})) - (defn report-form [] - (let [{:keys [form-inline raw-field]} balance-sheet-form - {:keys [data]} @(re-frame/subscribe [::forms/form ::form])] - (form-inline {} - [:div - [:div.report-controls - [:div.level - [:div.level-left - [:div.level-item - [:div.control - [:p.help "Date"] - (raw-field - [date-picker {:output :cljs-date - :type "date" - :field [:date]}])]] - [:div.level-item - [:div.control - [:div.mt-3] - [switch-field {:id "include-comparison" - :checked (:include-comparison data) - :on-change (fn [e] - (re-frame/dispatch [::change [:include-comparison] (.-checked (.-target e))])) - :label "Include comparison" - :type "checkbox"}]]] - [:div.level-item + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])] + [form-builder/builder {:change-event [::change] + :submit-event [::report-requested] + :id ::form} + [:div + [:div.report-controls + [:div.level + [:div.level-left + [:div.level-item + [:div.control + [form-builder/field + "Date" + [date-picker {:output :cljs-date + :type "date" + :field [:date]}]]]] + [:div.level-item + [form-builder/field + [:div.mt-5] + [switch-field {:id "include-comparison" + :field [:include-comparison] + :label "Include compariison" + :type "checkbox"}]]] + [:div.level-item - (when (boolean (:include-comparison data)) - [:div.control - [:p.help "Comparison Date"] - (raw-field - [date-picker {:output :cljs-date - :type "date" - :field [:comparison-date]}])])]] - [:div.level-right - [:div.buttons + (when (boolean (:include-comparison data)) + [form-builder/field + "Comparison Date" + [date-picker {:output :cljs-date + :type "date" + :field [:comparison-date]}]])]] + [:div.level-right + [:div.buttons - (when @(re-frame/subscribe [::subs/is-admin?]) - [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) - [:button.button.is-primary "Run"]]]]]]))) + (when @(re-frame/subscribe [::subs/is-admin?]) + [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) + [:button.button.is-primary "Run"]]]]]]])) (defn balance-sheet-report [{:keys [args report-data]}] - (let [pnl-data (concat (->> (:balance-sheet-accounts report-data) + (let [pnl-data (concat (->> (:balance-sheet-accounts report-data) (map (fn [b] (assoc b :period (:date args) diff --git a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs index 05f0a861..76728d5e 100644 --- a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs @@ -31,7 +31,8 @@ [react-dom :as react-dom] [reagent.core :as reagent] [vimsical.re-frame.cofx.inject :as inject] - [vimsical.re-frame.fx.track :as track])) + [vimsical.re-frame.fx.track :as track] + [auto-ap.forms.builder :as form-builder])) @@ -223,14 +224,11 @@ NOTE: Please review the transactions we may have question for you here: https:// (fn [_] true)) -(def pnl-form (forms/vertical-form {:can-submit [::can-submit] - :change-event [::change] - :submit-event [::report-requested] - :id ::form})) (defn report-control-detail [{:keys [active box which]} children] (when (and @box (= which @active)) + (println @box) (react-dom/createPortal (reagent/as-element [:div.notification.is-light [:a.delete {:on-click (fn [] (reset! active nil))}] @@ -243,191 +241,189 @@ NOTE: Please review the transactions we may have question for you here: https:// [:div.control [:a.button {:class (when (= selected-preset title) "is-active") - :on-click (dispatch-event - [::change - [:periods] - periods - [:selected-preset] title])} + :on-click (fn [] + (re-frame/dispatch-sync [::change + [:periods] + periods + [:selected-preset] title]) + (re-frame/dispatch-sync [::change + [:show-advanced?] + false]))} title]])) -(defn report-controls [_] - (let [!box (reagent/atom nil) +(defn report-controls [] + (let [!box (atom nil) active (reagent/atom nil)] - (fn [pnl-form] - (let [{:keys [raw-field]} pnl-form - {:keys [data]} @(re-frame/subscribe [::forms/form ::form]) + (fn [] + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) {:keys [periods selected-preset include-deltas column-per-location]} data] - [:div.report-controls - [:div.level.mb-2 - [:div.level-left - [:div.level-item - [buttons/dropdown {:on-click (fn [] (reset! active :clients))} - [:span (str "Companies" - (when-let [clients (:clients data)] - (str " (" (str/join ", " (map :name clients)) ")")))]] - [report-control-detail {:active active :box !box :which :clients} - [:div {:style {:width "20em"}} - [:h4.subtitle "Companies"] - [raw-field - [multi-field {:type "multi-field" - :field [:clients] - :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) - :entity->text :name - :type "typeahead-v3"}]]}]] - ]]] - [:div.level-item - [buttons/dropdown {:on-click (fn [] (reset! active :range))} - [:span (str "Range" - (when selected-preset - (str " (" selected-preset ")")))]] - [report-control-detail {:active active :box !box :which :range} - [:div - [:h4.subtitle "Range"] - [:div.field.is-grouped - [:div.control - [:div.field.has-addons - [:div.control - (raw-field + [form-builder/builder {:can-submit [::can-submit] + :change-event [::change] + :submit-event [::report-requested] + :id ::form} + [:div.report-controls + [:div.level.mb-2 + [:div.level-left + [:div.level-item + [buttons/dropdown {:on-click (fn [] (reset! active :clients))} + [:span (str "Companies" + (when-let [clients (:clients data)] + (str " (" (str/join ", " (map :name clients)) ")")))]] + [report-control-detail {:active active :box !box :which :clients} + [:div {:style {:width "20em"}} + [:h4.subtitle "Companies"] + [form-builder/raw-field + [multi-field {:type "multi-field" + :field [:clients] + :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) + :style {:width "18em"} + :entity->text :name + :type "typeahead-v3"}]]}]]]]] + [:div.level-item + [buttons/dropdown {:on-click (fn [] (reset! active :range))} + [:span (str "Range" + (when selected-preset + (str " (" selected-preset ")")))]] + [report-control-detail {:active active :box !box :which :range} + [:div + [:h4.subtitle "Range"] + [:div.field.is-grouped + [:div.control + [:div.field.has-addons + [:div.control + [form-builder/raw-field [date-picker {:placeholder "End date" :type "date" :output :cljs-date - :field [:thirteen-periods-end]}])] - [period-preset-button {:title "13 periods" - :periods (let [today (or (some-> (:thirteen-periods-end data)) - (local-today))] - (into - [{:start (t/plus (t/minus today (t/weeks (* 13 4))) - (t/days 1)) - :end today - :title "Total"}] - (for [i (range 13)] - {:start (t/plus (t/minus today (t/weeks (* (inc i) 4))) + :field [:thirteen-periods-end]}]]] + [period-preset-button {:title "13 periods" + :periods (let [today (or (some-> (:thirteen-periods-end data)) + (local-today))] + (into + [{:start (t/plus (t/minus today (t/weeks (* 13 4))) (t/days 1)) - :end (t/minus today (t/weeks (* i 4)))})))}]]] + :end today + :title "Total"}] + (for [i (range 13)] + {:start (t/plus (t/minus today (t/weeks (* (inc i) 4))) + (t/days 1)) + :end (t/minus today (t/weeks (* i 4)))})))}]]] - [:div.control - [:div.field.has-addons - [:div.control - (raw-field - [date-picker {:placeholder "End date" - :output :cljs-date - :type "date" - :field [:twelve-periods-end]}])] - [period-preset-button {:title "12 months" - :periods (let [end-date (or (some-> (:twelve-periods-end data)) - (local-today)) - this-month (t/local-date (t/year end-date) - (t/month end-date) - 1)] - (into - [{:start (t/minus this-month (t/months 11)) - :end (t/minus (t/plus this-month (t/months 1)) - (t/days 1)) - :title "Total"}] - (for [i (range 12)] - {:start (t/minus this-month (t/months (- 11 i))) - :end (t/minus (t/minus this-month (t/months (- 10 i))) - (t/days 1))})))}]]] + [:div.control + [:div.field.has-addons + [:div.control + [form-builder/raw-field + [date-picker {:placeholder "End date" + :output :cljs-date + :type "date" + :field [:twelve-periods-end]}]]] + [period-preset-button {:title "12 months" + :periods (let [end-date (or (some-> (:twelve-periods-end data)) + (local-today)) + this-month (t/local-date (t/year end-date) + (t/month end-date) + 1)] + (into + [{:start (t/minus this-month (t/months 11)) + :end (t/minus (t/plus this-month (t/months 1)) + (t/days 1)) + :title "Total"}] + (for [i (range 12)] + {:start (t/minus this-month (t/months (- 11 i))) + :end (t/minus (t/minus this-month (t/months (- 10 i))) + (t/days 1))})))}]]] - [period-preset-button {:periods (let [last-sunday (loop [current (local-today)] - (if (= 7 (t/day-of-week current)) - current - (recur (t/minus current (t/period :days 1)))))] - (and-last-year {:start (t/minus last-sunday (t/period :days 6)) - :end last-sunday})) - :title "Last week"}] + [period-preset-button {:periods (let [last-sunday (loop [current (local-today)] + (if (= 7 (t/day-of-week current)) + current + (recur (t/minus current (t/period :days 1)))))] + (and-last-year {:start (t/minus last-sunday (t/period :days 6)) + :end last-sunday})) + :title "Last week"}] - [period-preset-button {:periods (and-last-year {:start (loop [current (local-today)] - (if (= 1 (t/day-of-week current)) - current - (recur (t/minus current (t/period :days 1))))) - :end (local-today)}) - :title "Week to date"}] + [period-preset-button {:periods (and-last-year {:start (loop [current (local-today)] + (if (= 1 (t/day-of-week current)) + current + (recur (t/minus current (t/period :days 1))))) + :end (local-today)}) + :title "Week to date"}] - [period-preset-button {:periods (and-last-year {:start (t/minus (t/local-date (t/year (local-today)) - (t/month (local-today)) - 1) - (t/period :months 1)) - :end (t/minus (t/local-date (t/year (local-today)) - (t/month (local-today)) - 1) - (t/period :days 1))}) - :title "Last month"}] + [period-preset-button {:periods (and-last-year {:start (t/minus (t/local-date (t/year (local-today)) + (t/month (local-today)) + 1) + (t/period :months 1)) + :end (t/minus (t/local-date (t/year (local-today)) + (t/month (local-today)) + 1) + (t/period :days 1))}) + :title "Last month"}] - [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) - (t/month (local-today)) - 1) - :end (local-today)}) - :title "Month to date"}] + [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) + (t/month (local-today)) + 1) + :end (local-today)}) + :title "Month to date"}] - [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) 1 1) - :end - (local-today)}) - :title "Year to date"}] + [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) 1 1) + :end + (local-today)}) + :title "Year to date"}] - [period-preset-button {:periods [{:start (t/local-date (dec (t/year (local-today))) 1 1) - :end (t/local-date (dec (t/year (local-today))) 12 31)}] - :title "Last calendar year"}] + [period-preset-button {:periods [{:start (t/local-date (dec (t/year (local-today))) 1 1) + :end (t/local-date (dec (t/year (local-today))) 12 31)}] + :title "Last calendar year"}] - [period-preset-button {:periods (and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1)) - (t/period :days 1)) - :end (local-today)}) - :title "Full year"}]] - [:div - [:div.field - [:label.checkbox - (raw-field - [:input {:type "checkbox" - :field [:show-advanced?]}]) - " Show Advanced"]]] - (when (:show-advanced? data) - (doall - (for [[_ i] (map vector periods (range))] - ^{:key i} - [:div.field.is-grouped - [:div.control - [:p.help "From"] - (raw-field - [date-picker {:type "date" - :output :cljs-date - :field [:periods i :start]}])] + [period-preset-button {:periods (and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1)) + (t/period :days 1)) + :end (local-today)}) + :title "Full year"}]] + [:div + [:div.field + [:label.checkbox + [form-builder/raw-field + [:input {:type "checkbox" + :field [:show-advanced?]}]] + " Show Advanced"]]] + (when (:show-advanced? data) + [form-builder/raw-field + [multi-field {:type "multi-field" + :field [:periods] + :template [[date-picker {:type "date" + :output :cljs-date + :field [:start]}] + [date-picker {:type "date" + :output :cljs-date + :field [:end]}]]}]])]]] - [:div.control - [:p.help "To"] - (raw-field - [date-picker {:type "date" - :output :cljs-date - :field [:periods i :end]}])]])))]]] + [:div.level-item + [:div + [switch-field {:id "include-deltas" + :checked (boolean include-deltas) + :on-change (fn [e] + (re-frame/dispatch [::change + [:include-deltas] (.-checked (.-target e))])) + :label "Include deltas" + :type "checkbox"}]]] + [:div.level-item + [:div + [switch-field {:id "column-per-location" + :checked (boolean column-per-location) + :on-change (fn [e] + (re-frame/dispatch [::change + [:column-per-location] (.-checked (.-target e))])) + :label "Column per location" + :type "checkbox"}]]]] + [:div.level-right + [:div.buttons - [:div.level-item - [:div - [switch-field {:id "include-deltas" - :checked (boolean include-deltas) - :on-change (fn [e] - (re-frame/dispatch [::change - [:include-deltas] (.-checked (.-target e))])) - :label "Include deltas" - :type "checkbox"}]]] - [:div.level-item - [:div - [switch-field {:id "column-per-location" - :checked (boolean column-per-location) - :on-change (fn [e] - (re-frame/dispatch [::change - [:column-per-location] (.-checked (.-target e))])) - :label "Column per location" - :type "checkbox"}]]]] - [:div.level-right - [:div.buttons + (when @(re-frame/subscribe [::subs/is-admin?]) + [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) + [:button.button.is-primary "Run"]] - (when @(re-frame/subscribe [::subs/is-admin?]) - [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) - [:button.button.is-primary "Run"]] - - ]] - [:div.report-control-detail {:ref (fn [el] - (when-not @!box - (reset! !box el)))}]])))) + ]] + [:div.report-control-detail {:ref (fn [el] + (when (not= @!box el) + (reset! !box el)))}]]])))) @@ -466,13 +462,11 @@ NOTE: Please review the transactions we may have question for you here: https:// (defn profit-and-loss-content [] (let [status @(re-frame/subscribe [::status/single ::page]) - {:keys [data report]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline]} pnl-form] + {:keys [data report]} @(re-frame/subscribe [::forms/form ::form])] [:div - (form-inline {} - [:div - [status/status-notification {:statuses [[::status/single ::page]]}] - [report-controls pnl-form]]) + [:div + [status/status-notification {:statuses [[::status/single ::page]]}] + [report-controls]] [status/big-loader status] (when (and (not= :loading (:state status)) report) diff --git a/src/cljs/auto_ap/views/pages/transactions/bulk_updates.cljs b/src/cljs/auto_ap/views/pages/transactions/bulk_updates.cljs index a8480993..e63b1a5c 100644 --- a/src/cljs/auto_ap/views/pages/transactions/bulk_updates.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/bulk_updates.cljs @@ -19,7 +19,8 @@ [reagent.core :as r] [vimsical.re-frame.fx.track :as track] [auto-ap.events :as events] - [vimsical.re-frame.cofx.inject :as inject])) + [vimsical.re-frame.cofx.inject :as inject] + [auto-ap.forms.builder :as form-builder])) (re-frame/reg-sub ::can-submit @@ -105,45 +106,41 @@ (fn [] {::track/dispose {:id ::vendor-change}})) -(def code-form (forms/vertical-form {:submit-event [::code-selected] - :change-event [::changed] - :can-submit [::can-submit] - :id ::form})) (defn form-content [_] - (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline field]} code-form] + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])] + [form-builder/builder {:submit-event [::code-selected] + :change-event [::changed] + :can-submit [::can-submit] + :id ::form} - (form-inline {} - [:<> - (field "Vendor" - [search-backed-typeahead {:search-query (fn [i] - [:search_vendor - {:query i} - [:name :id]]) - :type "typeahead-v3" - :auto-focus true - :field [:vendor]}]) + + [form-builder/field "Vendor" + [search-backed-typeahead {:search-query (fn [i] + [:search_vendor + {:query i} + [:name :id]]) + :type "typeahead-v3" + :auto-focus true + :field [:vendor]}]] - (field "Approval Status" - [button-radio - {:type "button-radio" - :field [:transaction-approval-status] - :options [[:unapproved "Unapproved"] - [:requires-feedback "Client Review"] - [:approved "Approved"] - [:excluded "Excluded from Ledger"]]}]) + [form-builder/field + "Approval Status" + [button-radio + {:type "button-radio" + :field [:transaction-approval-status] + :options [[:unapproved "Unapproved"] + [:requires-feedback "Client Review"] + [:approved "Approved"] + [:excluded "Excluded from Ledger"]]}]] - (with-meta - (field nil - [expense-accounts-field {:type "expense-accounts" - :descriptor "account asssignment" - :percentage-only? true - :client (:client data) - :locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])) - :max 100 - :field [:accounts]}]) - {:key (some-> data :vendor :id str)}) - ]))) + [form-builder/raw-field + [expense-accounts-field {:type "expense-accounts" + :descriptor "account asssignment" + :percentage-only? true + :client (:client data) + :locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])) + :max 100 + :field [:accounts]}]]])) (defn form [_] (r/create-class {:display-name "transaction-bulk-update-form" diff --git a/src/cljs/auto_ap/views/pages/transactions/form.cljs b/src/cljs/auto_ap/views/pages/transactions/form.cljs index d14174f6..293ada3b 100644 --- a/src/cljs/auto_ap/views/pages/transactions/form.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/form.cljs @@ -13,13 +13,14 @@ :refer [search-backed-typeahead]] [auto-ap.views.pages.transactions.common :refer [transaction-read]] [auto-ap.views.utils - :refer [->$ date->str dispatch-event pretty with-user]] + :refer [->$ date->str date-picker dispatch-event pretty with-user]] [clojure.string :as str] [re-frame.core :as re-frame] [react :as react] [reagent.core :as r] [vimsical.re-frame.fx.track :as track] - [auto-ap.events :as events])) + [auto-ap.events :as events] + [auto-ap.forms.builder :as form-builder])) ;; SUBS (re-frame/reg-sub @@ -211,10 +212,6 @@ ;; VIEWS -(def transaction-form (forms/vertical-form {:can-submit [::can-submit] - :change-event [::changed] - :submit-event [::saving ] - :id ::form})) (defn potential-transaction-rule-matches-box [{:keys [potential-transaction-rule-matches]}] (let [states @(re-frame/subscribe [::status/multi ::matching])] @@ -340,122 +337,116 @@ [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form])} (let [{:keys [data] } @(re-frame/subscribe [::forms/form ::form]) locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))]) - {:keys [form-inline field error-notification submit-button ]} transaction-form is-admin? @(re-frame/subscribe [::subs/is-admin?]) is-power-user? @(re-frame/subscribe [::subs/is-power-user?]) should-disable-for-client? (and (not (or is-admin? is-power-user?)) (not= :requires-feedback (:original-status data))) is-already-matched? (:payment data)] - (with-meta - (form-inline {:title "Transaction"} - [:<> + [form-builder/builder {:can-submit [::can-submit] + :change-event [::changed] + :submit-event [::saving ] + :id ::form} + [form-builder/section {:title "Transaction"} + [:<> - (when (and @(re-frame/subscribe [::subs/is-admin?]) - (get-in data [:yodlee-merchant])) - [:div.control - [:p.help "Merchant"] - [:input.input {:type "text" - :disabled true - :value (str (get-in data [:yodlee-merchant :name]) - " - " - (get-in data [:yodlee-merchant :yodlee-id]))}]]) + (when is-admin? - (when is-admin? - (field "Matched Rule" - [:input.input {:type "text" - :field [:matched-rule :note] - :disabled "disabled"}])) - (field "Amount" - [:input.input {:type "text" - :field [:amount] - :disabled "disabled"}]) - (field "Description" - [:input.input {:type "text" - :field [:description-original] - :disabled "disabled"}]) + [form-builder/field + "Matched Rule" + [:input.input {:type "text" + :field [:matched-rule :note] + :disabled "disabled"}]]) + [form-builder/field "Amount" + [:input.input {:type "text" + :field [:amount] + :disabled "disabled"}]] + [form-builder/field + "Description" + [:input.input {:type "text" + :field [:description-original] + :disabled "disabled"}]] - (field "Date" - [:input.input {:type "text" - :field [:date] - :disabled "disabled"}]) + [form-builder/field "Date" + [date-picker {:type "date" + :field [:date] + :disabled "disabled"}]] - (when (and (:payment data) - (or is-admin? is-power-user?)) - [:p.notification.is-info.is-light>div.level>div.level-left - [:div.level-item "This transaction is linked to a payment "] - [:div.level-item [:button.button.is-warning {:on-click (dispatch-event [::unlink])} "Unlink"]]]) - [tabs {:default-tab :details} - (when - (and (seq (:potential-transaction-rule-matches data)) - (not (:matched-rule data)) - is-admin?) - [tab {:title "Transaction Rule" :key :transaction-rule} - [potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]]) - (when - (and (seq (:potential-autopay-invoices-matches data)) - (not is-already-matched?) - (or is-admin? is-power-user?)) - [tab {:title "Autopay Invoices" :key :autopay-invoices} - [potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]]) + (when (and (:payment data) + (or is-admin? is-power-user?)) + [:p.notification.is-info.is-light>div.level>div.level-left + [:div.level-item "This transaction is linked to a payment "] + [:div.level-item [:button.button.is-warning {:on-click (dispatch-event [::unlink])} "Unlink"]]]) + [tabs {:default-tab :details} + (when + (and (seq (:potential-transaction-rule-matches data)) + (not (:matched-rule data)) + is-admin?) + [tab {:title "Transaction Rule" :key :transaction-rule} + [potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]]) + (when + (and (seq (:potential-autopay-invoices-matches data)) + (not is-already-matched?) + (or is-admin? is-power-user?)) + [tab {:title "Autopay Invoices" :key :autopay-invoices} + [potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]]) - (when - (and (seq (:potential-unpaid-invoices-matches data)) - (not is-already-matched?) - (or is-admin? is-power-user?)) - [tab {:title "Unpaid Invoices" :key :unpaid-invoices} - [potential-unpaid-invoices-matches-box {:potential-unpaid-invoices-matches (:potential-unpaid-invoices-matches data)}]]) - (when - (and (seq (:potential-payment-matches data)) - (not is-already-matched?) - (or is-admin? is-power-user?)) - [tab {:title "Payment" :key :payment} - [potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]]) + (when + (and (seq (:potential-unpaid-invoices-matches data)) + (not is-already-matched?) + (or is-admin? is-power-user?)) + [tab {:title "Unpaid Invoices" :key :unpaid-invoices} + [potential-unpaid-invoices-matches-box {:potential-unpaid-invoices-matches (:potential-unpaid-invoices-matches data)}]]) + (when + (and (seq (:potential-payment-matches data)) + (not is-already-matched?) + (or is-admin? is-power-user?)) + [tab {:title "Payment" :key :payment} + [potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]]) - [tab {:title "Details" :key :details} - [:div - (field "Vendor" - [search-backed-typeahead {:search-query (fn [i] - [:search_vendor - {:query i} - [:name :id]]) - :type "typeahead-v3" - :auto-focus true - :field [:vendor] - :disabled (or (boolean (:payment data)) - should-disable-for-client?)}]) - (with-meta - (field nil - [expense-accounts-field - {:type "expense-accounts" - :field [:accounts] - :max (Math/abs (js/parseFloat (:amount data))) - :descriptor "credit account" - :client (:client data) - :disabled (or (boolean (:payment data)) - should-disable-for-client?) - :locations locations}]) - {:key (str (:id (:vendor data)))}) - (field "Approval Status" - [button-radio - {:type "button-radio" - :field [:approval-status] - :options [[:unapproved "Unapproved"] - [:requires-feedback "Client Review"] - [:approved "Approved"] - [:excluded "Excluded from Ledger"]] - :disabled should-disable-for-client?}]) + [tab {:title "Details" :key :details} + [:div + [form-builder/field + "Vendor" + [search-backed-typeahead {:search-query (fn [i] + [:search_vendor + {:query i} + [:name :id]]) + :type "typeahead-v3" + :auto-focus true + :field [:vendor] + :disabled (or (boolean (:payment data)) + should-disable-for-client?)}]] + [form-builder/raw-field [expense-accounts-field + {:type "expense-accounts" + :field [:accounts] + :max (Math/abs (js/parseFloat (:amount data))) + :descriptor "credit account" + :client (:client data) + :disabled (or (boolean (:payment data)) + should-disable-for-client?) + :locations locations}]] + [form-builder/field + "Approval Status" + [button-radio + {:type "button-radio" + :field [:approval-status] + :options [[:unapproved "Unapproved"] + [:requires-feedback "Client Review"] + [:approved "Approved"] + [:excluded "Excluded from Ledger"]] + :disabled should-disable-for-client?}]] - (field "Forecasted-transaction" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) - :entity->text :identifier - :type "typeahead-v3" - :field [:forecast-match]}]) - (error-notification) - (when-not should-disable-for-client? - (submit-button "Save"))]]]]) - {:key (:id data)}))]) + [form-builder/field + "Forecasted-transaction" + [typeahead-v3 {:entities @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) + :entity->text :identifier + :type "typeahead-v3" + :field [:forecast-match]}]] + [form-builder/error-notification] + (when-not should-disable-for-client? + [form-builder/submit-button "Save"])]]]]]])]) (defn form [_] (r/create-class diff --git a/src/cljs/auto_ap/views/pages/transactions/manual.cljs b/src/cljs/auto_ap/views/pages/transactions/manual.cljs index 89b4b143..15efbaff 100644 --- a/src/cljs/auto_ap/views/pages/transactions/manual.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/manual.cljs @@ -5,7 +5,8 @@ [auto-ap.subs :as subs] [auto-ap.views.components.modal :as modal] [auto-ap.views.utils :refer [dispatch-event with-user]] - [re-frame.core :as re-frame])) + [re-frame.core :as re-frame] + [auto-ap.forms.builder :as form-builder])) (re-frame/reg-sub ::can-submit @@ -13,22 +14,16 @@ (fn [{ {:keys [data]} :data}] (not-empty data))) -(def import-form (forms/vertical-form {:submit-event [::save] - :change-event [::forms/change ::form] - :can-submit [::can-submit] - :id ::form})) +(defn form [] + [form-builder/builder {:submit-event [::save] + :can-submit [::can-submit] + :id ::form} -(defn form [{import-completed-event :import-completed}] - (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form-inline horizontal-field field raw-field error-notification submit-button]} import-form] - - (form-inline {} - [:div.field - [:label.label - "Yodlee manual import table"] - [:div.control - [raw-field - [:textarea.textarea {:field [:data]}]]]]))) + [form-builder/field {:required? true} + "Yodlee manual import table" + [:div.control + [:textarea.textarea {:field [:data]}]]] + [form-builder/hidden-submit-button]]) (re-frame/reg-event-fx ::opening @@ -46,11 +41,9 @@ {:client-id (:id @(re-frame/subscribe [::subs/client])) :data ""}))})) - - (re-frame/reg-event-fx ::import-completed - (fn [{:keys [db]} [_ {:keys [imported errors] :as result}]] + (fn [_ _] {:dispatch [::modal/modal-closed ]})) (re-frame/reg-event-fx diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index a7e2cfe1..29e8af32 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -211,7 +211,7 @@ e)] (if (map? this-value) (update this-value :key (fnil identity (random-uuid))) - this-value)) )))) + this-value)))))) (on-change (mapv (fn [v] (dissoc v :new? :key)) @@ -251,7 +251,6 @@ options (if allow-nil? (with-keys (conj rest [:option {:value nil}])) (with-keys rest))] - (println "KEYS" keys (dissoc keys :allow-nil?)) (into [dom (dissoc keys :allow-nil?)] options))) @@ -281,21 +280,6 @@ keys (dissoc keys :field :subscription :event :spec)] (into [dom keys] (with-keys rest)))) -(defmethod do-bind "typeahead" [dom {:keys [field text-field event text-event subscription class spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - event (if (keyword? event) [event] event) - keys (assoc keys - :on-change (fn [selected text-value] - (re-frame/dispatch (conj (conj event field) selected)) - (when text-field - (re-frame/dispatch (conj (conj (or text-event event) text-field) text-value)))) - :value (get-in subscription field) - :class (str class - (when (and spec (not (s/valid? spec (get-in subscription field)))) - " is-danger"))) - keys (dissoc keys :field :subscription :event :spec)] - (into [dom keys] (with-keys rest)))) - (defmethod do-bind "multi-field" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) @@ -310,20 +294,6 @@ (into [dom keys] (with-keys rest)))) -(defmethod do-bind "typeahead-entity" [dom {:keys [field event subscription class spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - event (if (keyword? event) [event] event) - keys (assoc keys - :on-change (fn [selected] - (re-frame/dispatch (conj (conj event field) selected)) - #_(when text-field - (re-frame/dispatch (conj (conj (or text-event event) text-field) text-value)))) - :value (get-in subscription field) - :class (str class - (when (and spec (not (s/valid? spec (get-in subscription field)))) - " is-danger"))) - keys (dissoc keys :field :subscription :event :spec)] - (into [dom keys] (with-keys rest)))) (defmethod do-bind "typeahead-v3" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field)