Undoes old way of doing forms in favor of context version. Much easier

This commit is contained in:
2022-07-17 17:32:47 -07:00
parent a17f587ff3
commit b6962c5bfa
17 changed files with 782 additions and 1393 deletions

View File

@@ -138,68 +138,3 @@
(assoc-in [::forms id :error] nil))) (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]))})

View File

@@ -19,8 +19,10 @@
(let [data-sub (or data-sub [::forms/form id]) (let [data-sub (or data-sub [::forms/form id])
change-event (or change-event [::forms/change id]) change-event (or change-event [::forms/change id])
{:keys [data error] form-key :id} @(re-frame/subscribe data-sub) {:keys [data error] form-key :id} @(re-frame/subscribe data-sub)
status @(re-frame/subscribe [::status/single id])] status @(re-frame/subscribe [::status/single id])
(r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit) 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 :change-event change-event
:submit-event submit-event :submit-event submit-event
:error error :error error

View File

@@ -2,7 +2,6 @@
(:require (:require
[auto-ap.forms :as forms] [auto-ap.forms :as forms]
[auto-ap.status :as status] [auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.utils :refer [by]] [auto-ap.utils :refer [by]]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]]
@@ -10,17 +9,13 @@
[auto-ap.views.utils :refer [dispatch-event with-user]] [auto-ap.views.utils :refer [dispatch-event with-user]]
[clojure.string :as str] [clojure.string :as str]
[goog.string :as gstring] [goog.string :as gstring]
[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
(fn [db]
true))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::try-save ::try-save
[(forms/in-form ::form)] [(forms/in-form ::form)]
(fn [{:keys [db]} [_ id ]] (fn [{:keys [db]} _]
(let [{{:keys [ total]} :invoice (let [{{:keys [ total]} :invoice
:keys [expense-accounts]} (:data db) :keys [expense-accounts]} (:data db)
expense-accounts (vals expense-accounts) expense-accounts (vals expense-accounts)
@@ -44,7 +39,7 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::save ::save
[with-user (forms/in-form ::form)] [with-user (forms/in-form ::form)]
(fn [{:keys [db user] } [_ id]] (fn [{:keys [db user] } _]
(let [{{:keys [id]} :invoice (let [{{:keys [id]} :invoice
:keys [expense-accounts]} (:data db) :keys [expense-accounts]} (:data db)
expense-accounts (vals expense-accounts)] expense-accounts (vals expense-accounts)]
@@ -85,16 +80,10 @@
(fn [db [_ x]] (fn [db [_ x]]
(update-in db [:data :expense-accounts] dissoc 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 [] (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) expense-accounts (:expense-accounts data)
{:keys [total] :or {total 0} {:keys [locations] :as client} :client} (:invoice 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) multi-location? (> (count locations) 1)
expense-accounts-total (->> expense-accounts expense-accounts-total (->> expense-accounts
vals vals
@@ -108,72 +97,75 @@
[:div [:div
[:div [:div
[:a.button.is-outlined {:on-click (dispatch-event [::add-split])} "Add split"]] [:a.button.is-outlined {:on-click (dispatch-event [::add-split])} "Add split"]]
(form-inline {} [form-builder/builder {:submit-event [::try-save]
[:table.table :id ::form}
[:thead [:table.table
[:tr [:thead
[:th {:style {:width "500px"}} "Expense Account"] [:tr
(when multi-location? [:th {:style {:width "500px"}} "Expense Account"]
[:th {:style {:width "200px"}} "Location"]) (when multi-location?
[:th {:style {:width "200px"}} "Original Amount"] [:th {:style {:width "200px"}} "Location"])
[:th {:style {:width "300px"}} "Amount"] [:th {:style {:width "200px"}} "Original Amount"]
[:th {:style {:width "5em"}}]]] [:th {:style {:width "300px"}} "Amount"]
[:tbody [:th {:style {:width "5em"}}]]]
(doall (for [[id expense-account] expense-accounts] [:tbody
^{:key id} (doall (for [[id _] expense-accounts]
[:tr ^{:key id}
[:td.expandable [:div.control [:tr
(raw-field [:td.expandable [:div.control
[search-backed-typeahead {:search-query (fn [i] [form-builder/raw-field
[:search_account [search-backed-typeahead {:search-query (fn [i]
{:query i [:search_account
:client-id (:id client)} {:query i
[:name :id :location]]) :client-id (:id client)}
:type "typeahead-v3" [:name :id :location]])
:field [:expense-accounts id :account]}])]] :type "typeahead-v3"
:field [:expense-accounts id :account]}]]]]
(when multi-location? (when multi-location?
[:td [:td
(if-let [forced-location (get-in expense-accounts [id :account :location])] (if-let [forced-location (get-in expense-accounts [id :account :location])]
[:div.select [:div.select
[:select {:disabled "disabled" :value forced-location} [:option {:value forced-location} forced-location]]] [:select {:disabled "disabled" :value forced-location} [:option {:value forced-location} forced-location]]]
[:div.select [: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 [form-builder/raw-field
(str "$" (get-in expense-accounts [id :amount]))] [:select {:type "select"
[:td :field [:expense-accounts id :location]
[:div.control :spec (set locations)}
[:div.field.has-addons.is-extended (map (fn [l] ^{:key l} [:option {:value l} l]) locations)]]])])
[:p.control [:a.button.is-static "$"]]
[:p.control [:td
(raw-field (str "$" (get-in expense-accounts [id :amount]))]
[:input.input {:type "number" [:td
:field [:expense-accounts id :new-amount-temp] [:div.control
:style {:text-align "right"} [:div.field.has-addons.is-extended
:on-blur (dispatch-event [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])]) [:p.control [:a.button.is-static "$"]]
:on-key-down (fn [e ] [:p.control
(if (= 13 (.-keyCode e)) [form-builder/raw-field
(do [:input.input {:type "number"
(re-frame/dispatch [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])]) :field [:expense-accounts id :new-amount-temp]
true) :style {:text-align "right"}
false)) :on-blur (dispatch-event [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])])
:max (:total data) :on-key-down (fn [e ]
:step "0.01"}])]]]] (if (= 13 (.-keyCode e))
[:td [:a.button {:on-click (dispatch-event [::remove-expense-account-split id])} [:i.fa.fa-times]]]])) (do
[:tr (re-frame/dispatch [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])])
[:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Invoice total: "] true)
[:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" total ) )]] false))
[:tr :max (:total data)
[:td { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Account total: "] :step "0.01"}]]]]]]
[:td { :style { :text-align "right"} } (str (gstring/format "$%.2f" expense-accounts-total ) )]] [:td [:a.button {:on-click (dispatch-event [::remove-expense-account-split id])} [:i.fa.fa-times]]]]))
[:tr [:tr
[:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Difference: "] [: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 expense-accounts-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 (re-frame/reg-event-fx
::show ::show
@@ -184,11 +176,10 @@
:status-from [::status/single ::form] :status-from [::status/single ::form]
:class "is-primary" :class "is-primary"
:on-click (dispatch-event [::try-save]) :on-click (dispatch-event [::try-save])
:can-submit [::can-submit]
:close-event [::status/completed ::form]}}] :close-event [::status/completed ::form]}}]
:db (-> db :db (-> db
(forms/start-form ::form (forms/start-form ::form
{:expense-accounts (by :id {:expense-accounts (by :id
(:expense-accounts i)) (:expense-accounts i))
:invoice i}))})) :invoice i}))}))

View File

@@ -5,7 +5,7 @@
[react :as react])) [react :as react]))
(def good-$ #"^\-?[0-9]+(\.[0-9][0-9])?$") (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 (let [[ parsed-amount set-parsed-amount] (react/useState {:parsed value
:raw (cond :raw (cond
(str/blank? value) (str/blank? value)
@@ -40,6 +40,7 @@
[:div.control.has-icons-left [:div.control.has-icons-left
[:input.input {:type "text" [:input.input {:type "text"
:disabled disabled :disabled disabled
:placeholder placeholder
:class class :class class
:on-change (fn [e] :on-change (fn [e]
(let [raw (.. e -target -value) (let [raw (.. e -target -value)

View File

@@ -119,7 +119,9 @@
[:div.level-item [:div.level-item
[:div.control [:div.control
[:div.tags.has-addons [:div.tags.has-addons
[:span.tag (:name selectedItem)] [:span.tag (if entity->text
(entity->text selectedItem)
(:name selectedItem))]
(when name (when name
[:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}]) [:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}])
(when-not disabled (when-not disabled

View File

@@ -4,16 +4,16 @@
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.layouts :refer [side-bar]] [auto-ap.views.components.layouts :refer [side-bar]]
[auto-ap.views.components.typeahead :refer [typeahead-v3]] [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.spec.alpha :as s]
[clojure.string :as str] [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 types [:dividend :expense :asset :liability :equity :revenue])
(def applicabilities [:global :optional :customized]) (def applicabilities [:global :optional :customized])
(re-frame/reg-sub (re-frame/reg-sub
::request ::request
:<- [::forms/form ::form] :<- [::forms/form ::form]
@@ -51,103 +51,99 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::edited ::edited
[(forms/triggers-saved ::form :upsert-account)] [(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 (re-frame/reg-event-fx
::saving ::saving
(fn [{:keys [db]} _] [with-user (re-frame/inject-cofx ::inject/sub [::request]) ]
(fn [{:keys [user] ::keys [request]} _]
(when @(re-frame/subscribe [::can-submit]) (when @(re-frame/subscribe [::can-submit])
(let [{{:keys [id type name numeric-code account-set]} :data :as data} @(re-frame/subscribe [::forms/form ::form])] (let [_ @(re-frame/subscribe [::forms/form ::form])]
{:db (forms/loading db ::form ) {:graphql
:graphql {:owns-state {:single ::form}
{:token (-> db :user) :token user
:query-obj {:venia/operation {:operation/type :mutation :query-obj {:venia/operation {:operation/type :mutation
:operation/name "UpsertAccount"} :operation/name "UpsertAccount"}
:venia/queries [{:query/data [:upsert-account :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]]]]]]}]} [:id :type :name :account-set :numeric-code :location :applicability [:client-overrides [:name :id [:client [:id :name]]]]]]}]}
:on-success [::edited] :on-success [::edited]
:on-error [::forms/save-error ::form]}})))) :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 [_] (defn form [_]
(let [{error :error account :data } @(re-frame/subscribe [::forms/form ::form]) (let [{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)}
[side-bar {:on-close (dispatch-event [::forms/form-closing ::form])} [side-bar {:on-close (dispatch-event [::forms/form-closing ::form])}
(form-inline {:title (if (:id account) [form-builder/builder {:can-submit [::can-submit]
"Edit account" :change-event [::forms/change ::form]
"Add account")} :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" [form-builder/field
[:input.input {:type "text" "Code"
:field :account-set [:input.input {:type "text"
:disabled (boolean (:id account)) :field :numeric-code
:spec ::entity/account-set}]) :disabled (boolean (:id account))
:spec ::entity/numeric-code}]]
(field "Code" [form-builder/field
[:input.input {:type "text" "Name"
:field :numeric-code [:input.input {:type "text"
:disabled (boolean (:id account)) :field :name
:spec ::entity/numeric-code}]) :spec ::entity/name}]]
(field "Name" [form-builder/vertical-control
[:input.input {:type "text" "Account Type"
:field :name [:div.select
:spec ::entity/name}]) [form-builder/raw-field
[:select {:type "select"
(field-holder "Account Type" :field :type
[:div.select :spec (set types)}
(raw-field (map (fn [l]
[:select {:type "select" [:option {:value (name l)} (str/capitalize (name l))]) types)]]]]
:field :type
:spec (set types)}
(map (fn [l]
[:option {:value (name l)} (str/capitalize (name l))]) types)])])
(field "Location" [form-builder/field
[:input.input.known-field.location {:type "text" "Location"
:field :location [:input.input.known-field.location {:type "text"
:spec ::entity/location}]) :field :location
:spec ::entity/location}]]
[:h2.subtitle "Client"] [form-builder/section {:title "Client"}
(field-holder "Applicability" [:h2.subtitle "Client"]
[:div.select [form-builder/vertical-control
(raw-field "Applicability"
[:select {:type "select" [:div.select
:field :applicability [form-builder/raw-field
:spec (set applicabilities)} [:select {:type "select"
(map (fn [l] :field :applicability
[:option {:value (name l)} (str/capitalize (name l))]) applicabilities)])]) :spec (set applicabilities)}
(field "Customizations" (map (fn [l]
[multi-field {:type "multi-field" [:option {:value (name l)} (str/capitalize (name l))]) applicabilities)]]]]
:field [:client-overrides] [form-builder/field
:template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) "Customizations"
:style {:width "13em"} [multi-field {:type "multi-field"
:entity->text :name :field [:client-overrides]
:type "typeahead-v3" :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:field [:client]}] :style {:width "13em"}
[:input.input {:type "text" :entity->text :name
:style {:width "15em"} :type "typeahead-v3"
:placeholder "Bubblegum" :field [:client]}]
:field [:name]}] [:input.input {:type "text"
]}]) :style {:width "15em"}
(error-notification) :placeholder "Bubblegum"
:field [:name]}]
]}]]]
[form-builder/error-notification]
(submit-button "Save")])])) [form-builder/submit-button "Save"]]]]))

View File

@@ -3,13 +3,16 @@
[auto-ap.entities.transaction-rule :as entity] [auto-ap.entities.transaction-rule :as entity]
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.forms :as forms] [auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.status :as status] [auto-ap.status :as status]
[auto-ap.subs :as subs] [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.button-radio :refer [button-radio]]
[auto-ap.views.components.expense-accounts-field [auto-ap.views.components.expense-accounts-field
:as expense-accounts-field :as expense-accounts-field
:refer [expense-accounts-field]] :refer [expense-accounts-field]]
[auto-ap.views.components.layouts :as layouts] [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 :refer [typeahead-v3]]
[auto-ap.views.components.typeahead.vendor [auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]] :refer [search-backed-typeahead]]
@@ -29,7 +32,6 @@
::default-note ::default-note
:<- [::forms/form ::form] :<- [::forms/form ::form]
(fn [{{:keys [client description amount-lte amount-gte dom-lte dom-gte]} :data}] (fn [{{:keys [client description amount-lte amount-gte dom-lte dom-gte]} :data}]
(str/join " - " (filter (complement str/blank?) (str/join " - " (filter (complement str/blank?)
[(:code client) [(:code client)
description description
@@ -185,18 +187,21 @@
:on-success [::changed [:vendor-preferences]]}]}))) :on-success [::changed [:vendor-preferences]]}]})))
(re-frame/reg-event-db (re-frame/reg-event-db
::changed ::changed
(forms/change-handler ::form (forms/change-handler ::form
(fn [data field value] (fn [data field value]
(cond (and (= [:vendor-preferences] field) (cond (and (= [:vendor-preferences] field)
value value
(expense-accounts-field/can-replace-with-default? (:accounts data))) (expense-accounts-field/can-replace-with-default? (:accounts data)))
[[:accounts] (expense-accounts-field/default-account (:accounts data) [[:accounts] (expense-accounts-field/default-account (:accounts data)
(:default-account value) (:default-account value)
(:total data) (:total data)
[])] [])]
:else
[])))) (= [:client] field)
[[:bank-account] nil]
:else
[]))))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::saving ::saving
@@ -223,9 +228,8 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::updated ::updated
[(forms/triggers-stop ::form)] [(forms/triggers-stop ::form)]
(fn [{:keys [db]} [_ {:keys [rule-saved]} result]] (fn [{:keys [db]} [_ {:keys [rule-saved]} _]]
{:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])}) {:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])})}))
:dispatch (conj rule-saved (:upsert-transaction-rule result))}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::succeeded-test ::succeeded-test
@@ -238,10 +242,6 @@
;; VIEWS ;; VIEWS
(def rule-form (forms/vertical-form {:can-submit [::can-submit]
:change-event [::changed]
:submit-event [::saving ]
:id ::form}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::mounted ::mounted
@@ -263,131 +263,127 @@
(defn form-contents [params] (defn form-contents [params]
[layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])} [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])}
(let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::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])
default-note @(re-frame/subscribe [::default-note]) test-state @(re-frame/subscribe [::status/single ::test])]
test-state @(re-frame/subscribe [::status/single ::test])] [form-builder/builder {:can-submit [::can-submit]
^{:key id} :change-event [::changed]
(form-inline (assoc params :title "New Transaction Rule") :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" [form-builder/field
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) "Bank account"
:auto-focus true [typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)])
:entity->text :name :entity->text :name
:type "typeahead-v3" :type "typeahead-v3"
:field [:client] :field [:bank-account]
:spec ::entity/client}]) :spec ::entity/bank-account}]]
#_[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]}]]
(with-meta [form-builder/field
(field "Bank account" [:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ]
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)]) [:input.input {:type "text"
:entity->text :name :field [:description]
:type "typeahead-v3" :spec ::entity/description}]]
: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)))})
(field "Yodlee Merchant" [:div.field
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/yodlee-merchants]) [:p.help "Amount"]
:entity->text #(str (:name %) " - " (:yodlee-id %)) [left-stack
:type "typeahead-v3" [form-builder/raw-field
:field [:yodlee-merchant]}]) [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}]]]]
(field [:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ] [:div.field
[:input.input {:type "text" [:p.help "Day of Month"]
:field [:description] [:div.control
:spec ::entity/description}]) [: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 [:h2.title.is-4 "Outcomes"]
[: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 [form-builder/field "Assign Vendor"
[:p.help "Day of Month"] [search-backed-typeahead {:search-query (fn [i]
[:div.control [:search_vendor
[:div.columns {:query i}
[:div.column [:name :id]])
(raw-field :type "typeahead-v3"
[:input.input {:type "number" :field [:vendor]}]]
: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"] [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]}]]
(field "Assign Vendor" [form-builder/field
[search-backed-typeahead {:search-query (fn [i] "Approval Status"
[:search_vendor [button-radio
{:query i} {:type "button-radio"
[:name :id]]) :field [:transaction-approval-status]
:type "typeahead-v3" :options [[:unapproved "Unapproved"]
:field [:vendor]}]) [:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]}]]
(with-meta [form-builder/field "Note"
(field nil [:input.input {:type "text"
[expense-accounts-field {:type "expense-accounts" :field [:note]
:descriptor "account asssignment" :placeholder default-note
:percentage-only? true :spec (s/nilable ::entity/note)}]]
: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))})
(field "Approval Status" [:div.is-divider]
[button-radio [form-builder/error-notification]
{:type "button-radio" [:div.columns
:field [:transaction-approval-status] [:div.column
:options [[:unapproved "Unapproved"] [:a.button.is-medium.is-fullwidth.is-outlined {:on-click (dispatch-event [::test-clicked])
[:requires-feedback "Client Review"] :disabled (status/disabled-for test-state)
[:approved "Approved"] :class (status/class-for test-state)}
[:excluded "Excluded from Ledger"]]}]) "Test Rule"]]
[:div.column
(field "Note" [form-builder/submit-button {:class ["is-fullwidth"]}
[:input.input {:type "text" "Save"]]]]])])
: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")]]]))])
(defn form [_] (defn form [_]
(r/create-class (r/create-class

View File

@@ -1,49 +1,14 @@
(ns auto-ap.views.pages.admin.users.form (ns auto-ap.views.pages.admin.users.form
(:require [re-frame.core :as re-frame] (:require
[reagent.core :as reagent] [auto-ap.entities.clients :as entity]
[clojure.string :as str] [auto-ap.forms :as forms]
[auto-ap.subs :as subs] [auto-ap.forms.builder :as form-builder]
[auto-ap.events :as events] [auto-ap.status :as status]
[auto-ap.entities.clients :as entity] [auto-ap.subs :as subs]
[auto-ap.views.components.address :refer [address-field]] [auto-ap.views.components.modal :as modal]
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]] [auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.utils :refer [dispatch-event multi-field with-user]]
[auto-ap.views.utils :refer [login-url dispatch-value-change bind-field horizontal-field dispatch-event with-user]] [re-frame.core :as re-frame]))
[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)) %))))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::saving ::saving
@@ -56,7 +21,7 @@
:operation/name "EditUser"} :operation/name "EditUser"}
:venia/queries [{:query/data [:edit-user :venia/queries [{:query/data [:edit-user
{:edit-user (-> (:data db) {:edit-user (-> (:data db)
(update :clients #(map :id %)) (update :clients #(map (comp :id :client) %))
(select-keys #{:id :name :clients :role}))} (select-keys #{:id :name :clients :role}))}
[:id :name :role [:clients [:id :name]]]]}]} [:id :name :role [:clients [:id :name]]]]}]}
:on-success [::saved]}})) :on-success [::saved]}}))
@@ -64,66 +29,54 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::saved ::saved
(forms/triggers-stop ::form) (forms/triggers-stop ::form)
(fn [{:keys [db]} [_ {:keys [edit-user]}]] (fn [_ _]
{:dispatch [::modal/modal-closed]})) {:dispatch [::modal/modal-closed]}))
(def user-form (forms/vertical-form {:submit-event [::saving]
:change-event [::changed]
:can-submit [::can-submit]
:id ::form}))
(defn form [] (defn form []
(let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field raw-field error-notification submit-button]} user-form] clients @(re-frame/subscribe [::subs/clients])]
(form-inline {} [form-builder/builder {:submit-event [::saving]
[:<> :id ::form}
(field "Name" [form-builder/field
[:input.input {:type "text" "Name"
:field [:name] [:input.input {:type "text"
:spec ::entity/name}]) :field [:name]
[:div.field :spec ::entity/name}]]
[:p.help "Role"] [:div.field
[:div.control [:p.help "Role"]
[:div.select [:div.control
[raw-field [:div.select
[:select {:type "select" [form-builder/raw-field
:field [:role]} [:select {:type "select"
[:option {:value ":none"} "None"] :field [:role]}
[:option {:value ":user"} "User"] [:option {:value ":none"} "None"]
[:option {:value ":manager"} "Manager"] [:option {:value ":user"} "User"]
[:option {:value ":power_user"} "Power User"] [:option {:value ":manager"} "Manager"]
[:option {:value ":admin"} "Admin"]]]]]] [:option {:value ":power_user"} "Power User"]
(when (#{":user" ":manager" ":power_user"} (:role data)) [:option {:value ":admin"} "Admin"]]]]]]
[:div.field (when (#{":user" ":manager" ":power_user"} (:role data))
[:p.help "Clients"] [form-builder/field
[:div.control "Client"
[:div.field.has-addons [multi-field {:type "multi-field"
[:div.control :field [:clients]
[:div.select :template [[typeahead-v3 {:entities clients
[raw-field :entity->text :name
[:select {:type "select" :style {:width "13em"}
:field [:adding-client]} :type "typeahead-v3"
[:option] :field [:client]}]]}]])
(let [used-clients (set (map :id (:clients data)))] [form-builder/hidden-submit-button]]))
(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"]]]
[: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 (re-frame/reg-event-fx
::editing ::editing
(fn [{:keys [db]} [_ d]] (fn [{:keys [db]} [_ d]]
{:db (-> db {:db (-> db
(forms/start-form ::form d)) (forms/start-form ::form (update d :clients #(map (fn [x] {:client x}) %))))
:dispatch [::modal/modal-requested {:title (str "Edit user " (:name d)) :dispatch [::modal/modal-requested {:title (str "Edit user " (:name d))
:body [form] :body [form]
:cancel? false :cancel? false
:confirm {:value "Save" :confirm {:value "Save"
:status-from [::status/single ::form] :status-from [::status/single ::form]
:class "is-primary" :class "is-primary"
:on-click (dispatch-event [::saving]) :on-click (dispatch-event [::saving])
:close-event [::status/completed ::form]}}]})) :close-event [::status/completed ::form]}}]}))

View File

@@ -7,59 +7,51 @@
[auto-ap.views.components.typeahead.vendor [auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]] :refer [search-backed-typeahead]]
[auto-ap.views.utils :refer [dispatch-event]] [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 (re-frame/reg-sub
::can-submit ::can-submit
:<- [::forms/form ::form] :<- [::forms/form ::form]
(fn [{:keys [data]}] (fn [{:keys [data]}]
(println data)
(and (:from data) (and (:from data)
(:to 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 [] (defn form []
(let [_ @(re-frame/subscribe [::forms/form ::form]) [form-builder/builder {:submit-event [::save]
{:keys [form-inline field]} merge-form] :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]}]]
(form-inline {} [form-builder/field "To Vendor"
[:<> [search-backed-typeahead {:search-query (fn [i]
(field "Form Vendor (will be deleted)" [:search_vendor
[search-backed-typeahead {:search-query (fn [i] {:query i}
[:search_vendor [:name :id]])
{:query i} :type "typeahead-v3"
[:name :id]]) :field [:to]}]]
:type "typeahead-v3" [form-builder/hidden-submit-button]])
: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]}])])))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::show ::show
(fn [{:keys [db]} _] (fn [{:keys [db]} _]
{:dispatch [::modal/modal-requested {:title "Merge Vendors" {:dispatch [::modal/modal-requested {:title "Merge Vendors"
:body [form] :body [form]
:confirm {:value "Merge" :confirm {:value "Merge"
:status-from [::status/single ::form] :status-from [::status/single ::form]
:class "is-primary" :class "is-primary"
:on-click (dispatch-event [::save]) :on-click (dispatch-event [::save])
:can-submit [::can-submit] :can-submit [::can-submit]
:close-event [::status/completed ::form]}}] :close-event [::status/completed ::form]}}]
:db (forms/start-form db ::form {})} :db (forms/start-form db ::form {})}))
))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::complete ::complete

View File

@@ -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]}])

View File

@@ -1,19 +1,29 @@
(ns auto-ap.views.pages.invoices.advanced-print-checks (ns auto-ap.views.pages.invoices.advanced-print-checks
(:require [auto-ap.forms :as forms] (:require
[auto-ap.status :as status] [auto-ap.forms :as forms]
[auto-ap.subs :as subs] [auto-ap.forms.builder :as form-builder]
[auto-ap.utils :refer [by]] [auto-ap.status :as status]
[auto-ap.views.components.modal :as modal] [auto-ap.subs :as subs]
[auto-ap.views.pages.invoices.common :refer [invoice-read does-amount-exceed-outstanding?]] [auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.invoices.form :as form] [auto-ap.views.pages.invoices.common
[auto-ap.views.utils :refer [dispatch-event horizontal-field with-user]] :refer [does-amount-exceed-outstanding? invoice-read]]
[re-frame.core :as re-frame])) [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 (re-frame/reg-sub
::can-submit ::can-submit
:<- [::forms/form ::form] :<- [::forms/form ::form]
(fn [{ {:keys [invoices invoice-amounts]} :data}] (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]}] (fn [{:keys [id outstanding-balance]}]
(does-amount-exceed-outstanding? (get-in invoice-amounts [id :amount]) outstanding-balance )) (does-amount-exceed-outstanding? (get-in invoice-amounts [id :amount]) outstanding-balance ))
invoices)) invoices))
@@ -22,63 +32,55 @@
:else :else
true))) 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 [] (defn form []
(let [real-bank-accounts @(re-frame/subscribe [::subs/real-bank-accounts]) (let [real-bank-accounts @(re-frame/subscribe [::subs/real-bank-accounts])
{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) {:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
{:keys [form-inline horizontal-field field raw-field error-notification submit-button]} advanced-print-checks-form]
(form-inline {} [form-builder/builder {:submit-event [::save]
[:<> :can-submit [::can-submit]
[:div.field :id ::form}
[:label.label "Pay using"] [:div.field
[:div.control [:label.label "Pay using"]
[:span.select [:div.control
[raw-field [:span.select
[:select {:type "select" [form-builder/raw-field
:field :bank-account-id} [:select {:type "select"
(for [{:keys [id number name]} real-bank-accounts] :field :bank-account-id}
^{:key id} [:option {:value id} name])]]]]] (for [{:keys [id name]} real-bank-accounts]
^{:key id} [:option {:value id} name])]]]]]
[:table.table.is-fullwidth [:table.table.is-fullwidth
[:thead [:thead
[:tr [:tr
[:th "Vendor"] [:th "Vendor"]
[:th "Invoice ID"] [:th "Invoice ID"]
[:th {:style {"width" "10em"}} "Payment"]]] [:th {:style {"width" "10em"}} "Payment"]]]
[:tbody [:tbody
(doall (doall
(for [{:keys [vendor payment outstanding-balance invoice-number id] :as i} (:invoices data)] (for [{:keys [vendor invoice-number id] :as i} (:invoices data)]
^{:key id} ^{:key id}
[:tr [:tr
[:td (:name vendor)] [:td (:name vendor)]
[:td invoice-number] [:td invoice-number]
[:td [:div.field.has-addons.is-extended [:td
[:p.control [:a.button.is-static "$"]] [form-builder/raw-field
[:p.control [money-field {:type "money"
(raw-field :field [:invoice-amounts id :amount]
[:input.input.has-text-right {:type "number" :step "0.01"}]]]]))]]]))
:field [:invoice-amounts id :amount]
:step "0.01"}])]]]]))]]])))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::show ::show
(fn [{:keys [db]} [_ invoices]] (fn [{:keys [db]} [_ invoices]]
{:dispatch [::modal/modal-requested {:title "Print Checks" {:dispatch [::modal/modal-requested {:title "Print Checks"
:body [form] :body [form]
:confirm {:value "Print checks" :confirm {:value "Print checks"
:status-from [::status/single ::form] :status-from [::status/single ::form]
:class "is-primary" :class "is-primary"
:on-click (dispatch-event [::save]) :on-click (dispatch-event [::save])
:can-submit [::can-submit] :can-submit [::can-submit]
:close-event [::status/completed ::form]}}] :close-event [::status/completed ::form]}}]
:db (-> db :db (-> db
(forms/start-form ::form (forms/start-form ::form
{:bank-account-id (:id (first @(re-frame/subscribe [::subs/real-bank-accounts]))) {:bank-account-id (:id (first @(re-frame/subscribe [::subs/real-bank-accounts])))
:invoices invoices :invoices invoices

View File

@@ -23,7 +23,8 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[vimsical.re-frame.fx.track :as track] [vimsical.re-frame.fx.track :as track]
[vimsical.re-frame.cofx.inject :as inject] [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] (defn data-params->query-params [params]
(when params (when params
@@ -36,11 +37,6 @@
:to-numeric-code (:to-numeric-code params) :to-numeric-code (:to-numeric-code params)
:date-range (:date-range params)})) :date-range (:date-range params)}))
(re-frame/reg-sub
::can-submit
(fn [_]
true))
(re-frame/reg-sub (re-frame/reg-sub
::ledger-list-active? ::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])}})) :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 [] (defn report-form []
(let [{:keys [form-inline raw-field]} balance-sheet-form (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])] [form-builder/builder {:change-event [::change]
(form-inline {} :submit-event [::report-requested]
[:div :id ::form}
[:div.report-controls [:div
[:div.level [:div.report-controls
[:div.level-left [:div.level
[:div.level-item [:div.level-left
[:div.control [:div.level-item
[:p.help "Date"] [:div.control
(raw-field [form-builder/field
[date-picker {:output :cljs-date "Date"
:type "date" [date-picker {:output :cljs-date
:field [:date]}])]] :type "date"
[:div.level-item :field [:date]}]]]]
[:div.control [:div.level-item
[:div.mt-3] [form-builder/field
[switch-field {:id "include-comparison" [:div.mt-5]
:checked (:include-comparison data) [switch-field {:id "include-comparison"
:on-change (fn [e] :field [:include-comparison]
(re-frame/dispatch [::change [:include-comparison] (.-checked (.-target e))])) :label "Include compariison"
:label "Include comparison" :type "checkbox"}]]]
:type "checkbox"}]]] [:div.level-item
[:div.level-item
(when (boolean (:include-comparison data)) (when (boolean (:include-comparison data))
[:div.control [form-builder/field
[:p.help "Comparison Date"] "Comparison Date"
(raw-field [date-picker {:output :cljs-date
[date-picker {:output :cljs-date :type "date"
:type "date" :field [:comparison-date]}]])]]
:field [:comparison-date]}])])]] [:div.level-right
[:div.level-right [:div.buttons
[:div.buttons
(when @(re-frame/subscribe [::subs/is-admin?]) (when @(re-frame/subscribe [::subs/is-admin?])
[:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"])
[:button.button.is-primary "Run"]]]]]]))) [:button.button.is-primary "Run"]]]]]]]))
(defn balance-sheet-report [{:keys [args report-data]}] (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] (map (fn [b]
(assoc b (assoc b
:period (:date args) :period (:date args)

View File

@@ -31,7 +31,8 @@
[react-dom :as react-dom] [react-dom :as react-dom]
[reagent.core :as reagent] [reagent.core :as reagent]
[vimsical.re-frame.cofx.inject :as inject] [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 [_] (fn [_]
true)) 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] (defn report-control-detail [{:keys [active box which]} children]
(when (and @box (when (and @box
(= which @active)) (= which @active))
(println @box)
(react-dom/createPortal (reagent/as-element (react-dom/createPortal (reagent/as-element
[:div.notification.is-light [:div.notification.is-light
[:a.delete {:on-click (fn [] (reset! active nil))}] [: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 [:div.control
[:a.button [:a.button
{:class (when (= selected-preset title) "is-active") {:class (when (= selected-preset title) "is-active")
:on-click (dispatch-event :on-click (fn []
[::change (re-frame/dispatch-sync [::change
[:periods] [:periods]
periods periods
[:selected-preset] title])} [:selected-preset] title])
(re-frame/dispatch-sync [::change
[:show-advanced?]
false]))}
title]])) title]]))
(defn report-controls [_] (defn report-controls []
(let [!box (reagent/atom nil) (let [!box (atom nil)
active (reagent/atom nil)] active (reagent/atom nil)]
(fn [pnl-form] (fn []
(let [{:keys [raw-field]} pnl-form (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
{:keys [periods selected-preset include-deltas column-per-location]} data] {:keys [periods selected-preset include-deltas column-per-location]} data]
[:div.report-controls [form-builder/builder {:can-submit [::can-submit]
[:div.level.mb-2 :change-event [::change]
[:div.level-left :submit-event [::report-requested]
[:div.level-item :id ::form}
[buttons/dropdown {:on-click (fn [] (reset! active :clients))} [:div.report-controls
[:span (str "Companies" [:div.level.mb-2
(when-let [clients (:clients data)] [:div.level-left
(str " (" (str/join ", " (map :name clients)) ")")))]] [:div.level-item
[report-control-detail {:active active :box !box :which :clients} [buttons/dropdown {:on-click (fn [] (reset! active :clients))}
[:div {:style {:width "20em"}} [:span (str "Companies"
[:h4.subtitle "Companies"] (when-let [clients (:clients data)]
[raw-field (str " (" (str/join ", " (map :name clients)) ")")))]]
[multi-field {:type "multi-field" [report-control-detail {:active active :box !box :which :clients}
:field [:clients] [:div {:style {:width "20em"}}
:template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) [:h4.subtitle "Companies"]
:entity->text :name [form-builder/raw-field
:type "typeahead-v3"}]]}]] [multi-field {:type "multi-field"
]]] :field [:clients]
[:div.level-item :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
[buttons/dropdown {:on-click (fn [] (reset! active :range))} :style {:width "18em"}
[:span (str "Range" :entity->text :name
(when selected-preset :type "typeahead-v3"}]]}]]]]]
(str " (" selected-preset ")")))]] [:div.level-item
[report-control-detail {:active active :box !box :which :range} [buttons/dropdown {:on-click (fn [] (reset! active :range))}
[:div [:span (str "Range"
[:h4.subtitle "Range"] (when selected-preset
[:div.field.is-grouped (str " (" selected-preset ")")))]]
[:div.control [report-control-detail {:active active :box !box :which :range}
[:div.field.has-addons [:div
[:div.control [:h4.subtitle "Range"]
(raw-field [:div.field.is-grouped
[:div.control
[:div.field.has-addons
[:div.control
[form-builder/raw-field
[date-picker {:placeholder "End date" [date-picker {:placeholder "End date"
:type "date" :type "date"
:output :cljs-date :output :cljs-date
:field [:thirteen-periods-end]}])] :field [:thirteen-periods-end]}]]]
[period-preset-button {:title "13 periods" [period-preset-button {:title "13 periods"
:periods (let [today (or (some-> (:thirteen-periods-end data)) :periods (let [today (or (some-> (:thirteen-periods-end data))
(local-today))] (local-today))]
(into (into
[{:start (t/plus (t/minus today (t/weeks (* 13 4))) [{: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)))
(t/days 1)) (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.control
[:div.field.has-addons [:div.field.has-addons
[:div.control [:div.control
(raw-field [form-builder/raw-field
[date-picker {:placeholder "End date" [date-picker {:placeholder "End date"
:output :cljs-date :output :cljs-date
:type "date" :type "date"
:field [:twelve-periods-end]}])] :field [:twelve-periods-end]}]]]
[period-preset-button {:title "12 months" [period-preset-button {:title "12 months"
:periods (let [end-date (or (some-> (:twelve-periods-end data)) :periods (let [end-date (or (some-> (:twelve-periods-end data))
(local-today)) (local-today))
this-month (t/local-date (t/year end-date) this-month (t/local-date (t/year end-date)
(t/month end-date) (t/month end-date)
1)] 1)]
(into (into
[{:start (t/minus this-month (t/months 11)) [{:start (t/minus this-month (t/months 11))
:end (t/minus (t/plus this-month (t/months 1)) :end (t/minus (t/plus this-month (t/months 1))
(t/days 1)) (t/days 1))
:title "Total"}] :title "Total"}]
(for [i (range 12)] (for [i (range 12)]
{:start (t/minus this-month (t/months (- 11 i))) {:start (t/minus this-month (t/months (- 11 i)))
:end (t/minus (t/minus this-month (t/months (- 10 i))) :end (t/minus (t/minus this-month (t/months (- 10 i)))
(t/days 1))})))}]]] (t/days 1))})))}]]]
[period-preset-button {:periods (let [last-sunday (loop [current (local-today)] [period-preset-button {:periods (let [last-sunday (loop [current (local-today)]
(if (= 7 (t/day-of-week current)) (if (= 7 (t/day-of-week current))
current current
(recur (t/minus current (t/period :days 1)))))] (recur (t/minus current (t/period :days 1)))))]
(and-last-year {:start (t/minus last-sunday (t/period :days 6)) (and-last-year {:start (t/minus last-sunday (t/period :days 6))
:end last-sunday})) :end last-sunday}))
:title "Last week"}] :title "Last week"}]
[period-preset-button {:periods (and-last-year {:start (loop [current (local-today)] [period-preset-button {:periods (and-last-year {:start (loop [current (local-today)]
(if (= 1 (t/day-of-week current)) (if (= 1 (t/day-of-week current))
current current
(recur (t/minus current (t/period :days 1))))) (recur (t/minus current (t/period :days 1)))))
:end (local-today)}) :end (local-today)})
:title "Week to date"}] :title "Week to date"}]
[period-preset-button {:periods (and-last-year {:start (t/minus (t/local-date (t/year (local-today)) [period-preset-button {:periods (and-last-year {:start (t/minus (t/local-date (t/year (local-today))
(t/month (local-today)) (t/month (local-today))
1) 1)
(t/period :months 1)) (t/period :months 1))
:end (t/minus (t/local-date (t/year (local-today)) :end (t/minus (t/local-date (t/year (local-today))
(t/month (local-today)) (t/month (local-today))
1) 1)
(t/period :days 1))}) (t/period :days 1))})
:title "Last month"}] :title "Last month"}]
[period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today))
(t/month (local-today)) (t/month (local-today))
1) 1)
:end (local-today)}) :end (local-today)})
:title "Month to date"}] :title "Month to date"}]
[period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) 1 1) [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) 1 1)
:end :end
(local-today)}) (local-today)})
:title "Year to date"}] :title "Year to date"}]
[period-preset-button {:periods [{:start (t/local-date (dec (t/year (local-today))) 1 1) [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)}] :end (t/local-date (dec (t/year (local-today))) 12 31)}]
:title "Last calendar year"}] :title "Last calendar year"}]
[period-preset-button {:periods (and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1)) [period-preset-button {:periods (and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1))
(t/period :days 1)) (t/period :days 1))
:end (local-today)}) :end (local-today)})
:title "Full year"}]] :title "Full year"}]]
[:div [:div
[:div.field [:div.field
[:label.checkbox [:label.checkbox
(raw-field [form-builder/raw-field
[:input {:type "checkbox" [:input {:type "checkbox"
:field [:show-advanced?]}]) :field [:show-advanced?]}]]
" Show Advanced"]]] " Show Advanced"]]]
(when (:show-advanced? data) (when (:show-advanced? data)
(doall [form-builder/raw-field
(for [[_ i] (map vector periods (range))] [multi-field {:type "multi-field"
^{:key i} :field [:periods]
[:div.field.is-grouped :template [[date-picker {:type "date"
[:div.control :output :cljs-date
[:p.help "From"] :field [:start]}]
(raw-field [date-picker {:type "date"
[date-picker {:type "date" :output :cljs-date
:output :cljs-date :field [:end]}]]}]])]]]
:field [:periods i :start]}])]
[:div.control [:div.level-item
[:p.help "To"] [:div
(raw-field [switch-field {:id "include-deltas"
[date-picker {:type "date" :checked (boolean include-deltas)
:output :cljs-date :on-change (fn [e]
:field [:periods i :end]}])]])))]]] (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 (when @(re-frame/subscribe [::subs/is-admin?])
[:div [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"])
[switch-field {:id "include-deltas" [:button.button.is-primary "Run"]]
: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"]) [:div.report-control-detail {:ref (fn [el]
[:button.button.is-primary "Run"]] (when (not= @!box el)
(reset! !box el)))}]]]))))
]]
[:div.report-control-detail {:ref (fn [el]
(when-not @!box
(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 [] (defn profit-and-loss-content []
(let [status @(re-frame/subscribe [::status/single ::page]) (let [status @(re-frame/subscribe [::status/single ::page])
{:keys [data report]} @(re-frame/subscribe [::forms/form ::form]) {:keys [data report]} @(re-frame/subscribe [::forms/form ::form])]
{:keys [form-inline]} pnl-form]
[:div [:div
(form-inline {} [:div
[:div [status/status-notification {:statuses [[::status/single ::page]]}]
[status/status-notification {:statuses [[::status/single ::page]]}] [report-controls]]
[report-controls pnl-form]])
[status/big-loader status] [status/big-loader status]
(when (and (not= :loading (:state status)) (when (and (not= :loading (:state status))
report) report)

View File

@@ -19,7 +19,8 @@
[reagent.core :as r] [reagent.core :as r]
[vimsical.re-frame.fx.track :as track] [vimsical.re-frame.fx.track :as track]
[auto-ap.events :as events] [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 (re-frame/reg-sub
::can-submit ::can-submit
@@ -105,45 +106,41 @@
(fn [] (fn []
{::track/dispose {:id ::vendor-change}})) {::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 [_] (defn form-content [_]
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
{:keys [form-inline field]} code-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]}])
(field "Approval Status" [form-builder/field "Vendor"
[button-radio [search-backed-typeahead {:search-query (fn [i]
{:type "button-radio" [:search_vendor
:field [:transaction-approval-status] {:query i}
:options [[:unapproved "Unapproved"] [:name :id]])
[:requires-feedback "Client Review"] :type "typeahead-v3"
[:approved "Approved"] :auto-focus true
[:excluded "Excluded from Ledger"]]}]) :field [:vendor]}]]
(with-meta [form-builder/field
(field nil "Approval Status"
[expense-accounts-field {:type "expense-accounts" [button-radio
:descriptor "account asssignment" {:type "button-radio"
:percentage-only? true :field [:transaction-approval-status]
:client (:client data) :options [[:unapproved "Unapproved"]
:locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])) [:requires-feedback "Client Review"]
:max 100 [:approved "Approved"]
:field [:accounts]}]) [:excluded "Excluded from Ledger"]]}]]
{: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 [_] (defn form [_]
(r/create-class (r/create-class
{:display-name "transaction-bulk-update-form" {:display-name "transaction-bulk-update-form"

View File

@@ -13,13 +13,14 @@
:refer [search-backed-typeahead]] :refer [search-backed-typeahead]]
[auto-ap.views.pages.transactions.common :refer [transaction-read]] [auto-ap.views.pages.transactions.common :refer [transaction-read]]
[auto-ap.views.utils [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] [clojure.string :as str]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[react :as react] [react :as react]
[reagent.core :as r] [reagent.core :as r]
[vimsical.re-frame.fx.track :as track] [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 ;; SUBS
(re-frame/reg-sub (re-frame/reg-sub
@@ -211,10 +212,6 @@
;; VIEWS ;; 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]}] (defn potential-transaction-rule-matches-box [{:keys [potential-transaction-rule-matches]}]
(let [states @(re-frame/subscribe [::status/multi ::matching])] (let [states @(re-frame/subscribe [::status/multi ::matching])]
@@ -340,122 +337,116 @@
[layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form])} [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form])}
(let [{:keys [data] } @(re-frame/subscribe [::forms/form ::form]) (let [{:keys [data] } @(re-frame/subscribe [::forms/form ::form])
locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))]) 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-admin? @(re-frame/subscribe [::subs/is-admin?])
is-power-user? @(re-frame/subscribe [::subs/is-power-user?]) is-power-user? @(re-frame/subscribe [::subs/is-power-user?])
should-disable-for-client? (and (not (or is-admin? is-power-user?)) should-disable-for-client? (and (not (or is-admin? is-power-user?))
(not= :requires-feedback (:original-status data))) (not= :requires-feedback (:original-status data)))
is-already-matched? (:payment data)] is-already-matched? (:payment data)]
(with-meta [form-builder/builder {:can-submit [::can-submit]
(form-inline {:title "Transaction"} :change-event [::changed]
[:<> :submit-event [::saving ]
:id ::form}
[form-builder/section {:title "Transaction"}
[:<>
(when (and @(re-frame/subscribe [::subs/is-admin?]) (when 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? [form-builder/field
(field "Matched Rule" "Matched Rule"
[:input.input {:type "text" [:input.input {:type "text"
:field [:matched-rule :note] :field [:matched-rule :note]
:disabled "disabled"}])) :disabled "disabled"}]])
(field "Amount" [form-builder/field "Amount"
[:input.input {:type "text" [:input.input {:type "text"
:field [:amount] :field [:amount]
:disabled "disabled"}]) :disabled "disabled"}]]
(field "Description" [form-builder/field
[:input.input {:type "text" "Description"
:field [:description-original] [:input.input {:type "text"
:disabled "disabled"}]) :field [:description-original]
:disabled "disabled"}]]
(field "Date" [form-builder/field "Date"
[:input.input {:type "text" [date-picker {:type "date"
:field [:date] :field [:date]
:disabled "disabled"}]) :disabled "disabled"}]]
(when (and (:payment data) (when (and (:payment data)
(or is-admin? is-power-user?)) (or is-admin? is-power-user?))
[:p.notification.is-info.is-light>div.level>div.level-left [:p.notification.is-info.is-light>div.level>div.level-left
[:div.level-item "This transaction is linked to a payment "] [:div.level-item "This transaction is linked to a payment "]
[:div.level-item [:button.button.is-warning {:on-click (dispatch-event [::unlink])} "Unlink"]]]) [:div.level-item [:button.button.is-warning {:on-click (dispatch-event [::unlink])} "Unlink"]]])
[tabs {:default-tab :details} [tabs {:default-tab :details}
(when (when
(and (seq (:potential-transaction-rule-matches data)) (and (seq (:potential-transaction-rule-matches data))
(not (:matched-rule data)) (not (:matched-rule data))
is-admin?) is-admin?)
[tab {:title "Transaction Rule" :key :transaction-rule} [tab {:title "Transaction Rule" :key :transaction-rule}
[potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]]) [potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]])
(when (when
(and (seq (:potential-autopay-invoices-matches data)) (and (seq (:potential-autopay-invoices-matches data))
(not is-already-matched?) (not is-already-matched?)
(or is-admin? is-power-user?)) (or is-admin? is-power-user?))
[tab {:title "Autopay Invoices" :key :autopay-invoices} [tab {:title "Autopay Invoices" :key :autopay-invoices}
[potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]]) [potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]])
(when (when
(and (seq (:potential-unpaid-invoices-matches data)) (and (seq (:potential-unpaid-invoices-matches data))
(not is-already-matched?) (not is-already-matched?)
(or is-admin? is-power-user?)) (or is-admin? is-power-user?))
[tab {:title "Unpaid Invoices" :key :unpaid-invoices} [tab {:title "Unpaid Invoices" :key :unpaid-invoices}
[potential-unpaid-invoices-matches-box {:potential-unpaid-invoices-matches (:potential-unpaid-invoices-matches data)}]]) [potential-unpaid-invoices-matches-box {:potential-unpaid-invoices-matches (:potential-unpaid-invoices-matches data)}]])
(when (when
(and (seq (:potential-payment-matches data)) (and (seq (:potential-payment-matches data))
(not is-already-matched?) (not is-already-matched?)
(or is-admin? is-power-user?)) (or is-admin? is-power-user?))
[tab {:title "Payment" :key :payment} [tab {:title "Payment" :key :payment}
[potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]]) [potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]])
[tab {:title "Details" :key :details} [tab {:title "Details" :key :details}
[:div [:div
(field "Vendor" [form-builder/field
[search-backed-typeahead {:search-query (fn [i] "Vendor"
[:search_vendor [search-backed-typeahead {:search-query (fn [i]
{:query i} [:search_vendor
[:name :id]]) {:query i}
:type "typeahead-v3" [:name :id]])
:auto-focus true :type "typeahead-v3"
:field [:vendor] :auto-focus true
:disabled (or (boolean (:payment data)) :field [:vendor]
should-disable-for-client?)}]) :disabled (or (boolean (:payment data))
(with-meta should-disable-for-client?)}]]
(field nil [form-builder/raw-field [expense-accounts-field
[expense-accounts-field {:type "expense-accounts"
{:type "expense-accounts" :field [:accounts]
:field [:accounts] :max (Math/abs (js/parseFloat (:amount data)))
:max (Math/abs (js/parseFloat (:amount data))) :descriptor "credit account"
:descriptor "credit account" :client (:client data)
:client (:client data) :disabled (or (boolean (:payment data))
:disabled (or (boolean (:payment data)) should-disable-for-client?)
should-disable-for-client?) :locations locations}]]
:locations locations}]) [form-builder/field
{:key (str (:id (:vendor data)))}) "Approval Status"
(field "Approval Status" [button-radio
[button-radio {:type "button-radio"
{:type "button-radio" :field [:approval-status]
:field [:approval-status] :options [[:unapproved "Unapproved"]
:options [[:unapproved "Unapproved"] [:requires-feedback "Client Review"]
[:requires-feedback "Client Review"] [:approved "Approved"]
[:approved "Approved"] [:excluded "Excluded from Ledger"]]
[:excluded "Excluded from Ledger"]] :disabled should-disable-for-client?}]]
:disabled should-disable-for-client?}])
(field "Forecasted-transaction" [form-builder/field
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) "Forecasted-transaction"
:entity->text :identifier [typeahead-v3 {:entities @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))])
:type "typeahead-v3" :entity->text :identifier
:field [:forecast-match]}]) :type "typeahead-v3"
(error-notification) :field [:forecast-match]}]]
(when-not should-disable-for-client? [form-builder/error-notification]
(submit-button "Save"))]]]]) (when-not should-disable-for-client?
{:key (:id data)}))]) [form-builder/submit-button "Save"])]]]]]])])
(defn form [_] (defn form [_]
(r/create-class (r/create-class

View File

@@ -5,7 +5,8 @@
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.views.utils :refer [dispatch-event with-user]] [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 (re-frame/reg-sub
::can-submit ::can-submit
@@ -13,22 +14,16 @@
(fn [{ {:keys [data]} :data}] (fn [{ {:keys [data]} :data}]
(not-empty data))) (not-empty data)))
(def import-form (forms/vertical-form {:submit-event [::save] (defn form []
:change-event [::forms/change ::form] [form-builder/builder {:submit-event [::save]
:can-submit [::can-submit] :can-submit [::can-submit]
:id ::form})) :id ::form}
(defn form [{import-completed-event :import-completed}] [form-builder/field {:required? true}
(let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) "Yodlee manual import table"
{:keys [form-inline horizontal-field field raw-field error-notification submit-button]} import-form] [:div.control
[:textarea.textarea {:field [:data]}]]]
(form-inline {} [form-builder/hidden-submit-button]])
[:div.field
[:label.label
"Yodlee manual import table"]
[:div.control
[raw-field
[:textarea.textarea {:field [:data]}]]]])))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::opening ::opening
@@ -46,11 +41,9 @@
{:client-id (:id @(re-frame/subscribe [::subs/client])) {:client-id (:id @(re-frame/subscribe [::subs/client]))
:data ""}))})) :data ""}))}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::import-completed ::import-completed
(fn [{:keys [db]} [_ {:keys [imported errors] :as result}]] (fn [_ _]
{:dispatch [::modal/modal-closed ]})) {:dispatch [::modal/modal-closed ]}))
(re-frame/reg-event-fx (re-frame/reg-event-fx

View File

@@ -211,7 +211,7 @@
e)] e)]
(if (map? this-value) (if (map? this-value)
(update this-value :key (fnil identity (random-uuid))) (update this-value :key (fnil identity (random-uuid)))
this-value)) )))) this-value))))))
(on-change (mapv (on-change (mapv
(fn [v] (fn [v]
(dissoc v :new? :key)) (dissoc v :new? :key))
@@ -251,7 +251,6 @@
options (if allow-nil? options (if allow-nil?
(with-keys (conj rest [:option {:value nil}])) (with-keys (conj rest [:option {:value nil}]))
(with-keys rest))] (with-keys rest))]
(println "KEYS" keys (dissoc keys :allow-nil?))
(into [dom (dissoc keys :allow-nil?)] options))) (into [dom (dissoc keys :allow-nil?)] options)))
@@ -281,21 +280,6 @@
keys (dissoc keys :field :subscription :event :spec)] keys (dissoc keys :field :subscription :event :spec)]
(into [dom keys] (with-keys rest)))) (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] (defmethod do-bind "multi-field" [dom {:keys [field event subscription class spec] :as keys} & rest]
(let [field (if (keyword? field) [field] field) (let [field (if (keyword? field) [field] field)
event (if (keyword? event) [event] event) event (if (keyword? event) [event] event)
@@ -310,20 +294,6 @@
(into [dom keys] (with-keys rest)))) (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] (defmethod do-bind "typeahead-v3" [dom {:keys [field event subscription class spec] :as keys} & rest]
(let [field (if (keyword? field) [field] field) (let [field (if (keyword? field) [field] field)