progress on invoices.
This commit is contained in:
@@ -468,5 +468,4 @@ table.balance-sheet th.total {
|
||||
background-color: whitesmoke !important;
|
||||
border-color: whitesmoke !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -815,7 +815,7 @@
|
||||
(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")
|
||||
(com/radio-card {:options (ref->radio-options "transaction-approval-status")
|
||||
:value (fc/field-value)
|
||||
:name (fc/field-name)
|
||||
:size :small
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
:placeholder "Cash"
|
||||
:size :small}))
|
||||
(com/field {:label "Type"}
|
||||
(com/radio {:size :small
|
||||
(com/radio-card {:size :small
|
||||
:name "type"
|
||||
:value (:type (:parsed-query-params request))
|
||||
:options [{:value ""
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
(def navbar navbar/navbar-)
|
||||
|
||||
(def page page/page-)
|
||||
(def radio radio/radio-)
|
||||
(def radio-card radio/radio-card-)
|
||||
(def radio-list radio/radio-list-)
|
||||
|
||||
(def pill tags/pill-)
|
||||
(def badge tags/badge-)
|
||||
|
||||
@@ -17,25 +17,31 @@
|
||||
(nil? color)
|
||||
"white"
|
||||
|
||||
(sequential? color)
|
||||
(first color)
|
||||
|
||||
:else
|
||||
color)
|
||||
base-weight (or (when (sequential? color)
|
||||
(second color))
|
||||
500)
|
||||
disabled-weight (when disabled 400)]
|
||||
|
||||
(format " bg-%s-%d hover:bg-%s-%d focus:ring-%s-%d dark:bg-%s-%d dark:hover:bg-%s-%d "
|
||||
base-color
|
||||
(or disabled-weight 500)
|
||||
(or disabled-weight (+ base-weight 0))
|
||||
|
||||
base-color
|
||||
(or disabled-weight 600)
|
||||
(or disabled-weight (+ base-weight 100))
|
||||
|
||||
base-color
|
||||
(or disabled-weight 200)
|
||||
(or disabled-weight (int (* base-weight 0.5)))
|
||||
|
||||
base-color
|
||||
(or disabled-weight 600)
|
||||
(or disabled-weight (+ base-weight 100))
|
||||
|
||||
base-color
|
||||
(or disabled-weight 700)
|
||||
(or disabled-weight (+ base-weight 200))
|
||||
|
||||
)))
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
|
||||
[auto-ap.ssr.utils
|
||||
:refer [ html-response
|
||||
:refer [html-response
|
||||
assert-schema
|
||||
main-transformer
|
||||
modal-response
|
||||
@@ -31,7 +31,7 @@
|
||||
:hx-trigger "submit"
|
||||
:hx-target "this"
|
||||
"x-trap" "true"
|
||||
:class "h-full w-full" })
|
||||
:class "h-full w-full"})
|
||||
|
||||
(defprotocol ModalWizardStep
|
||||
(step-key [this])
|
||||
@@ -57,7 +57,7 @@
|
||||
(hydrate-from-request [this request])
|
||||
(get-current-step [this])
|
||||
(navigate [this step-key])
|
||||
|
||||
|
||||
(form-schema [this])
|
||||
(steps [this])
|
||||
(get-step [this step-key])
|
||||
@@ -197,9 +197,15 @@
|
||||
(defn wrap-ensure-step [handler]
|
||||
(->
|
||||
(fn [{:keys [wizard multi-form-state] :as request}]
|
||||
|
||||
(println "ENSURE STEP")
|
||||
(clojure.pprint/pprint (:step-params (:multi-form-state request)))
|
||||
|
||||
(assert-schema (step-schema (get-current-step wizard)) (:step-params multi-form-state))
|
||||
(handler request))
|
||||
(wrap-form-4xx-2 (fn [{:keys [wizard] :as request}] ;; THIS MAY BE BETTER TO JUST MAKE THE LINEAR WIZARD POPULATE FROM THE REQUEST
|
||||
(println "FINAL")
|
||||
(clojure.pprint/pprint (:step-params (:multi-form-state request)))
|
||||
(html-response
|
||||
(render-wizard wizard request)
|
||||
:headers {"x-transition-type" "none"
|
||||
@@ -223,25 +229,25 @@
|
||||
|
||||
(def next-handler
|
||||
(-> (fn [{:keys [wizard] :as request}]
|
||||
(let [current-step (get-current-step wizard)
|
||||
to-step (:to (:query-params request))
|
||||
wizard (navigate wizard to-step)
|
||||
new-step (get-current-step wizard)
|
||||
transition-type (get-transition-type wizard (step-key current-step) to-step)]
|
||||
(html-response
|
||||
(render-wizard wizard
|
||||
(-> request
|
||||
(assoc :multi-form-state (-> (:multi-form-state request)
|
||||
(merge-multi-form-state)
|
||||
(select-state
|
||||
(edit-path new-step request)
|
||||
(init-step-params- new-step request))))))
|
||||
:headers {"HX-reswap" (when transition-type "outerHTML swap:0.15s")
|
||||
"x-transition-type" (or transition-type "none")})))
|
||||
(wrap-ensure-step)
|
||||
(wrap-schema-enforce :query-schema
|
||||
[:map
|
||||
[:to step-key-schema]])))
|
||||
(let [current-step (get-current-step wizard)
|
||||
to-step (:to (:query-params request))
|
||||
wizard (navigate wizard to-step)
|
||||
new-step (get-current-step wizard)
|
||||
transition-type (get-transition-type wizard (step-key current-step) to-step)]
|
||||
(html-response
|
||||
(render-wizard wizard
|
||||
(-> request
|
||||
(assoc :multi-form-state (-> (:multi-form-state request)
|
||||
(merge-multi-form-state)
|
||||
(select-state
|
||||
(edit-path new-step request)
|
||||
(init-step-params- new-step request))))))
|
||||
:headers {"HX-reswap" (when transition-type "outerHTML swap:0.15s")
|
||||
"x-transition-type" (or transition-type "none")})))
|
||||
(wrap-ensure-step)
|
||||
(wrap-schema-enforce :query-schema
|
||||
[:map
|
||||
[:to step-key-schema]])))
|
||||
|
||||
(def discard-handler
|
||||
(->
|
||||
@@ -263,7 +269,7 @@
|
||||
(def submit-handler
|
||||
(-> (fn [{:keys [wizard multi-form-state] :as request}]
|
||||
(submit wizard (-> request
|
||||
(assoc :multi-form-state (merge-multi-form-state multi-form-state)))))
|
||||
(assoc :multi-form-state (merge-multi-form-state multi-form-state)))))
|
||||
(wrap-ensure-step)))
|
||||
|
||||
(defn default-render-wizard [linear-wizard {:keys [multi-form-state form-errors snapshot current-step] :as request} & {:keys [form-params]}]
|
||||
@@ -313,12 +319,8 @@
|
||||
|
||||
(defn open-wizard-handler [{:keys [wizard current-step] :as request}]
|
||||
(modal-response
|
||||
[:div {:x-data (hx/json {"transitionType" "none"
|
||||
|
||||
}
|
||||
)
|
||||
"@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) { $data.transitionType = event.detail.xhr.getResponseHeader('x-transition-type');}"
|
||||
}
|
||||
[:div {:x-data (hx/json {"transitionType" "none"})
|
||||
"@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) { $data.transitionType = event.detail.xhr.getResponseHeader('x-transition-type');}"}
|
||||
(render-wizard wizard request)]))
|
||||
|
||||
|
||||
@@ -333,6 +335,24 @@
|
||||
(wrap-init-multi-form-state
|
||||
handler
|
||||
(fn parse-multi-form-state [request]
|
||||
(println "HERE WE ARE FULL")
|
||||
(clojure.pprint/pprint (:step-params (:form-params request)))
|
||||
(println "OK NOW")
|
||||
(clojure.pprint/pprint (:step-params (map->MultiStepFormState (mc/decode [:map
|
||||
[:snapshot {:optional true
|
||||
:decode/arbitrary
|
||||
#(clojure.edn/read-string {:readers clj-time.coerce/data-readers
|
||||
:eof nil}
|
||||
%)}
|
||||
[:maybe :any]]
|
||||
[:edit-path {:optional true :decode/arbitrary (fn [z]
|
||||
(clojure.edn/read-string z))} [:maybe [:sequential {:min 0} any?]]]
|
||||
[:step-params {:optional true}
|
||||
[:maybe
|
||||
:any]]]
|
||||
(:form-params request)
|
||||
main-transformer))))
|
||||
|
||||
(map->MultiStepFormState (mc/decode [:map
|
||||
[:snapshot {:optional true
|
||||
:decode/arbitrary
|
||||
@@ -346,4 +366,20 @@
|
||||
[:maybe
|
||||
:any]]]
|
||||
(:form-params request)
|
||||
main-transformer)))))
|
||||
main-transformer)))))
|
||||
|
||||
(comment
|
||||
(def f {"snapshot"
|
||||
"{:invoices [{:invoice_id 17592297837035, :amount 23.0, :invoice {:db/id 17592297837035, :invoice/vendor {:db/id 17592186045722, :vendor/name \"Sysco\"}, :invoice/client {:db/id 17592232555238}, :invoice/outstanding-balance 23.0, :invoice/invoice-number \"702,34\"}} {:invoice_id 17592297837049, :amount 23.0, :invoice {:db/id 17592297837049, :invoice/vendor {:db/id 17592186045722, :vendor/name \"Sysco\"}, :invoice/client {:db/id 17592232555238}, :invoice/outstanding-balance 23.0, :invoice/invoice-number \"80[234234\"}}], :client 17592232555238}",
|
||||
"edit-path" "[]",
|
||||
"current-step" ":payment-details",
|
||||
"mode" "advanced",
|
||||
"step-params"
|
||||
{"invoices"
|
||||
{"0" {"invoice_id" "17592297837035", "amount" "1"},
|
||||
"1" {"invoice_id" "17592297837049", "amount" "23.00"}}}})
|
||||
(mc/decode [:map [:step-params {:optional true} [:maybe :any]]]
|
||||
f
|
||||
main-transformer)
|
||||
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require [auto-ap.ssr.hiccup-helper :as hh]
|
||||
[auto-ap.ssr.hx :as hx]))
|
||||
|
||||
(defn radio- [{:keys [options name title size orientation] :or {size :medium} selected-value :value}]
|
||||
(defn radio-card- [{:keys [options name title size orientation] :or {size :medium} selected-value :value}]
|
||||
[:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title]
|
||||
[:ul {:class (cond-> "w-48 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
(= orientation :horizontal) (-> (hh/add-class "flex gap-2 flex-wrap")
|
||||
@@ -26,6 +26,42 @@
|
||||
(str " " "text-sm"))}
|
||||
(= (cond-> selected-value (keyword? selected-value) clojure.core/name) value) (assoc :checked true))]
|
||||
[:label {:for (str "list-" name "-" value)
|
||||
:class
|
||||
(cond-> "w-full ml-2 font-medium text-gray-900 dark:text-gray-300"
|
||||
(= size :small)
|
||||
(str " " "text-xs py-2")
|
||||
|
||||
(= size :medium)
|
||||
(str " " "text-sm py-3")
|
||||
|
||||
(= orientation :horizontal)
|
||||
(hh/remove-class "w-full"))} content]]])])
|
||||
|
||||
(defn radio-list- [{:keys [options name x-model title size orientation] :or {size :medium} selected-value :value}]
|
||||
[:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title]
|
||||
[:ul {:class (cond-> "w-48 text-sm font-medium text-gray-900"
|
||||
(= orientation :horizontal) (-> (hh/add-class "flex gap-2 flex-wrap")
|
||||
(hh/remove-wildcard ["w-" "rounded-lg" "border" "bg-"])))}
|
||||
(for [{:keys [value content]} options]
|
||||
[:li {:class (cond-> "w-full"
|
||||
(= orientation :horizontal) (-> (hh/remove-wildcard ["w-full" "rounded-"])
|
||||
(hh/add-class "w-auto shrink-0 block px-3")))}
|
||||
[:div {:class (cond-> "flex items-center"
|
||||
(not= orientation :horizontal) (hh/add-class "pl-3"))}
|
||||
[:input (cond-> {:id (str "list-" name "-" value)
|
||||
:x-model x-model
|
||||
:type "radio",
|
||||
:value value
|
||||
:name name
|
||||
:class
|
||||
(cond-> "w-4 h-4 text-blue-600"
|
||||
(= size :small)
|
||||
(str " " "text-xs")
|
||||
|
||||
(= size :medium)
|
||||
(str " " "text-sm"))}
|
||||
(= (cond-> selected-value (keyword? selected-value) clojure.core/name) value) (assoc :checked true))]
|
||||
[:label {:for (str "list-" name "-" value)
|
||||
:class
|
||||
(cond-> "w-full ml-2 font-medium text-gray-900 dark:text-gray-300"
|
||||
(= size :small)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:refer [add-sorter-fields apply-pagination apply-sort-3
|
||||
audit-transact conn merge-query observable-query
|
||||
pull-many]]
|
||||
[auto-ap.graphql.checks :as gq-checks]
|
||||
[auto-ap.graphql.checks :as gq-checks :refer [print-checks-internal]]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client
|
||||
exception->notification
|
||||
extract-client-ids notify-if-locked]]
|
||||
@@ -17,15 +17,18 @@
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
|
||||
[auto-ap.ssr.components.multi-modal :as mm]
|
||||
[auto-ap.ssr.form-cursor :as fc]
|
||||
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
|
||||
[auto-ap.ssr.hiccup-helper :as hh]
|
||||
[auto-ap.ssr.hx :as hx]
|
||||
[auto-ap.ssr.pos.common :refer [date-range-field*]]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils
|
||||
:refer [apply-middleware-to-all-handlers clj-date-schema
|
||||
dissoc-nil-transformer entity-id html-response
|
||||
main-transformer modal-response ref->enum-schema strip
|
||||
wrap-entity wrap-merge-prior-hx wrap-schema-enforce]]
|
||||
main-transformer modal-response money ref->enum-schema
|
||||
strip wrap-entity wrap-merge-prior-hx wrap-schema-enforce]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[clj-time.coerce :as coerce]
|
||||
@@ -33,7 +36,8 @@
|
||||
[datomic.api :as dc]
|
||||
[hiccup.util :as hu]
|
||||
[malli.core :as mc]
|
||||
[malli.transform :as mt]))
|
||||
[malli.transform :as mt]
|
||||
[malli.util :as mut]))
|
||||
|
||||
|
||||
(defn exact-match-id* [request]
|
||||
@@ -310,10 +314,14 @@
|
||||
0)]
|
||||
|
||||
[:div {:hx-target "this"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
::route/pay-wizard)
|
||||
:hx-trigger "click from:#pay-button"
|
||||
:x-data (hx/json {:popper nil
|
||||
:hovering false})
|
||||
"x-init" "popper = Popper.createPopper($refs.button, $refs.tooltip, {placement: 'bottom', strategy: 'fixed', modifiers: [{name: 'preventOverflow'}, {name: 'offset', options: {offset: [0, 10]}}]});"}
|
||||
(com/button {:color :primary
|
||||
:id "pay-button"
|
||||
:disabled (or (= (count (:ids params)) 0)
|
||||
(not= 1 selected-client-count))
|
||||
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
|
||||
@@ -330,11 +338,11 @@
|
||||
|
||||
(str "Pay " (count (:ids params)) " invoices")
|
||||
"Pay")
|
||||
(when (or (= 0 (count ids))
|
||||
(when (or (= 0 (count ids))
|
||||
(> selected-client-count 1))
|
||||
(com/badge {} "!")))
|
||||
[:div (hx/alpine-appear {:x-ref "tooltip"
|
||||
|
||||
|
||||
:x-show "hovering"
|
||||
:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"})
|
||||
(cond
|
||||
@@ -468,17 +476,6 @@
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
|
||||
(comment
|
||||
(mc/decode query-schema {"exact-match-id" "123"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
(mc/decode query-schema {} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
(mc/decode query-schema {"exact-match-id" nil} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
(mc/decode query-schema {"exact-match-id" ""} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
(mc/decode query-schema {"start-date" "12/21/2023"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
(mc/decode query-schema {"payment-type" "food"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
(mc/decode query-schema {"vendor" "87"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
|
||||
|
||||
|
||||
(mc/decode query-schema {"start-date" #inst "2023-12-21T08:00:00.000-00:00"} (mt/transformer main-transformer mt/strip-extra-keys-transformer)))
|
||||
|
||||
;; TODO clientize accounts
|
||||
|
||||
@@ -596,6 +593,272 @@
|
||||
updated-count
|
||||
(count ids))})})))
|
||||
|
||||
(def payment-form-schema
|
||||
(mc/schema [:map
|
||||
[:client entity-id]
|
||||
[:invoices [:vector {:coerce? true}
|
||||
[:map
|
||||
[:invoice-id entity-id]
|
||||
[:amount money]]]]
|
||||
[:bank-account entity-id]
|
||||
[:method :string]]))
|
||||
|
||||
(defn bank-account-card-base [{:keys [bg-color text-color icon bank-account]}]
|
||||
[:div {:class "w-[30em] cursor-move"}
|
||||
(com/card {:class "w-full"}
|
||||
[:div.flex.items-stretch {:x-data (hx/json {:chosen false
|
||||
:popper nil})
|
||||
"x-init" "popper = Popper.createPopper($refs.button, $refs.tooltip, {placement: 'bottom', strategy: 'fixed', modifiers: [{name: 'preventOverflow'}, {name: 'offset', options: {offset: [0, 10]}}]});"}
|
||||
(com/hidden {:name "item"
|
||||
:value (:db/id bank-account)})
|
||||
[:div.grow-0.flex.flex-col.justify-center
|
||||
[:div.p-1.m-2.rounded-full
|
||||
{:class
|
||||
bg-color}
|
||||
[:div {:class
|
||||
(hh/add-class "p-1.5 w-8 h-8" text-color)}
|
||||
icon]]]
|
||||
[:div.flex.flex-col.grow.m-2
|
||||
[:div.font-medium.text-gray-700 (:bank-account/name bank-account)]
|
||||
[:div.font-light.text-gray-600 (:bank-account/bank-name bank-account)]]
|
||||
[:div.grow-0.m-2.self-center
|
||||
|
||||
(com/button {:x-ref "button"
|
||||
|
||||
"@click.prevent.capture" "chosen=true; $nextTick(() => popper.update())"}
|
||||
"Pay")
|
||||
[:div.flex.flex-col.gap-2 (hx/alpine-appear {:x-show "chosen" :x-ref "tooltip"
|
||||
:data-key "vis"
|
||||
:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"
|
||||
"@click.outside" "chosen=false"})
|
||||
(com/button {:color :primary
|
||||
:x-show "chosen"
|
||||
"@click.prevent.capture" "chosen=true"
|
||||
:hx-vals (hx/json {"step-params[bank-account]" (:db/id bank-account)})
|
||||
:hx-put (hu/url (bidi/path-for ssr-routes/only-routes ::route/pay-wizard-navigate)
|
||||
{:from (mm/encode-step-key :choose-method)
|
||||
:to (mm/encode-step-key :payment-details)})}
|
||||
"Print check")
|
||||
(com/button {:x-show "chosen"
|
||||
"@click.prevent.capture" "chosen=true"}
|
||||
"Debit")
|
||||
(com/button {:x-show "chosen"
|
||||
"@click.prevent.capture" "chosen=true"}
|
||||
"Handwrite check")]]])])
|
||||
|
||||
|
||||
(defmulti bank-account-card (comp :bank-account/type))
|
||||
(defmethod bank-account-card :bank-account-type/cash [bank-account]
|
||||
(bank-account-card-base {:bg-color "bg-green-50"
|
||||
:text-color "text-green-600"
|
||||
:icon svg/dollar
|
||||
:bank-account bank-account}))
|
||||
|
||||
(defmethod bank-account-card
|
||||
:bank-account-type/credit
|
||||
[bank-account]
|
||||
(bank-account-card-base {:bg-color "bg-purple-50"
|
||||
:text-color "text-purple-600"
|
||||
:icon svg/credit-card
|
||||
:bank-account bank-account}))
|
||||
|
||||
(defmethod bank-account-card
|
||||
:bank-account-type/check [bank-account]
|
||||
(bank-account-card-base {:bg-color "bg-blue-50"
|
||||
:text-color "text-blue-600"
|
||||
:icon svg/check
|
||||
:bank-account bank-account}))
|
||||
|
||||
|
||||
|
||||
(defrecord ChoosePaymentMethodModal [linear-wizard]
|
||||
mm/ModalWizardStep
|
||||
(step-name [_]
|
||||
"Payment method")
|
||||
(step-key [_]
|
||||
:choose-method)
|
||||
|
||||
(edit-path [_ _]
|
||||
[])
|
||||
|
||||
(step-schema [_]
|
||||
(mut/select-keys (mm/form-schema linear-wizard) #{:bank-account}))
|
||||
|
||||
(render-step [this request]
|
||||
(mm/default-render-step
|
||||
linear-wizard this
|
||||
:head [:div "Pay " (count (:invoices (:snapshot (:multi-form-state request))))]
|
||||
:body (mm/default-step-body
|
||||
{}
|
||||
(let [bank-accounts (->> (dc/q '[:find (pull ?ba [:bank-account/name :bank-account/sort-order :bank-account/visible
|
||||
:bank-account/bank-name
|
||||
:db/id
|
||||
{[:bank-account/type :xform iol-ion.query/ident] [:db/ident]}])
|
||||
:in $ ?c
|
||||
:where [?c :client/bank-accounts ?ba]]
|
||||
(dc/db conn)
|
||||
(:client (:snapshot (:multi-form-state request)))) ;; TODO
|
||||
(map first)
|
||||
(sort-by :bank-account/sort-order))]
|
||||
[:div.flex.flex-col.space-y-2
|
||||
(for [ba bank-accounts]
|
||||
(bank-account-card ba))]))
|
||||
:footer
|
||||
(mm/default-step-footer linear-wizard this :validation-route ::route/navigate)
|
||||
:validation-route ::route/navigate)))
|
||||
|
||||
(defrecord PaymentDetailsStep [linear-wizard]
|
||||
mm/ModalWizardStep
|
||||
(step-name [_]
|
||||
"Details")
|
||||
(step-key [_]
|
||||
:payment-details)
|
||||
|
||||
(edit-path [_ _]
|
||||
[])
|
||||
|
||||
(step-schema [_]
|
||||
(mut/select-keys (mm/form-schema linear-wizard) #{:invoices}))
|
||||
|
||||
(render-step [this request]
|
||||
(mm/default-render-step
|
||||
linear-wizard this
|
||||
:head [:div "HI"]
|
||||
:body (mm/default-step-body
|
||||
{}
|
||||
[:div {}
|
||||
(com/radio-list {:x-model "mode"
|
||||
:name "mode"
|
||||
:options [{:value "simple"
|
||||
:content "Pay in full"}
|
||||
{:value "advanced"
|
||||
:content "Customize payments"}]})
|
||||
[:div.space-y-4 (hx/alpine-appear {:x-show "mode==\"advanced\""})
|
||||
(fc/with-field :invoices
|
||||
(com/data-grid
|
||||
{:headers [(com/data-grid-header {} "Vendor")
|
||||
(com/data-grid-header {} "Invoice Number")
|
||||
(com/data-grid-header {:class "text-right"} "Total")
|
||||
(com/data-grid-header {:class "text-right"} "Pay")]}
|
||||
(fc/cursor-map
|
||||
(fn [i]
|
||||
(println fc/*current*)
|
||||
(com/data-grid-row
|
||||
{}
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
|
||||
(-> (fc/field-value) :invoice :invoice/vendor :vendor/name))
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
(fc/with-field :invoice-id
|
||||
(com/hidden {:name (fc/field-name)
|
||||
:value (fc/field-value)}))
|
||||
(-> (fc/field-value) :invoice :invoice/invoice-number))
|
||||
(com/data-grid-cell
|
||||
{:class "text-right"}
|
||||
(format "$%,.2f" (-> (fc/field-value) :invoice :invoice/outstanding-balance)))
|
||||
(com/data-grid-cell
|
||||
{:class "w-20"}
|
||||
(fc/with-field :amount
|
||||
(com/validated-field {:errors (fc/field-errors)}
|
||||
(com/money-input {:value (format "%.2f" (fc/field-value)) :class "w-20"
|
||||
:name (fc/field-name)
|
||||
:error? (fc/error?)})))))))))]])
|
||||
:footer
|
||||
(mm/default-step-footer linear-wizard this :validation-route ::route/pay-wizard-navigate)
|
||||
:validation-route ::route/navigate)))
|
||||
|
||||
|
||||
|
||||
(defrecord PayWizard [form-params current-step entity]
|
||||
mm/LinearModalWizard
|
||||
(hydrate-from-request
|
||||
[this request]
|
||||
this
|
||||
#_(assoc this :entity (:entity request)))
|
||||
(navigate [this step-key]
|
||||
(assoc this :current-step step-key))
|
||||
(get-current-step [this]
|
||||
(if current-step
|
||||
(mm/get-step this current-step)
|
||||
(mm/get-step this :choose-method)))
|
||||
(render-wizard [this {:keys [multi-form-state] :as request}]
|
||||
|
||||
;; TODO should this be customized based off the selections they make?
|
||||
(let [invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id]
|
||||
:invoice/client [:db/id]}
|
||||
:invoice/outstanding-balance
|
||||
:invoice/invoice-number
|
||||
:db/id])
|
||||
:in $ [?i ...]]
|
||||
(dc/db conn)
|
||||
(map :invoice-id (get-in request [:multi-form-state :step-params :invoices])))
|
||||
(map first)
|
||||
(sort-by (juxt (comp :invoice/vendor :vendor/name)
|
||||
:invoice/invoice-number)))
|
||||
request (update-in request [:multi-form-state :step-params :invoices]
|
||||
(fn [form-invoices]
|
||||
(mapv (fn [form-invoice i]
|
||||
(assoc form-invoice :invoice i)) form-invoices invoices)))]
|
||||
(clojure.pprint/pprint (:step-params (:multi-form-state request)))
|
||||
(mm/default-render-wizard
|
||||
this request
|
||||
:form-params
|
||||
(-> mm/default-form-props
|
||||
(assoc :hx-post
|
||||
(str (bidi/path-for ssr-routes/only-routes ::route/pay-submit)))
|
||||
(assoc :x-data (hx/json {:mode "simple"}))))))
|
||||
(steps [_]
|
||||
[:choose-method
|
||||
:payment-details])
|
||||
|
||||
(get-step [this step-key]
|
||||
(let [step-key-result (mc/parse mm/step-key-schema step-key)
|
||||
[step-key-type step-key] step-key-result]
|
||||
(if (= :step step-key-type)
|
||||
(get {:choose-method (->ChoosePaymentMethodModal this)
|
||||
:payment-details (->PaymentDetailsStep this)}
|
||||
step-key)
|
||||
|
||||
(get {:bank-account (->ChoosePaymentMethodModal this)}
|
||||
(first step-key)))))
|
||||
(form-schema [_] payment-form-schema)
|
||||
(submit [_ {:keys [multi-form-state request-method identity] :as request}]
|
||||
(let [snapshot (mc/decode
|
||||
payment-form-schema
|
||||
(:snapshot multi-form-state)
|
||||
mt/strip-extra-keys-transformer)
|
||||
result (print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
|
||||
:amount (:amount i)})
|
||||
(:invoices snapshot))
|
||||
(:client snapshot)
|
||||
(:bank-account snapshot)
|
||||
:payment-type/check
|
||||
identity)]
|
||||
(alog/info ::printed :result result)
|
||||
|
||||
(modal-response
|
||||
(com/modal {}
|
||||
(com/modal-card-advanced
|
||||
{:class "transition duration-300 ease-in-out htmx-swapping:-translate-x-2/3 htmx-swapping:opacity-0 htmx-swapping:scale-0 htmx-added:translate-x-2/3 htmx-added:opacity-0 htmx-added:scale-0 scale-100 translate-x-0 opacity-100"}
|
||||
|
||||
(com/modal-body {}
|
||||
[:div.flex.flex-col.mt-4.space-y-4.items-center
|
||||
[:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300.animate-gg
|
||||
|
||||
svg/thumbs-up]
|
||||
[:div "Your checks are ready. Click "
|
||||
(com/link {:href (:pdf-url result)} "here")
|
||||
" to download."]]))))
|
||||
#_(html-response
|
||||
[:div]
|
||||
|
||||
:headers {"hx-trigger" (hx/json {:notification (str "Printed!")})}))))
|
||||
|
||||
|
||||
(def pay-wizard
|
||||
(->PayWizard nil nil nil))
|
||||
|
||||
|
||||
(def key->handler
|
||||
@@ -611,7 +874,38 @@
|
||||
(wrap-admin))
|
||||
::route/bulk-delete (-> bulk-delete-dialog
|
||||
(wrap-admin))
|
||||
::route/pay-wizard (-> mm/open-wizard-handler
|
||||
|
||||
(mm/wrap-wizard pay-wizard)
|
||||
(mm/wrap-init-multi-form-state (fn [request]
|
||||
;; TODO handle locked
|
||||
(let [selected-ids (selected->ids request (:query-params request))
|
||||
invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id]
|
||||
:invoice/client [:db/id]}
|
||||
:invoice/outstanding-balance
|
||||
:invoice/invoice-number
|
||||
:db/id])
|
||||
:in $ [?i ...]]
|
||||
(dc/db conn)
|
||||
selected-ids)
|
||||
(map first)
|
||||
(sort-by (juxt (comp :invoice/vendor :vendor/name)
|
||||
:invoice/invoice-number)))]
|
||||
(mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i)
|
||||
:amount (:invoice/outstanding-balance i) })
|
||||
invoices)
|
||||
:client (-> invoices first :invoice/client :db/id)}
|
||||
[]
|
||||
{}))))
|
||||
#_(wrap-entity [:route-params :db/id] default-read)
|
||||
#_(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
|
||||
::route/pay-submit (-> mm/submit-handler
|
||||
(mm/wrap-wizard pay-wizard)
|
||||
(mm/wrap-decode-multi-form-state))
|
||||
::route/pay-wizard-navigate
|
||||
(-> mm/next-handler
|
||||
(mm/wrap-wizard pay-wizard)
|
||||
(mm/wrap-decode-multi-form-state))
|
||||
|
||||
::route/table (helper/table-route grid-page)}
|
||||
(fn [h]
|
||||
@@ -620,4 +914,7 @@
|
||||
(wrap-merge-prior-hx)
|
||||
(wrap-schema-enforce :query-schema query-schema)
|
||||
(wrap-schema-enforce :hx-schema query-schema)
|
||||
(wrap-client-redirect-unauthenticated)))))
|
||||
(wrap-client-redirect-unauthenticated)))))
|
||||
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
:placeholder "9999.34"
|
||||
:size :small})])
|
||||
(com/field {:label "Payment Type"}
|
||||
(com/radio {:size :small
|
||||
(com/radio-card {:size :small
|
||||
:name "payment-type"
|
||||
:value (:payment-type (:query-params request))
|
||||
:options [{:value ""
|
||||
@@ -117,7 +117,6 @@
|
||||
|
||||
|
||||
(def default-read '[*
|
||||
|
||||
[:payment/date :xform clj-time.coerce/from-date]
|
||||
{:invoice-payment/_payment [* {:invoice-payment/invoice [*]}]}
|
||||
{:payment/client [:client/name :db/id :client/code]}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
(defn processor-field* [request]
|
||||
(com/field {:label "Processor"}
|
||||
(com/radio {:size :small
|
||||
(com/radio-card {:size :small
|
||||
:name "processor"
|
||||
:value (:processor (:parsed-query-params request))
|
||||
:options [{:value ""
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
(total-field* request)
|
||||
[:div
|
||||
(com/field {:label "Payment Method"}
|
||||
(com/radio {:size :small
|
||||
(com/radio-card {:size :small
|
||||
:name "payment-method"
|
||||
:options [{:value ""
|
||||
:content "All"}
|
||||
|
||||
@@ -85,7 +85,8 @@ input[type=number] {
|
||||
"x-on:htmx:response-error" "unexpectedError=true;"
|
||||
"x-on:htmx:before-request" "unexpectedError=false"
|
||||
"@modalopen.document" "open=true; unexpectedError=null"
|
||||
"@modalclose.document" "open=false"}
|
||||
"@modalclose.document" "open=false"
|
||||
"@modalswap.document" "open=false; setTimeout(() => open=true)"}
|
||||
|
||||
[:div {:class "bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40 md:p-12"
|
||||
"x-show" "open"
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
:size :small}))
|
||||
|
||||
(com/field {:label "Role"}
|
||||
(com/radio {:size :small
|
||||
(com/radio-card {:size :small
|
||||
:name "role"
|
||||
:options [{:value ""
|
||||
:content "All"}
|
||||
@@ -292,49 +292,50 @@
|
||||
|
||||
|
||||
(defn dialog* [{:keys [form-params form-errors entity]}]
|
||||
(println "FORM PARMS" form-params)
|
||||
(fc/start-form
|
||||
form-params form-errors
|
||||
(com/modal
|
||||
{:hx-target "this"
|
||||
:hx-indicator "this"}
|
||||
[:form {:hx-ext "response-targets"
|
||||
:hx-put (str (bidi/path-for ssr-routes/only-routes
|
||||
:user-edit-save
|
||||
:request-method :put))
|
||||
:hx-swap "outerHTML swap:300ms"
|
||||
:hx-target-400 "#form-errors .error-content"
|
||||
:class "w-full h-full"}
|
||||
[:fieldset {:class "hx-disable h-full"}
|
||||
(com/modal-card
|
||||
{}
|
||||
[:div.flex [:div.p-2 "User"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:user/name entity)]]
|
||||
[:div.space-y-6
|
||||
(fc/with-field :db/id
|
||||
(com/hidden {:name (fc/field-name)
|
||||
:value (fc/field-value)}))
|
||||
(fc/with-field :user/role
|
||||
(com/validated-field {:label "Role"
|
||||
:errors (fc/field-errors)}
|
||||
(com/select {:name (fc/field-name)
|
||||
:class "w-36"
|
||||
:autofocus true
|
||||
:value (some->> (fc/field-value) name)
|
||||
:options (ref->select-options "user-role")})))
|
||||
(fc/with-field :user/clients
|
||||
(com/validated-field {:label "Clients"}
|
||||
(com/data-grid {:headers [(com/data-grid-header {} "Client")
|
||||
(com/data-grid-header {} )]
|
||||
:id "client-table"}
|
||||
(fc/cursor-map #(client-row* %))
|
||||
(com/data-grid-new-row {:colspan 2
|
||||
:index (count (fc/field-value))
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:user-client-new)}
|
||||
"Assign new client"))))]
|
||||
[:div
|
||||
(com/form-errors {:errors (:errors fc/*form-errors*)})
|
||||
(com/validated-save-button {:errors (seq form-errors)}
|
||||
"Save user")])]])))
|
||||
form-params form-errors
|
||||
(com/modal
|
||||
{:hx-target "this"
|
||||
:hx-indicator "this"}
|
||||
[:form {:hx-ext "response-targets"
|
||||
:hx-put (str (bidi/path-for ssr-routes/only-routes
|
||||
:user-edit-save
|
||||
:request-method :put))
|
||||
:hx-swap "outerHTML swap:300ms"
|
||||
:hx-target-400 "#form-errors .error-content"
|
||||
:class "w-full h-full"}
|
||||
[:fieldset {:class "hx-disable h-full"}
|
||||
(com/modal-card
|
||||
{}
|
||||
[:div.flex [:div.p-2 "User"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:user/name entity)]]
|
||||
[:div.space-y-6
|
||||
(fc/with-field :db/id
|
||||
(com/hidden {:name (fc/field-name)
|
||||
:value (fc/field-value)}))
|
||||
(fc/with-field :user/role
|
||||
(com/validated-field {:label "Role"
|
||||
:errors (fc/field-errors)}
|
||||
(com/select {:name (fc/field-name)
|
||||
:class "w-36"
|
||||
:autofocus true
|
||||
:value (some->> (fc/field-value) name)
|
||||
:options (ref->select-options "user-role")})))
|
||||
(fc/with-field :user/clients
|
||||
(com/validated-field {:label "Clients"}
|
||||
(com/data-grid {:headers [(com/data-grid-header {} "Client")
|
||||
(com/data-grid-header {})]
|
||||
:id "client-table"}
|
||||
(fc/cursor-map #(client-row* %))
|
||||
(com/data-grid-new-row {:colspan 2
|
||||
:index (count (fc/field-value))
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:user-client-new)}
|
||||
"Assign new client"))))]
|
||||
[:div
|
||||
(com/form-errors {:errors (:errors fc/*form-errors*)})
|
||||
(com/validated-save-button {:errors (seq form-errors)}
|
||||
"Save user")])]])))
|
||||
|
||||
(defn user-edit-save [{:keys [form-params identity] :as request}]
|
||||
(let [_ @(dc/transact conn [[:upsert-entity form-params]])
|
||||
|
||||
@@ -41,6 +41,16 @@
|
||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||
|
||||
(defn modal-replace-response [hiccup & {:as opts}]
|
||||
(apply html-response
|
||||
(into
|
||||
[hiccup]
|
||||
(mapcat identity
|
||||
(-> opts
|
||||
(assoc-in [:headers "hx-trigger"] "modalswap")
|
||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||
|
||||
(defn next-step-modal-response [hiccup & {:as opts}]
|
||||
(apply html-response
|
||||
(into
|
||||
@@ -135,7 +145,7 @@
|
||||
(if (sequential? x)
|
||||
x
|
||||
(into []
|
||||
(for [[k v] (sort-by (comp #(Long/parseLong %) name first) x)]
|
||||
(for [[k v] (sort-by (comp #(Long/parseLong %) first) x)]
|
||||
v))))})
|
||||
|
||||
(defn many-entity [params & keys]
|
||||
@@ -264,6 +274,11 @@
|
||||
data
|
||||
(sequential? data)
|
||||
data
|
||||
(and (map? data)
|
||||
(every? #(try (Long/parseLong %) true (catch Exception _ false)) (keys data)))
|
||||
(into [] (->> (keys data)
|
||||
sort
|
||||
(map data)))
|
||||
(nil? data)
|
||||
nil
|
||||
:else
|
||||
@@ -293,9 +308,9 @@
|
||||
(mt2/transformer
|
||||
parse-empty-as-nil
|
||||
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||
(mt2/transformer {:name :arbitrary})
|
||||
mt2/string-transformer
|
||||
mt2/json-transformer
|
||||
(mt2/transformer {:name :arbitrary})
|
||||
coerce-vector
|
||||
date-range-transformer
|
||||
pull-transformer
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
(ns auto-ap.routes.invoice)
|
||||
(def routes {"" {:get ::page}
|
||||
"/pay-button" ::pay-button
|
||||
"/pay" {:get ::pay-wizard
|
||||
"/navigate" ::pay-wizard-navigate
|
||||
:post ::pay-submit}
|
||||
"/bulk-delete" {:get ::bulk-delete
|
||||
:delete ::bulk-delete-confirm}
|
||||
["/" [#"\d+" :db/id]] {:delete ::delete}
|
||||
|
||||
@@ -19,9 +19,24 @@ module.exports = {
|
||||
'87.5%': { transform: 'translateX(5px)' },
|
||||
'100%': { transform: 'translateX(0px)' },
|
||||
},
|
||||
gentleGrow: {
|
||||
"0%": {
|
||||
"transform": "scale(1.0)",
|
||||
"animation-timing-function": "cubic-bezier(0.8, 0, 1, 1)"
|
||||
},
|
||||
"50%": {
|
||||
"transform": "scale(1.1)",
|
||||
"animation-timing-function": "cubic-bezier(0, 0, 0.2, 1)"
|
||||
},
|
||||
"100%": {
|
||||
"transform": "scale(1.0)",
|
||||
"animation-timing-function": "cubic-bezier(0.8, 0, 1, 1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'shake': 'shake 0.5s ease-out 1',
|
||||
"gg": "gentleGrow 1s infinite"
|
||||
},
|
||||
"fontFamily": {
|
||||
"sans": ["Calibri", "ui-sans-serif", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "Noto Sans", "sans-serif", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"]
|
||||
|
||||
Reference in New Issue
Block a user