diff --git a/resources/public/css/main.css b/resources/public/css/main.css index b13fa95d..5c914bf0 100644 --- a/resources/public/css/main.css +++ b/resources/public/css/main.css @@ -197,6 +197,11 @@ nav.navbar .navbar-item.is-active { background-color: #F9F9F9; border-right: 1px solid #DEDEDE; } + +.aside .subtitle { + padding-top: 2rem; + margin-bottom: 0.75rem; +} .messages { display:block; background-color: #fff; diff --git a/src/clj/auto_ap/datomic/clients.clj b/src/clj/auto_ap/datomic/clients.clj index e6bc6702..4780d55d 100644 --- a/src/clj/auto_ap/datomic/clients.clj +++ b/src/clj/auto_ap/datomic/clients.clj @@ -1,36 +1,40 @@ (ns auto-ap.datomic.clients (:require [datomic.api :as d] - [auto-ap.datomic :refer [uri]])) + [auto-ap.datomic :refer [uri]] + [clojure.tools.logging :as log])) +(defn cleanse [e] + (-> e + (update :client/location-matches + (fn [lms] + (map #(assoc % :location-match/match (first (:location-match/matches %))) lms))) + (update :client/bank-accounts + (fn [bas] + (map (fn [i ba] + (-> ba + (update :bank-account/type :db/ident ) + (update :bank-account/sort-order (fn [so] (or so i))))) + (range) bas))))) (defn get-all [] - - (->> (d/q '[:find (pull ?e [* {:client/address [*]}]) + (->> (d/q '[:find (pull ?e [* + {:client/address [*]} + {:client/bank-accounts [* {:bank-account/type [*]}]}]) :where [?e :client/name]] (d/db (d/connect uri))) (map first) - (map (fn [c] - (update c :client/location-matches - (fn [lms] - - (map #(assoc % :location-match/match (first (:location-match/matches %))) lms))))) - (map (fn [c] - (update c :client/bank-accounts - (fn [bas] - (map (fn [i ba] - (-> ba - (update :bank-account/type :db/ident ) - (update :bank-account/sort-order (fn [so] (or so i))))) - (range) bas))))) + (map cleanse) + )) (defn get-by-id [id] (->> - (d/query (-> {:query {:find ['(pull ?e [*])] + (d/query (-> {:query {:find ['(pull ?e [* {:client/bank-accounts [* {:bank-account/type [*]}]}])] :in ['$ '?e] :where [['?e]]} :args [(d/db (d/connect uri)) id]} )) (first) (first) + (cleanse) #_(map first) #_(first))) diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index e697439e..1a95b099 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -67,7 +67,7 @@ new-id-set (set (map :db/id vs)) retract-ids (filter (complement new-id-set) ids)] (cond-> [] - true (into (map (fn [i] [:db/retractEntity i ]) retract-ids)) + true (into (map (fn [i] [:db/retract e a i ]) retract-ids)) (seq vs) (conj {:db/id e a vs})))})}]]) @@ -308,7 +308,8 @@ {:db/ident :invoice/automatically-paid-when-due :db/doc "Whether this invoice should be marked as paid when it's due" :db/valueType :db.type/boolean - :db/cardinality :db.cardinality/one}]]}} + :db/cardinality :db.cardinality/one}]]} + :auto-ap/fix-reset-rels {:txes-fn `reset-function}} ] (println "Conforming database...") (c/ensure-conforms conn norms-map) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 3a9428ee..398800cc 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -65,7 +65,8 @@ { :location_match {:fields {:location {:type 'String} - :match {:type 'String}}} + :match {:type 'String} + :id {:type :id}}} :client {:fields {:id {:type :id} @@ -526,7 +527,8 @@ :invoice_payment_amount {:fields {:invoice_id {:type :id} :amount {:type 'Float}}} :edit_location_match {:fields {:location {:type 'String} - :match {:type 'String}}} + :match {:type 'String} + :id {:type :id}}} :edit_forecasted_transaction {:fields {:identifier {:type 'String} :id {:type :id} diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj index 259954bb..6917946f 100644 --- a/src/clj/auto_ap/graphql/clients.clj +++ b/src/clj/auto_ap/graphql/clients.clj @@ -3,7 +3,8 @@ [datomic.api :as d] [auto-ap.datomic :refer [uri remove-nils]] [auto-ap.graphql.utils :refer [->graphql assert-admin can-see-client?]] - [clojure.string :as str])) + [clojure.string :as str] + [clojure.tools.logging :as log])) (defn assert-client-code-is-unique [code] (when (seq (d/query {:query {:find '[?id] @@ -76,6 +77,7 @@ :forecasted-transaction/amount (:amount %)} ) (:forecasted_transactions edit_client))]] + _ (log/info "upserting client" transactions) result @(d/transact (d/connect uri) transactions)] (-> result :tempids (get id) (or id) d-clients/get-by-id (update :client/location-matches diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index e9d117c0..95ec1898 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -44,7 +44,7 @@ :graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations [:location-matches [:location :match]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ] + [:id :name :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations [:location-matches [:id :location :match]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ] [:address [:street1 :street2 :city :state :zip]] [:forecasted-transactions [:id :amount :identifier :day-of-month]]]] [:vendor @@ -75,7 +75,7 @@ (fn [{:keys [db]} [_ token user]] {:graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code :matches :locations :week-a-debits :week-a-credits :week-b-debits :week-b-credits [:location-matches [:location :match]] + [:id :name :code :matches :locations :week-a-debits :week-a-credits :week-b-debits :week-b-credits [:location-matches [:id :location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ] [:forecasted-transactions [:id :amount :identifier :day-of-month]]]] 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 8c2b9bab..f3b2dc1b 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -4,7 +4,7 @@ [auto-ap.subs :as subs] [auto-ap.views.components.address :refer [address-field]] [auto-ap.views.components.layouts :refer [side-bar]] - [auto-ap.views.utils :refer [dispatch-event horizontal-field nf]] + [auto-ap.views.utils :refer [dispatch-event horizontal-field nf multi-field]] [cljs-time.coerce :as coerce] [cljs-time.core :as t] [clojure.spec.alpha :as s] @@ -12,9 +12,11 @@ (re-frame/reg-sub ::can-submit - :<- [::forms/form ::form] - (fn [{:keys [data status]} _] - (s/valid? ::entity/client data))) + :<- [::new-client-request] + (fn [r _] + true + + #_(s/valid? ::entity/client r))) (re-frame/reg-sub ::new-client-request @@ -24,8 +26,8 @@ :name (:name new-client-data) :code (:code new-client-data) ;; TODO add validation can't change :email (:email new-client-data) - :locations (:locations new-client-data) - :matches (vec (:matches new-client-data)) + :locations (mapv :location (:locations new-client-data)) + :matches (mapv :match (:matches new-client-data)) :location-matches (:location-matches new-client-data) :week-a-credits (:week-a-credits new-client-data) :week-a-debits (:week-a-debits new-client-data) @@ -51,7 +53,7 @@ :id id :sort-order sort-order :visible visible - :locations (vec locations) + :locations (mapv :location locations) :yodlee-account-id (when yodlee-account-id (js/parseInt yodlee-account-id)) :code (if new? @@ -65,9 +67,19 @@ (re-frame/reg-event-fx ::editing (fn [{:keys [db]} [_ client-id]] + (println (get (:clients db) client-id)) {:db (-> db (forms/stop-form ::form) - (forms/start-form ::form (get (:clients db) client-id)))})) + (forms/start-form ::form (-> (get (:clients db) client-id) + (update :locations #(mapv (fn [l] {:location l}) %)) + (update :matches #(mapv (fn [l] {:match l}) %)) + (update :bank-accounts + (fn [bas] + (mapv (fn [ba] + (update ba :locations (fn [ls] + (map (fn [l] {:location l}) + ls)))) + bas))))))})) (re-frame/reg-event-fx ::save-new-client @@ -77,25 +89,22 @@ (let [new-client-req @(re-frame/subscribe [::new-client-request]) user @(re-frame/subscribe [::subs/token])] - (if (s/valid? ::entity/client new-client-req) - - {:db (-> new-client-form - (assoc :status :loading) - (assoc :error nil)) - :graphql - {:token user - :query-obj {:venia/operation {:operation/type :mutation - :operation/name "EditClient"} - :venia/queries [{:query/data [:edit-client - {:edit-client new-client-req} - [:id :name :code :email :locations :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits - [:location-matches [:location :match]] - [:address [:street1 :street2 :city :state :zip]] - [:forecasted-transactions [:id :amount :identifier :day-of-month]] - [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]} - :on-success [::save-complete] - :on-error [::forms/save-error ::new-client]}} - {:db new-client-form})))) + {:db (-> new-client-form + (assoc :status :loading) + (assoc :error nil)) + :graphql + {:token user + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "EditClient"} + :venia/queries [{:query/data [:edit-client + {:edit-client new-client-req} + [:id :name :code :email :locations :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits + [:location-matches [:location :match :id]] + [:address [:street1 :street2 :city :state :zip]] + [:forecasted-transactions [:id :amount :identifier :day-of-month]] + [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]} + :on-success [::save-complete] + :on-error [::forms/save-error ::form]}}))) (re-frame/reg-event-db ::save-complete (fn [db [_ client]] @@ -104,48 +113,6 @@ (assoc-in [:clients (:id (:edit-client client))] (update (:edit-client client) :bank-accounts (fn [bas] (->> bas (sort-by :sort-order) vec))))))) -(re-frame/reg-event-db - ::add-new-location - [(forms/in-form ::form) (re-frame/path [:data])] - (fn [client _] - (-> client - (update :locations conj (:location client)) - (dissoc :location)))) - -(re-frame/reg-event-db - ::add-location-to-bank-account - [(forms/in-form ::form) (re-frame/path [:data])] - (fn [client [_ which-account]] - (println client which-account) - (-> client - (update-in [:bank-accounts which-account :locations] #(conj (or % #{}) (get-in client [:bank-accounts which-account :location-select]))) - (update-in [:bank-accounts which-account] dissoc :location-select)))) - -(re-frame/reg-event-db - ::add-new-match - [(forms/in-form ::form) (re-frame/path [:data])] - (fn [client _] - (-> client - (update :matches conj (:match client)) - (update :matches set) - (dissoc :match)))) - -(re-frame/reg-event-db - ::remove-match - [(forms/in-form ::form) (re-frame/path [:data])] - (fn [client [_ which]] - (-> client - (update :matches set) - (update :matches disj which)))) - -(re-frame/reg-event-db - ::add-new-location-match - [(forms/in-form ::form) (re-frame/path [:data])] - (fn [client _] - (-> client - (update :location-matches conj (:location-match client)) - (dissoc :location-match)))) - (re-frame/reg-event-db ::add-forecasted-transaction [(forms/in-form ::form) (re-frame/path [:data])] @@ -168,17 +135,6 @@ [] %))))) -(re-frame/reg-event-db - ::remove-location-match - [(forms/in-form ::form) (re-frame/path [:data])] - (fn [client [_ i]] - (-> client - (update :location-matches (fn [lm] - (->> lm - (map vector (range)) - (filter (fn [[index item]] - (not= index i))) - (map second))))))) (re-frame/reg-event-db ::add-new-bank-account @@ -203,10 +159,7 @@ [(forms/in-form ::form) (re-frame/path [:data :bank-accounts])] (fn [bank-accounts [_ index]] (vec (concat (take index bank-accounts) - (drop (inc index) bank-accounts))) - #_(update db :bank-accounts - (fn [bas] - (filter #(not= (:code %) code) bas))))) + (drop (inc index) bank-accounts))))) (re-frame/reg-event-db ::sort-swapped @@ -362,23 +315,19 @@ [:label.label "Locations"] [:div.control [:p.help "If this account is location-specific, add the valid locations"] - [:div.field.has-addons - [:p.control - [:div.select - [raw-field - [:select {:type "select" - :style {:width "7em"} - :field [:bank-accounts sort-order :location-select] - :allow-nil? true - :spec (set (get-in new-client [:locations]))} - (map (fn [l] ^{:key l} [:option {:value l} l]) (get-in new-client [:locations]))]]]] - [:p.control {:on-click (dispatch-event [::add-location-to-bank-account sort-order]) } [:a.button "Add"]]]] - - (if-let [locations (seq (get-in new-client [:bank-accounts sort-order :locations]))] - [:ul - (for [location locations] - ^{:key location} [:li location ])] - [:i "This account applies to all locations"])] + [raw-field + [multi-field {:type "multi-field" + :field [:bank-accounts sort-order :locations] + :template [[:select.select {:type "select" + :style {:width "7em"} + :allow-nil? true + :field [:location] + :spec (set (map :location (get-in new-client [:locations])))} + [:<> + [:option ""] + [:<> (map (fn [l] ^{:key (:location l)} + [:option {:value (:location l)} (:location l)]) + (get-in new-client [:locations]))]]]]}]]]] [:div.field [:label.checkbox [raw-field @@ -394,168 +343,138 @@ (defn new-client-form [] (let [{new-client :data } @(re-frame/subscribe [::forms/form ::form]) - {:keys [form field raw-field error-notification submit-button ]} client-form + {:keys [form-inline field raw-field error-notification submit-button ]} client-form next-week-a (if (is-week-a? (t/now)) "This week" "Next week") next-week-b (if (is-week-a? (t/now)) "Next week" "This week")] + (println "ID" (:id new-client)) [side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])} - [form {:title "Add client"} - [field "Name" - [:input.input {:type "text" - :field [:name] - :spec ::entity/name - }]] + ^{:key (:id new-client)} + [:div ;; div is important for actually unmounting for now + (form-inline {:title "Add client"} + [:<> + (field "Name" + [:input.input {:type "text" + :field [:name] + :spec ::entity/name + }]) - [:div.field - [:p.help "Client code" - ] - (if (:id new-client) - [:div.control - (:code new-client)] - [:div.control - [raw-field - [:input.input {:type "code" - :field :code - :spec ::entity/code}]]])] - + [:div.field + [:p.help "Client code" + ] + (if (:id new-client) + [:div.control + (:code new-client)] + [:div.control + (raw-field + [:input.input {:type "code" + :field :code + :spec ::entity/code}])])] + - [field "Email" - [:input.input {:type "email" - :field :email - :spec ::entity/email}]] + (field "Email" + [:input.input {:type "email" + :field :email + :spec ::entity/email}]) - [:div.field - [:p.help "Matches"] - [:div.control - [:div.field.has-addons - [:p.control - [raw-field - [:input.input {:type "text" - :field :match}]]] - [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-match])} "Add"]]]] - [:ul - (for [match (:matches new-client)] - ^{:key match} [:li match [:a {:on-click (dispatch-event [::remove-match match])} [:span.icon [:span.fa.fa-times]]]])]] + - + [:h2.subtitle.is-5 "Name matches"] + [:div.control + (raw-field + [multi-field {:type "multi-field" + :field :matches + :template [[:input.input {:field [:match] + :placeholder "Harry's burger joint" + :style { :width "15em"}}]]}])] - [:div.field - [:p.help "Locations"] - [:div.control - [:div.field.has-addons - [:p.control - [raw-field - [:input.input {:type "text" - :field :location}]]] - [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location])} "Add"]]] - [:ul - (for [location (:locations new-client)] - ^{:key location} [:li location ])]]] + + [:h2.subtitle "Locations"] + [:div.control + (raw-field + [multi-field {:type "multi-field" + :field :locations + :allow-change? false + :template [[:input.input {:field [:location] + :maxlength 2 + :style { :width "4em"}}]]}])] - [:div.field - [:p.help "Location matches"] - [:div.control - [:div.field.has-addons - - [:p.control - - [raw-field - [:input.input {:type "text" - :placeholder "San Jose" - :field [:location-match :match]}]]] - [:p.control - [raw-field - [:input.input {:type "text" - :placeholder "DT" - :field [:location-match :location]}]]] - [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location-match])} "Add"]]] - - [:ul - (for [[index {:keys [location match]}] (map vector (range) (:location-matches new-client))] - ^{:key index} [:li match "->" location [:a {:on-click (dispatch-event [::remove-location-match index])} [:span.icon - [:span.fa.fa-times]]]])]]] + [:h2.subtitle "Location Matches"] + [:div.control + (raw-field + [multi-field {:type "multi-field" + :field :location-matches + :template [[:input.input {:field [:match] + :placeholder "Downtown" + :style { :width "15em"}}] + [:input.input {:field [:location] + :placeholder "DT" + :maxlength 2 + :style { :width "4em"}}]]}])] - [:div {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} - [:h2.subtitle "Address"] - [address-field {:field [:address] - :event [::forms/change ::form] - :subscription new-client}]] + [:div {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} + [:h2.subtitle "Address"] + [address-field {:field [:address] + :event [::forms/change ::form] + :subscription new-client}]] - [:h2.subtitle "Bank accounts"] - (for [bank-account (sort-by :sort-order (:bank-accounts new-client))] - ^{:key (:sort-order bank-account)} - [bank-account-card new-client bank-account (= 0 (:sort-order bank-account)) (= (:sort-order bank-account) (dec (count (:bank-accounts new-client))))]) + [:h2.subtitle "Bank accounts"] + (for [bank-account (sort-by :sort-order (:bank-accounts new-client))] + ^{:key (:sort-order bank-account)} + [bank-account-card new-client bank-account (= 0 (:sort-order bank-account)) (= (:sort-order bank-account) (dec (count (:bank-accounts new-client))))]) - [:div.columns - [:div.column.is-third - [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :credit])} "Add Credit Account"]] - [:div.column.is-third - [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :check])} "Add Checking Account"]] - [:div.column.is-third - [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]] + [:div.columns + [:div.column.is-third + [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :credit])} "Add Credit Account"]] + [:div.column.is-third + [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :check])} "Add Checking Account"]] + [:div.column.is-third + [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]] - [:h2.subtitle "Cash flow"] - [:label.label (str "Week A (" next-week-a ")")] - [field "Regular Credits" - [:input.input {:type "number" - :placeholder "500.00" - :field [:week-a-credits] - :step "0.01"}]] - [field "Regular Debits" - [:input.input {:type "number" - :placeholder "150.00" - :field [:week-a-debits] - :step "0.01"}]] - [:label.label (str "Week B (" next-week-b ")")] - [field "Regular Credits" - [:input.input {:type "number" - :placeholder "1000.00" - :field [:week-b-credits] - :step "0.01"}]] - [field "Regular Debits" - [:input.input {:type "number" - :placeholder "250.00" - :field [:week-b-debits] - :step "0.01"}]] - [:div.field - [:label.label "Forecasted transactions"] - [:div.control - [horizontal-field - nil + [:h2.subtitle "Cash flow"] + [:label.label (str "Week A (" next-week-a ")")] + [field "Regular Credits" + [:input.input {:type "number" + :placeholder "500.00" + :field [:week-a-credits] + :step "0.01"}]] + [field "Regular Debits" + [:input.input {:type "number" + :placeholder "150.00" + :field [:week-a-debits] + :step "0.01"}]] + [:label.label (str "Week B (" next-week-b ")")] + [field "Regular Credits" + [:input.input {:type "number" + :placeholder "1000.00" + :field [:week-b-credits] + :step "0.01"}]] + [field "Regular Debits" + [:input.input {:type "number" + :placeholder "250.00" + :field [:week-b-debits] + :step "0.01"}]] + [:div.field + [:label.label "Forecasted transactions"] - [:p.control - [:p.help "Identifier"] - [raw-field - [:input.input {:type "text" - :placeholder "Identifier" - :field [:new-forecasted-transaction :identifier]}]]] - [:p.control - [:p.help "Day of month"] - [raw-field - [:input.input {:type "number" - :placeholder "Day of month" - :step "1" - :field [:new-forecasted-transaction :day-of-month]}]]] - [:p.control - [:p.help "Amount"] - [raw-field - [:input.input {:type "number" - :placeholder "250.00" - :field [:new-forecasted-transaction :amount] - :step "0.01"}]]] - [:a.button {:on-click (dispatch-event [::add-forecasted-transaction])} "Add"]]] - - [:ul - (for [forecasted-transaction (:forecasted-transactions new-client)] - ^{:key (or (:id forecasted-transaction) - (:temp-id forecasted-transaction))} - [:li (:identifier forecasted-transaction) ": " (nf (:amount forecasted-transaction)) " on day " (:day-of-month forecasted-transaction) " of the month" - [:a {:on-click (dispatch-event [::remove-forecasted-transaction (or (:id forecasted-transaction) - (:temp-id forecasted-transaction))])} [:span.icon [:span.fa.fa-times]]]])]] - - [error-notification] - [submit-button "Save"]]])) + [:div.control + (raw-field + [multi-field {:type "multi-field" + :field :forecasted-transactions + :template [[:input.input {:type "text" + :placeholder "Identifier" + :field [ :identifier]}] + [:input.input {:type "number" + :placeholder "Day of month" + :step "1" + :field [:day-of-month]}] + [:input.input {:type "number" + :placeholder "250.00" + :field [:amount] + :step "0.01"}]]}])]] + (error-notification) + (submit-button "Save")])]])) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index 905a97b9..9dc78b12 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -12,7 +12,8 @@ [goog.i18n.NumberFormat.Format] [cljs-time.core :as t] [clojure.string :as str] - [goog.crypt.base64 :as base64]) + [goog.crypt.base64 :as base64] + [reagent.core :as r]) (:import (goog.i18n NumberFormat) (goog.i18n.NumberFormat Format))) @@ -100,6 +101,85 @@ (defn with-keys [children] (map-indexed (fn [i c] ^{:key i} c) children)) + +(def css-transition-group + (reagent/adapt-react-class js/ReactTransitionGroup.CSSTransition)) + + +(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 multi-field [{:keys [override-key override-value-key change-event default-key data value template on-change allow-change?]} ] + (let [value-repr (r/atom (mapv + (fn [x] + (assoc x :key (random-uuid) :new? false)) + value))] + (fn [{:keys [override-key override-value-key change-event default-key data value template on-change allow-change?]} ] + (let [value @value-repr + already-has-new-row? (= [:key :new?] (keys (last value))) + value (if already-has-new-row? + value + (conj value {:key (random-uuid) + :new? true}))] + [:div + (for [[i override] (map vector (range) value) + :let [is-disabled? (if (= false allow-change?) + (not (boolean (:new? override))) + nil)]] + ^{:key (:key override)} + [:div.level + [:div.level-left + [:div.level-item + (if (:new? override) + + [:div.icon.is-medium {:class (if (not= i (dec (count value))) + "has-text-info")} + [:i.fa.fa-plus]] + [:div.icon.is-medium])] + [:<> (for [[idx template] (map vector (range ) template)] + ^{:key idx} + + [:div.level-item + [update template 1 assoc + :value (get-in override (get-in template [1 :field])) + :disabled is-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])) (.. e -target -value ) )))) + (on-change (mapv + (fn [v] + (dissoc v :new? :key)) + @value-repr)))]]) + ] + [:div.level-item + [:a.button + + {:disabled is-disabled? + :on-click (fn [] + + (when-not is-disabled? + (reset! value-repr (into [] + (filter (fn [{:keys [key ] :as v}] + (not= key (:key override))) + 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 value spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) @@ -158,6 +238,20 @@ 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] + (println 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-entity" [dom {:keys [field event text-event subscription class spec match->text] :as keys} & rest] (let [field (if (keyword? field) [field] field) @@ -309,24 +403,11 @@ [: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)))