307 lines
13 KiB
Clojure
307 lines
13 KiB
Clojure
(ns auto-ap.views.utils
|
|
(:require [re-frame.core :as re-frame]
|
|
[cljsjs.react-transition-group]
|
|
[cljsjs.react-datepicker]
|
|
[reagent.core :as reagent]
|
|
[clojure.spec.alpha :as s]
|
|
[cljs-time.coerce :as c]
|
|
[cljs-time.core :as time]
|
|
[auto-ap.events :as events]
|
|
[auto-ap.subs :as subs]
|
|
[cljs-time.format :as format]
|
|
[goog.i18n.NumberFormat.Format]
|
|
[cljs-time.core :as t]
|
|
[clojure.string :as str])
|
|
(:import
|
|
(goog.i18n NumberFormat)
|
|
(goog.i18n.NumberFormat Format)))
|
|
(def nff
|
|
(NumberFormat. Format/CURRENCY))
|
|
|
|
(defn- nf
|
|
[num]
|
|
(.format nff (str num)))
|
|
|
|
(defn ->$ [x]
|
|
(nf x))
|
|
|
|
|
|
|
|
(defn active-when= [active-page candidate]
|
|
(when (= active-page candidate) " is-active"))
|
|
|
|
(defn active-when [active-page f & rest]
|
|
|
|
(when (apply f (into [active-page] rest)) " is-active"))
|
|
|
|
(def login-url
|
|
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
|
|
redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"))]
|
|
(str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile")))
|
|
|
|
(defn dispatch-value-change [event]
|
|
(fn [e]
|
|
(.preventDefault e)
|
|
(re-frame/dispatch (conj event (.. e -target -value)))))
|
|
|
|
|
|
(defn delayed-dispatch [e]
|
|
(fn [x]
|
|
(js/setTimeout #(re-frame/dispatch e) 150)
|
|
false))
|
|
|
|
(defn dispatch-event [event]
|
|
(fn [e]
|
|
(when (.-stopPropagation e)
|
|
(.stopPropagation e)
|
|
(.preventDefault e))
|
|
(re-frame/dispatch-sync event)))
|
|
|
|
(def pretty-long (format/formatter "MM/dd/yyyy HH:mm:ss"))
|
|
(def pretty (format/formatter "MM/dd/yyyy"))
|
|
(def standard (format/formatter "yyyy-MM-dd"))
|
|
|
|
(defn date->str
|
|
([d] (date->str d pretty))
|
|
([d format]
|
|
(when d
|
|
(format/unparse format d))))
|
|
|
|
(defn date-time->str [d]
|
|
(when d
|
|
(format/unparse pretty-long d)))
|
|
|
|
(defn str->date [d f]
|
|
(when d
|
|
(format/parse f d)))
|
|
|
|
(defn dispatch-date-change [event]
|
|
(fn [e g]
|
|
(re-frame/dispatch (conj event
|
|
(if (str/blank? e)
|
|
e
|
|
(date->str (time/from-default-time-zone (c/from-date e)) standard))))))
|
|
|
|
(defmulti do-bind (fn [a {:keys [type] :as x}]
|
|
type))
|
|
|
|
(defn with-keys [children]
|
|
(map-indexed (fn [i c] ^{:key i} c) children))
|
|
|
|
(defmethod do-bind "select" [dom {:keys [field allow-nil? 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))
|
|
|
|
: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 value 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 "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-description 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 "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)
|
|
selected (get-in subscription field)
|
|
x (str->date selected standard)
|
|
selected (if (string? selected)
|
|
(c/to-date (time/to-default-time-zone (time/from-default-time-zone x)))
|
|
selected )
|
|
keys (assoc keys
|
|
:on-change (dispatch-date-change (conj event field))
|
|
:selected 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 precision event subscription class spec] :as keys :or {precision 2}} & 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 (not (str/blank? 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 :or {precision 2}} & 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)))])
|
|
|
|
|
|
|
|
(def css-transition-group
|
|
(reagent/adapt-react-class js/ReactTransitionGroup.CSSTransition))
|
|
|
|
(def date-picker
|
|
(do
|
|
|
|
|
|
(reagent/adapt-react-class (aget js/DatePicker "default"))))
|
|
|
|
|
|
(defn appearing [{:keys [visible? enter-class exit-class timeout]} & children ]
|
|
|
|
(let [final-state (reagent/atom visible?)]
|
|
(fn [{:keys [visible?]} & children]
|
|
[css-transition-group {:in visible? :class-names {:exit exit-class :enter enter-class} :timeout timeout :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))}
|
|
(if (or @final-state visible?)
|
|
(first children)
|
|
[:span])])))
|
|
|
|
(defn local-now []
|
|
(t/to-default-time-zone (t/now)))
|
|
|
|
(def with-user
|
|
(re-frame/->interceptor
|
|
:id :with-user
|
|
:before (fn [context]
|
|
(-> context
|
|
(assoc-in [:coeffects :user] (get-in context [:coeffects :db :user]))))))
|