Compare commits
19 Commits
e8979738ab
...
4221d6a0d6
| Author | SHA1 | Date | |
|---|---|---|---|
| 4221d6a0d6 | |||
| 918ddd14ff | |||
| acd4184ef0 | |||
| 857a1536ef | |||
| 535ef4d113 | |||
| 351659f8eb | |||
| 4739769297 | |||
| 567db50a66 | |||
| dbfa04c766 | |||
| 0692089e39 | |||
| 8189a7648b | |||
| dd4d1a6d4f | |||
| 4f32527732 | |||
| 0811771ae6 | |||
| c6b55ce567 | |||
| 1f9a7080e1 | |||
| 6f7f1c7815 | |||
| 065d1182d7 | |||
| b42e2a6a44 |
@@ -0,0 +1,152 @@
|
||||
# Transaction Account Amount Mode Toggle - Design Spec
|
||||
|
||||
**Date:** 2026-05-20
|
||||
**Feature:** Global $/% Toggle for Transaction Accounts (Manual Action)
|
||||
|
||||
## Overview
|
||||
|
||||
In the transaction edit modal's "manual" action view, replace the static "$" column header with a sliding toggle that allows users to switch between viewing amounts as dollar values or percentages. When toggled, the entire account grid re-renders via HTMX with converted values. Percentages are multiplied by 100 (e.g., $200 on a $200 transaction → 100%). When switching back to dollars, use `spread-cents` to ensure accurate cent distribution.
|
||||
|
||||
## Motivation
|
||||
|
||||
The cljs master version supports per-row $/% toggles. Users want this capability in the SSR version, but with a single global toggle in the table header for simplicity and consistency with the bulk coding interface.
|
||||
|
||||
## Schema Changes
|
||||
|
||||
### Form State
|
||||
|
||||
Add `amount-mode` to the edit form's step params:
|
||||
|
||||
```clojure
|
||||
[:amount-mode [:enum "$" "%"] {:default "$"}]
|
||||
```
|
||||
|
||||
Stored in `multi-form-state` alongside existing transaction data. Not persisted to Datomic—purely a UI preference.
|
||||
|
||||
## UI Design
|
||||
|
||||
### Table Header
|
||||
|
||||
Replace the static `"$"` header cell (line ~739 in `edit.clj`) with a radio toggle:
|
||||
|
||||
```clojure
|
||||
(com/radio-card {:options [{:value "$" :content "$"}
|
||||
{:value "%" :content "%"}]
|
||||
:value (or amount-mode "$")
|
||||
:name "step-params[amount-mode]"
|
||||
:hx-post (bidi/path-for ssr-routes/only-routes ::route/toggle-amount-mode)
|
||||
:hx-target "#account-grid-body"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-include "closest form"})
|
||||
```
|
||||
|
||||
### Grid Body
|
||||
|
||||
Wrap the account grid rows in a container with id `account-grid-body`:
|
||||
|
||||
```clojure
|
||||
[:div#account-grid-body
|
||||
(fc/cursor-map #(transaction-account-row* ...))
|
||||
...total/balance rows...]
|
||||
```
|
||||
|
||||
When toggled, only this container re-renders. All other form fields (vendor, memo, approval status) are preserved.
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Toggle Request (HTMX)
|
||||
|
||||
1. User clicks toggle
|
||||
2. HTMX serializes entire form via `hx-include "closest form"`
|
||||
3. POST to `::route/toggle-amount-mode`
|
||||
4. Server:
|
||||
- Merges form params into existing `multi-form-state`
|
||||
- Extracts old mode and new mode
|
||||
- Converts all `:transaction-account/amount` values:
|
||||
- If old="$" new="%": multiply by 100/total
|
||||
- If old="%" new="$": use `percentages->dollars` (see Conversion Logic)
|
||||
- Updates `amount-mode` in state
|
||||
- Re-renders `#account-grid-body`
|
||||
5. Client swaps grid body. Tab order preserved.
|
||||
|
||||
### Conversion Logic
|
||||
|
||||
**$ → %:**
|
||||
```clojure
|
||||
(defn ->percentage [amount total]
|
||||
(when (and amount total (not= total 0))
|
||||
(* 100.0 (/ amount total))))
|
||||
```
|
||||
|
||||
**% → $ (using spread-cents):**
|
||||
```clojure
|
||||
(defn percentages->dollars [percentages total]
|
||||
(let [total-cents (int (* 100 (Math/abs total)))
|
||||
pct-sum (reduce + 0 percentages)
|
||||
;; Normalize percentages to sum to 100
|
||||
normalized-pcts (if (zero? pct-sum)
|
||||
(repeat (count percentages) 0)
|
||||
(map #(* (/ % pct-sum) 100) percentages))
|
||||
;; Convert each pct to its share of cents
|
||||
individual-cents (map #(int (* total-cents (/ % 100))) normalized-pcts)
|
||||
short-by (- total-cents (reduce + 0 individual-cents))
|
||||
;; Distribute remainder using spread-cents pattern
|
||||
adjustments (concat (take short-by (repeat 1)) (repeat 0))
|
||||
final-cents (map + individual-cents adjustments)]
|
||||
(map #(* 0.01 %) final-cents)))
|
||||
```
|
||||
|
||||
Example: One account at 100% of $200.00 → `total-cents=20000`, `individual-cents=[20000]`, result: `[200.00]`
|
||||
|
||||
### Save Handling
|
||||
|
||||
Before validation in `save-handler :manual`:
|
||||
|
||||
```clojure
|
||||
(let [snapshot (:snapshot multi-form-state)
|
||||
accounts (:transaction/accounts snapshot)
|
||||
total (Math/abs (:transaction/amount existing-tx))
|
||||
mode (:amount-mode snapshot "$")
|
||||
;; If in % mode, convert back to $ before saving
|
||||
accounts' (if (= "%" mode)
|
||||
(let [percentages (map :transaction-account/amount accounts)
|
||||
dollar-amounts (percentages->dollars percentages total)]
|
||||
(map #(assoc %1 :transaction-account/amount %2) accounts dollar-amounts))
|
||||
accounts)]
|
||||
...)
|
||||
```
|
||||
|
||||
## Form Preservation
|
||||
|
||||
The HTMX toggle is designed to preserve:
|
||||
- **Tab order:** All inputs remain in DOM with same `tabindex` attributes
|
||||
- **Other form fields:** Vendor, memo, approval status are outside `#account-grid-body`
|
||||
- **Alpine.js state:** `x-data` on rows uses `data-key="show"` for animation—this is re-established on re-render
|
||||
- **Field names:** Account/location/amount field names follow `step-params[transaction/accounts][N][...]` pattern
|
||||
|
||||
## Error Handling
|
||||
|
||||
- **Zero transaction amount:** If total is $0, percentages are all 0%. Toggle is disabled or shows error.
|
||||
- **Percentage sum ≠ 100:** After editing in % mode, if percentages don't sum to 100, normalize proportionally before converting back to $.
|
||||
- **Invalid input:** If user types non-numeric in % mode, existing form validation catches it on submit.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Toggle $→%:** 200/200 transaction shows 100.0
|
||||
2. **Toggle %→$:** 100% on 200 transaction shows 200.00
|
||||
3. **Multiple accounts:** 50/50 split on 200 → 100.00/100.00 after conversion
|
||||
4. **Cent distribution:** 33.33/33.33/33.34% on $100 → uses spread-cents for accurate distribution
|
||||
5. **Form preservation:** Toggle doesn't lose vendor/memo data
|
||||
6. **Save in % mode:** Correctly converts back to $ before Datomic transaction
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `src/clj/auto_ap/ssr/transaction/edit.clj` — main implementation
|
||||
- `src/clj/auto_ap/routes/transactions.clj` — add `::route/toggle-amount-mode`
|
||||
- `src/clj/auto_ap/ssr/transaction/edit.clj` routes map — register handler
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- This pattern could be extracted for reuse in invoice expense accounts
|
||||
- Consider persisting user's last-used mode preference in localStorage
|
||||
- Could add visual indicator when percentages don't sum to 100%
|
||||
File diff suppressed because one or more lines are too long
@@ -148,7 +148,9 @@
|
||||
next-button-content]}]
|
||||
[:div.flex.justify-end
|
||||
[:div.flex.items-baseline.gap-x-4
|
||||
(com/form-errors {:errors (:errors (:step-params fc/*form-errors*))})
|
||||
(let [step-errors (:step-params fc/*form-errors*)]
|
||||
(com/form-errors {:errors (or (:errors step-errors)
|
||||
(when (sequential? step-errors) step-errors))}))
|
||||
(when (not= (first (steps linear-wizard))
|
||||
(step-key step))
|
||||
(when validation-route
|
||||
|
||||
@@ -2,42 +2,44 @@
|
||||
(:require [auto-ap.ssr.hiccup-helper :as hh]
|
||||
[auto-ap.ssr.hx :as hx]))
|
||||
|
||||
(defn radio-card- [{:keys [options name title size orientation width] :or {size :medium width "w-48"} selected-value :value}]
|
||||
[:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title]
|
||||
[:ul {:class (cond-> " 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")
|
||||
(hh/remove-wildcard ["w-" "rounded-lg" "border" "bg-"]))
|
||||
true (str " " width " "))}
|
||||
(for [{:keys [value content]} options]
|
||||
[:li {:class (cond-> "w-full border-b border-gray-200 rounded-t-lg dark:border-gray-600"
|
||||
(= orientation :horizontal) (-> (hh/remove-wildcard ["w-full" "rounded-"])
|
||||
(hh/add-class "w-auto shrink-0 block rounded-lg border border-gray-200 dark:border-gray-600 px-3")))}
|
||||
[:div {:class (cond-> "flex items-center"
|
||||
(not= orientation :horizontal) (hh/add-class "pl-3"))}
|
||||
[:input (cond-> {:id (str "list-" name "-" value)
|
||||
:type "radio",
|
||||
:value value
|
||||
:name name
|
||||
:class
|
||||
(cond-> "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
|
||||
(= size :small)
|
||||
(str " " "text-xs")
|
||||
(defn radio-card- [{:keys [options name title size orientation width] :or {size :medium width "w-48"} selected-value :value :as attrs}]
|
||||
(let [htmx-attrs (select-keys attrs [:hx-post :hx-target :hx-swap :hx-include :hx-trigger])]
|
||||
[:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title]
|
||||
[:ul {:class (cond-> " 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")
|
||||
(hh/remove-wildcard ["w-" "rounded-lg" "border" "bg-"]))
|
||||
true (str " " width " "))}
|
||||
(for [{:keys [value content]} options]
|
||||
[:li {:class (cond-> "w-full border-b border-gray-200 rounded-t-lg dark:border-gray-600"
|
||||
(= orientation :horizontal) (-> (hh/remove-wildcard ["w-full" "rounded-"])
|
||||
(hh/add-class "w-auto shrink-0 block rounded-lg border border-gray-200 dark:border-gray-600 px-3")))}
|
||||
[:div {:class (cond-> "flex items-center"
|
||||
(not= orientation :horizontal) (hh/add-class "pl-3"))}
|
||||
[:input (cond-> (merge {:id (str "list-" name "-" value)
|
||||
:type "radio",
|
||||
:value value
|
||||
:name name
|
||||
:class
|
||||
(cond-> "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
|
||||
(= 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)
|
||||
(str " " "text-xs py-2")
|
||||
(= size :medium)
|
||||
(str " " "text-sm"))}
|
||||
htmx-attrs)
|
||||
(= (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")
|
||||
(= size :medium)
|
||||
(str " " "text-sm py-3")
|
||||
|
||||
(= orientation :horizontal)
|
||||
(hh/remove-class "w-full"))} content]]])]))
|
||||
|
||||
(= 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"
|
||||
@@ -55,7 +57,7 @@
|
||||
:value value
|
||||
:name name
|
||||
:class
|
||||
(cond-> "w-4 h-4 text-blue-600"
|
||||
(cond-> "w-4 h-4 text-blue-600"
|
||||
(= size :small)
|
||||
(str " " "text-xs")
|
||||
|
||||
@@ -65,11 +67,11 @@
|
||||
[: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 :small)
|
||||
(str " " "text-xs py-2")
|
||||
|
||||
(= size :medium)
|
||||
(str " " "text-sm py-3")
|
||||
(= size :medium)
|
||||
(str " " "text-sm py-3")
|
||||
|
||||
(= orientation :horizontal)
|
||||
(hh/remove-class "w-full"))} content]]])])
|
||||
(= orientation :horizontal)
|
||||
(hh/remove-class "w-full"))} content]]])])
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
(ns auto-ap.ssr.transaction.common
|
||||
(:require
|
||||
[auto-ap.datomic :refer [add-sorter-fields apply-pagination apply-sort-4
|
||||
conn merge-query observable-query pull-many]]
|
||||
[auto-ap.graphql.utils :refer [extract-client-ids]]
|
||||
[auto-ap.datomic :refer [add-sorter-fields apply-pagination apply-sort-4
|
||||
conn merge-query observable-query pull-many]]
|
||||
[auto-ap.datomic.accounts :as d-accounts]
|
||||
[auto-ap.graphql.utils :refer [extract-client-ids is-admin?]]
|
||||
[auto-ap.routes.invoice :as invoice-routes]
|
||||
[auto-ap.routes.ledger :as ledger-routes]
|
||||
[auto-ap.routes.payments :as payment-routes]
|
||||
@@ -32,11 +33,26 @@
|
||||
[:amount-gte {:optional true} [:maybe :double]]
|
||||
[:amount-lte {:optional true} [:maybe :double]]
|
||||
[:client-id {:optional true} [:maybe entity-id]]
|
||||
[:import-batch-id {:optional true} [:maybe entity-id]]
|
||||
[:description {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||
[:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]]
|
||||
[:bank-account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :bank-account/numeric-code]}]]]
|
||||
#_[:status {:optional true} [:maybe (ref->enum-schema "transaction-status")]]
|
||||
[:import-batch-id {:optional true} [:maybe entity-id]]
|
||||
[:unresolved {:optional true}
|
||||
[:maybe [:boolean {:decode/string {:enter #(cond (= % "on") true
|
||||
(= % "") false
|
||||
:else
|
||||
(boolean %))}}]]]
|
||||
[:description {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||
[:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]]
|
||||
[:bank-account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :bank-account/numeric-code]}]]]
|
||||
[:account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :account/name]}]]]
|
||||
[:linked-to {:optional true}
|
||||
[:maybe [:enum {:decode/string {:enter #(if (seq %) % nil)}}
|
||||
"payment" "expected-deposit" "invoice" "none"]]]
|
||||
[:location {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||
[:potential-duplicates {:optional true}
|
||||
[:maybe [:boolean {:decode/string {:enter #(cond (= % "on") true
|
||||
(= % "") false
|
||||
:else
|
||||
(boolean %))}}]]]
|
||||
#_[:status {:optional true} [:maybe (ref->enum-schema "transaction-status")]]
|
||||
[:exact-match-id {:optional true} [:maybe entity-id]]
|
||||
[:all-selected {:optional true :default nil} [:maybe :boolean]]
|
||||
[:selected {:optional true :default nil} [:maybe [:vector {:coerce? true}
|
||||
@@ -145,16 +161,75 @@
|
||||
:where ['[?e :transaction/bank-account ?ba]]}
|
||||
:args [(:db/id (:bank-account args))]})
|
||||
|
||||
(:vendor args)
|
||||
(merge-query {:query {:in ['?vendor-id]
|
||||
:where ['[?e :transaction/vendor ?vendor-id]]}
|
||||
:args [(:db/id (:vendor args))]})
|
||||
(:import-batch-id args)
|
||||
(merge-query {:query {:in ['?import-batch-id]
|
||||
:where ['[?import-batch-id :import-batch/entry ?e]]}
|
||||
:args [(:import-batch-id args)]})
|
||||
(:vendor args)
|
||||
(merge-query {:query {:in ['?vendor-id]
|
||||
:where ['[?e :transaction/vendor ?vendor-id]]}
|
||||
:args [(:db/id (:vendor args))]})
|
||||
|
||||
(:status route-params)
|
||||
(:db/id (:account args))
|
||||
(merge-query {:query {:in ['?account-id]
|
||||
:where ['[?e :transaction/accounts ?tas]
|
||||
'[?tas :transaction-account/account ?account-id]]}
|
||||
:args [(:db/id (:account args))]})
|
||||
(:import-batch-id args)
|
||||
(merge-query {:query {:in ['?import-batch-id]
|
||||
:where ['[?import-batch-id :import-batch/entry ?e]]}
|
||||
:args [(:import-batch-id args)]})
|
||||
|
||||
(:unresolved args)
|
||||
(merge-query {:query {:where ['[?e :transaction/date]
|
||||
'(or-join [?e]
|
||||
(not [?e :transaction/accounts])
|
||||
(and [?e :transaction/accounts ?tas]
|
||||
(not [?tas :transaction-account/account]))) ]}})
|
||||
|
||||
(seq (:location args))
|
||||
(merge-query {:query {:in ['?location]
|
||||
:where ['[?e :transaction/accounts ?tas]
|
||||
'[?tas :transaction-account/location ?location]]}
|
||||
:args [(:location args)]})
|
||||
|
||||
(= (:linked-to args) "payment")
|
||||
(merge-query {:query {:where ['[?e :transaction/payment]]}})
|
||||
|
||||
(= (:linked-to args) "expected-deposit")
|
||||
(merge-query {:query {:where ['[?e :transaction/expected-deposit]]}})
|
||||
|
||||
(= (:linked-to args) "invoice")
|
||||
(merge-query {:query {:where ['[?e :transaction/payment ?p]
|
||||
'[_ :invoice-payment/payment ?p]]}})
|
||||
|
||||
(= (:linked-to args) "none")
|
||||
(merge-query {:query {:where ['(not [?e :transaction/payment])
|
||||
'(not [?e :transaction/expected-deposit])]}})
|
||||
|
||||
(:potential-duplicates args)
|
||||
(merge-query (let [bank-account-id (:db/id (:bank-account args))
|
||||
_ (when-not bank-account-id
|
||||
(throw (ex-info "In order to select potential duplicates, you must choose a bank account."
|
||||
{:validation-error "In order to select potential duplicates, you must choose a bank account."})))
|
||||
duplicate-ids (->> (dc/q '[:find ?tx ?amount ?date
|
||||
:in $ ?ba
|
||||
:where
|
||||
[?tx :transaction/bank-account ?ba]
|
||||
[?tx :transaction/amount ?amount]
|
||||
[?tx :transaction/date ?date]
|
||||
(not [?tx :transaction/approval-status :transaction-approval-status/suppressed])]
|
||||
db
|
||||
bank-account-id)
|
||||
(group-by (fn [[_ amount date]]
|
||||
[amount date]))
|
||||
(filter (fn [[_ txes]]
|
||||
(> (count txes) 1)))
|
||||
(vals)
|
||||
(mapcat identity)
|
||||
(map first)
|
||||
set)]
|
||||
{:query {:in '[[?e ...]]
|
||||
:where []}
|
||||
:args [duplicate-ids]}))
|
||||
|
||||
(:status route-params)
|
||||
(merge-query {:query {:in ['?status]
|
||||
:where ['[?e :transaction/approval-status ?status]]}
|
||||
:args [(:status route-params)]})
|
||||
@@ -203,6 +278,18 @@
|
||||
svg/x)]])]
|
||||
[:div {:id "exact-match-id-tag"}]))
|
||||
|
||||
(defn import-batch-id* [request]
|
||||
(when-let [import-batch-id (:import-batch-id (:query-params request))]
|
||||
[:div {:x-data (hx/json {:import_batch_id import-batch-id}) :id "import-batch-id-tag"}
|
||||
(com/hidden {:name "import-batch-id"
|
||||
"x-model" "import_batch_id"})
|
||||
(com/pill {:color :primary}
|
||||
[:span.inline-flex.space-x-2.items-center
|
||||
[:div (str "Batch " import-batch-id)]
|
||||
[:div.w-3.h-3
|
||||
(com/link {"@click" "import_batch_id=null; $nextTick(() => $dispatch('change'))"}
|
||||
svg/x)]])]))
|
||||
|
||||
|
||||
|
||||
(defn bank-account-filter* [request]
|
||||
@@ -237,43 +324,93 @@
|
||||
(com/hidden {:name "status"
|
||||
:value (some-> (:status (:query-params request)) name)})
|
||||
[:fieldset.space-y-6
|
||||
(com/field {:label "Vendor"}
|
||||
(com/typeahead {:name "vendor"
|
||||
:id "vendor"
|
||||
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
|
||||
:value (:vendor (:query-params request))
|
||||
:value-fn :db/id
|
||||
:content-fn :vendor/name}))
|
||||
(bank-account-filter* request)
|
||||
(com/field {:label "Vendor"}
|
||||
(com/typeahead {:name "vendor"
|
||||
:id "vendor"
|
||||
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
|
||||
:value (:vendor (:query-params request))
|
||||
:value-fn :db/id
|
||||
:content-fn :vendor/name}))
|
||||
(com/field {:label "Financial Account"}
|
||||
(com/typeahead {:name "account"
|
||||
:id "account"
|
||||
:url (bidi/path-for ssr-routes/only-routes :account-search)
|
||||
:value (:account (:query-params request))
|
||||
:value-fn :db/id
|
||||
:content-fn #(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read (:db/id %))
|
||||
(:db/id (:client request))))}))
|
||||
(bank-account-filter* request)
|
||||
|
||||
(date-range-field* request)
|
||||
(com/field {:label "Description"}
|
||||
(com/text-input {:name "description"
|
||||
:id "description"
|
||||
:class "hot-filter"
|
||||
:value (:description (:query-params request))
|
||||
:placeholder "e.g., Groceries"
|
||||
:size :small}))
|
||||
(com/field {:label "Description"}
|
||||
(com/text-input {:name "description"
|
||||
:id "description"
|
||||
:class "hot-filter"
|
||||
:value (:description (:query-params request))
|
||||
:placeholder "e.g., Groceries"
|
||||
:size :small}))
|
||||
|
||||
(com/field {:label "Amount"}
|
||||
[:div.flex.space-x-4.items-baseline
|
||||
(com/money-input {:name "amount-gte"
|
||||
:id "amount-gte"
|
||||
:hx-preserve "true"
|
||||
:class "hot-filter w-20"
|
||||
:value (:amount-gte (:query-params request))
|
||||
:placeholder "0.01"
|
||||
:size :small})
|
||||
[:div.align-baseline
|
||||
"to"]
|
||||
(com/money-input {:name "amount-lte"
|
||||
:hx-preserve "true"
|
||||
:id "amount-lte"
|
||||
:class "hot-filter w-20"
|
||||
:value (:amount-lte (:query-params request))
|
||||
:placeholder "9999.34"
|
||||
:size :small})])
|
||||
(exact-match-id* request)]])
|
||||
(com/field {:label "Location"}
|
||||
(com/text-input {:name "location"
|
||||
:id "location"
|
||||
:class "hot-filter"
|
||||
:value (:location (:query-params request))
|
||||
:placeholder "SC"
|
||||
:size :small}))
|
||||
|
||||
(com/field {:label "Amount"}
|
||||
[:div.flex.space-x-4.items-baseline
|
||||
(com/money-input {:name "amount-gte"
|
||||
:id "amount-gte"
|
||||
:hx-preserve "true"
|
||||
:class "hot-filter w-20"
|
||||
:value (:amount-gte (:query-params request))
|
||||
:placeholder "0.01"
|
||||
:size :small})
|
||||
[:div.align-baseline
|
||||
"to"]
|
||||
(com/money-input {:name "amount-lte"
|
||||
:hx-preserve "true"
|
||||
:id "amount-lte"
|
||||
:class "hot-filter w-20"
|
||||
:value (:amount-lte (:query-params request))
|
||||
:placeholder "9999.34"
|
||||
:size :small})])
|
||||
|
||||
(com/field {:label "Linking"}
|
||||
(com/radio-card {:size :small
|
||||
:name "linked-to"
|
||||
:value (or (:linked-to (:query-params request)) "")
|
||||
:options [{:value ""
|
||||
:content "All"}
|
||||
{:value "none"
|
||||
:content "None"}
|
||||
{:value "invoice"
|
||||
:content "Invoice"}
|
||||
{:value "expected-deposit"
|
||||
:content "Expected Deposit"}
|
||||
{:value "payment"
|
||||
:content "Payment"}]}))
|
||||
|
||||
(when (is-admin? (:identity request))
|
||||
[:div.mt-4 {:x-data (hx/json {:unresolvedOnly (:unresolved (:query-params request))})}
|
||||
(com/hidden {:name "unresolved"
|
||||
":value" "unresolvedOnly ? 'on' : ''"})
|
||||
(com/checkbox {:value (:unresolved (:query-params request))
|
||||
:x-model "unresolvedOnly"}
|
||||
"Unresolved only")])
|
||||
|
||||
(when (and (is-admin? (:identity request))
|
||||
(:db/id (:bank-account (:query-params request))))
|
||||
[:div.mt-4 {:x-data (hx/json {:potentialDuplicates (:potential-duplicates (:query-params request))})}
|
||||
(com/hidden {:name "potential-duplicates"
|
||||
":value" "potentialDuplicates ? 'on' : ''"})
|
||||
(com/checkbox {:value (:potential-duplicates (:query-params request))
|
||||
:x-model "potentialDuplicates"}
|
||||
"Same Amount + Date")])
|
||||
|
||||
(import-batch-id* request)
|
||||
(exact-match-id* request)]])
|
||||
|
||||
|
||||
(def grid-page
|
||||
@@ -283,10 +420,11 @@
|
||||
:page-specific-nav filters
|
||||
:fetch-page fetch-page
|
||||
:query-schema query-schema
|
||||
:oob-render
|
||||
(fn [request]
|
||||
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)
|
||||
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)])
|
||||
:oob-render
|
||||
(fn [request]
|
||||
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)
|
||||
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)
|
||||
(some-> (import-batch-id* request) (assoc-in [1 :hx-swap-oob] true))])
|
||||
:action-buttons (fn [request]
|
||||
[
|
||||
(com/button {:color :primary
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,9 +29,10 @@
|
||||
} }
|
||||
"/edit-submit" ::edit-submit
|
||||
"/location-select" ::location-select
|
||||
"/account-total" ::account-total
|
||||
"/account-balance" ::account-balance
|
||||
"/edit-wizard-new-account" ::edit-wizard-new-account
|
||||
"/account-total" ::account-total
|
||||
"/account-balance" ::account-balance
|
||||
"/toggle-amount-mode" ::toggle-amount-mode
|
||||
"/edit-wizard-new-account" ::edit-wizard-new-account
|
||||
"/match-payment" ::link-payment
|
||||
"/match-autopay-invoices" ::link-autopay-invoices
|
||||
"/match-unpaid-invoices" ::link-unpaid-invoices
|
||||
|
||||
Reference in New Issue
Block a user