Makes outgoing invoice look a little better

This commit is contained in:
2024-04-01 20:19:28 -07:00
parent 092377a93b
commit b7c6493bd6
4 changed files with 188 additions and 140 deletions

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@
(defn content-card- [params & children] (defn content-card- [params & children]
[:section (merge params {:class (hh/add-class " py-3 sm:py-5" (:class params))}) [:section (merge params {:class (hh/add-class " py-3 sm:py-5" (:class params))})
[:div {:class "max-w-screen-2xl"} [:div {:class (:max-w params "max-w-screen-2xl")}
(into (into
[:div {:class "relative overflow-hidden shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white"}] [:div {:class "relative overflow-hidden shadow-md dark:bg-gray-800 sm:rounded-lg border-2 border-gray-200 dark:border-gray-900 bg-white"}]
children)]]) children)]])

View File

@@ -6,10 +6,13 @@
[auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated
wrap-secure]] wrap-secure]]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
[auto-ap.ssr.components :as com] [auto-ap.ssr.components :as com]
[auto-ap.ssr.form-cursor :as fc] [auto-ap.ssr.form-cursor :as fc]
[auto-ap.ssr.grid-page-helper :refer [wrap-trim-client-ids]] [auto-ap.ssr.grid-page-helper :refer [wrap-trim-client-ids]]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]] [auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.ui :refer [base-page]]
[auto-ap.ssr.utils :refer [clj-date-schema modal-response money [auto-ap.ssr.utils :refer [clj-date-schema modal-response money
percentage strip wrap-schema-decode]] percentage strip wrap-schema-decode]]
@@ -27,7 +30,7 @@
[:outgoing-invoice/tax percentage] [:outgoing-invoice/tax percentage]
[:outgoing-invoice/to-address [:map [:outgoing-invoice/to-address [:map
[:street1 :string] [:street1 :string]
[:street2 {:optional true} [:maybe [ :string { :decode/string strip}]]] [:street2 {:optional true} [:maybe [:string {:decode/string strip}]]]
[:city :string] [:city :string]
[:state :string] [:state :string]
[:zip :string]]] [:zip :string]]]
@@ -38,136 +41,168 @@
[:outgoing-invoice-line-item/unit-price money] [:outgoing-invoice-line-item/unit-price money]
[:outgoing-invoice-line-item/quantity money]]]]])) [:outgoing-invoice-line-item/quantity money]]]]]))
(defn line-item [z]
(com/data-grid-row
(hx/alpine-mount-then-appear {:x-data (hx/json {:show false})
:data-key "show"
:x-ref "p"})
(fc/with-field :db/id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
(com/data-grid-cell
{}
(fc/with-field :outgoing-invoice-line-item/description
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:class "w-full"
:placeholder "Catered sandwiches"})))
(com/data-grid-cell
{:class "w-8"}
(fc/with-field :outgoing-invoice-line-item/quantity
(com/money-input {:name (fc/field-name)
:value (fc/field-value)
:class "w-24"
:placeholder "20"})))
(com/data-grid-cell
{}
(fc/with-field :outgoing-invoice-line-item/unit-price
(com/money-input {:name (fc/field-name)
:value (fc/field-value)
:class "w-24"
:placeholder "23.50"})))
(com/data-grid-cell {:class "align-top"}
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))
(defn form* [{:keys [form-params form-errors]}] (defn form* [{:keys [form-params form-errors]}]
(fc/start-form (fc/start-form
form-params form-params
form-errors form-errors
[:form {:hx-post (bidi.bidi/path-for ssr-routes/only-routes [:form.flex.gap-4 {:hx-post (bidi.bidi/path-for ssr-routes/only-routes
::route/new-submit)} ::route/new-submit)}
(fc/with-field :outgoing-invoice/invoice-number
(com/validated-field {:errors (fc/field-errors)
:label "Invoice #"}
[:div.w-96
(com/text-input {:name (fc/field-name)
:autofocus true
:error? (fc/error?)
:class "w-96"
:placeholder "10000"
:value (fc/field-value)})]))
(fc/with-field :outgoing-invoice/date
(com/validated-field {:errors (fc/field-errors)
:label "Date"}
[:div.w-96
(com/date-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-96"
:placeholder "10000"
:value (-> (fc/field-value)
(atime/unparse-local atime/normal-date))})]))
[:div.flex.gap-4
(fc/with-field :outgoing-invoice/client
(com/validated-field {:errors (fc/field-errors)
:label "From (client)"}
[:div.w-96
(com/typeahead {:name (fc/field-name)
:error? (fc/error?)
:class "w-96"
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes :company-search)
:value (fc/field-value)
:content-fn (fn [c] (pull-attr (dc/db conn) :client/name c))})]))
[:div.flex.flex-col.gap-2
(fc/with-field :outgoing-invoice/to
(com/validated-field {:errors (fc/field-errors)
:label "To"}
[:div.w-96
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-96"
:placeholder "Hello Company"
:value (fc/field-value)})]))
(fc/with-field-default :outgoing-invoice/to-address {} (com/content-card {:max-w "max-w-screen-lg"}
(list [:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"}
(fc/with-field :address/street1 [:div
(com/validated-field {:label "Address" [:h1.text-2xl.mb-3.font-bold "New outgoing invoice"]]
:errors (fc/field-errors)} [:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"}]]
(com/text-input {:name (fc/field-name) [:div.p-4
:error? (fc/error?) (fc/with-field :outgoing-invoice/client
:class "w-full" (com/validated-field {:errors (fc/field-errors)
:placeholder "1200 Pennsylvania Avenue" :label "From (client)"}
:value (fc/field-value)}))) [:div.w-96
(fc/with-field :address/street2 (com/typeahead {:name (fc/field-name)
(com/validated-field {:errors (fc/field-errors)} :error? (fc/error?)
(com/text-input {:name (fc/field-name) :autofocus true
:error? (fc/error?) :class "w-96"
:class "w-full" :placeholder "Search..."
:placeholder "Suite 300" :url (bidi/path-for ssr-routes/only-routes :company-search)
:value (fc/field-value)}))) :value (fc/field-value)
[:div.flex.w-full.space-x-4 :content-fn (fn [c] (pull-attr (dc/db conn) :client/name c))})]))
(fc/with-field :address/city
(com/validated-field {:errors (fc/field-errors) (fc/with-field :outgoing-invoice/invoice-number
:class "w-full grow shrink"} (com/validated-field {:errors (fc/field-errors)
(com/text-input {:name (fc/field-name) :label "Invoice #"}
:error? (fc/error?) [:div.w-96
:class "w-full" (com/text-input {:name (fc/field-name)
:placeholder "Cupertino" :error? (fc/error?)
:value (fc/field-value)}))) :class "w-96"
(fc/with-field :address/state :placeholder "10000"
(com/validated-field {:errors (fc/field-errors) :value (fc/field-value)})]))
:class "w-16 shrink-0"} (fc/with-field :outgoing-invoice/date
(com/text-input {:name (fc/field-name) (com/validated-field {:errors (fc/field-errors)
:error? (fc/error?) :label "Date"}
:class "w-full" [:div.w-96
:placeholder "CA" (com/date-input {:name (fc/field-name)
:value (fc/field-value)}))) :error? (fc/error?)
(fc/with-field :address/zip :class "w-96"
(com/validated-field {:errors (fc/field-errors) :placeholder "10000"
:class "w-24 shrink-0"} :value (-> (fc/field-value)
(com/text-input {:name (fc/field-name) (atime/unparse-local atime/normal-date))})]))
:error? (fc/error?)
:placeholder "98101"
:class "w-full"
:value (fc/field-value)})))]))]]
(fc/with-field-default :outgoing-invoice/line-items [{} {} {}]
(com/validated-field {:errors (fc/field-errors)
:label "Line items"}
(com/data-grid {:headers [(com/data-grid-header {} "Description")
(com/data-grid-header {} "Quantity")
(com/data-grid-header {} "Unit Price")]}
(fc/cursor-map
(fn [z]
(com/data-grid-row
{}
(com/data-grid-cell
{}
(fc/with-field :outgoing-invoice-line-item/description
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:class "w-full"
:placeholder "Catered sandwiches"})))
(com/data-grid-cell
{}
(fc/with-field :outgoing-invoice-line-item/quantity
(com/money-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "20"})))
(com/data-grid-cell
{}
(fc/with-field :outgoing-invoice-line-item/unit-price
(com/money-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "23.50"})))))))))
(fc/with-field-default :outgoing-invoice/tax 10.0
(com/validated-field {:errors (fc/field-errors) (fc/with-field-default :outgoing-invoice/line-items [{:db/id "first"}]
:label "Tax %"} (com/validated-field {:errors (fc/field-errors)
(com/money-input {:name (fc/field-name) :label "Line items"}
:value (fc/field-value)}))) (com/data-grid {:headers [(com/data-grid-header {} "Description")
(com/data-grid-header {} "Quantity")
(com/data-grid-header {} "Unit Price")
(com/data-grid-header {} "")]}
(fc/cursor-map line-item)
(com/data-grid-new-row {:colspan 4
:index (count (fc/field-value))
:hx-get (bidi/path-for ssr-routes/only-routes
[:div.flex.flex-row-reverse (com/button {:color :primary :class "w-24"} "Generate")]])) ::route/new-line-item)} "Add line"))))
(fc/with-field-default :outgoing-invoice/tax 10.0
(com/validated-field {:errors (fc/field-errors)
:label "Tax %"}
(com/money-input {:name (fc/field-name)
:value (fc/field-value)})))
[:div.flex.flex-row-reverse (com/button {:color :primary :class "w-24"} "Generate")]])
(com/content-card {:max-w "max-w-24"}
[:div.p-4
[:h3.text-lg "Recipient details"]
[:div.flex.flex-col.gap-2
(fc/with-field :outgoing-invoice/to
(com/validated-field {:errors (fc/field-errors)
:label "To"}
[:div.w-96
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-96"
:placeholder "Hello Company"
:value (fc/field-value)})]))
(fc/with-field-default :outgoing-invoice/to-address {}
(list
(fc/with-field :address/street1
(com/validated-field {:label "Address"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-full"
:placeholder "1200 Pennsylvania Avenue"
:value (fc/field-value)})))
(fc/with-field :address/street2
(com/validated-field {:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-full"
:placeholder "Suite 300"
:value (fc/field-value)})))
[:div.flex.w-full.space-x-4
(fc/with-field :address/city
(com/validated-field {:errors (fc/field-errors)
:class "w-full grow shrink"}
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-full"
:placeholder "Cupertino"
:value (fc/field-value)})))
(fc/with-field :address/state
(com/validated-field {:errors (fc/field-errors)
:class "w-16 shrink-0"}
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:class "w-full"
:placeholder "CA"
:value (fc/field-value)})))
(fc/with-field :address/zip
(com/validated-field {:errors (fc/field-errors)
:class "w-24 shrink-0"}
(com/text-input {:name (fc/field-name)
:error? (fc/error?)
:placeholder "98101"
:class "w-full"
:value (fc/field-value)})))]))]])]))
(defn- fmt-money [total]
(format "$%,.2f" (or total 0)))
(defn submit [{:keys [form-params]}] (defn submit [{:keys [form-params]}]
(let [line-items (->> form-params (let [line-items (->> form-params
@@ -180,16 +215,26 @@
subtotal (reduce + 0.0 (map :outgoing-invoice-line-item/total line-items)) subtotal (reduce + 0.0 (map :outgoing-invoice-line-item/total line-items))
tax (* subtotal (:outgoing-invoice/tax form-params)) tax (* subtotal (:outgoing-invoice/tax form-params))
total (+ subtotal tax) total (+ subtotal tax)
final-outgoing-invoice (-> form-params
(assoc :outgoing-invoice/line-items
line-items
:outgoing-invoice/total total
:outgoing-invoice/tax tax))
result result
(-> (lambda/invoke {:function-name "genpdf" :payload (-> (lambda/invoke {:function-name "genpdf" :payload
(json/write-str (json/write-str
(-> form-params (-> final-outgoing-invoice
(assoc :outgoing-invoice/line-items (update :outgoing-invoice/total fmt-money)
line-items (update :outgoing-invoice/tax fmt-money)
:outgoing-invoice/total total (update :outgoing-invoice/line-items
:outgoing-invoice/tax tax) (fn [lis]
(update :outgoing-invoice/date (mapv
#(-> %
(update :outgoing-invoice-line-item/total fmt-money)
(update :outgoing-invoice-line-item/unit-price fmt-money))
lis)))
(update :outgoing-invoice/date
#(some-> % (atime/unparse-local atime/normal-date)))))}) #(some-> % (atime/unparse-local atime/normal-date)))))})
:payload :payload
slurp slurp
@@ -219,13 +264,7 @@
[:a {:href (bidi/path-for ssr-routes/only-routes [:a {:href (bidi/path-for ssr-routes/only-routes
::route/new)} ::route/new)}
"New"]]) "New"]])
(com/content-card {} (form* request))
[:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"}
[:div
[:h1.text-2xl.mb-3.font-bold "New outgoing invoice"]]
[:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"}]]
[:div.px-4
(form* request)]))
"New outgoing invoice")) "New outgoing invoice"))
(wrap-trim-client-ids) (wrap-trim-client-ids)
(wrap-secure) (wrap-secure)
@@ -234,5 +273,13 @@
(def route->handler (def route->handler
{::route/new page {::route/new page
::route/new-submit (-> submit ::route/new-submit (-> submit
(wrap-schema-decode :form-schema form-schema) (wrap-schema-decode :form-schema form-schema)
(wrap-nested-form-params))}) (wrap-nested-form-params))
::route/new-line-item (->
(add-new-entity-handler [:outgoing-invoice/line-items]
(fn render [cursor request]
(line-item
{:value cursor }))
(fn build-new-row [base _]
base)))})

View File

@@ -1,3 +1,4 @@
(ns auto-ap.routes.outgoing-invoice) (ns auto-ap.routes.outgoing-invoice)
(def routes {"/" {"new" {:get ::new (def routes {"/" {"new" {:get ::new
:post ::new-submit} }}) :post ::new-submit}
"line-item/new" {:get ::new-line-item}}})