From 899efe0efac4c01e7c6b946f8823aa4dbf5169e7 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Wed, 20 Jul 2022 08:10:30 -0700 Subject: [PATCH] more progress for client fdorm. --- src/cljs/auto_ap/forms/builder.cljs | 94 ++++++++++--------- src/cljs/auto_ap/schema.cljs | 1 + src/cljs/auto_ap/views/components.cljs | 5 +- src/cljs/auto_ap/views/components/multi.cljs | 42 +++++---- .../views/pages/admin/clients/form.cljs | 88 ++++++++++------- 5 files changed, 130 insertions(+), 100 deletions(-) diff --git a/src/cljs/auto_ap/forms/builder.cljs b/src/cljs/auto_ap/forms/builder.cljs index c5753e0c..ecb66368 100644 --- a/src/cljs/auto_ap/forms/builder.cljs +++ b/src/cljs/auto_ap/forms/builder.cljs @@ -44,19 +44,21 @@ (defn builder [{:keys [value on-change can-submit data-sub error-messages change-event submit-event id fullwidth? schema validation-error-string]}] (when (and change-event on-change) (throw "Either the form is to be managed by ::forms, or it should have value and on-change passed in")) - (let [data-sub (or data-sub [::forms/form id]) - change-event (when-not on-change - (or change-event [::forms/change id])) + (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?] 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) - true) - problems (when schema - (m/explain schema data))] + data (or value data) + status @(re-frame/subscribe [::status/single id]) + can-submit (if can-submit @(re-frame/subscribe can-submit) + true) + ;; TODO ONLY validate on blur + problems (when schema + + (m/explain schema data))] (r/create-element Provider #js {:value #js {:can-submit can-submit :error-messages (or error-messages - nil) + nil) :on-change on-change :change-event change-event :blur-event [::forms/visited id] @@ -155,14 +157,14 @@ ["visited" "attempted-submit?" "problems" "error-messages"] (fn [visited attempted-submit? problems error-messages] (consume FormScopeConsumer - ["form-scope"] - (fn [form-scope] + ["scope"] + (fn [scope] (let [full-field-path (cond (sequential? field) - (into form-scope field) + (into scope field) field - (conj form-scope field) + (conj scope field) :else nil) @@ -181,43 +183,43 @@ ["visited" "attempted-submit?" "data" "on-change" "change-event" "blur-event" "problems"] (fn [visited attempted-submit? data on-change change-event blur-event problems] (consume FormScopeConsumer - ["form-scope"] - (fn [form-scope] + ["scope"] + (fn [scope] (update child 1 (fn [child-props] - (let [ - full-field-path (cond - (sequential? field) - (into form-scope field) + (let [ + full-field-path (cond + (sequential? field) + (into scope field) - field - (conj form-scope field) - - :else - nil) - visited? (get visited full-field-path) - value (get-in data full-field-path)] - (-> child-props - (assoc :on-change - (if on-change - (partial form-change-handler data full-field-path on-change) - (partial change-handler full-field-path change-event)) - :on-blur (partial blur-handler full-field-path blur-event) - :value value) - (update :class (fn [class] - (str class - (cond - (and (not visited?) (not attempted-submit?)) - "" - (not (valid-field? problems full-field-path)) - " is-danger" - - value - "is-success" + field + (conj scope field) :else - "")))))))))))))) + nil) + visited? (get visited full-field-path) + value (get-in data full-field-path)] + (-> child-props + (assoc :on-change + (if on-change + (partial form-change-handler data full-field-path on-change) + (partial change-handler full-field-path change-event)) + :on-blur (partial blur-handler full-field-path blur-event) + :value value) + (update :class (fn [class] + (str class + (cond + (and (not visited?) (not attempted-submit?)) + "" + (not (valid-field? problems full-field-path)) + " is-danger" + + value + "is-success" + + :else + "")))))))))))))) (defn with-scope [{:keys [scope]}] - (r/create-element FormScopeProvider #js {:value scope} + (r/create-element FormScopeProvider #js {:value #js {:scope scope}} (r/as-element (into [:<>] (r/children (r/current-component)))))) diff --git a/src/cljs/auto_ap/schema.cljs b/src/cljs/auto_ap/schema.cljs index 279868d9..64a276e0 100644 --- a/src/cljs/auto_ap/schema.cljs +++ b/src/cljs/auto_ap/schema.cljs @@ -11,6 +11,7 @@ (def money (m/schema [float? {:error/message "Invalid money"}])) (def not-empty-string (m/schema [:re {:error/message "Required"} #"\S+"])) +(def code-string (m/schema [:re #"[A-Z0-9\-]+"])) (def keyword (m/schema [:fn (fn [d] (keyword? d))])) diff --git a/src/cljs/auto_ap/views/components.cljs b/src/cljs/auto_ap/views/components.cljs index df95d2fb..f358e28b 100644 --- a/src/cljs/auto_ap/views/components.cljs +++ b/src/cljs/auto_ap/views/components.cljs @@ -1,6 +1,7 @@ (ns auto-ap.views.components (:require [reagent.core :as r] - [clojure.string :as str])) + [clojure.string :as str] + [auto-ap.views.components.multi :as multi])) (defn checkbox [{:keys [on-change @@ -32,3 +33,5 @@ [:option {:value nil}]) (for [[k v] options] ^{:key k} [:option {:value k} v])]]]) + +(def multi-field-v2 multi/multi-field-v2) diff --git a/src/cljs/auto_ap/views/components/multi.cljs b/src/cljs/auto_ap/views/components/multi.cljs index dbf56f89..c7afdf01 100644 --- a/src/cljs/auto_ap/views/components/multi.cljs +++ b/src/cljs/auto_ap/views/components/multi.cljs @@ -20,9 +20,9 @@ [:div {:style {:margin-bottom "0.25em"}} (into [appearing-group] (for [[i override] (map vector (range) prop-value) - :let [is-disabled? (if (= false allow-change?) - (not (boolean (::new? override))) - nil)]] + :let [extant? (boolean (key-fn override)) + is-disabled? (boolean (and (= false allow-change?) + extant?))]] ^{:key (or (key-fn override) (::key override))} @@ -31,25 +31,31 @@ (::key override))} [:div.level {:style {:margin-bottom "0.25em"}} [:div.level-left {:style {:padding "0.5em 1em"} - :class (when-not (key-fn override) + :class (when-not extant? "has-background-info-light")} (let [template (if (fn? template) (template override) template)] - (for [[idx template] (map vector (range ) template)] - ^{:key idx} - [:div.level-item - template])) - (when-not disable-remove? - [:div.level-item - [:a.button.level-item - {:disabled is-disabled? - :on-click (fn [] - (on-change (into [] - (for [[idx item] (map vector (range) prop-value) - :when (not= idx i)] - item))))} - [:span.icon [:span.icon-remove]]]])]]])) + [:fieldset.level-left {:disabled is-disabled? + :style {:padding "0.5em 1em"} + :class (when-not extant? + "has-background-info-light")} + (for [[idx template] (map vector (range ) template)] + ^{:key idx} + [:div.level-item + template]) + (when-not (and disable-remove? + extant?) + [:div.level-item + [:a.button.level-item + {:disabled is-disabled? + :on-click (fn [] + (on-change (into [] + (for [[idx item] (map vector (range) prop-value) + :when (not= idx i)] + item))))} + [:span.icon [:span.icon-remove]]]])]) + ]]])) (when-not disable-new? [:button.button.is-outline {:type "button" 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 196fff4e..b6915a11 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -10,6 +10,7 @@ [react-signature-canvas] [auto-ap.views.components.typeahead :refer [typeahead-v3]] [auto-ap.views.components.level :refer [left-stack]] + [auto-ap.views.components :as com] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] [auto-ap.views.utils @@ -24,10 +25,25 @@ [re-frame.core :as re-frame] [reagent.core :as r] [react-signature-canvas] - [vimsical.re-frame.cofx.inject :as inject])) + [vimsical.re-frame.cofx.inject :as inject] + [auto-ap.schema :as schema] + [auto-ap.views.components :as com] + [malli.core :as m])) (def signature-canvas (r/adapt-react-class (.-default react-signature-canvas))) +(def location-schema (m/schema [:map + [:location schema/not-empty-string]])) +(def email-schema [:map + [:email schema/not-empty-string] + [:description schema/not-empty-string]]) + +(def client-schema [:map + [:name schema/not-empty-string] + [:code schema/code-string] + [:locations [:sequential location-schema]] + [:emails [:sequential email-schema]]]) + (defn upload-replacement-button [{:keys [on-change]} text] (let [button (atom nil)] (r/create-class {:display-name "Upload button" @@ -109,7 +125,8 @@ {:id (:id new-client-data), :name (:name new-client-data) :code (:code new-client-data) ;; TODO add validation can't change - :emails (:emails new-client-data) + :emails (map #(select-keys % [:id :email :description]) + (:emails new-client-data)) :square-auth-token (:square-auth-token new-client-data) :square-locations (map (fn [x] @@ -193,13 +210,15 @@ {:id (:id sl) :square-location sl :client-location (:client-location sl)})))) - (update :locations #(mapv (fn [l] {:location l}) %)) + (update :locations #(mapv (fn [l] {:location l + :id (random-uuid)}) %)) (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}) + (map (fn [l] {:location l + :id (random-uuid)}) ls)))) bas))))))})) @@ -535,57 +554,54 @@ (defn general-section [] (let [{new-client :data} @(re-frame/subscribe [::forms/form ::form])] [form-builder/section {:title "General"} - [form-builder/field + [form-builder/field-v2 {:field :name} "Name" [:input.input {:type "text" - :style {:width "20em"} - :field [:name] - :spec ::entity/name}]] - [form-builder/field + :style {:width "20em"}}]] + [form-builder/field-v2 {:field :code} "Client code" [:input.input {:type "code" :style {:width "5em"} - :field :code :disabled (boolean (:id new-client)) :spec ::entity/code}]] - [form-builder/field + [form-builder/field-v2 {:field :locations} "Locations" - [multi-field {:type "multi-field" - :field :locations - :allow-change? false - :template [[:input.input {:field [:location] - :max-length 2 - :style { :width "4em"}}]]}]] + [com/multi-field-v2 {:allow-change? false + :template [[form-builder/raw-field-v2 {:field :location} + [:input.input {:max-length 2 + :style { :width "4em"}}]]] + :disable-remove? true + :key-fn :id + :schema [:sequential location-schema] + :next-key (random-uuid)}]] - [:div.field - [:label.label "Signature"] + [form-builder/vertical-control + "Signature" [signature {:signature-file (:signature-file new-client) :signature-data (:signature-data new-client) :on-change (fn [uri] (re-frame/dispatch [::forms/change ::form [:signature-data] uri]))}]] - [form-builder/field + [form-builder/field-v2 {:field :locked-until} "Locked Until" - [date-picker {:type "date" - :output :cljs-date - :field [:locked-until] - :style {:width "15em"}}]]])) + [date-picker {:output :cljs-date + :style {:width "15em"}}]]])) (defn contacts-section [] [form-builder/section {:title "Contacts"} - [form-builder/field + [form-builder/field-v2 {:field :emails} "Emails (address/description)" - [multi-field {:type "multi-field" - :field :emails - :template [[:input.input {:type "email" - :field [:email] - :placeholder "tom@myspace.com" - :spec ::entity/email}] - [:input.input {:type "text" - :placeholder "Manager" - :field [:description]}]]}]] + [com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :email} + [:input.input {:type "email" + :placeholder "tom@myspace.com"}]] + [form-builder/raw-field-v2 {:field :description} + [:input.input {:type "text" + :placeholder "Manager"}]]] + :key-fn :id + :schema [:sequential email-schema] + :next-key (random-uuid)}]] [form-builder/vertical-control "Address" @@ -735,6 +751,7 @@ :step "0.01"}]] :disable-remove? true}]]]) + (defn form-content [] (let [_ @(re-frame/subscribe [::client]) {new-client :data} @(re-frame/subscribe [::forms/form ::form])] @@ -743,7 +760,8 @@ "new")} [form-builder/builder {:submit-event [::save-new-client ] :id ::form - :fullwidth? false} + :fullwidth? false + :schema client-schema} [general-section] [contacts-section]