diff --git a/min.cljs.edn b/min.cljs.edn index 8935b4f4..43ef91dc 100644 --- a/min.cljs.edn +++ b/min.cljs.edn @@ -1,5 +1,6 @@ {:main auto-ap.core :output-to "resources/public/js/compiled/app.js" + :parallel-build true :optimizations :advanced :closure-defines {goog.DEBUG false} diff --git a/src/cljs/auto_ap/views/components/expense_accounts_field.cljs b/src/cljs/auto_ap/views/components/expense_accounts_field.cljs index a1a96e99..9b54eec0 100644 --- a/src/cljs/auto_ap/views/components/expense_accounts_field.cljs +++ b/src/cljs/auto_ap/views/components/expense_accounts_field.cljs @@ -1,7 +1,7 @@ (ns auto-ap.views.components.expense-accounts-field (:require [auto-ap.forms :as forms] [auto-ap.subs :as subs] - [auto-ap.views.components.typeahead :refer [typeahead]] + [auto-ap.views.components.typeahead :refer [typeahead typeahead-entity]] [auto-ap.views.utils :refer [bind-field dispatch-event ->$]] [goog.string :as gstring] [re-frame.core :as re-frame] @@ -13,7 +13,9 @@ (not (get-in accounts [0 :account :id])))) (defn default-account [accounts default-account amount] - [{:id (str "new-" (random-uuid)) + [{:id (doto (get-in accounts [0 :id] + (str "new-" (random-uuid))) + println) :amount (Math/abs amount) :amount-percentage 100 :amount-mode "%" @@ -24,7 +26,6 @@ (defn from-graphql [accounts total locations] - (println accounts total locations) (if (seq accounts) (vec (map (fn [a] @@ -133,20 +134,20 @@ [:div.column.is-narrow (when-not disabled [:a.delete {:on-click (dispatch-event [::remove-expense-account event expense-accounts id])}])]] + (println "TYPAHEAD" expense-accounts) [:div.field [:div.columns [:div.column [:p.help "Account"] [:div.control.is-fullwidth [bind-field - [typeahead {:matches (map (fn [x] [(:id x) (str (:numeric-code x) " - " (:name x))]) chooseable-expense-accounts) - :disabled disabled - :type "typeahead" - :field [index :account :id] - #_#_:text-field [index :account :name] - - :event [::expense-account-changed event expense-accounts max-value] - :subscription expense-accounts}]]]] + [typeahead-entity {:matches chooseable-expense-accounts + :match->text (fn [x ] (str (:numeric-code x) " - " (:name x))) + :disabled disabled + :type "typeahead-entity" + :field [index :account] + :event [::expense-account-changed event expense-accounts max-value] + :subscription expense-accounts}]]]] [:div.column.is-narrow [:p.help "Location"] [:div.control diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index b35c8e61..4801f7f4 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -110,7 +110,7 @@ (defn appearing-side-bar [{:keys [visible?]} & children ] [appearing {:visible? visible? :enter-class "slide-in-right" :exit-class "slide-out-right" :timeout 500} [:aside {:class "column is-4 aside menu" :style {:height "calc(100vh - 46px)" :overflow "auto"}} - [:div.sub-main {} children ]]]) + (into [:div.sub-main {} ] children )]]) (defn side-bar-layout [{:keys [side-bar main ap bottom right-side-bar]}] (let [ap @(re-frame/subscribe [::subs/active-page]) diff --git a/src/cljs/auto_ap/views/components/typeahead.cljs b/src/cljs/auto_ap/views/components/typeahead.cljs index 245ebc6c..d5bb29c4 100644 --- a/src/cljs/auto_ap/views/components/typeahead.cljs +++ b/src/cljs/auto_ap/views/components/typeahead.cljs @@ -108,3 +108,113 @@ :else nil)]))}))) + +(defn get-valid-entity-matches [matches not-found-description not-found-value text match->text] + (let [valid-matches (take 5 (for [[match i] (map vector matches (range)) + :let [t (match->text match)] + :when (str/includes? (or (some-> t .toLowerCase) "") (or (some-> text .toLowerCase) ""))] + match)) + valid-matches (if (and not-found-description text) + (concat valid-matches [[:not-found (not-found-description text) (not-found-value text)]]) + valid-matches)] + valid-matches)) +(defn typeahead-entity [{:keys [matches on-change field value class not-found-description + disabled not-found-value auto-focus]}] + (let [text (r/atom (or (second (first (filter #(= (first %) value) matches))) "")) + highlighted (r/atom nil) + ] + (r/create-class + {:reagent-render (fn [{:keys [matches on-change disabled match->text field value class not-found-description]}] + (println "RENDERING ZZ" value) + (let [select (fn [entity] + (when on-change + (if (= :not-found entity) + (on-change nil) + (on-change entity)))) + text-atom text + text (or (when value (match->text value)) @text) + valid-matches (get-valid-entity-matches matches not-found-description not-found-value text match->text)] + (println "TEXT" value (match->text value) text) + [:div.typeahead + (if disabled + + ^{:key (str "typeahead" text) } [:input.input {:disabled "disabled" :value text} ] + + (if value + ^{:key "typeahead"} [:div.input {:class class + :tab-index "0" + :on-key-up (fn [e] + (if (= 8 (.-keyCode e)) + (do + (select nil) + (reset! text-atom nil) + true) + false))} + [:div.control + [:div.tags.has-addons + [:span.tag text] + [:a.tag.is-delete {:on-click (fn [] (select nil))}]]]] + ^{:key "typeahead"} [:input.input {:type "text" + :class class + + :value text + :auto-focus auto-focus + :on-blur (fn [e] + (cond value + nil + + (#{"" nil} text) + nil + + @highlighted + (do (select @highlighted) + + true) + + :else + (do (select nil) + (reset! text-atom nil) + true))) + :on-key-down (fn [e] + (condp = (.-keyCode e) + ; up + 38 (do + (when-let [new-match (->> valid-matches + (take-while #(not= % @highlighted)) + (last))] + (reset! highlighted new-match)) + true) + ;; dwon + 40 (do + (when-let [new-match (->> valid-matches + (drop-while #(not= % @highlighted)) + (drop 1) + (first))] + (reset! highlighted new-match)) + true) + 13 (do (.preventDefault e) + (when @highlighted + + (select @highlighted) + false)) + true)) + :on-change (fn [e] + (let [new-matches (get-valid-entity-matches matches not-found-description not-found-value (.. e -target -value) match->text)] + (reset! highlighted (first new-matches))) + (reset! text-atom (.. e -target -value)))}])) + + (cond + (and (seq text) + (not value) + (seq valid-matches)) + (let [h @highlighted] + [:div.typeahead-menu + [:ul + (for [entity valid-matches] + (let [t (match->text entity)] + ^{:key entity} [:li.typeahead-suggestion {:class (if (= entity h) + "typeahead-highlighted") + :on-mouse-down #(do (select entity))} t]))]]) + + :else + nil)]))}))) diff --git a/src/cljs/auto_ap/views/pages/transactions/form.cljs b/src/cljs/auto_ap/views/pages/transactions/form.cljs index dd3a7181..ee603666 100644 --- a/src/cljs/auto_ap/views/pages/transactions/form.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/form.cljs @@ -65,6 +65,7 @@ (re-frame/reg-event-fx ::saving (fn [{:keys [db]} [_ edit-completed]] + (println "saving..." edit-completed) (when @(re-frame/subscribe [::can-submit]) (let [{{:keys [id vendor-id account-id location]} :data :as data} @(re-frame/subscribe [::forms/form ::edit-transaction])] {:db (forms/loading db ::edit-transaction ) @@ -98,6 +99,7 @@ ::change-vendor [(forms/in-form ::edit-transaction)] (fn [{{:keys [data]} :db} [_ field value]] + (println "HIIII") (if (and value (expense-accounts-field/can-replace-with-default? (:accounts data))) {:dispatch [::forms/change ::edit-transaction field value @@ -115,50 +117,72 @@ ;; VIEWS + +(defn vertical-form [{:keys [can-submit id change-event ]}] + {:form (fn [{:keys [title] :as params} & children] + (let [{:keys [data active? error]} @(re-frame/subscribe [::forms/form id])] + + (into ^{:key id} [:form { :on-submit (fn [e] + (when (.-stopPropagation e) + (.stopPropagation e) + (.preventDefault e)) + (re-frame/dispatch-sync [::saving (:edit-completed params)]))} + [:h1.title.is-2 title] + + ] + children))) + :field (fn [label control] + (let [{:keys [data]} @(re-frame/subscribe [::forms/form id])] + [:div.field + (when label [:p.help label]) + [:div.control [bind-field (assoc-in control [1 :subscription] data)]]])) + + :error-notification (fn [] + (when-let [error (:error @(re-frame/subscribe [::forms/form id]))] + ^{:key error} + [:div.notification.is-warning.animated.fadeInUp error])) + :submit-button (fn [child] + (let [error (:error @(re-frame/subscribe [::forms/form id]))] + [:button.button.is-medium.is-primary.is-fullwidth {:disabled (if @(re-frame/subscribe [::can-submit]) + "" + "disabled") + :class (str @(re-frame/subscribe [::forms/loading-class id]) + (when error " animated shake"))} child]))}) + +(re-frame/reg-sub + ::error + :<- [::forms/form ::edit-transaction] + (fn [{:keys [error]}] + error)) + + +(def my-form (vertical-form {:can-submit [::can-submit] + :change-event [::forms/change ::edit-transaction] + :id ::edit-transaction})) + + (defn form [{:keys [edit-completed]}] [forms/side-bar-form {:form ::edit-transaction } - (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::edit-transaction]) - locations @(re-frame/subscribe [::subs/locations-for-client (:client-id data)]) - change-event [::forms/change ::edit-transaction]] - ^{:key id} - [:form { :on-submit (fn [e] - (when (.-stopPropagation e) - (.stopPropagation e) - (.preventDefault e)) - (re-frame/dispatch-sync [::saving edit-completed]))} - [:h1.title.is-2 "Edit Transaction"] - - [:div.field - [:p.help "Merchant"] - [:div.control - [bind-field - [:input.input {:type "text" - :field [:yodlee-merchant :name] - :disabled "disabled" - :subscription data}]]]] - - [:div.field - [:p.help "Amount"] - [:div.control - [bind-field - [:input.input {:type "text" - :field [:amount] - :disabled "disabled" - :subscription data}]]]] - - [:div.field - [:p.help "Description"] - [:div.control - [bind-field - [:input.input {:type "text" - :field [:description-original] - :disabled "disabled" - :subscription data}]]]] - - + (let [locations #{"BR" "DT"} #_@(re-frame/subscribe [::subs/locations-for-client (:client-id data)]) + change-event [::forms/change ::edit-transaction] + {:keys [data] } @(re-frame/subscribe [::forms/form ::edit-transaction]) + {:keys [form field error-notification submit-button ]} my-form] + (println "DATA" data) + [form {:title "Hello" :edit-completed edit-completed} + [field "Merchant" + [:input.input {:type "text" + :field [:yodlee-merchant :name] + :disabled "disabled"}]] + [field "Amount" + [:input.input {:type "text" + :field [:amount] + :disabled "disabled"}]] + [field "Description" + [:input.input {:type "text" + :field [:description-original] + :disabled "disabled"}]] (if (and (seq (:potential-payment-matches data)) (not (:payment data))) - [:div.box [:div.columns [:div.column @@ -176,51 +200,22 @@ [:a.button.is-primary.is-small {:on-click (dispatch-event [::matching edit-completed id])} "Match"]]]))] ] - [:div - [:div.field - [:p.help "Vendor"] - [:div.control - [bind-field - [typeahead {:matches (map (fn [x] [(:id x) (:name x)]) @(re-frame/subscribe [::subs/vendors])) - :type "typeahead" - :auto-focus true - :field [:vendor-id] - :disabled (boolean (:payment data)) - :event [::change-vendor] - :subscription data}]]]] - - - - [:div.field - [bind-field - [expense-accounts-field - {:type "expense-accounts" - :field [:accounts] - :max (Math/abs (js/parseFloat (:amount data))) - :descriptor "credit account" - :disabled (boolean (:payment data)) - :locations locations - :event change-event - :subscription data}]]] - - [:div.field - [:p.help] - [:div.control - [:label.checkbox - [bind-field - [:input.checkbox {:type "checkbox" - :field [:exclude-from-ledger] - :subscription data - :event change-event} - ]] - " Exclude from ledger?" - ]]] - - (when error - ^{:key error} [:div.notification.is-warning.animated.fadeInUp - error]) - [:button.button.is-medium.is-primary.is-fullwidth {:disabled (if @(re-frame/subscribe [::can-submit]) - "" - "disabled") - :class (str @(re-frame/subscribe [::forms/loading-class ::edit-transaction]) - (when error " animated shake"))} "Save"]])])]) + [:div + [field "Vendor" + [typeahead {:matches (map (fn [x] [(:id x) (:name x)]) @(re-frame/subscribe [::subs/vendors])) + :type "typeahead" + :auto-focus true + :field [:vendor-id] + :disabled (boolean (:payment data)) + :event [::change-vendor]}]] + [field nil + [expense-accounts-field + {:type "expense-accounts" + :field [:accounts] + :max (Math/abs (js/parseFloat (:amount data))) + :descriptor "credit account" + :disabled (boolean (:payment data)) + :locations locations + :event change-event}]] + [error-notification] + [submit-button "Save"]])])]) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index 132fc9ca..b878af31 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -142,6 +142,22 @@ keys (dissoc keys :field :subscription :event :spec)] (into [dom keys] (with-keys rest)))) + +(defmethod do-bind "typeahead-entity" [dom {:keys [field event text-event subscription class spec match->text] :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 "date" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event)