Polish sales summary grid and edit dialog
Aligns debit/credit amounts to a right column with tabular-nums; replaces the in-cell delta and balanced text with chip-style status indicators; shortens the edit dialog and clarifies its totals/unbalanced footer rows; gives manual line items a subtle accent so they're distinguishable from auto-generated rows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -173,69 +173,59 @@
|
|||||||
{:key "debits"
|
{:key "debits"
|
||||||
:name "Debits"
|
:name "Debits"
|
||||||
:sort-key "debits"
|
:sort-key "debits"
|
||||||
:class "w-64 align-top"
|
:class "w-72 align-top"
|
||||||
:render (fn [ss]
|
:render (fn [ss]
|
||||||
(let [items (:sales-summary/items ss)
|
(let [items (:sales-summary/items ss)
|
||||||
debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
|
debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
|
||||||
credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
|
credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
|
||||||
total-debits (total-debits items)
|
total-debits (total-debits items)]
|
||||||
total-credits (total-credits items)
|
|
||||||
delta (- total-debits total-credits)]
|
|
||||||
[:div.flex.flex-col.h-full
|
[:div.flex.flex-col.h-full
|
||||||
[:div.font-semibold.text-sm "Debits"]
|
[:ul.flex-grow
|
||||||
[:ul.space-y-0.5.flex-grow
|
|
||||||
(for [si debit-items]
|
(for [si debit-items]
|
||||||
[:li.text-sm.text-gray-700
|
[:li.flex.items-baseline.gap-2.py-0.5.text-sm.text-gray-700
|
||||||
[:span.text-gray-500 (truncate (:sales-summary-item/category si) 30)]
|
[:span.flex-1.min-w-0.truncate.text-gray-600
|
||||||
[:span.ml-2 "→"]
|
(:sales-summary-item/category si)]
|
||||||
[:span.ml-2.font-mono (format "$%,.2f" (:ledger-mapped/amount si))]
|
|
||||||
(when-not (:ledger-mapped/account si)
|
(when-not (:ledger-mapped/account si)
|
||||||
[:span.ml-2 (com/pill {:color :red} "?")])])
|
[:span.shrink-0 (com/pill {:color :red} "?")])
|
||||||
|
[:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
|
||||||
|
(format "$%,.2f" (:ledger-mapped/amount si))]])
|
||||||
(for [_ (range (max 0 (- credit-count (count debit-items))))]
|
(for [_ (range (max 0 (- credit-count (count debit-items))))]
|
||||||
[:li "\u00a0"])]
|
[:li.py-0.5.text-sm " "])]
|
||||||
[:div.border-t.mt-1.pt-1.flex.justify-between.items-center.w-full
|
[:div.border-t-2.border-gray-300.mt-1.pt-1.flex.justify-between.items-baseline
|
||||||
[:span.font-semibold "Total"]
|
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-gray-500 "Total"]
|
||||||
[:span.font-mono.font-semibold (format "$%,.2f" total-debits)]]
|
[:span.font-mono.tabular-nums.font-bold.text-gray-900
|
||||||
(when-not (dollars-0? delta)
|
(format "$%,.2f" total-debits)]]]))}
|
||||||
[:div.text-xs.text-red-600.flex.justify-between.w-full.mt-1
|
|
||||||
[:span "Delta:"]
|
|
||||||
[:span.font-mono (format "$%,.2f" delta)]])]))}
|
|
||||||
|
|
||||||
{:key "credits"
|
{:key "credits"
|
||||||
:name "Credits"
|
:name "Credits"
|
||||||
:sort-key "credits"
|
:sort-key "credits"
|
||||||
:class "w-64 align-top"
|
:class "w-72 align-top"
|
||||||
:render (fn [ss]
|
:render (fn [ss]
|
||||||
(let [items (:sales-summary/items ss)
|
(let [items (:sales-summary/items ss)
|
||||||
credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
|
credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
|
||||||
debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
|
debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
|
||||||
total-debits (total-debits items)
|
total-credits (total-credits items)]
|
||||||
total-credits (total-credits items)
|
|
||||||
delta (- total-credits total-debits)]
|
|
||||||
[:div.flex.flex-col.h-full
|
[:div.flex.flex-col.h-full
|
||||||
[:div.font-semibold.text-sm "Credits"]
|
[:ul.flex-grow
|
||||||
[:ul.space-y-0.5.flex-grow
|
|
||||||
(for [si credit-items]
|
(for [si credit-items]
|
||||||
[:li.text-sm.text-gray-700
|
[:li.flex.items-baseline.gap-2.py-0.5.text-sm.text-gray-700
|
||||||
[:span.text-gray-500 (truncate (:sales-summary-item/category si) 30)]
|
[:span.flex-1.min-w-0.truncate.text-gray-600
|
||||||
[:span.ml-2 "→"]
|
(:sales-summary-item/category si)]
|
||||||
[:span.ml-2.font-mono (format "$%,.2f" (:ledger-mapped/amount si))]
|
|
||||||
(when-not (:ledger-mapped/account si)
|
(when-not (:ledger-mapped/account si)
|
||||||
[:span.ml-2 (com/pill {:color :red} "?")])])
|
[:span.shrink-0 (com/pill {:color :red} "?")])
|
||||||
|
[:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
|
||||||
|
(format "$%,.2f" (:ledger-mapped/amount si))]])
|
||||||
(for [_ (range (max 0 (- debit-count (count credit-items))))]
|
(for [_ (range (max 0 (- debit-count (count credit-items))))]
|
||||||
[:li "\u00a0"])]
|
[:li.py-0.5.text-sm " "])]
|
||||||
[:div.border-t.mt-1.pt-1.flex.justify-between.items-center.w-full
|
[:div.border-t-2.border-gray-300.mt-1.pt-1.flex.justify-between.items-baseline
|
||||||
[:span.font-semibold "Total"]
|
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-gray-500 "Total"]
|
||||||
[:span.font-mono.font-semibold (format "$%,.2f" total-credits)]]
|
[:span.font-mono.tabular-nums.font-bold.text-gray-900
|
||||||
(when-not (dollars-0? delta)
|
(format "$%,.2f" total-credits)]]]))}
|
||||||
[:div.text-xs.text-green-600.flex.justify-between.w-full.mt-1
|
|
||||||
[:span "Delta:"]
|
|
||||||
[:span.font-mono (format "$%,.2f" delta)]])]))}
|
|
||||||
|
|
||||||
{:key "balance"
|
{:key "balance"
|
||||||
:name "Balance"
|
:name "Status"
|
||||||
:sort-key "balance"
|
:sort-key "balance"
|
||||||
:class "w-24"
|
:class "w-28 align-top"
|
||||||
:render (fn [ss]
|
:render (fn [ss]
|
||||||
(let [items (:sales-summary/items ss)
|
(let [items (:sales-summary/items ss)
|
||||||
total-debits (total-debits items)
|
total-debits (total-debits items)
|
||||||
@@ -243,16 +233,19 @@
|
|||||||
delta (- total-debits total-credits)
|
delta (- total-debits total-credits)
|
||||||
balanced? (dollars= total-debits total-credits)
|
balanced? (dollars= total-debits total-credits)
|
||||||
missing-account? (some #(not (:ledger-mapped/account %)) items)]
|
missing-account? (some #(not (:ledger-mapped/account %)) items)]
|
||||||
[:div.flex.flex-col.items-center.pt-2
|
[:div.flex.flex-col.items-center.gap-1.pt-2
|
||||||
(when missing-account?
|
(when missing-account?
|
||||||
[:span.text-red-600.font-bold.text-xs "Missing acct"])
|
[:span.inline-block.text-xs.font-semibold.uppercase.tracking-wider.text-amber-800.bg-amber-100.border.border-amber-300.rounded-sm.px-1.5.py-0.5
|
||||||
|
"Missing acct"])
|
||||||
(if balanced?
|
(if balanced?
|
||||||
(when-not missing-account?
|
(when-not missing-account?
|
||||||
[:span.text-green-600.font-bold "✓ Balanced"])
|
[:span.inline-block.text-xs.font-semibold.uppercase.tracking-wider.text-emerald-800.bg-emerald-100.border.border-emerald-300.rounded-sm.px-1.5.py-0.5
|
||||||
(list
|
"Balanced"])
|
||||||
[:span.text-red-600.font-mono (format "$%,.2f" (Math/abs delta))]
|
[:div.flex.flex-col.items-center
|
||||||
[:span.text-xs.text-gray-500.mt-1
|
[:span.font-mono.tabular-nums.text-red-700.font-bold.text-sm
|
||||||
(if (> total-debits total-credits) "Debit over" "Credit over")]))]))}
|
(format "$%,.2f" (Math/abs delta))]
|
||||||
|
[:span.text-xs.uppercase.tracking-wider.text-red-600.font-medium.mt-0.5
|
||||||
|
(if (> total-debits total-credits) "Debit over" "Credit over")]])]))}
|
||||||
|
|
||||||
{:key "links"
|
{:key "links"
|
||||||
:name "Links"
|
:name "Links"
|
||||||
@@ -311,16 +304,22 @@
|
|||||||
(total-debits))]
|
(total-debits))]
|
||||||
|
|
||||||
(com/data-grid-row {:id "total-row"
|
(com/data-grid-row {:id "total-row"
|
||||||
|
:class "bg-slate-50 border-t-2 border-slate-300"
|
||||||
:hx-trigger "change from:closest form target:.amount-field"
|
:hx-trigger "change from:closest form target:.amount-field"
|
||||||
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total)
|
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total)
|
||||||
:hx-target "this"
|
:hx-target "this"
|
||||||
:hx-swap "innerHTML"}
|
:hx-swap "innerHTML"}
|
||||||
(com/data-grid-cell {})
|
(com/data-grid-cell {})
|
||||||
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "TOTAL"])
|
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(com/data-grid-cell {:class "text-right"}
|
||||||
(format "$%,.2f" total-debits))
|
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-slate-600
|
||||||
|
"Total"])
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(com/data-grid-cell {:class "text-right"}
|
||||||
(format "$%,.2f" total-credits)))))
|
[:span.font-mono.tabular-nums.font-bold.text-slate-900
|
||||||
|
(format "$%,.2f" total-debits)])
|
||||||
|
(com/data-grid-cell {:class "text-right"}
|
||||||
|
[:span.font-mono.tabular-nums.font-bold.text-slate-900
|
||||||
|
(format "$%,.2f" total-credits)])
|
||||||
|
(com/data-grid-cell {}))))
|
||||||
|
|
||||||
(defn unbalanced-row* [request]
|
(defn unbalanced-row* [request]
|
||||||
(let [total-credits (-> request
|
(let [total-credits (-> request
|
||||||
@@ -332,25 +331,31 @@
|
|||||||
:multi-form-state
|
:multi-form-state
|
||||||
:step-params
|
:step-params
|
||||||
:sales-summary/items
|
:sales-summary/items
|
||||||
(total-debits))]
|
(total-debits))
|
||||||
|
unbalanced? (not (dollars= total-credits total-debits))
|
||||||
|
debit-over? (and unbalanced? (> total-debits total-credits))
|
||||||
|
credit-over? (and unbalanced? (> total-credits total-debits))]
|
||||||
|
|
||||||
(com/data-grid-row {:id "total-row"
|
(com/data-grid-row {:id "unbalanced-row"
|
||||||
|
:class (when unbalanced? "bg-red-50 border-t border-red-200")
|
||||||
:hx-trigger "change from:closest form target:.amount-field"
|
:hx-trigger "change from:closest form target:.amount-field"
|
||||||
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total)
|
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total)
|
||||||
:hx-target "this"
|
:hx-target "this"
|
||||||
:hx-swap "innerHTML"}
|
:hx-swap "innerHTML"}
|
||||||
(com/data-grid-cell {})
|
(com/data-grid-cell {})
|
||||||
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "UNBALANCED"])
|
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(com/data-grid-cell {:class "text-right"}
|
||||||
(when (and
|
(when unbalanced?
|
||||||
(not (dollars= total-credits total-debits))
|
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-red-700
|
||||||
(> total-debits total-credits))
|
"Out of balance"]))
|
||||||
(format "$%,.2f" (- total-debits total-credits))))
|
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(com/data-grid-cell {:class "text-right"}
|
||||||
(when
|
(when debit-over?
|
||||||
(and (not (dollars= total-credits total-debits))
|
[:span.font-mono.tabular-nums.font-bold.text-red-700
|
||||||
(> total-credits total-debits))
|
(format "$%,.2f" (- total-debits total-credits))]))
|
||||||
(format "$%,.2f" (- total-credits total-debits)))))))
|
(com/data-grid-cell {:class "text-right"}
|
||||||
|
(when credit-over?
|
||||||
|
[:span.font-mono.tabular-nums.font-bold.text-red-700
|
||||||
|
(format "$%,.2f" (- total-credits total-debits))]))
|
||||||
|
(com/data-grid-cell {}))))
|
||||||
|
|
||||||
(defn- account-typeahead*
|
(defn- account-typeahead*
|
||||||
[{:keys [name value client-id]}]
|
[{:keys [name value client-id]}]
|
||||||
@@ -368,7 +373,9 @@
|
|||||||
(defn sales-summary-item-row* [{:keys [value client-id]}]
|
(defn sales-summary-item-row* [{:keys [value client-id]}]
|
||||||
(let [manual? (fc/field-value (:sales-summary-item/manual? value))]
|
(let [manual? (fc/field-value (:sales-summary-item/manual? value))]
|
||||||
(com/data-grid-row (cond-> {:x-ref "p"
|
(com/data-grid-row (cond-> {:x-ref "p"
|
||||||
:x-data (hx/json {})}
|
:x-data (hx/json {})
|
||||||
|
:class (when manual?
|
||||||
|
"bg-indigo-50/40 border-l-2 border-indigo-300")}
|
||||||
(fc/field-value (:new? value)) (hx/htmx-transition-appear))
|
(fc/field-value (:new? value)) (hx/htmx-transition-appear))
|
||||||
(fc/with-field :db/id
|
(fc/with-field :db/id
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/hidden {:name (fc/field-name)
|
||||||
@@ -377,7 +384,7 @@
|
|||||||
(fc/with-field :sales-summary-item/manual?
|
(fc/with-field :sales-summary-item/manual?
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/hidden {:name (fc/field-name)
|
||||||
:value true})))
|
:value true})))
|
||||||
(com/data-grid-cell {}
|
(com/data-grid-cell {:class "align-top"}
|
||||||
(fc/with-field :sales-summary-item/category
|
(fc/with-field :sales-summary-item/category
|
||||||
(if manual?
|
(if manual?
|
||||||
(com/validated-field {:errors (fc/field-errors)}
|
(com/validated-field {:errors (fc/field-errors)}
|
||||||
@@ -388,35 +395,38 @@
|
|||||||
(list
|
(list
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/hidden {:name (fc/field-name)
|
||||||
:value (fc/field-value)})
|
:value (fc/field-value)})
|
||||||
(fc/field-value (:sales-summary-item/category value))))))
|
[:span.text-sm.text-gray-700
|
||||||
(com/data-grid-cell {}
|
(fc/field-value (:sales-summary-item/category value))]))))
|
||||||
|
(com/data-grid-cell {:class "align-top"}
|
||||||
(fc/with-field :ledger-mapped/account
|
(fc/with-field :ledger-mapped/account
|
||||||
(com/validated-field {:errors (fc/field-errors)}
|
(com/validated-field {:errors (fc/field-errors)}
|
||||||
(account-typeahead* {:value (fc/field-value)
|
(account-typeahead* {:value (fc/field-value)
|
||||||
:client-id client-id
|
:client-id client-id
|
||||||
:name (fc/field-name)}))))
|
:name (fc/field-name)}))))
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(com/data-grid-cell {:class "text-right align-top"}
|
||||||
|
|
||||||
(if manual?
|
(if manual?
|
||||||
(fc/with-field :debit
|
(fc/with-field :debit
|
||||||
(com/validated-field {:errors (fc/field-errors)}
|
(com/validated-field {:errors (fc/field-errors)}
|
||||||
(com/money-input {:class "w-24"
|
(com/money-input {:class "w-24 text-right font-mono tabular-nums"
|
||||||
:name (fc/field-name)
|
:name (fc/field-name)
|
||||||
:value (fc/field-value)})))
|
:value (fc/field-value)})))
|
||||||
(when (= (fc/field-value (:ledger-mapped/ledger-side value))
|
(when (= (fc/field-value (:ledger-mapped/ledger-side value))
|
||||||
:ledger-side/debit)
|
:ledger-side/debit)
|
||||||
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
|
[:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))])))
|
||||||
|
(com/data-grid-cell {:class "text-right align-top"}
|
||||||
|
|
||||||
(if manual?
|
(if manual?
|
||||||
(fc/with-field :credit
|
(fc/with-field :credit
|
||||||
(com/validated-field {:errors (fc/field-errors)}
|
(com/validated-field {:errors (fc/field-errors)}
|
||||||
(com/money-input {:class "w-24"
|
(com/money-input {:class "w-24 text-right font-mono tabular-nums"
|
||||||
:name (fc/field-name)
|
:name (fc/field-name)
|
||||||
:value (fc/field-value)})))
|
:value (fc/field-value)})))
|
||||||
(when (= (fc/field-value (:ledger-mapped/ledger-side value))
|
(when (= (fc/field-value (:ledger-mapped/ledger-side value))
|
||||||
:ledger-side/credit)
|
:ledger-side/credit)
|
||||||
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
|
[:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap
|
||||||
|
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))])))
|
||||||
(com/data-grid-cell {:class "align-top"}
|
(com/data-grid-cell {:class "align-top"}
|
||||||
(when manual?
|
(when manual?
|
||||||
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
|
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
|
||||||
@@ -467,7 +477,7 @@
|
|||||||
:footer
|
:footer
|
||||||
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
|
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
|
||||||
:validation-route ::route/edit-wizard-navigate
|
:validation-route ::route/edit-wizard-navigate
|
||||||
:width-height-class "lg:w-[850px] lg:h-[900px]")))
|
:width-height-class "lg:w-[920px] lg:h-[640px]")))
|
||||||
|
|
||||||
(defn attach-ledger [i]
|
(defn attach-ledger [i]
|
||||||
(cond-> i
|
(cond-> i
|
||||||
|
|||||||
Reference in New Issue
Block a user