From 825443ef2c4724fc5478a9f306a69222ef7be6b8 Mon Sep 17 00:00:00 2001 From: Bryce Date: Mon, 23 Oct 2023 10:57:35 -0700 Subject: [PATCH] Made accounts page look great --- src/clj/auto_ap/ssr/admin/accounts.clj | 235 +++++++----- .../auto_ap/ssr/admin/transaction_rules.clj | 347 +++++++++--------- src/clj/auto_ap/ssr/components/buttons.clj | 4 +- src/clj/auto_ap/ssr/hx.clj | 14 + src/clj/auto_ap/ssr/utils.clj | 2 + 5 files changed, 337 insertions(+), 265 deletions(-) diff --git a/src/clj/auto_ap/ssr/admin/accounts.clj b/src/clj/auto_ap/ssr/admin/accounts.clj index e10eb667..ca2af3b1 100644 --- a/src/clj/auto_ap/ssr/admin/accounts.clj +++ b/src/clj/auto_ap/ssr/admin/accounts.clj @@ -7,6 +7,7 @@ audit-transact conn merge-query + pull-attr pull-many query2]] [auto-ap.query-params :as query-params] @@ -17,17 +18,19 @@ [auto-ap.ssr.components :as com] [auto-ap.ssr.form-cursor :as fc] [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.hx :as hx] [auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]] [auto-ap.ssr.svg :as svg] [auto-ap.ssr.utils :refer [apply-middleware-to-all-handlers entity-id + field-validation-error + form-validation-error html-response many-entity ref->enum-schema ref->select-options temp-id - validation-error wrap-form-4xx-2 wrap-schema-decode]] [bidi.bidi :as bidi] @@ -181,7 +184,28 @@ (= :post request-method) (assoc :db/id "new")) _ (cond (= :post request-method) (when-let [extant (seq (dc/q '[:find ?x :in $ ?nc :where [?x :account/numeric-code ?nc]] (dc/db conn) (:account/numeric-code entity)))] - (validation-error (format "The code %d is already in use." (:account/numeric-code entity))))) + (field-validation-error (format "The code %d is already in use." (:account/numeric-code entity)) + [:account/numeric-code] + :form form-params)) + ) + ;; TODO the following would work better if the schema was hydrated automatically with needed values + _ (some->> form-params + :account/client-overrides + (group-by :account-client-override/client) + (filter (fn [[client overrides]] + (> (count overrides) 1))) + (map first) + seq + (#(form-validation-error (format "Client(s) %s have more than one override." + (str/join ", " + (map (fn [client] + (format "'%s'" (pull-attr (dc/db conn) + :client/name + (-> client))) + ) %))) + :form form-params)) + + ) {:keys [tempids]} (audit-transact [[:upsert-entity (cond-> entity (:account/numeric-code entity) (assoc :account/code (str (:account/numeric-code entity))))]] (:identity request)) @@ -213,11 +237,21 @@ ;; TODO use cursor ;; TODO index based list not dbid +;; TODO lots of weird edge cases with indexes +;; for example, what happens when you have 0, 1, 2, but then delete 1? +;; what happens when you delete 2 but then add another one? +;; preference is that the field parsing logic does better grouping of what +;; goes with what, building index on the server side +;; not needing to pass index in + (defn client-override* [override] - [:div.flex.gap-2.mb-2.client-override + [:div.flex.gap-2.mb-2.client-override (-> {"x-ref" "p" + :data-key "show" + } + hx/alpine-mount-then-appear) [:div.w-96 (fc/with-field :db/id - (com/hidden {:name (fc/field-name) + (com/hidden {:name (fc/field-name) :value (fc/field-value)})) (fc/with-field :account-client-override/client (com/validated-field {:errors (fc/field-errors)} @@ -226,15 +260,17 @@ :url (bidi/path-for ssr-routes/only-routes :company-search) :value (fc/field-value) - :value-fn :db/id ;; TODO hydration something here - :content-fn :client/name})))] + :value-fn (some-fn :db/id identity) ;; TODO better hydration + :content-fn (fn [value] + (:client/name (cond->> value + (nat-int? value) (dc/pull (dc/db conn) [:client/name]))))})))] (fc/with-field :account-client-override/name [:div.w-96 (com/validated-field {:errors (fc/field-errors)} (com/text-input {:name (fc/field-name) :class "w-full" :value (fc/field-value)}))]) - [:div (com/a-icon-button {"_" (hiccup/raw "on click halt the event then transition the closest <.client-override />'s opacity to 0 then remove closest <.client-override />")} svg/x)]]) + [:div (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)]]) ;; TODO each form: ;; elimante typeahead1 @@ -246,94 +282,108 @@ ;; componentize ;; ensure all dependency oriented stuff works the same way ;; make sure that "new row index" stuff works ok +;; TODO figure out when hx-targets are decided +;; ensure that adding a new one results in a new row -(defn dialog* [& {:keys [account form-params form-errors]}] - (com/modal - {} - [:form#edit-form (merge {:hx-ext "response-targets" - :hx-swap "outerHTML swap:300ms" - :hx-target-400 "#form-errors .error-content"} - form-params) - [:fieldset {:class "hx-disable"} - (com/modal-card - {} - [:div.flex [:div.p-2 "Account"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:account/numeric-code account) " - " (:account/name account)]] +(defn dialog* [& {:keys [entity form-params form-errors]}] + (fc/start-form entity form-errors + [:div {:x-data (hx/json {"accountName" (:account/name entity) + "accountCode" (:account/numeric-code entity)})} + (com/modal + {} + [:form#edit-form (merge {:hx-ext "response-targets" + :hx-swap "outerHTML swap:300ms" + :hx-target-400 "#form-errors .error-content"} + form-params) + [:fieldset {:class "hx-disable"} + (com/modal-card + {} + [:div.flex [:div.p-2 "Account"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 + [:span {:x-text "accountCode"}] + " - " + [:span {:x-text "accountName"}]]] + [:div.space-y-1 + (when-let [id (:db/id entity)] + (com/hidden {:name "db/id" + :value id})) - (fc/start-form - account form-errors - [:div.space-y-1 - (when-let [id (:db/id account)] - (com/hidden {:name "db/id" - :value id})) + (fc/with-field :account/numeric-code + (if (nil? (:db/id entity)) + (com/validated-field {:label "Numeric code" + :errors (fc/field-errors)} + (com/text-input {:name (fc/field-name) + :x-model "accountCode" + :autofocus true + :class "w-32"})) + (com/hidden {:name (fc/field-name) + :value (fc/field-value)}))) + (fc/with-field :account/name + (com/validated-field {:label "Name" + :errors (fc/field-errors)} + (com/text-input {:name (fc/field-name) + :x-model "accountName" - (fc/with-field :account/numeric-code - (when (nil? (fc/field-value)) - (com/validated-field {:label "Numeric code" - :errors (fc/field-errors)} - (com/text-input {:name (fc/field-name) - :autofocus true - :class "w-32"})))) - (fc/with-field :account/name - (com/validated-field {:label "Name" - :errors (fc/field-errors)} - (com/text-input {:name (fc/field-name) - :autofocus true - :class "w-32" - :value (fc/field-value)}))) - (fc/with-field :account/type - (com/validated-field {:label "Account Type" - :errors (fc/field-errors)} - (com/select {:name (fc/field-name) - :class "w-36" - :id "type" - :value (some-> (fc/field-value) name) - :options (ref->select-options "account-type")}))) - (fc/with-field :account/location - (com/validated-field {:label "Location" - :errors (fc/field-errors)} - (com/text-input {:name (fc/field-name) - :class "w-16" - :value (fc/field-value)}))) + :class "w-64" + :value (fc/field-value)}))) + (fc/with-field :account/type + (com/validated-field {:label "Account Type" + :errors (fc/field-errors)} + (com/select {:name (fc/field-name) + :class "w-36" + :id "type" + :value (some-> (fc/field-value) name) + :options (ref->select-options "account-type")}))) + (fc/with-field :account/location + (com/validated-field {:label "Location" + :errors (fc/field-errors)} + (com/text-input {:name (fc/field-name) + :class "w-16" + :value (fc/field-value)}))) - (fc/with-field :account/invoice-allowance - (com/validated-field {:label "Invoice Allowance" - :errors (fc/field-errors)} - (com/select {:name (fc/field-name) - :value (some-> (fc/field-value) name) - :class "w-36" - :options (ref->select-options "allowance")}))) - (fc/with-field :account/vendor-allowance - (com/validated-field {:label "Vendor Allowance" - :errors (fc/field-errors)} - (com/select {:name (fc/field-name) - :class "w-36" - :value (some-> (fc/field-value) name) - :options (ref->select-options "allowance")}))) - (fc/with-field :account/applicability - (com/validated-field {:label "Applicability" - :errors (fc/field-errors)} - (com/select {:name (fc/field-name) - :class "w-36" - :value (some-> (fc/field-value) name) - :options (ref->select-options "account-applicability")}))) + [:div.flex.flex-wrap.gap-4 + (fc/with-field :account/invoice-allowance + (com/validated-field {:label "Invoice Allowance" + :errors (fc/field-errors)} + (com/select {:name (fc/field-name) + :value (some-> (fc/field-value) name) + :class "w-36" + :options (ref->select-options "allowance")}))) + (fc/with-field :account/vendor-allowance + (com/validated-field {:label "Vendor Allowance" + :errors (fc/field-errors)} + (com/select {:name (fc/field-name) + :class "w-36" + :value (some-> (fc/field-value) name) + :options (ref->select-options "allowance")})))] + (fc/with-field :account/applicability + (com/validated-field {:label "Applicability" + :errors (fc/field-errors)} + (com/select {:name (fc/field-name) + :class "w-36" + :value (some-> (fc/field-value) name) + :options (ref->select-options "account-applicability")}))) - (fc/with-field :account/client-overrides - (com/field {:label "Client Overrides" :id "client-overrides"} - (when (fc/field-value) - (doall - (for [override fc/*current*] - (fc/with-cursor override - (client-override* override))))))) - (com/a-button {:hx-get (bidi/path-for ssr-routes/only-routes - :admin-account-client-override-new) - :hx-vals (hiccup/raw "js:{index: document.getElementById('client-overrides').children.length - 1}") - :hx-target "#client-overrides" - :hx-swap "beforeend"} - "New override") - [:div#form-errors [:span.error-content]]]) - (com/validated-save-button {:errors []} ;; TODO - "Save account"))]])) + (fc/with-field :account/client-overrides + (com/field {:label "Client Overrides" :id "client-overrides"} + (when (fc/field-value) + (doall + (for [override fc/*current*] + (fc/with-cursor override + (client-override* override))))))) + (com/a-button {:hx-get (bidi/path-for ssr-routes/only-routes + :admin-account-client-override-new) + :hx-vals (hiccup/raw "js:{index: document.getElementById('client-overrides').children.length - 1}") + :hx-target "#client-overrides" + :hx-swap "beforeend"} + "New override") + ] + [:div + [:div [:div#form-errors (when (:errors fc/*form-errors*) + [:span.error-content + (com/errors {:errors (:errors fc/*form-errors*)})])]] + (com/validated-save-button {:errors (seq form-errors)} + "Save account")])]])])) (defn new-client-override [{ {:keys [index]} :query-params}] (let [index (or index 0) @@ -346,14 +396,15 @@ (defn account-edit-dialog [request] (let [account (some-> request :route-params :db/id (#(dc/pull (dc/db conn) default-read %)))] - (html-response (dialog* :account account + (html-response (dialog* :entity account :form-params {:hx-put (str (bidi/path-for ssr-routes/only-routes :admin-account-edit-save))}) :headers {"hx-trigger" "modalopen"}))) (defn account-new-dialog [_] - (html-response (dialog* :account nil + (html-response (dialog* :entity {} + :form-errors {} :form-params {:hx-post (str (bidi/path-for ssr-routes/only-routes :admin-account-new-save))}) :headers {"hx-trigger" "modalopen"})) @@ -362,7 +413,7 @@ ;; TODO hydration ;; TODO consistency of error handling and passing, on all form examples (let [entity (some-> request :last-form)] - (html-response (dialog* :account entity + (html-response (dialog* :entity entity :form-errors (:form-errors request) :form-params (if (:db/id entity) {:hx-put (str (bidi/path-for ssr-routes/only-routes diff --git a/src/clj/auto_ap/ssr/admin/transaction_rules.clj b/src/clj/auto_ap/ssr/admin/transaction_rules.clj index 972ec8a6..93ceadb2 100644 --- a/src/clj/auto_ap/ssr/admin/transaction_rules.clj +++ b/src/clj/auto_ap/ssr/admin/transaction_rules.clj @@ -401,189 +401,192 @@ ;; TODO dialog is no longer closeable (defn dialog* [& {:keys [entity form-params form-errors]}] - (com/modal - {:modal-class "max-w-2xl"} + (fc/start-form entity form-errors + (com/modal + {:modal-class "max-w-2xl"} - [:form#edit-form (merge {:hx-ext "response-targets" - :hx-swap "outerHTML swap:300ms" - :hx-target "#modal-holder" ;; TODO sort - :hx-target-400 "#form-errors .error-content" - :x-trap "true" - :class "group/form"} - form-params) - (com/modal-card - {} - [:div.flex [:div.p-2 "Transaction Rule"]] - [:fieldset {:class "hx-disable" - :hx-disinherit "hx-target" ;; TODO why disinherit - :x-data (hx/json {:clientId (or (:db/id (:transaction-rule/client entity)) - (:transaction-rule/client entity))})} + [:form#edit-form (merge {:hx-ext "response-targets" + :hx-swap "outerHTML swap:300ms" + :hx-target "#modal-holder" ;; TODO sort + :hx-target-400 "#form-errors .error-content" + :x-trap "true" + :class "group/form"} + form-params) + (com/modal-card + {} + [:div.flex [:div.p-2 "Transaction Rule"]] + [:fieldset {:class "hx-disable" + :hx-disinherit "hx-target" ;; TODO why disinherit + :x-data (hx/json {:clientId (or (:db/id (:transaction-rule/client entity)) + (:transaction-rule/client entity))})} - (fc/start-form entity form-errors - [:div.space-y-1 - (when-let [id (:db/id entity)] - (com/hidden {:name "db/id" - :value id})) - (fc/with-field :transaction-rule/description - (com/validated-field {:label "Description" - :errors (fc/field-errors)} - (com/text-input {:name (fc/field-name) - :error? (fc/error?) - :x-init "$el.focus()" - :placeholder "HOME DEPOT" - :class "w-96" - :value (fc/field-value)}))) - [:div.filters {:x-data (hx/json {:clientFilter (boolean (fc/field-value (:transaction-rule/client fc/*current*))) - :bankAccountFilter (boolean (fc/field-value (:transaction-rule/bank-account fc/*current*))) - :amountFilter (boolean (or (fc/field-value (:transaction-rule/amount-gte fc/*current*)) - (fc/field-value (:transaction-rule/amount-lte fc/*current*)))) - :domFilter (boolean (or (fc/field-value (:transaction-rule/dom-gte fc/*current*)) - (fc/field-value (:transaction-rule/dom-lte fc/*current*))))})} + [:div.space-y-1 + (when-let [id (:db/id entity)] + (com/hidden {:name "db/id" + :value id})) + (fc/with-field :transaction-rule/description + (com/validated-field {:label "Description" + :errors (fc/field-errors)} + (com/text-input {:name (fc/field-name) + :error? (fc/error?) + :x-init "$el.focus()" + :placeholder "HOME DEPOT" + :class "w-96" + :value (fc/field-value)}))) + [:div.filters {:x-data (hx/json {:clientFilter (boolean (fc/field-value (:transaction-rule/client fc/*current*))) + :bankAccountFilter (boolean (fc/field-value (:transaction-rule/bank-account fc/*current*))) + :amountFilter (boolean (or (fc/field-value (:transaction-rule/amount-gte fc/*current*)) + (fc/field-value (:transaction-rule/amount-lte fc/*current*)))) + :domFilter (boolean (or (fc/field-value (:transaction-rule/dom-gte fc/*current*)) + (fc/field-value (:transaction-rule/dom-lte fc/*current*))))})} - [:div.flex.gap-2.mb-2 - (com/a-button {"@click" "clientFilter=true" - "x-show" "!clientFilter"} "Filter client") - (com/a-button {"@click" "bankAccountFilter=true" - "x-show" "clientFilter && !bankAccountFilter"} "Filter bank account") - (com/a-button {"@click" "amountFilter=true" - "x-show" "!amountFilter"} "Filter amount") - (com/a-button {"@click" "domFilter=true" - "x-show" "!domFilter"} "Filter day of month")] - (fc/with-field :transaction-rule/client + [:div.flex.gap-2.mb-2 + (com/a-button {"@click" "clientFilter=true" + "x-show" "!clientFilter"} "Filter client") + (com/a-button {"@click" "bankAccountFilter=true" + "x-show" "clientFilter && !bankAccountFilter"} "Filter bank account") + (com/a-button {"@click" "amountFilter=true" + "x-show" "!amountFilter"} "Filter amount") + (com/a-button {"@click" "domFilter=true" + "x-show" "!domFilter"} "Filter day of month")] + (fc/with-field :transaction-rule/client - (com/validated-field - (-> {:label "Client" - :errors (fc/field-errors) - :x-show "clientFilter"} - (hx/alpine-appear)) - [:div.w-96 - (com/typeahead-2 {:name (fc/field-name) - :error? (fc/error?) - :class "w-96" - :placeholder "Search..." - :url (bidi/path-for ssr-routes/only-routes :company-search) - :x-model "clientId" - :value (fc/field-value) - :value-fn (some-fn :db/id identity) - :content-fn (fn [c] (cond->> c - (nat-int? c) (dc/pull (dc/db conn) '[:client/name]) - true :client/name))})])) + (com/validated-field + (-> {:label "Client" + :errors (fc/field-errors) + :x-show "clientFilter"} + (hx/alpine-appear)) + [:div.w-96 + (com/typeahead-2 {:name (fc/field-name) + :error? (fc/error?) + :class "w-96" + :placeholder "Search..." + :url (bidi/path-for ssr-routes/only-routes :company-search) + :x-model "clientId" + :value (fc/field-value) + :value-fn (some-fn :db/id identity) + :content-fn (fn [c] (cond->> c + (nat-int? c) (dc/pull (dc/db conn) '[:client/name]) + true :client/name))})])) - (fc/with-field :transaction-rule/bank-account - (com/validated-field - (-> {:label "Bank Account" - :errors (fc/field-errors) - :x-show "bankAccountFilter"} - hx/alpine-appear) - [:div.w-96 - [:div#bank-account-changer {:hx-get (bidi/path-for ssr-routes/only-routes :bank-account-typeahead) - :hx-trigger "changed" - :hx-target "next *" - :hx-include "#bank-account-changer" - :hx-swap "innerHTML" + (fc/with-field :transaction-rule/bank-account + (com/validated-field + (-> {:label "Bank Account" + :errors (fc/field-errors) + :x-show "bankAccountFilter"} + hx/alpine-appear) + [:div.w-96 + [:div#bank-account-changer {:hx-get (bidi/path-for ssr-routes/only-routes :bank-account-typeahead) + :hx-trigger "changed" + :hx-target "next *" + :hx-include "#bank-account-changer" + :hx-swap "innerHTML" - :hx-vals (format "js:{name: '%s', 'client-id': event.detail.clientId}" (fc/field-name)) - :x-init "$watch('clientId', cid => $dispatch('changed', $data))"}] + :hx-vals (format "js:{name: '%s', 'client-id': event.detail.clientId}" (fc/field-name)) + :x-init "$watch('clientId', cid => $dispatch('changed', $data))"}] - (bank-account-typeahead* {:client-id ((some-fn :db/id identity) (:transaction-rule/client entity)) - :name (fc/field-name) - :value (fc/field-value)})])) + (bank-account-typeahead* {:client-id ((some-fn :db/id identity) (:transaction-rule/client entity)) + :name (fc/field-name) + :value (fc/field-value)})])) - (com/field (-> {:label "Amount" - :x-show "amountFilter"} - hx/alpine-appear) - [:div.flex.gap-2 - (fc/with-field :transaction-rule/amount-gte - [:div.flex.flex-col - (com/money-input {:name (fc/field-name) - :placeholder ">=" - :class "w-24" - :value (fc/field-value)}) - (com/errors {:errors (fc/field-errors)})]) - (fc/with-field :transaction-rule/amount-lte - [:div.flex.flex-col - (com/money-input {:name (fc/field-name) - :placeholder "<=" - :class "w-24" - :value (fc/field-value)}) - (com/errors {:errors (fc/field-errors)})])]) + (com/field (-> {:label "Amount" + :x-show "amountFilter"} + hx/alpine-appear) + [:div.flex.gap-2 + (fc/with-field :transaction-rule/amount-gte + [:div.flex.flex-col + (com/money-input {:name (fc/field-name) + :placeholder ">=" + :class "w-24" + :value (fc/field-value)}) + (com/errors {:errors (fc/field-errors)})]) + (fc/with-field :transaction-rule/amount-lte + [:div.flex.flex-col + (com/money-input {:name (fc/field-name) + :placeholder "<=" + :class "w-24" + :value (fc/field-value)}) + (com/errors {:errors (fc/field-errors)})])]) - (com/field (-> {:label "Day of month" - :x-show "domFilter"} - hx/alpine-appear) - [:div.flex.gap-2 - (fc/with-field :transaction-rule/dom-gte - (com/validated-field - {:errors (fc/field-errors)} - (com/int-input {:name (fc/field-name) - :placeholder ">=" - :class "w-24" - :value (fc/field-value)}))) - (fc/with-field :transaction-rule/dom-lte - (com/validated-field - {:errors (fc/field-errors)} - (com/int-input {:name (fc/field-name) - :placeholder ">=" - :class "w-24" - :value (fc/field-value)})))])] + (com/field (-> {:label "Day of month" + :x-show "domFilter"} + hx/alpine-appear) + [:div.flex.gap-2 + (fc/with-field :transaction-rule/dom-gte + (com/validated-field + {:errors (fc/field-errors)} + (com/int-input {:name (fc/field-name) + :placeholder ">=" + :class "w-24" + :value (fc/field-value)}))) + (fc/with-field :transaction-rule/dom-lte + (com/validated-field + {:errors (fc/field-errors)} + (com/int-input {:name (fc/field-name) + :placeholder ">=" + :class "w-24" + :value (fc/field-value)})))])] - [:h2.text-lg "Outcomes"] - (fc/with-field :transaction-rule/vendor - (com/validated-field {:label "Assign Vendor" - :errors (fc/field-errors)} - [:div.w-96 - (com/typeahead-2 {:name (fc/field-name) - :placeholder "Search..." - :url (bidi/path-for ssr-routes/only-routes :vendor-search) - :id (str "form-vendor-search") - :class "w-96" - :value (fc/field-value) - :value-fn (some-fn :db/id identity) - :content-fn (some-fn :vendor/name #(pull-attr (dc/db conn) :vendor/name %))})])) + [:h2.text-lg "Outcomes"] + (fc/with-field :transaction-rule/vendor + (com/validated-field {:label "Assign Vendor" + :errors (fc/field-errors)} + [:div.w-96 + (com/typeahead-2 {:name (fc/field-name) + :placeholder "Search..." + :url (bidi/path-for ssr-routes/only-routes :vendor-search) + :id (str "form-vendor-search") + :class "w-96" + :value (fc/field-value) + :value-fn (some-fn :db/id identity) + :content-fn (some-fn :vendor/name #(pull-attr (dc/db conn) :vendor/name %))})])) - (fc/with-field :transaction-rule/accounts - (list - (com/data-grid {:headers [(com/data-grid-header {} "Account") - (com/data-grid-header {:class "w-32"} "Location") - (com/data-grid-header {:class "w-16"} "%") - (com/data-grid-header {:class "w-16"})] - :id "transaction-rule-account-table"} - (when @fc/*current* - (doall (for [tra fc/*current*] - (fc/with-cursor tra - (transaction-rule-account-row* entity tra))))) - (com/data-grid-row - {:class "new-row"} - (com/data-grid-cell {:colspan 4 - :class "bg-gray-100"} - [:div.flex.justify-center - (com/a-button {:hx-get (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-new-account) - :color :secondary - :hx-include "#edit-form" - :hx-ext "rename-params" - ;; TODO - :hx-rename-params-ex (cheshire/generate-string {"transaction-rule/client" "client-id" - "index" "index"}) - :hx-vals (hiccup/raw "js:{index: countRows(\"#transaction-rule-account-table\")}") - :hx-target "#edit-form .new-row" - :hx-swap "beforebegin"} - "New account")]))) - (com/errors {:errors (fc/field-errors)}))) + (fc/with-field :transaction-rule/accounts + (list + (com/data-grid {:headers [(com/data-grid-header {} "Account") + (com/data-grid-header {:class "w-32"} "Location") + (com/data-grid-header {:class "w-16"} "%") + (com/data-grid-header {:class "w-16"})] + :id "transaction-rule-account-table"} + (when @fc/*current* + (doall (for [tra fc/*current*] + (fc/with-cursor tra + (transaction-rule-account-row* entity tra))))) + (com/data-grid-row + {:class "new-row"} + (com/data-grid-cell {:colspan 4 + :class "bg-gray-100"} + [:div.flex.justify-center + (com/a-button {:hx-get (bidi/path-for ssr-routes/only-routes + :admin-transaction-rule-new-account) + :color :secondary + :hx-include "#edit-form" + :hx-ext "rename-params" + ;; TODO + :hx-rename-params-ex (cheshire/generate-string {"transaction-rule/client" "client-id" + "index" "index"}) + :hx-vals (hiccup/raw "js:{index: countRows(\"#transaction-rule-account-table\")}") + :hx-target "#edit-form .new-row" + :hx-swap "beforebegin"} + "New account")]))) + (com/errors {:errors (fc/field-errors)}))) - (fc/with-field :transaction-rule/transaction-approval-status - (com/validated-field {:label "Approval status" - :errors (fc/field-errors)} - (com/radio {:options (ref->radio-options "transaction-approval-status") - :value (fc/field-value) - :name (fc/field-name) - :size :small - :orientation :horizontal}))) + (fc/with-field :transaction-rule/transaction-approval-status + (com/validated-field {:label "Approval status" + :errors (fc/field-errors)} + (com/radio {:options (ref->radio-options "transaction-approval-status") + :value (fc/field-value) + :name (fc/field-name) + :size :small + :orientation :horizontal}))) - [:div#form-errors (when (:errors fc/*form-errors*) - [:span.error-content - (com/errors {:errors (:errors fc/*form-errors*)})])]])] - (com/validated-save-button {:errors form-errors} "Save rule"))])) + ;; TODO componentize + ]] + [:div + [:div#form-errors (when (:errors fc/*form-errors*) + [:span.error-content + (com/errors {:errors (:errors fc/*form-errors*)})])] + (com/validated-save-button {:errors form-errors} "Save rule")])]))) ;; TODO Should forms have some kind of helper to render an individual field? saving @@ -592,8 +595,8 @@ ;; pull out the single field to swap (defn new-account [{{:keys [client-id index]} :query-params}] - (let [index (or index 0) ;; TODO schema decode is not working - transaction-rule {:transaction-rule/client (dc/pull (dc/db conn) '[:client/name :client/locations :db/id] + (let [index (or index 0) ;; TODO schema decode is not working + transaction-rule {:transaction-rule/client (dc/pull (dc/db conn) '[:client/name :client/locations :db/id] client-id) :transaction-rule/accounts (conj (into [] (repeat index {} )) {:db/id (str (java.util.UUID/randomUUID)) diff --git a/src/clj/auto_ap/ssr/components/buttons.clj b/src/clj/auto_ap/ssr/components/buttons.clj index 076945f9..9df54b67 100644 --- a/src/clj/auto_ap/ssr/components/buttons.clj +++ b/src/clj/auto_ap/ssr/components/buttons.clj @@ -134,7 +134,9 @@ (defn a-icon-button- [params & children] (into - [:a (update params :class str " inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100") + [:a (-> params (update :class str " inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100" + ) + (update :href #(or % ""))) [:div.h-4.w-4 children]])) (defn save-button- [params & children] diff --git a/src/clj/auto_ap/ssr/hx.clj b/src/clj/auto_ap/ssr/hx.clj index cb5e5c00..895d024e 100644 --- a/src/clj/auto_ap/ssr/hx.clj +++ b/src/clj/auto_ap/ssr/hx.clj @@ -24,3 +24,17 @@ "x-transition:enter" "transition duration-500" "x-transition:enter-start" "opacity-0" "x-transition:enter-end" "opacity-100")) + +(defn alpine-disappear [m] + (assoc m + "x-transition:leave" "transition duration-500" + "x-transition:leave-start" "opacity-100" + "x-transition:leave-end" "opacity-0")) + +(defn alpine-mount-then-appear [{:keys [data-key] :as params}] + (merge (-> {"x-data" (json {data-key false}) + "x-init" (format "$nextTick(() => %s=true)" (name data-key)) + "x-show" (name data-key)} + alpine-appear + alpine-disappear) + (dissoc params :data-key))) diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index 6529aebb..b80c08bd 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -146,6 +146,8 @@ (defn keyword->str [k] (subs (str k) 1)) + +;; TODO need to remove or at least remove usages as the form is not included (defn validation-error [m & {:as data}] (throw+ (ex-info m (merge data {:type :validation}))))