From 7f5a2ea3531178dacaf0573cae369f69f22043d5 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Fri, 22 Jul 2022 09:44:19 -0700 Subject: [PATCH] user progress --- src/clj/auto_ap/graphql.clj | 9 +- src/clj/auto_ap/graphql/transaction_rules.clj | 75 +++---- src/clj/auto_ap/graphql/users.clj | 10 +- src/cljs/auto_ap/forms.cljs | 18 +- src/cljs/auto_ap/forms/builder.cljs | 16 +- src/cljs/auto_ap/views/components.cljs | 8 +- .../components/expense_accounts_field.cljs | 30 +-- .../auto_ap/views/pages/admin/rules/form.cljs | 183 ++++++++---------- .../auto_ap/views/pages/admin/users/form.cljs | 78 ++++---- src/cljs/auto_ap/views/utils.cljs | 14 ++ 10 files changed, 233 insertions(+), 208 deletions(-) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 35b86f76..0e12350f 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -234,7 +234,7 @@ :user {:fields {:id {:type :id} :name {:type 'String} - :role {:type 'String} + :role {:type :role} :clients {:type '(list :client)}}} :account_client_override @@ -415,7 +415,7 @@ :edit_user {:fields {:id {:type :id} :name {:type 'String} - :role {:type 'String} + :role {:type :role} :clients {:type '(list String)}}} :add_contact @@ -520,6 +520,11 @@ :applicability {:values [{:enum-value :global} {:enum-value :optional} {:enum-value :customized}]} + :role {:values [{:enum-value :none} + {:enum-value :user} + {:enum-value :manager} + {:enum-value :power_user} + {:enum-value :admin}]} :account_type {:values [{:enum-value :dividend} {:enum-value :expense} {:enum-value :asset} diff --git a/src/clj/auto_ap/graphql/transaction_rules.clj b/src/clj/auto_ap/graphql/transaction_rules.clj index 864bcd92..6f1e95e5 100644 --- a/src/clj/auto_ap/graphql/transaction_rules.clj +++ b/src/clj/auto_ap/graphql/transaction_rules.clj @@ -1,59 +1,49 @@ (ns auto-ap.graphql.transaction-rules - (:require [auto-ap.datomic - :refer - [audit-transact merge-query remove-nils replace-nils-with-retract uri conn]] - [auto-ap.datomic.transaction-rules :as tr] - [auto-ap.datomic.transactions :as d-transactions] - [auto-ap.graphql.utils - :refer - [->graphql - <-graphql - assert-admin - ident->enum-f - limited-clients - result->page - snake->kebab]] - [auto-ap.rule-matching :as rm] - [auto-ap.utils :refer [dollars=]] - [clj-time.coerce :as coerce] - [clojure.set :as set] - [clojure.string :as str] - [clojure.tools.logging :as log] - [datomic.api :as d] - [clj-time.coerce :as c]) - (:import java.time.temporal.ChronoField)) + (:require + [auto-ap.datomic + :refer [audit-transact + conn + merge-query + remove-nils + replace-nils-with-retract]] + [auto-ap.datomic.transaction-rules :as tr] + [auto-ap.datomic.transactions :as d-transactions] + [auto-ap.graphql.utils + :refer [->graphql + <-graphql + assert-admin + ident->enum-f + limited-clients + result->page + snake->kebab]] + [auto-ap.rule-matching :as rm] + [auto-ap.utils :refer [dollars=]] + [clj-time.coerce :as c] + [clojure.set :as set] + [clojure.string :as str] + [datomic.api :as d])) -(defn get-transaction-rule-page [context args value] +(defn get-transaction-rule-page [context args _] (let [args (assoc args :id (:id context)) [journal-entries journal-entries-count] (tr/get-graphql (<-graphql args))] (result->page (->> journal-entries (map (ident->enum-f :transaction-rule/transaction-approval-status))) journal-entries-count :transaction_rules args))) -(defn get-transaction-rule-matches [context args value] +(defn get-transaction-rule-matches [context args _] (if (= "admin" (:user/role (:id context))) (let [all-rules (tr/get-all) - transaction (update (d-transactions/get-by-id (:transaction_id args)) :transaction/date coerce/to-date)] + transaction (update (d-transactions/get-by-id (:transaction_id args)) :transaction/date c/to-date)] (map ->graphql (rm/get-matching-rules transaction all-rules))) nil)) -(defn deleted-accounts [transaction accounts] - (let [current-accounts (:transaction-rule/accounts transaction) - specified-ids (->> accounts - (map :id) - set) - existing-ids (->> current-accounts - (map :db/id) - set)] - (set/difference existing-ids specified-ids))) - (defn transaction-rule-account->entity [{:keys [id account_id percentage location]}] (remove-nils #:transaction-rule-account {:percentage percentage :db/id id :account account_id :location location})) -(defn delete-transaction-rule [context {:keys [transaction_rule_id ]} value] +(defn delete-transaction-rule [context {:keys [transaction_rule_id ]} _] (assert-admin (:id context)) (let [existing-transaction-rule (tr/get-by-id transaction_rule_id)] (when-not (:transaction-rule/description existing-transaction-rule) @@ -62,10 +52,9 @@ (audit-transact [[:db/retractEntity transaction_rule_id]] (:id context)) transaction_rule_id)) -(defn upsert-transaction-rule [context {{:keys [id description yodlee_merchant_id note client_id bank_account_id amount_lte amount_gte vendor_id accounts transaction_approval_status dom_gte dom_lte]} :transaction_rule :as z} value] +(defn upsert-transaction-rule [context {{:keys [id description yodlee_merchant_id note client_id bank_account_id amount_lte amount_gte vendor_id accounts transaction_approval_status dom_gte dom_lte]} :transaction_rule} _] (assert-admin (:id context)) (let [existing-transaction (tr/get-by-id id) - deleted (deleted-accounts existing-transaction accounts) account-total (reduce + 0 (map (fn [x] (:percentage x)) accounts)) _ (when-not (dollars= 1.0 account-total) (let [error (str "Account total (" account-total ") does not reach 100%")] @@ -75,7 +64,7 @@ (let [error (str "You must provide a description or a yodlee merchant")] (throw (ex-info error {:validation-error error})))) _ (doseq [a accounts - :let [{:keys [:account/location :account/name] :as account} (d/entity (d/db conn) (:account_id a)) + :let [{:keys [:account/location :account/name]} (d/entity (d/db conn) (:account_id a)) client (d/entity (d/db conn) client_id) ]] (when (and location (not= location (:location a))) @@ -120,7 +109,7 @@ (defn tr [z x] (re-find (re-pattern z) x)) -(defn -test-transaction-rule [id {:keys [:transaction-rule/description :transaction-rule/note :transaction-rule/client :transaction-rule/bank-account :transaction-rule/amount-lte :transaction-rule/amount-gte :transaction-rule/dom-lte :transaction-rule/dom-gte :transaction-rule/yodlee-merchant]} include-coded? count] +(defn -test-transaction-rule [id {:keys [:transaction-rule/description :transaction-rule/client :transaction-rule/bank-account :transaction-rule/amount-lte :transaction-rule/amount-gte :transaction-rule/dom-lte :transaction-rule/dom-gte :transaction-rule/yodlee-merchant]} include-coded? count] (->> (d/query (cond-> {:query {:find ['(pull ?e [* {:transaction/client [:client/name] @@ -204,7 +193,7 @@ (map ->graphql)) conj []))) -(defn test-transaction-rule [{:keys [id]} {{:keys [description note client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule :as z} value] +(defn test-transaction-rule [{:keys [id]} {{:keys [description client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule} _] (assert-admin id) (-test-transaction-rule id #:transaction-rule {:description description :client (when client_id {:db/id client_id}) @@ -217,6 +206,6 @@ true 15)) -(defn run-transaction-rule [{:keys [id]} {:keys [transaction_rule_id count]} value] +(defn run-transaction-rule [{:keys [id]} {:keys [transaction_rule_id count]} _] (assert-admin id) (-test-transaction-rule id (tr/get-by-id transaction_rule_id) false count)) diff --git a/src/clj/auto_ap/graphql/users.clj b/src/clj/auto_ap/graphql/users.clj index bf2fc5f6..50ff139a 100644 --- a/src/clj/auto_ap/graphql/users.clj +++ b/src/clj/auto_ap/graphql/users.clj @@ -3,11 +3,11 @@ [auto-ap.datomic.users :as d-users] [auto-ap.graphql.utils :refer [->graphql assert-admin]])) -(def role->datomic-role {":none" :user-role/none - ":admin" :user-role/admin - ":power_user" :user-role/power-user - ":manager" :user-role/manager - ":user" :user-role/user}) +(def role->datomic-role {:none :user-role/none + :admin :user-role/admin + :power_user :user-role/power-user + :manager :user-role/manager + :user :user-role/user}) (defn edit-user [context {:keys [edit_user] :as args} value] (assert-admin (:id context)) diff --git a/src/cljs/auto_ap/forms.cljs b/src/cljs/auto_ap/forms.cljs index c13fd09a..cedb5029 100644 --- a/src/cljs/auto_ap/forms.cljs +++ b/src/cljs/auto_ap/forms.cljs @@ -41,6 +41,11 @@ :complete-listener complete-listener}) (assoc-in [::status/status form] nil)))) +(re-frame/reg-event-db + ::start-form + (fn [db [_ id data]] + (start-form db id data))) + (defn triggers-saved [form data-key] (i/->interceptor :id :triggers-saved @@ -81,6 +86,11 @@ db (partition 2 path-pairs)))) +(re-frame/reg-event-db + ::reset + (fn [db [_ form v]] + (assoc-in db [::forms form :data] v))) + (re-frame/reg-event-db ::visited (fn [db [_ form & paths]] @@ -90,7 +100,12 @@ (re-frame/reg-event-db ::check-problems (fn [db [_ form schema]] - (println "Checking problems") + (println "Checking problems" + form + (get-in db [::forms form :data]) + (keys (get-in db [::forms])) + schema + (m/explain schema (get-in db [::forms form :data]))) (assoc-in db [::forms form :problems] (when schema (m/explain schema (get-in db [::forms form :data])))))) @@ -116,6 +131,7 @@ (re-frame/reg-event-db ::save-error (fn [db [_ form result]] + (println result) (-> db (assoc-in [::forms form :status] :error) (assoc-in [::forms form :error] (or (:message (first result)) diff --git a/src/cljs/auto_ap/forms/builder.cljs b/src/cljs/auto_ap/forms/builder.cljs index 1a7e5bdb..2fd5a0fe 100644 --- a/src/cljs/auto_ap/forms/builder.cljs +++ b/src/cljs/auto_ap/forms/builder.cljs @@ -53,7 +53,7 @@ (let [data-sub (or data-sub [::forms/form id]) change-event (when-not on-change (or change-event [::forms/change id])) - {:keys [data visited attempted-submit? problems] form-key :id} @(re-frame/subscribe data-sub) + {:keys [data visited attempted-submit? problems error] form-key :id} @(re-frame/subscribe data-sub) data (or value data) status @(re-frame/subscribe [::status/single id]) can-submit (if can-submit @(re-frame/subscribe can-submit) @@ -68,7 +68,7 @@ :submit-event submit-event :problems problems :attempted-submit? attempted-submit? - :error (-> status :error first :message) + :error (or error (-> status :error first :message)) :status status :id id :data data @@ -91,7 +91,8 @@ )))) (defn virtual-builder [] - (let [key (r/atom (random-uuid))] + (let [starting-key (random-uuid) + key (r/atom starting-key)] (fn [{:keys [value on-change can-submit error-messages fullwidth? schema]}] (let [data-sub [::forms/form @key] {:keys [data error problems visited]} @(re-frame/subscribe data-sub) @@ -99,8 +100,12 @@ (r/create-element Provider #js {:value #js {:can-submit can-submit :error-messages (or error-messages nil) - :on-change on-change - :blur-event [::blurred schema @key] + ;; wrap to make sure raw form updates too + :on-change (fn [v] + (re-frame/dispatch-sync [::forms/reset @key v]) + (on-change v)) + :blur-event [::blurred schema @key ] + :problems problems :visited visited :error error :id @key @@ -201,6 +206,7 @@ nil) visited? (get visited full-field-path) value (get-in data full-field-path)] + (println "VISITED " visited full-field-path problems) (-> child-props (assoc :on-change (if on-change diff --git a/src/cljs/auto_ap/views/components.cljs b/src/cljs/auto_ap/views/components.cljs index 6e25a3aa..80b7a82b 100644 --- a/src/cljs/auto_ap/views/components.cljs +++ b/src/cljs/auto_ap/views/components.cljs @@ -3,7 +3,8 @@ [clojure.string :as str] [auto-ap.views.components.multi :as multi] [auto-ap.views.components.money-field :as money] - [auto-ap.views.components.number :as number])) + [auto-ap.views.components.number :as number] + [auto-ap.views.components.typeahead.vendor :as typeahead])) (defn checkbox [{:keys [on-change @@ -53,3 +54,8 @@ (def number-input number/number-input) (def money-input money/field) + +(def search-backed-typeahead typeahead/search-backed-typeahead) +(def entity-typeahead typeahead/typeahead-v3) + +(def button-radio-input auto-ap.views.components.button-radio/button-radio) 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 73daa9b5..9b15e7e6 100644 --- a/src/cljs/auto_ap/views/components/expense_accounts_field.cljs +++ b/src/cljs/auto_ap/views/components/expense_accounts_field.cljs @@ -302,20 +302,22 @@ locations)) :disabled (boolean (:location account)) :allow-nil? true}]]]]] - - [left-stack - [:div.field.has-addons - [form-builder/raw-field-v2 {:field [index :amount-mode]} - [button-radio/button-radio {:options [["$" "Amount"] - ["%" "Percent"]]}]] - (if (= "$" amount-mode) - [form-builder/raw-field-v2 {:field [index :amount]} - [money-field {}] - ] - [form-builder/raw-field-v2 {:field [index :amount-percentage]} - [percentage-field {}]])] - (when (= "%" amount-mode) - [:div.tag.is-primary.is-light (gstring/format "$%.2f" (or amount 0) )])]]])) + (if percentage-only? + [form-builder/raw-field-v2 {:field [index :amount-percentage]} + [percentage-field {}]] + [left-stack + [:div.field.has-addons + [form-builder/raw-field-v2 {:field [index :amount-mode]} + [button-radio/button-radio {:options [["$" "Amount"] + ["%" "Percent"]]}]] + (if (= "$" amount-mode) + [form-builder/raw-field-v2 {:field [index :amount]} + [money-field {}] + ] + [form-builder/raw-field-v2 {:field [index :amount-percentage]} + [percentage-field {}]])] + (when (= "%" amount-mode) + [:div.tag.is-primary.is-light (gstring/format "$%.2f" (or amount 0) )])])]])) (when-not disabled [:p.buttons [:a.button {:on-click (fn [] diff --git a/src/cljs/auto_ap/views/pages/admin/rules/form.cljs b/src/cljs/auto_ap/views/pages/admin/rules/form.cljs index 14bd8639..2eafab72 100644 --- a/src/cljs/auto_ap/views/pages/admin/rules/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/rules/form.cljs @@ -1,26 +1,22 @@ (ns auto-ap.views.pages.admin.rules.form (:require - [auto-ap.entities.transaction-rule :as entity] [auto-ap.events :as events] [auto-ap.forms :as forms] [auto-ap.forms.builder :as form-builder] + [auto-ap.schema :as schema] [auto-ap.status :as status] [auto-ap.subs :as subs] - [auto-ap.views.components.level :refer [left-stack]] - [auto-ap.views.components.button-radio :refer [button-radio]] + [auto-ap.views.components :as com] [auto-ap.views.components.expense-accounts-field :as expense-accounts-field - :refer [expense-accounts-field]] + :refer [expense-accounts-field-v2]] [auto-ap.views.components.layouts :as layouts] - [auto-ap.views.components.money-field :refer [money-field]] - [auto-ap.views.components.typeahead :refer [typeahead-v3]] - [auto-ap.views.components.typeahead.vendor - :refer [search-backed-typeahead]] + [auto-ap.views.components.level :refer [left-stack]] [auto-ap.views.pages.admin.rules.common :refer [default-read]] [auto-ap.views.pages.admin.rules.results-modal :as results-modal] - [auto-ap.views.utils :refer [dispatch-event with-user]] - [clojure.spec.alpha :as s] + [auto-ap.views.utils :refer [coerce-float dispatch-event with-user]] [clojure.string :as str] + [malli.core :as m] [re-frame.core :as re-frame] [reagent.core :as r] [vimsical.re-frame.cofx.inject :as inject] @@ -49,12 +45,6 @@ (when dom-lte (str "<" dom-lte))))])))) -(re-frame/reg-sub - ::can-submit - :<- [::forms/form ::form] - (fn [{:keys [data]} _] - (s/valid? ::entity/transaction-rule data))) - (re-frame/reg-sub ::query :<- [::forms/form ::form] @@ -91,10 +81,6 @@ (assoc :bank-account-id (:id (:bank-account data))))} default-read]}]})) -(defn ungraphql-transaction-rule [x] - (-> x - (update :amount-lte #(some-> % js/parseFloat)) - (update :amount-gte #(some-> % js/parseFloat)))) (re-frame/reg-sub ::test-query @@ -162,6 +148,9 @@ :accounts :yodlee-merchant :transaction-approval-status]) + + (update :amount-lte coerce-float) + (update :amount-gte coerce-float) (update :accounts (fn [xs] (mapv #(-> % (assoc :amount-percentage (* (:percentage %) 100.0))) @@ -210,9 +199,9 @@ {:graphql {:token user :query-obj query + :owns-state {:single ::form} :on-success (fn [result] - [::updated (:upsert-transaction-rule result)]) - :on-error [::forms/save-error ::form]}})) + [::updated (:upsert-transaction-rule result)])}})) (re-frame/reg-event-fx ::test-clicked @@ -228,7 +217,7 @@ (re-frame/reg-event-fx ::updated [(forms/triggers-stop ::form)] - (fn [{:keys [db]} [_ {:keys [rule-saved]} _]] + (fn [{:keys [db]} _] {:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])})})) (re-frame/reg-event-fx @@ -261,117 +250,107 @@ {::track/dispose [{:id ::client} {:id ::vendor-change}]})) -(defn form-contents [params] +(def rule-schema + (m/schema [:map + [:client {:optional true} + [:maybe schema/reference]] + [:bank-account {:optional true} + [:maybe schema/reference]] + [:description + schema/not-empty-string] + [:amount-gte {:optional true} + [:maybe schema/money]] + [:amount-lte {:optional true} + [:maybe schema/money]] + [:dom-gte {:optional true} + [:maybe [:int {:min 1 :max 31}]]] + [:dom-lte {:optional true} + [:maybe [:int {:min 1 :max 31}]]] + [:vendor {:optional true} + [:maybe schema/reference]] + [:transaction-approval-status {:optional true} + [:maybe [:enum :unapproved :requires-feedback :approved :excluded]]] + [:note {:optional true} + [:maybe :string]]])) + +(defn form-contents [] [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])} - (let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::form]) + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) default-note @(re-frame/subscribe [::default-note]) test-state @(re-frame/subscribe [::status/single ::test])] - [form-builder/builder {:can-submit [::can-submit] - :change-event [::changed] + [form-builder/builder {:change-event [::changed] :submit-event [::saving ] - :id ::form} + :id ::form + :schema rule-schema} [form-builder/section {:title "Transaction Rule"} - [form-builder/field {:required? true} + [form-builder/field-v2 {:required? true + :field :client} "Client" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) + [com/entity-typeahead {:entities @(re-frame/subscribe [::subs/clients]) :auto-focus true - :entity->text :name - :type "typeahead-v3" - :field [:client] - :spec ::entity/client}]] + :entity->text :name}]] - [form-builder/field + [form-builder/field-v2 {:field [:bank-account]} "Bank account" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)]) - :entity->text :name - :type "typeahead-v3" - :field [:bank-account] - :spec ::entity/bank-account}]] + [com/entity-typeahead {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)]) + :entity->text :name}]] - #_[form-builder/field - "Yodlee Merchant" - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/yodlee-merchants]) - :entity->text #(str (:name %) " - " (:yodlee-id %)) - :type "typeahead-v3" - :field [:yodlee-merchant]}]] - - [form-builder/field + [form-builder/field-v2 {:field :description + :required? true} [:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ] - [:input.input {:type "text" - :field [:description] - :spec ::entity/description}]] + [:input.input {:type "text"}]] [:div.field [:p.help "Amount"] [left-stack - [form-builder/raw-field - [money-field {:type "money" - :placeholder ">=" - :field [:amount-gte] - :spec ::entity/amount-gte}]] + [form-builder/raw-field-v2 {:field :amount-gte} + [com/money-input {:placeholder ">="}]] "-" - [form-builder/raw-field - [money-field {:type "money" - :placeholder "<=" - :field [:amount-lte] - :spec ::entity/amount-lte}]]]] + [form-builder/raw-field-v2 {:field :amount-lte} + [com/money-input {:placeholder "<="}]]]] [:div.field - [:p.help "Day of Month"] - [:div.control - [:div.columns - [:div.column - [form-builder/raw-field - [:input.input {:type "number" - :placeholder ">=" - :field [:dom-gte] - :spec ::entity/dom-gte - :precision 0 - :step "1"}]]] - [:div.column - [form-builder/raw-field - [:input.input {:type "number" - :placeholder "<=" - :field [:dom-lte] - :spec ::entity/dom-lte - :precision 0 - :step "1"}]]]]]] + [:p.help "Day of month"] + [left-stack + [form-builder/raw-field-v2 {:field :dom-gte} + [com/number-input {:placeholder ">=" + :style {:width "7em"}}]] + "-" + [form-builder/raw-field-v2 {:field :dom-lte} + [com/number-input {:placeholder "<=" + :style {:width "7em"}}]]]] + [:h2.title.is-4 "Outcomes"] - [form-builder/field "Assign Vendor" - [search-backed-typeahead {:search-query (fn [i] + [form-builder/field-v2 {:field :vendor} + "Assign Vendor" + [com/search-backed-typeahead {:search-query (fn [i] [:search_vendor {:query i} - [:name :id]]) - :type "typeahead-v3" - :field [:vendor]}]] + [:name :id]])}]] - [form-builder/raw-field - [expense-accounts-field {:type "expense-accounts" - :descriptor "account asssignment" - :percentage-only? true - :client (:client data) - :locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client-or-bank-account (:id (:client data)) (:id (:bank-account data))])) - :max 100 - :field [:accounts]}]] + [form-builder/field-v2 {:field :accounts} + "Accounts" + [expense-accounts-field-v2 {:descriptor "account asssignment" + :percentage-only? true + :client (:client data) + :locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client-or-bank-account (:id (:client data)) (:id (:bank-account data))])) + :max 100}]] - [form-builder/field + [form-builder/field-v2 {:field :transaction-approval-status} "Approval Status" - [button-radio - {:type "button-radio" - :field [:transaction-approval-status] - :options [[:unapproved "Unapproved"] + [com/button-radio-input + {:options [[:unapproved "Unapproved"] [:requires-feedback "Client Review"] [:approved "Approved"] [:excluded "Excluded from Ledger"]]}]] - [form-builder/field "Note" + [form-builder/field-v2 {:field :note} + "Note" [:input.input {:type "text" - :field [:note] - :placeholder default-note - :spec (s/nilable ::entity/note)}]] + :placeholder default-note}]] [:div.is-divider] [form-builder/error-notification] diff --git a/src/cljs/auto_ap/views/pages/admin/users/form.cljs b/src/cljs/auto_ap/views/pages/admin/users/form.cljs index f2d5fc19..15963f4b 100644 --- a/src/cljs/auto_ap/views/pages/admin/users/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/users/form.cljs @@ -1,18 +1,15 @@ (ns auto-ap.views.pages.admin.users.form (:require - [auto-ap.entities.clients :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.subs :as subs] + [auto-ap.views.components :as com] [auto-ap.views.components.modal :as modal] - [auto-ap.views.components.typeahead :refer [typeahead-v3]] - [auto-ap.views.utils :refer [dispatch-event multi-field with-user]] - [react-popper :refer [usePopper] :as react-popper] - - [re-frame.core :as re-frame] - [react :as react] - [reagent.core :as r])) + [auto-ap.views.utils :refer [dispatch-event with-user]] + [malli.core :as m] + [re-frame.core :as re-frame])) (re-frame/reg-event-fx ::saving @@ -37,41 +34,52 @@ {:dispatch [::modal/modal-closed]})) +(def client-schema + (m/schema [:map [:client schema/reference]])) + +(def user-schema + (m/schema + [:map + [:name schema/not-empty-string] + [:role [:enum :none :user :manager :power_user :admin]] + [:clients {:optional true} + [:maybe + [:sequential client-schema]]]])) (defn form [] (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form]) clients @(re-frame/subscribe [::subs/clients])] + (println data) [:<> [form-builder/builder {:submit-event [::saving] - :id ::form} - [form-builder/field + :id ::form + :schema user-schema} + [form-builder/field-v2 {:required? true + :field :name} "Name" - [:input.input {:type "text" - :field [:name] - :spec ::entity/name}]] - [:div.field - [:p.help "Role"] - [:div.control - [:div.select - [form-builder/raw-field - [:select {:type "select" - :field [:role]} - [:option {:value ":none"} "None"] - [:option {:value ":user"} "User"] - [:option {:value ":manager"} "Manager"] - [:option {:value ":power_user"} "Power User"] - [:option {:value ":admin"} "Admin"]]]]]] - (when (#{":user" ":manager" ":power_user"} (:role data)) - [form-builder/field + [:input.input {:type "text"}]] + [form-builder/field-v2 {:required? true + :field :role} + "Role" + [com/select-field {:options [[:none "None"] + [:user "User"] + [:manager "Manager"] + [:power_user "Power User"] + [:admin "Admin"]] + :allow-nil? false + :keywordize? true}]] + (when (#{:user :manager :power_user} (:role data)) + [form-builder/field-v2 {:field :clients} "Client" - [multi-field {:type "multi-field" - :field [:clients] - :template [[typeahead-v3 {:entities clients - :entity->text :name - :style {:width "13em"} - :type "typeahead-v3" - :field [:client]}]]}]]) + [com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :client} + [com/entity-typeahead + {:entities clients + :entity->text :name + :style {:width "13em"}}]]] + :key-fn :id + :schema [:sequential client-schema] + :new-text "Grant access to client"}]]) [form-builder/hidden-submit-button]]])) @@ -79,7 +87,7 @@ ::editing (fn [{:keys [db]} [_ d]] {:db (-> db - (forms/start-form ::form (update d :clients #(map (fn [x] {:client x}) %)))) + (forms/start-form ::form (update d :clients #(mapv (fn [x] {:client x :id (random-uuid)}) %)))) :dispatch [::modal/modal-requested {:title (str "Edit user " (:name d)) :body [form] :cancel? false diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index 7cb88925..e2502f58 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -622,3 +622,17 @@ second base64/decodeString)] (js->clj (.parse js/JSON json) :keywordize-keys true))) + +(defn coerce-float [f] + (cond (str/blank? f) + nil + + (float? f) + f + + (and (string? f) + (not (js/Number.isNaN (js/parseFloat f)))) + (js/parseFloat f) + + :else + nil))