diff --git a/bun.lock b/bun.lock
index 94430d1f..7e2abcb2 100644
--- a/bun.lock
+++ b/bun.lock
@@ -42,6 +42,7 @@
"lucide-react": "^0.553.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "recharts": "^3.5.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.0",
@@ -337,6 +338,8 @@
"@react-types/shared": ["@react-types/shared@3.31.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg=="],
+ "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-hBjYg0aaRL1O2Z0IqWhnTLytnjDIxekmRxm1snsHjHaKVmIF1HiImWqsq+PuEbn6zdMlkIj9WofK1vR8jjx+Xw=="],
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.38", "", {}, "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.46.2", "", { "os": "android", "cpu": "arm" }, "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA=="],
@@ -379,6 +382,10 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.9.5", "", { "os": "win32", "cpu": "x64" }, "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ=="],
+ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+
+ "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
+
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.12", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.12" } }, "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ=="],
@@ -427,6 +434,24 @@
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
+ "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
+
+ "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
+
+ "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
+
+ "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
+
+ "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
+
+ "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
+
+ "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="],
+
+ "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
+
+ "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/json-logic-js": ["@types/json-logic-js@2.0.8", "", {}, "sha512-WgNsDPuTPKYXl0Jh0IfoCoJoAGGYZt5qzpmjuLSEg7r0cKp/kWtWp0HAsVepyPSPyXiHo6uXp/B/kW/2J1fa2Q=="],
@@ -443,6 +468,8 @@
"@types/react-dom": ["@types/react-dom@19.2.1", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A=="],
+ "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
+
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.39.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/type-utils": "8.39.1", "@typescript-eslint/utils": "8.39.1", "@typescript-eslint/visitor-keys": "8.39.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.39.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.39.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/types": "8.39.1", "@typescript-eslint/typescript-estree": "8.39.1", "@typescript-eslint/visitor-keys": "8.39.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg=="],
@@ -557,6 +584,28 @@
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+ "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
+
+ "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
+
+ "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
+
+ "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
+
+ "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
+
+ "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
+
+ "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
+
+ "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
+
+ "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
+
+ "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
+
+ "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
+
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
@@ -567,6 +616,8 @@
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
+ "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
+
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
@@ -609,6 +660,8 @@
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
+ "es-toolkit": ["es-toolkit@1.42.0", "", {}, "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA=="],
+
"esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": "bin/esbuild" }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -637,6 +690,8 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+ "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
@@ -719,6 +774,8 @@
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+ "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="],
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
@@ -727,6 +784,8 @@
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
+ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
+
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
@@ -941,6 +1000,8 @@
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+ "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
+
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
"react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="],
@@ -949,12 +1010,20 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
+ "recharts": ["recharts@3.5.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+v+HJojK7gnEgG6h+b2u7k8HH7FhyFUzAc4+cPrsjL4Otdgqr/ecXzAnHciqlzV1ko064eNcsdzrYOM78kankA=="],
+
+ "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
+
+ "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
+
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+ "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
+
"resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -1035,6 +1104,8 @@
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
+ "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -1079,6 +1150,8 @@
"vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
+ "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
+
"vite": ["vite@7.1.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ=="],
"vite-plugin-full-reload": ["vite-plugin-full-reload@1.2.0", "", { "dependencies": { "picocolors": "^1.0.0", "picomatch": "^2.3.1" } }, "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA=="],
@@ -1145,6 +1218,8 @@
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+ "@reduxjs/toolkit/immer": ["immer@11.0.1", "", {}, "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA=="],
+
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
diff --git a/resources/css/app.css b/resources/css/app.css
index ecb49c6a..8fbeffc2 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -13,7 +13,7 @@
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
- --font-mono: "Google Sans Code", monospace;
+ --font-mono: 'Google Sans Code', monospace;
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
@@ -180,7 +180,6 @@
}
}
-
.font-heading {
font-family: 'Stack Sans Text', ui-sans-serif, system-ui, sans-serif;
}
diff --git a/resources/js/app.tsx b/resources/js/app.tsx
index c4111d4c..59bb207f 100644
--- a/resources/js/app.tsx
+++ b/resources/js/app.tsx
@@ -2,6 +2,13 @@ import '../css/app.css';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
+import {
+ CircleCheckIcon,
+ InfoIcon,
+ Loader2Icon,
+ OctagonXIcon,
+ TriangleAlertIcon,
+} from 'lucide-react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { Toaster } from 'sonner';
@@ -9,7 +16,6 @@ import { EncryptionKeyProvider } from './contexts/encryption-key-context';
import { SyncProvider } from './contexts/sync-context';
import { initializeTheme } from './hooks/use-appearance';
import type { SharedData } from './types';
-import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon } from 'lucide-react';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
@@ -35,13 +41,20 @@ createInertiaApp({
>
- ,
+ success: (
+
+ ),
info: ,
- warning: ,
+ warning: (
+
+ ),
error: ,
- loading: ,
+ loading: (
+
+ ),
}}
/>
diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx
index c34846f3..a1cadbfc 100644
--- a/resources/js/components/app-logo-icon.tsx
+++ b/resources/js/components/app-logo-icon.tsx
@@ -1,12 +1,18 @@
import { cn } from '@/lib/utils';
import { BirdIcon, SVGAttributes } from 'lucide-react';
-export default function AppLogoIcon({ className }: { className?: SVGAttributes['className'] }) {
+export default function AppLogoIcon({
+ className,
+}: {
+ className?: SVGAttributes['className'];
+}) {
return (
-
+
);
}
diff --git a/resources/js/components/automation-rules/rule-builder.tsx b/resources/js/components/automation-rules/rule-builder.tsx
index 898ade48..c5c012f4 100644
--- a/resources/js/components/automation-rules/rule-builder.tsx
+++ b/resources/js/components/automation-rules/rule-builder.tsx
@@ -146,48 +146,45 @@ export function RuleBuilder({ value, onChange, error }: RuleBuilderProps) {
{group.conditions.map((condition) => (
-
-
{
- updateGroup(group.id, {
- ...group,
- conditions:
- group.conditions.map(
- (c) =>
- c.id ===
- condition.id
- ? updatedCondition
- : c,
- ),
- });
- }}
- onRemove={() => {
- if (
- group.conditions
- .length === 1
- ) {
- return;
- }
- updateGroup(group.id, {
- ...group,
- conditions:
- group.conditions.filter(
- (c) =>
- c.id !==
- condition.id,
- ),
- });
- }}
- canRemove={
- group.conditions.length > 1
+
+ {
+ updateGroup(group.id, {
+ ...group,
+ conditions:
+ group.conditions.map(
+ (c) =>
+ c.id ===
+ condition.id
+ ? updatedCondition
+ : c,
+ ),
+ });
+ }}
+ onRemove={() => {
+ if (
+ group.conditions.length ===
+ 1
+ ) {
+ return;
}
- />
-
- ),
- )}
+ updateGroup(group.id, {
+ ...group,
+ conditions:
+ group.conditions.filter(
+ (c) =>
+ c.id !==
+ condition.id,
+ ),
+ });
+ }}
+ canRemove={
+ group.conditions.length > 1
+ }
+ />
+
+ ))}
-
+
{account.type}
@@ -58,7 +58,7 @@ export function AccountBalanceCard({
{formatter.format(account.currentBalance / 100)}
-
+
{
if (!previous) return undefined;
const previousNet = previous.income - previous.expense;
if (previousNet === 0) return undefined;
-
+
const diff = currentNet - previousNet;
const percentage = (diff / Math.abs(previousNet)) * 100;
diff --git a/resources/js/components/dashboard/net-worth-chart.tsx b/resources/js/components/dashboard/net-worth-chart.tsx
index f263f8f8..fffd19c5 100644
--- a/resources/js/components/dashboard/net-worth-chart.tsx
+++ b/resources/js/components/dashboard/net-worth-chart.tsx
@@ -65,7 +65,10 @@ export function NetWorthChart({ data, loading }: NetWorthChartProps) {
/>
-
+
);
}
-
-
diff --git a/resources/js/components/dashboard/spending-summary-card.tsx b/resources/js/components/dashboard/spending-summary-card.tsx
index 8d22556c..85ffe74b 100644
--- a/resources/js/components/dashboard/spending-summary-card.tsx
+++ b/resources/js/components/dashboard/spending-summary-card.tsx
@@ -23,7 +23,7 @@ export function SpendingSummaryCard({
if (!previous) return undefined;
const diff = current - previous;
const percentage = (diff / previous) * 100;
-
+
return {
value: percentage,
label: 'from previous period',
diff --git a/resources/js/components/dashboard/stat-card.tsx b/resources/js/components/dashboard/stat-card.tsx
index 9429b16c..65e110a4 100644
--- a/resources/js/components/dashboard/stat-card.tsx
+++ b/resources/js/components/dashboard/stat-card.tsx
@@ -31,7 +31,7 @@ export function StatCard({
{title}
- {Icon && }
+ {Icon && }
@@ -45,12 +45,12 @@ export function StatCard({
{title}
- {Icon && }
+ {Icon && }
{value}
{(description || trend) && (
-
+
{trend && (
);
}
-
-
diff --git a/resources/js/components/dashboard/top-categories-card.tsx b/resources/js/components/dashboard/top-categories-card.tsx
index da62007d..e2bf135c 100644
--- a/resources/js/components/dashboard/top-categories-card.tsx
+++ b/resources/js/components/dashboard/top-categories-card.tsx
@@ -8,7 +8,10 @@ interface TopCategoriesCardProps {
loading?: boolean;
}
-export function TopCategoriesCard({ categories, loading }: TopCategoriesCardProps) {
+export function TopCategoriesCard({
+ categories,
+ loading,
+}: TopCategoriesCardProps) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
@@ -37,7 +40,9 @@ export function TopCategoriesCard({ categories, loading }: TopCategoriesCardProp
{categories.map((item) => {
- const Icon = (Icons[item.category.icon as keyof typeof Icons] || Icons.HelpCircle) as LucideIcon;
+ const Icon = (Icons[
+ item.category.icon as keyof typeof Icons
+ ] || Icons.HelpCircle) as LucideIcon;
return (
-
+
{item.category.name}
-
+
{formatter.format(item.amount / 100)}
@@ -64,7 +69,7 @@ export function TopCategoriesCard({ categories, loading }: TopCategoriesCardProp
);
})}
{categories.length === 0 && (
-
+
No spending data this month
)}
@@ -73,5 +78,3 @@ export function TopCategoriesCard({ categories, loading }: TopCategoriesCardProp
);
}
-
-
diff --git a/resources/js/components/encrypted-text.tsx b/resources/js/components/encrypted-text.tsx
index 49a3763e..f6165475 100644
--- a/resources/js/components/encrypted-text.tsx
+++ b/resources/js/components/encrypted-text.tsx
@@ -72,7 +72,9 @@ export function EncryptedText(props: EncryptedTextProps) {
iv: string;
value: string;
} | null>(null);
- const [displayState, setDisplayState] = useState
(() => getInitialDisplayState(isKeySet));
+ const [displayState, setDisplayState] = useState(() =>
+ getInitialDisplayState(isKeySet),
+ );
const prevIsKeySetRef = useRef(isKeySet);
useEffect(() => {
diff --git a/resources/js/components/shared/category-combobox.tsx b/resources/js/components/shared/category-combobox.tsx
index a789e48c..9fd88558 100644
--- a/resources/js/components/shared/category-combobox.tsx
+++ b/resources/js/components/shared/category-combobox.tsx
@@ -13,8 +13,13 @@ import {
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { type Category, getCategoryColorClasses } from '@/types/category';
-import { Check, ChevronsUpDown, HelpCircle, type LucideIcon } from 'lucide-react';
import * as Icons from 'lucide-react';
+import {
+ Check,
+ ChevronsUpDown,
+ HelpCircle,
+ type LucideIcon,
+} from 'lucide-react';
import { memo, useState } from 'react';
const iconCache = new Map();
@@ -24,7 +29,9 @@ function getIconComponent(iconName?: string): LucideIcon | null {
if (iconCache.has(iconName)) {
return iconCache.get(iconName)!;
}
- const icon = Icons[iconName as keyof typeof Icons] as LucideIcon | undefined;
+ const icon = Icons[iconName as keyof typeof Icons] as
+ | LucideIcon
+ | undefined;
if (icon) {
iconCache.set(iconName, icon);
}
@@ -69,7 +76,7 @@ export function CategoryCombobox({
role="combobox"
aria-expanded={open}
className={cn(
- 'max-w-full w-full justify-between !pl-2',
+ 'w-full max-w-full justify-between !pl-2',
triggerClassName,
)}
disabled={disabled}
@@ -77,7 +84,9 @@ export function CategoryCombobox({
{selectedCategory ? (
- {selectedCategory.name}
+
+ {selectedCategory.name}
+
) : value === 'null' ? (
@@ -134,7 +143,9 @@ export function CategoryCombobox({
>
- {category.name}
+
+ {category.name}
+
-
+
);
});
-const DynamicIcon = memo(function DynamicIcon({ name, className }: { name?: string; className?: string }) {
+const DynamicIcon = memo(function DynamicIcon({
+ name,
+ className,
+}: {
+ name?: string;
+ className?: string;
+}) {
const Icon = getIconComponent(name);
if (!Icon) return null;
return ;
});
-
diff --git a/resources/js/components/sync-status-button.tsx b/resources/js/components/sync-status-button.tsx
index be9dca93..2b000973 100644
--- a/resources/js/components/sync-status-button.tsx
+++ b/resources/js/components/sync-status-button.tsx
@@ -74,4 +74,3 @@ export function SyncStatusButton() {
);
}
-
diff --git a/resources/js/components/transactions/bulk-actions-bar.tsx b/resources/js/components/transactions/bulk-actions-bar.tsx
index 93c4ad53..877ea044 100644
--- a/resources/js/components/transactions/bulk-actions-bar.tsx
+++ b/resources/js/components/transactions/bulk-actions-bar.tsx
@@ -35,8 +35,8 @@ export function BulkActionsBar({
}
return (
-
-
+
+
{selectedCount} transaction{selectedCount !== 1 ? 's' : ''}{' '}
selected
diff --git a/resources/js/components/transactions/edit-transaction-dialog.tsx b/resources/js/components/transactions/edit-transaction-dialog.tsx
index b0ae246e..6616ebc3 100644
--- a/resources/js/components/transactions/edit-transaction-dialog.tsx
+++ b/resources/js/components/transactions/edit-transaction-dialog.tsx
@@ -65,7 +65,9 @@ export function EditTransactionDialog({
const [decryptedAccountNames, setDecryptedAccountNames] = useState<
Map
>(new Map());
- const [automationRules, setAutomationRules] = useState([]);
+ const [automationRules, setAutomationRules] = useState(
+ [],
+ );
useEffect(() => {
if (mode === 'edit' && transaction) {
@@ -145,7 +147,12 @@ export function EditTransactionDialog({
async function checkAndApplyAutomationRules() {
if (mode !== 'create' || automationRules.length === 0) {
- return { categoryId: null, notes: null, notesIv: null, ruleName: null };
+ return {
+ categoryId: null,
+ notes: null,
+ notesIv: null,
+ ruleName: null,
+ };
}
const result = evaluateRulesForNewTransaction(
@@ -163,7 +170,12 @@ export function EditTransactionDialog({
);
if (!result) {
- return { categoryId: null, notes: null, notesIv: null, ruleName: null };
+ return {
+ categoryId: null,
+ notes: null,
+ notesIv: null,
+ ruleName: null,
+ };
}
let finalNotes = notes.trim();
@@ -222,7 +234,10 @@ export function EditTransactionDialog({
toast.error('Date is required');
return;
}
- } else if (mode === 'edit' && transaction?.source === 'manually_created') {
+ } else if (
+ mode === 'edit' &&
+ transaction?.source === 'manually_created'
+ ) {
if (!description.trim()) {
toast.error('Description is required');
return;
@@ -288,8 +303,8 @@ export function EditTransactionDialog({
const updatedCategory = finalCategoryId
? categories.find(
- (category) => category.id === finalCategoryId,
- ) || null
+ (category) => category.id === finalCategoryId,
+ ) || null
: null;
const newTransaction: DecryptedTransaction = {
@@ -315,7 +330,8 @@ export function EditTransactionDialog({
return;
}
- const selectedCategoryId = categoryId === 'null' ? null : categoryId;
+ const selectedCategoryId =
+ categoryId === 'null' ? null : categoryId;
const trimmedNotes = notes.trim();
const trimmedDescription = description.trim();
@@ -340,10 +356,17 @@ export function EditTransactionDialog({
notes_iv: notesIv,
};
- let finalDecryptedDescription = transaction.decryptedDescription;
+ let finalDecryptedDescription =
+ transaction.decryptedDescription;
- if (transaction.source === 'manually_created' && trimmedDescription) {
- const encryptedDescription = await encrypt(trimmedDescription, key);
+ if (
+ transaction.source === 'manually_created' &&
+ trimmedDescription
+ ) {
+ const encryptedDescription = await encrypt(
+ trimmedDescription,
+ key,
+ );
updateData.description = encryptedDescription.encrypted;
updateData.description_iv = encryptedDescription.iv;
finalDecryptedDescription = trimmedDescription;
@@ -356,8 +379,8 @@ export function EditTransactionDialog({
);
const updatedCategory = selectedCategoryId
? categories.find(
- (category) => category.id === selectedCategoryId,
- ) || null
+ (category) => category.id === selectedCategoryId,
+ ) || null
: null;
const updatedTransaction: DecryptedTransaction = {
@@ -365,8 +388,10 @@ export function EditTransactionDialog({
category_id: selectedCategoryId,
category: updatedCategory,
decryptedDescription: finalDecryptedDescription,
- description: updateData.description ?? transaction.description,
- description_iv: updateData.description_iv ?? transaction.description_iv,
+ description:
+ updateData.description ?? transaction.description,
+ description_iv:
+ updateData.description_iv ?? transaction.description_iv,
decryptedNotes: trimmedNotes || null,
notes: encryptedNotes,
notes_iv: notesIv,
@@ -388,9 +413,7 @@ export function EditTransactionDialog({
}
}
- const selectedAccount = accounts.find(
- (acc) => acc.id === accountId,
- );
+ const selectedAccount = accounts.find((acc) => acc.id === accountId);
return (
)}
@@ -592,8 +623,8 @@ export function EditTransactionDialog({
{isSubmitting
? 'Saving...'
: mode === 'create'
- ? 'Create Transaction'
- : 'Save Changes'}
+ ? 'Create Transaction'
+ : 'Save Changes'}
diff --git a/resources/js/components/transactions/import-step-mapping.tsx b/resources/js/components/transactions/import-step-mapping.tsx
index 81ad185b..78117c75 100644
--- a/resources/js/components/transactions/import-step-mapping.tsx
+++ b/resources/js/components/transactions/import-step-mapping.tsx
@@ -23,7 +23,10 @@ interface ImportStepMappingProps {
dateFormatDetected: boolean;
parsedData: ParsedRow[];
currencyCode: string;
- onMappingChange: (field: keyof ColumnMapping, value: string | string[]) => void;
+ onMappingChange: (
+ field: keyof ColumnMapping,
+ value: string | string[],
+ ) => void;
onDateFormatChange: (format: DateFormat) => void;
onNext: () => void;
onBack: () => void;
@@ -69,7 +72,7 @@ export function ImportStepMapping({
const handleDescriptionChange = (index: number, value: string) => {
const newColumns = [...descriptionColumns];
-
+
if (value === '__none__') {
newColumns.splice(index, 1);
} else {
@@ -166,7 +169,9 @@ export function ImportStepMapping({
{descriptionColumns.length === 0 ? (
))}
{descriptionColumns.length > 0 &&
descriptionColumns.length < 3 &&
- descriptionColumns[descriptionColumns.length - 1] !== '' && (
+ descriptionColumns[
+ descriptionColumns.length - 1
+ ] !== '' && (