diff --git a/resources/public/css/main.css b/resources/public/css/main.css index 65d123c6..d6d9a2c2 100644 --- a/resources/public/css/main.css +++ b/resources/public/css/main.css @@ -220,7 +220,7 @@ nav.navbar .navbar-item.is-active { margin: 0 -50px; padding-left: 50px; } -.aside .main .icon { +.aside .main .menu-item .icon { font-size: 19px; padding-right: 30px; color: #A0A0A0; diff --git a/src/cljs/auto_ap/forms/builder.cljs b/src/cljs/auto_ap/forms/builder.cljs index 6fac1bcc..66bcd1ec 100644 --- a/src/cljs/auto_ap/forms/builder.cljs +++ b/src/cljs/auto_ap/forms/builder.cljs @@ -201,7 +201,7 @@ " is-danger" value - "is-success" + " is-success" :else "")))))))))))))) diff --git a/src/cljs/auto_ap/views/components/date_range_filter.cljs b/src/cljs/auto_ap/views/components/date_range_filter.cljs index 060ff224..35890012 100644 --- a/src/cljs/auto_ap/views/components/date_range_filter.cljs +++ b/src/cljs/auto_ap/views/components/date_range_filter.cljs @@ -1,55 +1,51 @@ (ns auto-ap.views.components.date-range-filter (:require - [auto-ap.views.utils :refer [bind-field date-picker date->str local-now standard]] + [auto-ap.views.utils :refer [date-picker date->str local-now standard]] [cljs-time.core :as t] - [re-frame.core :as re-frame])) + [re-frame.core :as re-frame] + [auto-ap.forms.builder :as form-builder])) -(defn dispatch-change [on-change-event start end] - (fn [_] - (re-frame/dispatch (into on-change-event [[:start] start]) ) - (re-frame/dispatch (into on-change-event [[:end] end])))) + +(defn set-value [on-change-event v] + (re-frame/dispatch (conj on-change-event v))) (defn date-range-filter [{:keys [value on-change-event]}] - [:div - [:div.field.has-addons - [:p.control [:a.button.is-small {:on-click - (dispatch-change on-change-event - (date->str (t/minus (local-now) (t/period :days 7)) standard) - (date->str (local-now) standard))} - "Week" ]] - [:p.control [:a.button.is-small {:on-click - (dispatch-change on-change-event - (date->str (t/minus (local-now) (t/period :months 1)) standard) - (date->str (local-now) standard))} - "Month" ]] - [:p.control [:a.button.is-small {:on-click - (dispatch-change on-change-event - (date->str (t/minus (local-now) (t/period :years 1)) standard) - (date->str (local-now) standard))} - "Year"]] - [:p.control [:a.button.is-small {:on-click - (dispatch-change on-change-event - nil - nil)} - "All"]]] - [:div.field.has-addons - [:div.control - [bind-field - [date-picker - {:event on-change-event - :type "date" - :placeholder "Start" - :class "is-small" - :field [:start] - :subscription value - :output :text}]]] - [:div.control - [bind-field - [date-picker - {:event on-change-event - :type "date" - :class "is-small" - :placeholder "End" - :field [:end] - :subscription value - :output :text}]]]]]) + [form-builder/virtual-builder {:value (or value {}) + :on-change (fn [v] + (set-value on-change-event v))} + + [:div + [:div.field.has-addons + [:p.control [:a.button.is-small {:on-click + #(set-value on-change-event + {:start (date->str (t/minus (local-now) (t/period :days 7)) standard) + :end (date->str (local-now) standard)})} + "Week" ]] + [:p.control [:a.button.is-small {:on-click + #(set-value on-change-event + {:start (date->str (t/minus (local-now) (t/period :months 1)) standard) + :end (date->str (local-now) standard)})} + "Month" ]] + [:p.control [:a.button.is-small {:on-click + + #(set-value on-change-event + {:start (date->str (t/minus (local-now) (t/period :years 1)) standard) + :end (date->str (local-now) standard)})} + "Year"]] + [:p.control [:a.button.is-small {:on-click + #(set-value on-change-event nil)} + "All"]]] + [:div.field.has-addons + [:div.control + + [form-builder/raw-field-v2 {:field :start} + [date-picker + {:placeholder "Start" + :class "is-small" + :output :text}]]] + [:div.control + [form-builder/raw-field-v2 {:field :end} + [date-picker + {:class "is-small" + :placeholder "End" + :output :text}]]]]]]) diff --git a/src/cljs/auto_ap/views/components/number_filter.cljs b/src/cljs/auto_ap/views/components/number_filter.cljs index a4dc993c..e90ecfb0 100644 --- a/src/cljs/auto_ap/views/components/number_filter.cljs +++ b/src/cljs/auto_ap/views/components/number_filter.cljs @@ -1,25 +1,21 @@ (ns auto-ap.views.components.number-filter (:require - [auto-ap.views.utils :refer [bind-field]] - [re-frame.core :as re-frame])) + [re-frame.core :as re-frame] + [auto-ap.forms.builder :as form-builder] + [auto-ap.views.components :as com])) (defn number-filter [{:keys [value on-change-event]}] - [:div.field - [:div.control - [:div.columns - [:div.column - [bind-field - [:input.input {:type "number" - :placeholder ">=" - :field [:amount-gte] - :step "0.01" - :event on-change-event - :subscription value}]]] - [:div.column - [bind-field - [:input.input {:type "number" - :placeholder "<=" - :field [:amount-lte] - :event on-change-event - :step "0.01" - :subscription value}]]]]]]) + [form-builder/virtual-builder {:value (or value {}) + :on-change (fn [v] + (re-frame/dispatch (conj on-change-event v)))} + + [:div.columns + [:div.column + [:div.control + [form-builder/raw-field-v2 {:field :amount-gte} + [com/money-input {:placeholder ">="}]]]] + [:div.column + [:div.control + [form-builder/raw-field-v2 {:field :amount-lte} + [com/money-input {:placeholder "<="}]]]]]] + ) diff --git a/src/cljs/auto_ap/views/components/vendor_dialog.cljs b/src/cljs/auto_ap/views/components/vendor_dialog.cljs index c79eff99..d6e7fbe3 100644 --- a/src/cljs/auto_ap/views/components/vendor_dialog.cljs +++ b/src/cljs/auto_ap/views/components/vendor_dialog.cljs @@ -1,29 +1,25 @@ (ns auto-ap.views.components.vendor-dialog (:require - [auto-ap.entities.contact :as contact] - [auto-ap.entities.vendors :as entity] [auto-ap.forms :as forms] [auto-ap.forms.builder :as form-builder] + [auto-ap.schema :as schema] [auto-ap.status :as status] - [auto-ap.views.components.level :refer [left-stack]] [auto-ap.subs :as subs] [auto-ap.views.components :as com] [auto-ap.views.components.address :refer [address2-field]] + [auto-ap.views.components.level :refer [left-stack]] [auto-ap.views.components.modal :as modal] + [auto-ap.views.components.multi :refer [multi-field-v2]] [auto-ap.views.components.number :refer [number-input]] [auto-ap.views.components.typeahead :refer [typeahead-v3]] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] [auto-ap.views.pages.admin.vendors.common :as common] - [auto-ap.views.components.multi :refer [multi-field-v2]] [auto-ap.views.utils - :refer [dispatch-event multi-field str->int with-is-admin? with-user]] - [clojure.spec.alpha :as s] - [re-frame.core :as re-frame] - [reagent.core :as r] + :refer [dispatch-event str->int with-is-admin? with-user]] [malli.core :as m] - [auto-ap.schema :as schema] - [malli.error :as me])) + [re-frame.core :as re-frame] + [reagent.core :as r])) ;; Remaining cleanup todos: ;; test minification diff --git a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs index 12df4343..fd81848c 100644 --- a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs @@ -1,18 +1,17 @@ (ns auto-ap.views.pages.admin.accounts.form - (:require [auto-ap.entities.account :as entity] - [auto-ap.forms :as forms] - [auto-ap.subs :as subs] - [auto-ap.views.components.layouts :refer [side-bar]] - [auto-ap.views.components.typeahead :refer [typeahead-v3]] - [auto-ap.views.utils :refer [dispatch-event multi-field with-user]] - [clojure.spec.alpha :as s] - [clojure.string :as str] - [re-frame.core :as re-frame] - [auto-ap.forms.builder :as form-builder] - [vimsical.re-frame.cofx.inject :as inject] - [auto-ap.views.components :as com] - [malli.core :as m] - [auto-ap.schema :as schema])) + (:require + [auto-ap.forms :as forms] + [auto-ap.forms.builder :as form-builder] + [auto-ap.schema :as schema] + [auto-ap.subs :as subs] + [auto-ap.views.components :as com] + [auto-ap.views.components.layouts :refer [side-bar]] + [auto-ap.views.components.typeahead :refer [typeahead-v3]] + [auto-ap.views.utils :refer [dispatch-event with-user]] + [clojure.string :as str] + [malli.core :as m] + [re-frame.core :as re-frame] + [vimsical.re-frame.cofx.inject :as inject])) (def types [:dividend :expense :asset :liability :equity :revenue]) (def applicabilities [:global :optional :customized]) diff --git a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs index 3a7ef1c3..aa0599e4 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -15,13 +15,10 @@ :refer [search-backed-typeahead]] [auto-ap.views.utils :refer [date-picker - dispatch-event - horizontal-field - multi-field]] + dispatch-event]] [bidi.bidi :as bidi] [cljs-time.coerce :as coerce] [cljs-time.core :as t] - [clojure.string :as str] [re-frame.core :as re-frame] [reagent.core :as r] [react-signature-canvas] diff --git a/src/cljs/auto_ap/views/pages/admin/excel_import.cljs b/src/cljs/auto_ap/views/pages/admin/excel_import.cljs index 7f80638f..4606dfd3 100644 --- a/src/cljs/auto_ap/views/pages/admin/excel_import.cljs +++ b/src/cljs/auto_ap/views/pages/admin/excel_import.cljs @@ -15,8 +15,7 @@ ::save [ with-user (forms/in-form ::form)] (fn [{:keys [db user]}] - { - :http {:token user + {:http {:token user :method :post :body (pr-str {:excel-rows (:excel-rows (:data db))}) :headers {"Content-Type" "application/edn"} diff --git a/src/cljs/auto_ap/views/pages/ledger/external_import.cljs b/src/cljs/auto_ap/views/pages/ledger/external_import.cljs index be752b57..8a5d150f 100644 --- a/src/cljs/auto_ap/views/pages/ledger/external_import.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/external_import.cljs @@ -2,37 +2,28 @@ (:require [auto-ap.events :as events] [auto-ap.forms :as forms] + [auto-ap.forms.builder :as form-builder] [auto-ap.status :as status] - [auto-ap.subs :as subs] + [auto-ap.views.components :as com] [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] [auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]] - [auto-ap.views.utils :refer [bind-field dispatch-event]] + [auto-ap.views.utils :refer [dispatch-event]] [clojure.string :as str] [re-frame.core :as re-frame] [reagent.core :as r])) -(re-frame/reg-sub - ::loading - (fn [db] - (-> db ::loading))) - -(re-frame/reg-sub - ::can-submit - (fn [db] - true)) - -(defn line->id [{:keys [source id client-code date vendor-name] :as line}] +(defn line->id [{:keys [source id client-code]}] (str client-code "-" source "-" id)) (re-frame/reg-sub ::request :<- [::forms/form ::form] - (fn [{{lines :line-items :as d} :data :as g}] + (fn [{{lines :line-items} :data}] (into [] - (for [[external-id lines] (group-by line->id lines) - :let [{:keys [source id client-code date vendor-name note cleared-against] :as line} (first lines)]] + (for [[_ lines] (group-by line->id lines) + :let [{:keys [source client-code date vendor-name note cleared-against] :as line} (first lines)]] {:source source :external-id (line->id line) :client-code client-code @@ -139,6 +130,8 @@ [:form.form (if value [:div + [:a.button {:on-click #(on-change nil)} + "reset"] [:table.table {:style {:width "100%"}} [:thead [:tr @@ -190,68 +183,72 @@ (def balance-sheet-content (with-meta (fn [] - (let [current-client @(re-frame/subscribe [::subs/client]) - user @(re-frame/subscribe [::subs/user]) - status @(re-frame/subscribe [::status/single ::import]) - {:keys [data result active? error id]} @(re-frame/subscribe [::forms/form ::form]) ] - [:div - [:div.level - [:div.level-left - [:h1.title "Eternal Import"]] - - [:div.level-right - [:button.button.is-primary.is-pulled-right.is-large {:disabled (or (not data) - (= :loading (:state status ))) - :on-click (dispatch-event [::importing])} "Import"]]] - [status/status-notification {:statuses [[::status/single ::import]]} ] - (when result - [:div.notification - "Imported with " - (count (:errors result)) " errors, " - (count (:ignored result)) " ignored, " - (count (:success result)) " successful."]) - (if (= :loading (:state status )) - [status/big-loader status] - [:div - [:div.is-clearfix - [:div.is-pulled-right - [:label.checkbox - [bind-field - [:input {:type "checkbox" - :event [::forms/change ::form] - :subscription data - :field [:only-show-errors?]}]] - "Only show errors"]]] - [:div - [bind-field - [textarea->table {:type "textarea->table" - :field [:line-items] - :headings [["Id" :id] - ["Client" :client-code] - ["Source" :source] - ["Vendor" :vendor-name] - ["Date" :date] - ["Account" :account-identifier] - ["Location" :location] - ["Debit" :debit] - ["Credit" :credit] - ["Note" :note] - ["Cleared against" :cleared-against]] - :read-only-headings - [["status" :status]] + (let [status @(re-frame/subscribe [::status/single ::import]) + {:keys [data result]} @(re-frame/subscribe [::forms/form ::form]) ] + [form-builder/builder {:id ::form + :submit-event [::importing]} + [:div + [:div.level + [:div.level-left + [:h1.title "Eternal Import"]] + + [:div.level-right + [form-builder/submit-button "Import"]]] + [status/status-notification {:statuses [[::status/single ::import]]} ] + (when result + [:div.notification + "Imported with " + (count (:errors result)) " errors, " + (count (:ignored result)) " ignored, " + (count (:success result)) " successful."]) + (if (= :loading (:state status )) + [status/big-loader status] + [:div + [:div.is-clearfix + [:div.is-pulled-right + [form-builder/raw-field-v2 {:field :only-show-errors?} + [com/checkbox {:label "Only show errors"}]]]] + [:div + [form-builder/raw-field-v2 {:field :line-items} + [textarea->table {:headings [["Id" :id] + ["Client" :client-code] + ["Source" :source] + ["Vendor" :vendor-name] + ["Date" :date] + ["Account" :account-identifier] + ["Location" :location] + ["Debit" :debit] + ["Credit" :credit] + ["Note" :note] + ["Cleared against" :cleared-against]] + :read-only-headings + [["status" :status]] - :row-filter - (fn [{:keys [status-category]}] - (if (:only-show-errors? data) - (= :error status-category) - true)) - - :event [::forms/change ::form] - :subscription data} - ]]]])])) + :row-filter + (fn [{:keys [status-category]}] + (if (:only-show-errors? data) + (= :error status-category) + true))}]]]])]])) {})) -(defn external-import-page [] +(re-frame/reg-event-fx + ::mounted + (fn [_ _] + {:dispatch [::forms/start-form ::form]})) + +(re-frame/reg-event-fx + ::unmounted + (fn [_ _] + {:dispatch [::forms/form-closing ::form]})) + +(defn external-import-page-internal [] [side-bar-layout {:side-bar [ledger-side-bar] :main [balance-sheet-content]}]) + +(defn external-import-page [] + (r/create-class + {:display-name "external-import-page" + :component-will-unmount #(re-frame/dispatch-sync [::unmounted]) + :component-did-mount #(re-frame/dispatch [::mounted]) + :reagent-render external-import-page-internal})) diff --git a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs index dc529d84..eecd0704 100644 --- a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs @@ -10,7 +10,6 @@ :refer [appearing-side-bar side-bar-layout]] [auto-ap.views.components.modal :as modal] [auto-ap.views.components.switch-field :refer [switch-field]] - [auto-ap.views.components.typeahead :refer [typeahead-v3]] [auto-ap.views.pages.data-page :as data-page] [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]] [auto-ap.views.pages.ledger.table :as ledger-table] @@ -19,7 +18,6 @@ date-picker dispatch-event local-today - multi-field query-params standard str->date diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs index 691e92ea..bd50a7a4 100644 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -51,7 +51,8 @@ (re-frame/reg-event-fx ::unmounted (fn [{:keys [db]} _] - {:dispatch [::data-page/dispose :invoices] + {:dispatch-n [[::data-page/dispose :invoices] + [::forms/form-closing ::form/form]] ::forward/dispose [{:id ::updated} {:id ::checks-printed}] ::track/dispose [{:id ::params}]})) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index e2502f58..257ffa3e 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -106,26 +106,6 @@ (when d (format/parse f d))) -(defn dispatch-date-change [event] - (fn [e] - (re-frame/dispatch (conj event - (if (str/blank? e) - e - (date->str (t/from-default-time-zone (c/from-date e)) standard)))))) - -(defn dispatch-cljs-date-change [event] - (fn [e] - (re-frame/dispatch (conj event - (if (str/blank? e) - e - (c/to-local-date e)))))) - -;; TODO inline on-changes causes each field to be rerendered each time. When we fix this -;; let's make sure that we find away not to trigger a re-render for every component any time any form field -;; changes -(defmulti do-bind (fn [_ {:keys [type]}] - type)) - (defn with-keys [children] (map-indexed (fn [i c] ^{:key i} c) children)) @@ -178,290 +158,6 @@ 0.0)}} child])))])]))) - -(defn multi-field [{:keys [value]} ] - (let [value-repr (reagent/atom (mapv - (fn [x] - (assoc x :key (random-uuid) :new? false)) - value))] - (fn [{:keys [template on-change allow-change? disable-new? disable-remove?]} ] - (let [value @value-repr - already-has-new-row? (= [:key :new?] (keys (last value))) - value (if (or already-has-new-row? disable-new?) - value - (conj value {:key (random-uuid) - :new? true}))] - [:div {:style {:margin-bottom "0.25em"}} - (for [[i override] (map vector (range) value) - :let [is-disabled? (if (= false allow-change?) - (not (boolean (:new? override))) - nil)] - ] - ^{:key (:key override)} - [:div.level {:style {:margin-bottom "0.25em"}} - [:div.level-left {:style {:padding "0.5em 1em"} - :class (cond - (and (= i (dec (count value))) - (:new? override)) - "has-background-light" - - (:new? override) - "has-background-info-light" - :else - "")} - (let [template (if (fn? template) - (template override) - template)] - - [:<> (for [[idx template] (map vector (range ) template)] - ^{:key idx} - - [:div.level-item - (update template 1 assoc - :value (let [value (get-in override (get-in template [1 :field])) ;; TODO this is really ugly to support maps or strings - value (if (map? value) - (dissoc value :key :new?) - value)] - (if (= value {}) - nil - value)) - :disabled (or is-disabled? (get-in template [1 :disabled])) - :on-change (fn [e] - (reset! value-repr - (into [] - (filter (fn [r] - (not= [:key :new?] (keys r))) - (assoc-in value - (into [i] (get-in template [1 :field])) - (let [this-value (if (and e (.. e -target)) - (.. e -target -value ) - e)] - (if (map? this-value) - (update this-value :key (fnil identity (random-uuid))) - this-value)))))) - (on-change (mapv - (fn [v] - (dissoc v :new? :key)) - @value-repr))))]) - ]) - (when-not disable-remove? - [:div.level-item - [:a.button.level-item - {:disabled is-disabled? - :on-click (fn [] - (when-not is-disabled? - (reset! value-repr (into [] - (filter (fn [{:keys [key ]}] - (not= key (:key override))) - (filter (fn [r] - (not= [:key :new?] (keys r))) - value)))) - - (on-change (mapv - (fn [v] - (dissoc v :new? :key)) - @value-repr))))} - [:span.icon [:span.icon-remove]]]]) - ]])])))) - - - -(defmethod do-bind "select" [dom {:keys [field allow-nil? subscription event class spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - event (if (keyword? event) [event] event) - keys (assoc keys - :on-change (dispatch-value-change (conj event field)) - - :value (or (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) - options (if allow-nil? - (with-keys (conj rest [:option {:value nil}])) - (with-keys rest))] - (into [dom (dissoc keys :allow-nil?)] options))) - - -(defmethod do-bind "radio" [dom {:keys [field subscription event class value spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - event (if (keyword? event) [event] event) - keys (assoc keys - :on-change (dispatch-value-change (conj event field)) - :checked (= (get-in subscription field) value) - :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 "checkbox" [dom {:keys [field subscription event class spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - event (if (keyword? event) [event] event) - keys (assoc keys - :on-change (dispatch-event (-> event - (conj field) - (conj (not (get-in subscription field))))) - :checked (boolean (get-in subscription field)) - :class (str class - (when (and spec (not (s/valid? spec (get-in subscription field )))) - " is-danger"))) - keys (dissoc keys :field :subscription :event :spec)] - (into [dom keys] (with-keys rest)))) - -(defmethod do-bind "multi-field" [dom {:keys [field event subscription class spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - event (if (keyword? event) [event] event) - keys (assoc keys - :on-change (fn [value] - (re-frame/dispatch (conj (conj event field) value))) - :value (get-in subscription field) - :class (str class - (when (and spec (not (s/valid? spec (get-in subscription field)))) - " is-danger"))) - keys (dissoc keys :field :subscription :event :spec)] - (into [dom keys] (with-keys rest)))) - - - -(defmethod do-bind "typeahead-v3" [dom {:keys [field event subscription class spec] :as keys} & rest] - (let [field (if (keyword? field) [field] field) - 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) - selected (get-in subscription field) - keys (assoc keys - :on-change (fn [v] - (re-frame/dispatch (-> event (conj field) (conj v)))) - - :value selected - :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 "expense-accounts" [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 - :value (get-in subscription field) - :event (conj event field) - :class (str class - (when (and spec (not (s/valid? spec (get-in subscription field)))) - " is-danger"))) - keys (dissoc keys :field :subscription :spec)] - (into [dom keys] (with-keys rest)))) - - -(defmethod do-bind "button-radio" [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 - :value (get-in subscription field) - :on-change (fn [v] - (re-frame/dispatch (-> event (conj field) (conj v)))) - :class (str class - (when (and spec (not (s/valid? spec (get-in subscription field)))) - " is-danger"))) - keys (dissoc keys :field :event :subscription :spec)] - (into [dom keys] (with-keys rest)))) - -(defmethod do-bind "number" [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 [e] - (.preventDefault e) - (re-frame/dispatch (-> event - (conj field) - (conj (let [val (.. e -target -value)] - (cond (and val - (re-matches #"[\-]?(\d+)(\.\d{2})?" val)) - (js/parseFloat val) - - (str/blank? val ) - nil - - :else - val)))))) - :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 "textarea->table" [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 [x] - (re-frame/dispatch (-> event - (conj field) - (conj x)))) - :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 "money" [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 [x] - (re-frame/dispatch (-> event - (conj field) - (conj x)))) - :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 :default [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 (dispatch-value-change (conj event field)) - :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)))) - -(defn bind-field [all] - (apply do-bind all)) - - - -(defn horizontal-field [label & controls] - [:div.field.is-horizontal - (when label - [:div.field-label - label - ]) - (into - [:div.field-body] - (with-keys (map (fn [x] [:div.field x]) controls)))]) - (defn coerce-date [d] (cond (and (string? d) (some->> (re-find #"^(\d{4})" d)