218 lines
11 KiB
Clojure
218 lines
11 KiB
Clojure
(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 typeahead-entity]]
|
|
[auto-ap.views.utils :refer [bind-field dispatch-event ->$]]
|
|
[goog.string :as gstring]
|
|
[re-frame.core :as re-frame]
|
|
[clojure.string :as str]))
|
|
|
|
(defn can-replace-with-default? [accounts]
|
|
(and (or (not (seq accounts))
|
|
(<= 1 (count accounts)))
|
|
(not (get-in accounts [0 :account :id]))))
|
|
|
|
(defn default-account [accounts default-account amount locations]
|
|
[{:id (get-in accounts [0 :id]
|
|
(str "new-" (random-uuid)))
|
|
:amount (Math/abs amount)
|
|
:amount-percentage 100
|
|
:amount-mode "%"
|
|
:location (or
|
|
(:location default-account)
|
|
(get-in accounts [0 :account :location])
|
|
(if (= 1 (count locations))
|
|
(first locations)
|
|
nil))
|
|
:account default-account}])
|
|
|
|
|
|
(defn from-graphql [accounts total locations]
|
|
(if (seq accounts)
|
|
(vec (map
|
|
(fn [a]
|
|
(-> a
|
|
(update :amount js/parseFloat)
|
|
(assoc :amount-percentage (* 100 (/ (js/parseFloat (:amount a))
|
|
(Math/abs (js/parseFloat total)))))
|
|
(assoc :amount-mode "$")))
|
|
accounts))
|
|
[{:id (str "new-" (random-uuid))
|
|
:amount-mode "$"
|
|
:amount (Math/abs total)
|
|
:amount-percentage 100
|
|
:location (if (= 1 (count locations))
|
|
(first locations)
|
|
nil)}]))
|
|
|
|
|
|
;; EVENTS
|
|
|
|
(re-frame/reg-event-fx
|
|
::add-expense-account
|
|
(fn [_ [_ event expense-accounts locations]]
|
|
{:dispatch (conj event (conj expense-accounts
|
|
{:amount 0 :id (str "new-" (random-uuid))
|
|
:amount-mode "%"
|
|
:amount-percentage 0
|
|
:location (if (= 1 (count locations))
|
|
(first locations)
|
|
nil)}))}))
|
|
|
|
(re-frame/reg-event-fx
|
|
::remove-expense-account
|
|
(fn [_ [_ event expense-accounts id]]
|
|
{:dispatch (conj event (transduce (filter
|
|
(fn [ea]
|
|
(not= (:id ea) id)) )
|
|
conj
|
|
[]
|
|
expense-accounts))}))
|
|
|
|
(defn recalculate-amounts [expense-accounts total]
|
|
(mapv
|
|
(fn [ea]
|
|
(assoc ea :amount
|
|
(js/parseFloat
|
|
(goog.string/format "%.2f"
|
|
(* (/ (js/parseFloat (:amount-percentage ea)) 100.0) (js/parseFloat total))))))
|
|
expense-accounts))
|
|
|
|
(re-frame/reg-event-fx
|
|
::spread-evenly
|
|
(fn [_ [_ event expense-accounts max-value]]
|
|
{:dispatch (into event [(recalculate-amounts (mapv
|
|
(fn [ea]
|
|
(assoc ea :amount-percentage (js/parseFloat
|
|
(goog.string/format "%.2f"
|
|
(* 100 (/ 1 (count expense-accounts)))))))
|
|
expense-accounts)
|
|
max-value)])}))
|
|
(re-frame/reg-event-fx
|
|
::expense-account-changed
|
|
(fn [_ [_ event expense-accounts max-value field value]]
|
|
(let [updated-accounts (cond-> expense-accounts
|
|
true (assoc-in field value)
|
|
(= (list :account :id) (drop 1 field)) (assoc-in [(first field) :account] @(re-frame/subscribe [::subs/account value]))
|
|
(= (list :amount-percentage) (drop 1 field)) (assoc-in [(first field) :amount]
|
|
(js/parseFloat
|
|
(goog.string/format "%.2f"
|
|
(* (/ (cond-> value
|
|
(not (float? value)) (js/parseFloat )) 100.0)
|
|
(cond-> max-value
|
|
(not (float? max-value)) (js/parseFloat)))))))
|
|
updated-accounts (if-let [location (get-in updated-accounts [(first field) :account :location])]
|
|
(assoc-in updated-accounts [(first field) :location] location)
|
|
updated-accounts)]
|
|
{:dispatch (into event [updated-accounts])})))
|
|
|
|
|
|
;; VIEWS
|
|
(defn expense-accounts-field [{expense-accounts :value max-value :max locations :locations event :event descriptor :descriptor disabled :disabled percentage-only? :percentage-only? :or {percentage-only? false}}]
|
|
(let [chooseable-expense-accounts @(re-frame/subscribe [::subs/chooseable-expense-accounts])
|
|
accounts-by-id @(re-frame/subscribe [::subs/accounts-for-client-by-id])]
|
|
[:div
|
|
[:div.columns
|
|
[:div.column
|
|
[:h1.subtitle.is-4.is-inline (str/capitalize descriptor) "s"]
|
|
(when-not percentage-only?
|
|
[:p.help "Remaining " (->$ (- max-value (reduce + 0 (map (comp js/parseFloat :amount) expense-accounts))))])]
|
|
[:div.column.is-narrow
|
|
(when-not disabled
|
|
[:p.buttons
|
|
[:a.button {:on-click (dispatch-event [::spread-evenly event expense-accounts max-value])} "Spread evenly"]
|
|
[:a.button {:on-click (dispatch-event [::add-expense-account event expense-accounts locations])} "Add"]])]]
|
|
|
|
(for [[index {:keys [account id location amount amount-percentage amount-mode] :as expense-account}] (map vector (range) expense-accounts)
|
|
:let [account (accounts-by-id (:id account))]]
|
|
^{:key id}
|
|
[:div.box
|
|
[:div.columns
|
|
[:div.column
|
|
[:h1.subtitle.is-6 (cond (and account (not percentage-only?))
|
|
(str (:name account) " - "
|
|
location ": "
|
|
(gstring/format "$%.2f" (or amount 0) ))
|
|
|
|
account
|
|
(str (:name account) " - "
|
|
location ": %"
|
|
amount-percentage)
|
|
|
|
:else
|
|
[:i "New " descriptor])]]
|
|
[:div.column.is-narrow
|
|
(when-not disabled
|
|
[:a.delete {:on-click (dispatch-event [::remove-expense-account event expense-accounts id])}])]]
|
|
|
|
[:div.field
|
|
[:div.columns
|
|
[:div.column
|
|
[:p.help "Account"]
|
|
[:div.control.is-fullwidth
|
|
[bind-field
|
|
[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
|
|
(if-let [forced-location (:location account)]
|
|
[:div.select
|
|
[:select {:disabled "disabled" :style {:width "5em"} :value forced-location} [:option {:value forced-location} forced-location]]]
|
|
[:div.select
|
|
[bind-field
|
|
[:select {:type "select"
|
|
:disabled (boolean (or (:location account)
|
|
disabled))
|
|
:style {:width "5em"}
|
|
:field [index :location]
|
|
:allow-nil? true
|
|
:spec (set locations)
|
|
:event [::expense-account-changed event expense-accounts max-value]
|
|
:subscription expense-accounts}
|
|
(map (fn [l] ^{:key l} [:option {:value l} l]) locations)]]])]]]]
|
|
|
|
[:div.field
|
|
[:p.help "Amount"]
|
|
[:div.control
|
|
[:div.field.has-addons.is-extended
|
|
[:p.control [:span.select
|
|
[bind-field
|
|
[:select {:type "select"
|
|
:disabled (or disabled percentage-only?)
|
|
:field [index :amount-mode]
|
|
:allow-nil? false
|
|
:event [::expense-account-changed event expense-accounts max-value]
|
|
:subscription expense-accounts}
|
|
[:option "$"]
|
|
[:option "%"]]]]]
|
|
[:p.control
|
|
(if (= "$" amount-mode)
|
|
[bind-field
|
|
[:input.input {:type "number"
|
|
:field [index :amount]
|
|
:style {:text-align "right" :width "7em"}
|
|
:event [::expense-account-changed event expense-accounts max-value]
|
|
:disabled disabled
|
|
:subscription expense-accounts
|
|
:precision 2
|
|
:value (get-in expense-account [:amount])
|
|
:max max-value
|
|
:step "0.01"}]]
|
|
[bind-field
|
|
[:input.input {:type "number"
|
|
:field [index :amount-percentage]
|
|
:style {:text-align "right" :width "7em"}
|
|
:disabled disabled
|
|
:event [::expense-account-changed event expense-accounts max-value]
|
|
:precision 2
|
|
:subscription expense-accounts
|
|
:value (get-in expense-account [:amount-percentage])
|
|
:max "100"
|
|
:step "0.01"}]])]]]]])]))
|