Merge branch 'integreat-fix-errors' into staging

This commit is contained in:
2026-05-24 21:54:54 -07:00
6 changed files with 5522 additions and 74 deletions

View File

@@ -5,7 +5,7 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@opencode-ai/plugin": "1.14.31" "@opencode-ai/plugin": "1.15.10"
} }
}, },
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
@@ -87,32 +87,36 @@
] ]
}, },
"node_modules/@opencode-ai/plugin": { "node_modules/@opencode-ai/plugin": {
"version": "1.14.31", "version": "1.15.10",
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.14.31.tgz", "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.15.10.tgz",
"integrity": "sha512-ZF7UoNKtZDtgW/2KrcFw5I7R2HRj/NigBuRwKPonvSZS36LnghZ7PYcXYZFGCjEgBmLUMMrLVgxccKLyxsgB0g==", "integrity": "sha512-V2p7CvpBtKWB+FID7Dl1y0Ci02zUT40A9b2RD9R9BOiuD8ZcKhHWov+irN0xVJA0Eg6OhEBfA0lPKRn1FNKPlw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "1.14.31", "@opencode-ai/sdk": "1.15.10",
"effect": "4.0.0-beta.57", "effect": "4.0.0-beta.66",
"zod": "4.1.8" "zod": "4.1.8"
}, },
"peerDependencies": { "peerDependencies": {
"@opentui/core": ">=0.2.0", "@opentui/core": ">=0.2.15",
"@opentui/solid": ">=0.2.0" "@opentui/keymap": ">=0.2.15",
"@opentui/solid": ">=0.2.15"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@opentui/core": { "@opentui/core": {
"optional": true "optional": true
}, },
"@opentui/keymap": {
"optional": true
},
"@opentui/solid": { "@opentui/solid": {
"optional": true "optional": true
} }
} }
}, },
"node_modules/@opencode-ai/sdk": { "node_modules/@opencode-ai/sdk": {
"version": "1.14.31", "version": "1.15.10",
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.14.31.tgz", "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.15.10.tgz",
"integrity": "sha512-QaV+ti3NYUITmgIDqtNMqGIYBXJOx2zheN1g+7w4HC8QQsbaW1c7glxXExQHRbdUzcQPP2vUQhnXOcEsTw5CcQ==", "integrity": "sha512-CUhpmMGGOqzvPnNNjjWmEIodAfP6Qnuki2ChIUKWYF7UImZ4zUcMZnzO5BtUxu/Ni1P8qzWxDioXs+7aIZQEhA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cross-spawn": "7.0.6" "cross-spawn": "7.0.6"
@@ -149,9 +153,9 @@
} }
}, },
"node_modules/effect": { "node_modules/effect": {
"version": "4.0.0-beta.57", "version": "4.0.0-beta.66",
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.57.tgz", "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.66.tgz",
"integrity": "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g==", "integrity": "sha512-4arEr62cziFa8BBVDUwJCJJmaVepXf/kRg7KtC0h8+bufngscrHbwWFhr9c+HonwOF+31U3iD3xUJmw9KzX7Dw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.1.0", "@standard-schema/spec": "^1.1.0",
@@ -167,9 +171,9 @@
} }
}, },
"node_modules/fast-check": { "node_modules/fast-check": {
"version": "4.7.0", "version": "4.8.0",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz",
"integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -216,9 +220,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/msgpackr": { "node_modules/msgpackr": {
"version": "1.11.10", "version": "1.11.12",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz", "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.12.tgz",
"integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==", "integrity": "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==",
"license": "MIT", "license": "MIT",
"optionalDependencies": { "optionalDependencies": {
"msgpackr-extract": "^3.0.2" "msgpackr-extract": "^3.0.2"
@@ -323,9 +327,9 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "13.0.1", "version": "13.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.1.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz",
"integrity": "sha512-9ezox2roIft6ExBVTVqibSd5dc5/47Sw/uY6b4SjQUT2TzQ0tltNquWA46y4xPQmdZYqvnio22SgWd41M86+jw==", "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==",
"funding": [ "funding": [
"https://github.com/sponsors/broofa", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan" "https://github.com/sponsors/ctavan"
@@ -351,9 +355,9 @@
} }
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.8.3", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"

View File

@@ -1,14 +1,14 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
async function openEditModal(page: any) { async function openEditModal(page: any, transactionIndex: number = 0) {
// Navigate to transactions page // Navigate to transactions page
await page.goto('/transaction2'); await page.goto('/transaction2');
// Wait for the table to load // Wait for the table to load
await page.waitForSelector('table tbody tr'); await page.waitForSelector('table tbody tr');
// Find and click the edit button for the test transaction // Find and click the edit button for the specified transaction
const editButton = page.locator('button[hx-get*="/transaction2/"][hx-get*="/edit"]').first(); const editButton = page.locator('button[hx-get*="/transaction2/"][hx-get*="/edit"]').nth(transactionIndex);
await editButton.click(); await editButton.click();
// Wait for the modal to open // Wait for the modal to open
@@ -137,6 +137,49 @@ async function addNewAccount(page: any) {
await page.waitForTimeout(500); await page.waitForTimeout(500);
} }
async function setAccountLocation(page: any, rowIndex: number, location: string) {
const row = await findAccountRow(page, rowIndex);
const locationSelect = row.locator('select[name*="location"]').first();
// If the option doesn't exist, add it (for testing invalid locations)
const optionExists = await locationSelect.locator(`option[value="${location}"]`).count() > 0;
if (!optionExists) {
await locationSelect.evaluate((el: HTMLSelectElement, value: string) => {
const option = document.createElement('option');
option.value = value;
option.textContent = value;
el.appendChild(option);
}, location);
}
await locationSelect.selectOption(location);
await locationSelect.dispatchEvent('change');
await page.waitForTimeout(300);
}
async function getAccountLocation(page: any, rowIndex: number): Promise<string> {
const row = await findAccountRow(page, rowIndex);
const locationSelect = row.locator('select[name*="location"]').first();
return await locationSelect.inputValue();
}
async function removeAllAccounts(page: any) {
const allRows = page.locator('#account-grid-body tbody tr');
const rowCount = await allRows.count();
for (let i = rowCount - 1; i >= 0; i--) {
const row = allRows.nth(i);
const hasAccountInput = await row.locator('input[name*="transaction-account/account"]').count() > 0;
if (hasAccountInput) {
// Click the X button to remove (it's an <a> tag, not a <button>)
const removeButton = row.locator('a').first();
await removeButton.click();
// Wait for the Alpine.js removal animation (500ms + buffer)
await page.waitForTimeout(700);
}
}
}
async function saveTransaction(page: any) { async function saveTransaction(page: any) {
// Submit the form directly instead of clicking the button // Submit the form directly instead of clicking the button
// The Done button might not have type="submit" // The Done button might not have type="submit"
@@ -175,6 +218,44 @@ async function toggleToDollarMode(page: any) {
test.describe.configure({ mode: 'serial' }); test.describe.configure({ mode: 'serial' });
test.describe('Transaction Edit Shared Location', () => {
test('should spread Shared location to client locations on save and display correctly on reopen', async ({ page }) => {
// Use the second transaction to avoid interfering with other tests
const transactionIndex = 1;
// Step 1: Open edit modal and add an account with Shared location
await openEditModal(page, transactionIndex);
// Add a new account row
await addNewAccount(page);
// Select the account
await selectAccountFromTypeahead(page, 0, 'Test');
// Set location to Shared
await setAccountLocation(page, 0, 'Shared');
// Set amount to $200 (the full transaction amount for the second transaction)
await setAccountAmount(page, 0, '200');
// Save the transaction
await saveTransaction(page);
// Step 2: Re-open and verify location is not "Shared" but the actual client location
await openEditModal(page, transactionIndex);
// Wait for accounts to load
await page.waitForTimeout(500);
// Get the location of the first account
const location = await getAccountLocation(page, 0);
// The location should be the actual client location ("DT" in test data), not "Shared"
expect(location).not.toBe('Shared');
expect(location).toBe('DT');
});
});
test.describe('Transaction Edit Full Workflow', () => { test.describe('Transaction Edit Full Workflow', () => {
test('should code transaction with vendor using percentage, then split 50/50, then switch to dollars', async ({ page }) => { test('should code transaction with vendor using percentage, then split 50/50, then switch to dollars', async ({ page }) => {
// Step 1: Open edit modal and code with 100% to one account // Step 1: Open edit modal and code with 100% to one account
@@ -246,14 +327,15 @@ test.describe('Transaction Edit Full Workflow', () => {
test.describe('Transaction Edit Validation', () => { test.describe('Transaction Edit Validation', () => {
test('should show validation error when account totals do not match transaction amount', async ({ page }) => { test('should show validation error when account totals do not match transaction amount', async ({ page }) => {
await openEditModal(page); // Use the third transaction to avoid interference from other tests
await openEditModal(page, 2);
// Stay in dollar mode (default) // Stay in dollar mode (default)
// Add an account // Add an account
await addNewAccount(page); await addNewAccount(page);
await selectAccountFromTypeahead(page, 0, 'Test'); await selectAccountFromTypeahead(page, 0, 'Test');
// Set amount to $50 (which doesn't match the $100 transaction) // Set amount to $50 (which doesn't match the $300 transaction)
await setAccountAmount(page, 0, '50'); await setAccountAmount(page, 0, '50');
// Try to save - this should fail because $50 != $100 // Try to save - this should fail because $50 != $100
@@ -279,6 +361,12 @@ test.describe('Transaction Edit Validation', () => {
const amountInput = page.locator('input[name*="transaction-account/amount"]').first(); const amountInput = page.locator('input[name*="transaction-account/amount"]').first();
const value = await amountInput.inputValue(); const value = await amountInput.inputValue();
expect(parseFloat(value)).toBeCloseTo(50.0, 1); expect(parseFloat(value)).toBeCloseTo(50.0, 1);
// Verify the user-friendly error message is displayed
const errorElement = page.locator('#form-errors .error-content');
await expect(errorElement).toBeVisible();
const errorText = await errorElement.textContent();
expect(errorText).toContain('The total of your expense accounts ($50.00) must equal the transaction amount ($300.00)');
}); });
}); });

5342
resources/public/js/htmx.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -215,11 +215,11 @@
(fn [{:keys [wizard multi-form-state] :as request}] (fn [{:keys [wizard multi-form-state] :as request}]
(assert-schema (step-schema (get-current-step wizard)) (:step-params multi-form-state)) (assert-schema (step-schema (get-current-step wizard)) (:step-params multi-form-state))
(handler request)) (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 (wrap-form-4xx-2 (fn [{:keys [wizard] :as request}] ;; THIS MAY BE BETTER TO JUST MAKE THE LINEAR WIZARD POPULATE FROM THE REQUEST
(html-response (html-response
(render-wizard wizard request) (render-wizard wizard request)
:headers {"x-transition-type" "none" :headers {"x-transition-type" "none"
"HX-reswap" "outerHTML"}))))) "HX-reswap" "outerHTML"})))))
(defn get-transition-type [wizard from-step-key to-step-key] (defn get-transition-type [wizard from-step-key to-step-key]
(let [to-step-index (.indexOf (steps wizard) to-step-key) (let [to-step-index (.indexOf (steps wizard) to-step-key)

View File

@@ -161,7 +161,7 @@
[["Shared" "Shared"]]))] [["Shared" "Shared"]]))]
(com/select {:options options (com/select {:options options
:name name :name name
:value (ffirst options) :value (or value (ffirst options))
:class "w-full"}))) :class "w-full"})))
(defn account-typeahead* (defn account-typeahead*
@@ -313,7 +313,9 @@
(filter number?) (filter number?)
(reduce + 0.0)) (reduce + 0.0))
balance (- balance (-
(Math/abs (-> request :multi-form-state :snapshot :transaction/amount)) (Math/abs (or (-> request :entity :transaction/amount)
(-> request :multi-form-state :snapshot :transaction/amount)
0.0))
total)] total)]
[:span {:class (when-not (dollars= 0.0 balance) [:span {:class (when-not (dollars= 0.0 balance)
"text-red-300")} "text-red-300")}
@@ -355,7 +357,9 @@
(defn account-grid-body* [request] (defn account-grid-body* [request]
(let [snapshot (-> request :multi-form-state :snapshot) (let [snapshot (-> request :multi-form-state :snapshot)
amount-mode (or (:amount-mode snapshot) "$") amount-mode (or (:amount-mode snapshot) "$")
total (Math/abs (or (:transaction/amount snapshot) 0.0))] total (Math/abs (or (-> request :entity :transaction/amount)
(:transaction/amount snapshot)
0.0))]
(com/data-grid {:headers [(com/data-grid-header {} "Account") (com/data-grid {:headers [(com/data-grid-header {} "Account")
(com/data-grid-header {:class "w-32"} "Location") (com/data-grid-header {:class "w-32"} "Location")
(com/data-grid-header {:class "w-16"} (com/data-grid-header {:class "w-16"}
@@ -913,7 +917,7 @@
[:div {:x-show "activeForm === 'manual'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"} [:div {:x-show "activeForm === 'manual'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
[:div {} [:div {}
[:div {:hx-trigger "change" [:div {:hx-trigger "change"
:hx-get (bidi/path-for ssr-routes/only-routes ::route/edit-vendor-changed) :hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-vendor-changed)
:hx-target "#account-grid-body" :hx-target "#account-grid-body"
:hx-swap "outerHTML" :hx-swap "outerHTML"
:hx-include "closest form"} :hx-include "closest form"}
@@ -1246,14 +1250,16 @@
(when (and (= :transaction-approval-status/approved (keyword (:transaction/approval-status tx-data))) (when (and (= :transaction-approval-status/approved (keyword (:transaction/approval-status tx-data)))
(not (seq (:transaction/accounts tx-data)))) (not (seq (:transaction/accounts tx-data))))
(throw (ex-info "Approved transactions must have accounts assigned." (throw (ex-info "Approved transactions must have accounts assigned."
{:validation-error "Approved transactions must have accounts assigned."}))) {:type :form-validation
:form-validation-errors ["Approved transactions must have accounts assigned."]})))
(when (seq (:transaction/accounts tx-data)) (when (seq (:transaction/accounts tx-data))
(let [account-total (reduce + 0 (map :transaction-account/amount (:transaction/accounts tx-data)))] (let [account-total (reduce + 0 (map :transaction-account/amount (:transaction/accounts tx-data)))
(when (not (dollars= (Math/abs (:transaction/amount existing-tx)) account-total)) tx-amount (Math/abs (:transaction/amount existing-tx))]
(throw (ex-info (str "Account total (" account-total ") does not equal transaction amount (" (when (not (dollars= tx-amount account-total))
(Math/abs (:transaction/amount existing-tx)) ")") (throw (ex-info (format "The total of your expense accounts ($%,.2f) must equal the transaction amount ($%,.2f)." account-total tx-amount)
{:validation-error "Account totals must match transaction amount."}))))) {:type :form-validation
:form-validation-errors [(format "The total of your expense accounts ($%,.2f) must equal the transaction amount ($%,.2f)." account-total tx-amount)]})))))
(let [transaction-result (audit-transact [transaction] (:identity request))] (let [transaction-result (audit-transact [transaction] (:identity request))]
(try (try
@@ -1348,8 +1354,6 @@
(mm/get-step this current-step) (mm/get-step this current-step)
(mm/get-step this :basic-details))) (mm/get-step this :basic-details)))
(render-wizard [this {:keys [multi-form-state] :as request}] (render-wizard [this {:keys [multi-form-state] :as request}]
(println "HERE XYZ" (:form-errors request))
(clojure.pprint/pprint (:snapshot multi-form-state))
(mm/default-render-wizard (mm/default-render-wizard
this request this request
:form-params :form-params
@@ -1394,34 +1398,40 @@
[] []
entity))) entity)))
(defn- render-account-grid-body [request]
(fc/start-form (:multi-form-state request) nil
(fc/with-field :step-params
(fc/with-field :transaction/accounts
(account-grid-body* request)))))
(defn edit-vendor-changed-handler [request] (defn edit-vendor-changed-handler [request]
(let [snapshot (:snapshot (:multi-form-state request)) (let [multi-form-state (:multi-form-state request)
snapshot (:snapshot multi-form-state)
step-params (:step-params multi-form-state)
client-id (or (:transaction/client snapshot) client-id (or (:transaction/client snapshot)
(-> request :entity :transaction/client :db/id)) (-> request :entity :transaction/client :db/id))
vendor-id (:transaction/vendor snapshot) vendor-id (or (:transaction/vendor step-params) (:transaction/vendor snapshot))
total (Math/abs (or (:transaction/amount snapshot) 0.0)) total (Math/abs (or (-> request :entity :transaction/amount)
amount-mode (or (:amount-mode snapshot) "$")] (:transaction/amount snapshot)
(if (and (empty? (:transaction/accounts snapshot)) 0.0))
vendor-id amount-mode (or (:amount-mode snapshot) "$")
client-id) existing-accounts (or (seq (:transaction/accounts step-params))
(if-let [default-account (vendor-default-account vendor-id client-id)] (seq (:transaction/accounts snapshot)))
(let [new-account {:db/id (str (java.util.UUID/randomUUID)) default-account (when (and (empty? existing-accounts) vendor-id client-id)
:transaction-account/account (:db/id default-account) (vendor-default-account vendor-id client-id))
:transaction-account/location (or (:account/location default-account) "Shared")} render-request
new-account (if (= amount-mode "%") (if (and (empty? existing-accounts) vendor-id client-id)
(assoc new-account :transaction-account/amount 100.0) (let [new-account (cond-> {:db/id (str (java.util.UUID/randomUUID))
(assoc new-account :transaction-account/amount total)) :transaction-account/location (or (:account/location default-account) "Shared")
updated-snapshot (assoc snapshot :transaction/accounts [new-account]) :transaction-account/amount (if (= amount-mode "%") 100.0 total)}
updated-request (assoc-in request [:multi-form-state :snapshot] updated-snapshot)] default-account (assoc :transaction-account/account (:db/id default-account)))]
(html-response (-> request
[:div#account-grid-body (assoc-in [:multi-form-state :snapshot :transaction/accounts] [new-account])
(account-grid-body* updated-request)])) (assoc-in [:multi-form-state :step-params :transaction/accounts] [new-account])))
(html-response request)]
[:div#account-grid-body (html-response
(account-grid-body* request)])) [:div#account-grid-body
(html-response (render-account-grid-body render-request)])))
[:div#account-grid-body
(account-grid-body* request)]))))
(def key->handler (def key->handler
(apply-middleware-to-all-handlers (apply-middleware-to-all-handlers
@@ -1443,6 +1453,7 @@
(mm/wrap-decode-multi-form-state)) (mm/wrap-decode-multi-form-state))
::route/edit-vendor-changed (-> edit-vendor-changed-handler ::route/edit-vendor-changed (-> edit-vendor-changed-handler
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state)) (mm/wrap-decode-multi-form-state))
::route/location-select (-> location-select ::route/location-select (-> location-select
(wrap-schema-enforce :query-schema [:map (wrap-schema-enforce :query-schema [:map
@@ -1453,12 +1464,15 @@
[:maybe entity-id]]])) [:maybe entity-id]]]))
::route/account-total (-> account-total ::route/account-total (-> account-total
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state)) (mm/wrap-decode-multi-form-state))
::route/account-balance (-> account-balance ::route/account-balance (-> account-balance
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state)) (mm/wrap-decode-multi-form-state))
::route/toggle-amount-mode (-> toggle-amount-mode ::route/toggle-amount-mode (-> toggle-amount-mode
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-decode-multi-form-state)) (mm/wrap-decode-multi-form-state))
::route/edit-wizard-new-account (-> ::route/edit-wizard-new-account (->
(add-new-entity-handler [:step-params :transaction/accounts] (add-new-entity-handler [:step-params :transaction/accounts]

View File

@@ -30,7 +30,7 @@
[:script {:src "https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" :defer true}] [:script {:src "https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" :defer true}]
[:link {:rel "stylesheet" :href "/css/tippy/tippy.css"}] [:link {:rel "stylesheet" :href "/css/tippy/tippy.css"}]
[:link {:rel "stylesheet" :href "/css/tippy/light.css"}] [:link {:rel "stylesheet" :href "/css/tippy/light.css"}]
[:script {:src "/js/htmx.min.js" [:script {:src "/js/htmx.js"
:crossorigin= "anonymous"}] :crossorigin= "anonymous"}]
[:script {:src "https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"}] [:script {:src "https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"}]