1 Commits

Author SHA1 Message Date
de1c154706 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>
2026-05-18 11:13:32 -07:00

View File

@@ -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