Compare commits

...

6 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
ccdfdd10bf fix(frontend): Correct running balance calculation in transactions table
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
2025-09-17 20:51:13 +01:00
copilot-swe-agent[bot]
0c7578db35 Initial plan 2025-09-17 20:51:10 +01:00
Elisiário Couto
81d7d16301 fix(frontend): Add index signature to PieDataPoint interface.
Resolves TypeScript error where PieDataPoint[] was not assignable to
ChartDataInput[] by adding the required string index signature.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 18:30:36 +01:00
Elisiário Couto
84e609a774 refactor(frontend): Replace LoadingSpinner with shadcn skeleton components.
- Created AccountsSkeleton.tsx and NotificationsSkeleton.tsx components
- Updated AccountsOverview.tsx and Notifications.tsx to use skeletons
- Removed unused LoadingSpinner.tsx component
- Improved loading state UX by showing content structure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 18:30:36 +01:00
copilot-swe-agent[bot]
fb310a5953 fix(frontend): Resolve linting issue in skeleton component
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
2025-09-16 18:30:36 +01:00
copilot-swe-agent[bot]
c83386b1d5 feat(frontend): Complete shadcn migration of skeleton and styling components
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
2025-09-16 18:30:36 +01:00
21 changed files with 970 additions and 383 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/forms": "^0.5.10",
@@ -29,6 +30,7 @@
"react-day-picker": "^9.10.0",
"react-dom": "^19.1.1",
"recharts": "^3.2.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"
@@ -1637,6 +1639,30 @@
}
}
},
"node_modules/@radix-ui/react-progress": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
"integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
@@ -1897,9 +1923,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz",
"integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz",
"integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
"cpu": [
"arm"
],
@@ -1911,9 +1937,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz",
"integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz",
"integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
"cpu": [
"arm64"
],
@@ -1925,9 +1951,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz",
"integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz",
"integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
"cpu": [
"arm64"
],
@@ -1939,9 +1965,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz",
"integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz",
"integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
"cpu": [
"x64"
],
@@ -1953,9 +1979,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz",
"integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz",
"integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
"cpu": [
"arm64"
],
@@ -1967,9 +1993,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz",
"integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz",
"integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
"cpu": [
"x64"
],
@@ -1981,9 +2007,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz",
"integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz",
"integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
"cpu": [
"arm"
],
@@ -1995,9 +2021,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz",
"integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz",
"integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
"cpu": [
"arm"
],
@@ -2009,9 +2035,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz",
"integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz",
"integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
"cpu": [
"arm64"
],
@@ -2023,9 +2049,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz",
"integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz",
"integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
"cpu": [
"arm64"
],
@@ -2036,10 +2062,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz",
"integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==",
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz",
"integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
"cpu": [
"loong64"
],
@@ -2051,9 +2077,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz",
"integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz",
"integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
"cpu": [
"ppc64"
],
@@ -2065,9 +2091,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz",
"integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz",
"integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
"cpu": [
"riscv64"
],
@@ -2079,9 +2105,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz",
"integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz",
"integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
"cpu": [
"riscv64"
],
@@ -2093,9 +2119,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz",
"integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz",
"integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
"cpu": [
"s390x"
],
@@ -2107,9 +2133,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz",
"integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz",
"integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
"cpu": [
"x64"
],
@@ -2121,9 +2147,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz",
"integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz",
"integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
"cpu": [
"x64"
],
@@ -2135,9 +2161,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz",
"integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz",
"integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
"cpu": [
"arm64"
],
@@ -2149,9 +2175,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz",
"integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz",
"integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
"cpu": [
"arm64"
],
@@ -2163,9 +2189,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz",
"integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz",
"integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
"cpu": [
"ia32"
],
@@ -2177,9 +2203,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz",
"integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz",
"integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
"cpu": [
"x64"
],
@@ -2228,9 +2254,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "5.87.4",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.87.4.tgz",
"integrity": "sha512-uNsg6zMxraEPDVO2Bn+F3/ctHi+Zsk+MMpcN8h6P7ozqD088F6mFY5TfGM7zuyIrL7HKpDyu6QHfLWiDxh3cuw==",
"version": "5.89.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.89.0.tgz",
"integrity": "sha512-joFV1MuPhSLsKfTzwjmPDrp8ENfZ9N23ymFu07nLfn3JCkSHy0CFgsyhHTJOmWaumC/WiNIKM0EJyduCF/Ih/Q==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2238,12 +2264,12 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "5.87.4",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.87.4.tgz",
"integrity": "sha512-T5GT/1ZaNsUXf5I3RhcYuT17I4CPlbZgyLxc/ZGv7ciS6esytlbjb3DgUFO6c8JWYMDpdjSWInyGZUErgzqhcA==",
"version": "5.89.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.89.0.tgz",
"integrity": "sha512-SXbtWSTSRXyBOe80mszPxpEbaN4XPRUp/i0EfQK1uyj3KCk/c8FuPJNIRwzOVe/OU3rzxrYtiNabsAmk1l714A==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.87.4"
"@tanstack/query-core": "5.89.0"
},
"funding": {
"type": "github",
@@ -2254,14 +2280,14 @@
}
},
"node_modules/@tanstack/react-router": {
"version": "1.131.41",
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.131.41.tgz",
"integrity": "sha512-QEbTYpAosiD8e4qEZRr9aJipGSb8pQc+pfZwK6NCD2Tcxwu2oF6MVtwv0bIDLRpZP0VJMBpxXlTRISUDNMNqIA==",
"version": "1.131.44",
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.131.44.tgz",
"integrity": "sha512-LREJfrl8lSedXHCRAAt0HvnHFP9ikAQWnVhYRM++B26w4ZYQBbLvgCT1BCDZVY7MR6rslcd4OfgpZMOyVhNzFg==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.131.2",
"@tanstack/react-store": "^0.7.0",
"@tanstack/router-core": "1.131.41",
"@tanstack/router-core": "1.131.44",
"isbot": "^5.1.22",
"tiny-invariant": "^1.3.3",
"tiny-warning": "^1.0.3"
@@ -2317,12 +2343,12 @@
}
},
"node_modules/@tanstack/router-cli": {
"version": "1.131.41",
"resolved": "https://registry.npmjs.org/@tanstack/router-cli/-/router-cli-1.131.41.tgz",
"integrity": "sha512-EpLnnCwwCd94HRCWHoa1GZGtIWIffx4rPBb6gbWm4cvyEIGV2Gq+27vL2OEw819/elxyBQmG2RrPB8+7dfVACw==",
"version": "1.131.44",
"resolved": "https://registry.npmjs.org/@tanstack/router-cli/-/router-cli-1.131.44.tgz",
"integrity": "sha512-fKt2Whxbi3mvLXxVphSqlvVQyKe6BjdmOj5n+9LOgXQnFGyRwxrFR5cnNF+N4gUGp9iwnJpguZHXH4rNF5Xc+A==",
"license": "MIT",
"dependencies": {
"@tanstack/router-generator": "1.131.41",
"@tanstack/router-generator": "1.131.44",
"chokidar": "^3.6.0",
"yargs": "^17.7.2"
},
@@ -2338,9 +2364,9 @@
}
},
"node_modules/@tanstack/router-core": {
"version": "1.131.41",
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.131.41.tgz",
"integrity": "sha512-VoLly00DWM0abKuVPRm8wiwGtRBHOKs6K896fy48Q/KYoDVLs8kRCRjFGS7rGnYC2FIkmmvHqYRqNg7jgCx2yg==",
"version": "1.131.44",
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.131.44.tgz",
"integrity": "sha512-Npi9xB3GSYZhRW8+gPhP6bEbyx0vNc8ZNwsi0JapdiFpIiszgRJ57pesy/rklruv46gYQjLVA5KDOsuaCT/urA==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.131.2",
@@ -2360,12 +2386,12 @@
}
},
"node_modules/@tanstack/router-generator": {
"version": "1.131.41",
"resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.131.41.tgz",
"integrity": "sha512-HsDkBU1u/KvHrzn76v/9oeyMFuxvVlE3dfIu4fldZbPy/i903DWBwODIDGe6fVUsYtzPPrRvNtbjV18HVz5GCA==",
"version": "1.131.44",
"resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.131.44.tgz",
"integrity": "sha512-CnrlRkGatdQXdvTteflOTMANupb1z59CO3DSV+UzBkTG+g+vfWgJeKQ0EkfwZ2QuS6Su2v5r5EMHs/AookeZZw==",
"license": "MIT",
"dependencies": {
"@tanstack/router-core": "1.131.41",
"@tanstack/router-core": "1.131.44",
"@tanstack/router-utils": "1.131.2",
"@tanstack/virtual-file-routes": "1.131.2",
"prettier": "^3.5.0",
@@ -2383,9 +2409,9 @@
}
},
"node_modules/@tanstack/router-plugin": {
"version": "1.131.41",
"resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.131.41.tgz",
"integrity": "sha512-MENVYQwvhKFIPZ/YO/CGCwbh3Ba3TRvUYZ2y2KiU6aa1CWao4KHDRsungzv34AbbUBSmzbc8mKVeqd+G+E9cDQ==",
"version": "1.131.44",
"resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.131.44.tgz",
"integrity": "sha512-CvheUPlB8vxXf23RSDz6q97l1EI5H3f+1qJ/LEBvy7bhls8vYouJ3xyTeu4faz8bEEieLUoVQrCcr+xFY0lkuw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2395,8 +2421,8 @@
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.27.7",
"@babel/types": "^7.27.7",
"@tanstack/router-core": "1.131.41",
"@tanstack/router-generator": "1.131.41",
"@tanstack/router-core": "1.131.44",
"@tanstack/router-generator": "1.131.44",
"@tanstack/router-utils": "1.131.2",
"@tanstack/virtual-file-routes": "1.131.2",
"babel-dead-code-elimination": "^1.0.10",
@@ -2413,7 +2439,7 @@
},
"peerDependencies": {
"@rsbuild/core": ">=1.0.2",
"@tanstack/react-router": "^1.131.41",
"@tanstack/react-router": "^1.131.44",
"vite": ">=5.0.0 || >=6.0.0",
"vite-plugin-solid": "^2.11.2",
"webpack": ">=5.92.0"
@@ -2458,13 +2484,13 @@
}
},
"node_modules/@tanstack/router-vite-plugin": {
"version": "1.131.41",
"resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.131.41.tgz",
"integrity": "sha512-UNMLW5BsueJX77lAWwddWGKTDElXS23XfpvaEnxpAPS8rnu+7HEpV4bWciN5VruuTZZM5plP6bXAGec+Bi51Hw==",
"version": "1.131.44",
"resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.131.44.tgz",
"integrity": "sha512-5q87hJuT/ybQ3K77Qd649L2curIIh50TIx4OkPCInaqqBeBx101G/6FWubOm2UTl7dVOfDsPPJHcmSbgwvowcA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/router-plugin": "1.131.41"
"@tanstack/router-plugin": "1.131.44"
},
"engines": {
"node": ">=12"
@@ -2556,9 +2582,9 @@
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
@@ -2633,9 +2659,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"version": "19.1.13",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -2659,17 +2685,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz",
"integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz",
"integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.43.0",
"@typescript-eslint/type-utils": "8.43.0",
"@typescript-eslint/utils": "8.43.0",
"@typescript-eslint/visitor-keys": "8.43.0",
"@typescript-eslint/scope-manager": "8.44.0",
"@typescript-eslint/type-utils": "8.44.0",
"@typescript-eslint/utils": "8.44.0",
"@typescript-eslint/visitor-keys": "8.44.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2683,7 +2709,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.43.0",
"@typescript-eslint/parser": "^8.44.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -2699,16 +2725,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz",
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz",
"integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.43.0",
"@typescript-eslint/types": "8.43.0",
"@typescript-eslint/typescript-estree": "8.43.0",
"@typescript-eslint/visitor-keys": "8.43.0",
"@typescript-eslint/scope-manager": "8.44.0",
"@typescript-eslint/types": "8.44.0",
"@typescript-eslint/typescript-estree": "8.44.0",
"@typescript-eslint/visitor-keys": "8.44.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2724,14 +2750,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz",
"integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz",
"integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.43.0",
"@typescript-eslint/types": "^8.43.0",
"@typescript-eslint/tsconfig-utils": "^8.44.0",
"@typescript-eslint/types": "^8.44.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2746,14 +2772,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz",
"integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz",
"integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.43.0",
"@typescript-eslint/visitor-keys": "8.43.0"
"@typescript-eslint/types": "8.44.0",
"@typescript-eslint/visitor-keys": "8.44.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2764,9 +2790,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz",
"integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz",
"integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2781,15 +2807,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz",
"integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz",
"integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.43.0",
"@typescript-eslint/typescript-estree": "8.43.0",
"@typescript-eslint/utils": "8.43.0",
"@typescript-eslint/types": "8.44.0",
"@typescript-eslint/typescript-estree": "8.44.0",
"@typescript-eslint/utils": "8.44.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2806,9 +2832,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz",
"integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz",
"integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2820,16 +2846,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz",
"integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz",
"integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.43.0",
"@typescript-eslint/tsconfig-utils": "8.43.0",
"@typescript-eslint/types": "8.43.0",
"@typescript-eslint/visitor-keys": "8.43.0",
"@typescript-eslint/project-service": "8.44.0",
"@typescript-eslint/tsconfig-utils": "8.44.0",
"@typescript-eslint/types": "8.44.0",
"@typescript-eslint/visitor-keys": "8.44.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2888,16 +2914,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz",
"integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz",
"integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.43.0",
"@typescript-eslint/types": "8.43.0",
"@typescript-eslint/typescript-estree": "8.43.0"
"@typescript-eslint/scope-manager": "8.44.0",
"@typescript-eslint/types": "8.44.0",
"@typescript-eslint/typescript-estree": "8.44.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2912,13 +2938,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz",
"integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz",
"integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.43.0",
"@typescript-eslint/types": "8.44.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -3126,9 +3152,9 @@
}
},
"node_modules/axios": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz",
"integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -3156,9 +3182,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.2.tgz",
"integrity": "sha512-NvcIedLxrs9llVpX7wI+Jz4Hn9vJQkCPKrTaHIE0sW/Rj1iq6Fzby4NbyTZjQJNoypBXNaG7tEHkTgONZpwgxQ==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz",
"integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
@@ -3200,9 +3226,9 @@
}
},
"node_modules/browserslist": {
"version": "4.26.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz",
"integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==",
"version": "4.26.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
"integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
"funding": [
{
"type": "opencollective",
@@ -3219,7 +3245,7 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.8.2",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
"electron-to-chromium": "^1.5.218",
"node-releases": "^2.0.21",
@@ -3265,9 +3291,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001741",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
"integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==",
"version": "1.0.30001743",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
"integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
"funding": [
{
"type": "opencollective",
@@ -3663,9 +3689,9 @@
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -5171,9 +5197,19 @@
}
},
"node_modules/postcss-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
@@ -5181,10 +5217,6 @@
"engines": {
"node": "^12 || ^14 || >= 16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
@@ -5527,9 +5559,9 @@
}
},
"node_modules/recharts": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.0.tgz",
"integrity": "sha512-fX0xCgNXo6mag9wz3oLuANR+dUQM4uIlTYBGTGq9CBRgW/8TZPzqPGYs5NTt8aENCf+i1CI8vqxT1py8L/5J2w==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz",
"integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==",
"license": "MIT",
"dependencies": {
"@reduxjs/toolkit": "1.x.x || 2.x.x",
@@ -5633,9 +5665,9 @@
}
},
"node_modules/rollup": {
"version": "4.50.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz",
"integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==",
"version": "4.50.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz",
"integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5649,27 +5681,27 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.50.1",
"@rollup/rollup-android-arm64": "4.50.1",
"@rollup/rollup-darwin-arm64": "4.50.1",
"@rollup/rollup-darwin-x64": "4.50.1",
"@rollup/rollup-freebsd-arm64": "4.50.1",
"@rollup/rollup-freebsd-x64": "4.50.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.50.1",
"@rollup/rollup-linux-arm-musleabihf": "4.50.1",
"@rollup/rollup-linux-arm64-gnu": "4.50.1",
"@rollup/rollup-linux-arm64-musl": "4.50.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.50.1",
"@rollup/rollup-linux-ppc64-gnu": "4.50.1",
"@rollup/rollup-linux-riscv64-gnu": "4.50.1",
"@rollup/rollup-linux-riscv64-musl": "4.50.1",
"@rollup/rollup-linux-s390x-gnu": "4.50.1",
"@rollup/rollup-linux-x64-gnu": "4.50.1",
"@rollup/rollup-linux-x64-musl": "4.50.1",
"@rollup/rollup-openharmony-arm64": "4.50.1",
"@rollup/rollup-win32-arm64-msvc": "4.50.1",
"@rollup/rollup-win32-ia32-msvc": "4.50.1",
"@rollup/rollup-win32-x64-msvc": "4.50.1",
"@rollup/rollup-android-arm-eabi": "4.50.2",
"@rollup/rollup-android-arm64": "4.50.2",
"@rollup/rollup-darwin-arm64": "4.50.2",
"@rollup/rollup-darwin-x64": "4.50.2",
"@rollup/rollup-freebsd-arm64": "4.50.2",
"@rollup/rollup-freebsd-x64": "4.50.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.50.2",
"@rollup/rollup-linux-arm-musleabihf": "4.50.2",
"@rollup/rollup-linux-arm64-gnu": "4.50.2",
"@rollup/rollup-linux-arm64-musl": "4.50.2",
"@rollup/rollup-linux-loong64-gnu": "4.50.2",
"@rollup/rollup-linux-ppc64-gnu": "4.50.2",
"@rollup/rollup-linux-riscv64-gnu": "4.50.2",
"@rollup/rollup-linux-riscv64-musl": "4.50.2",
"@rollup/rollup-linux-s390x-gnu": "4.50.2",
"@rollup/rollup-linux-x64-gnu": "4.50.2",
"@rollup/rollup-linux-x64-musl": "4.50.2",
"@rollup/rollup-openharmony-arm64": "4.50.2",
"@rollup/rollup-win32-arm64-msvc": "4.50.2",
"@rollup/rollup-win32-ia32-msvc": "4.50.2",
"@rollup/rollup-win32-x64-msvc": "4.50.2",
"fsevents": "~2.3.2"
}
},
@@ -5765,6 +5797,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
@@ -6172,16 +6214,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.43.0.tgz",
"integrity": "sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.0.tgz",
"integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.43.0",
"@typescript-eslint/parser": "8.43.0",
"@typescript-eslint/typescript-estree": "8.43.0",
"@typescript-eslint/utils": "8.43.0"
"@typescript-eslint/eslint-plugin": "8.44.0",
"@typescript-eslint/parser": "8.44.0",
"@typescript-eslint/typescript-estree": "8.44.0",
"@typescript-eslint/utils": "8.44.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View File

@@ -12,6 +12,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/forms": "^0.5.10",
@@ -31,6 +32,7 @@
"react-day-picker": "^9.10.0",
"react-dom": "^19.1.1",
"recharts": "^3.2.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"

View File

@@ -22,7 +22,7 @@ import {
} from "./ui/card";
import { Button } from "./ui/button";
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import LoadingSpinner from "./LoadingSpinner";
import AccountsSkeleton from "./AccountsSkeleton";
import type { Account, Balance } from "../types/api";
// Helper function to get status indicator color and styles
@@ -37,23 +37,23 @@ const getStatusIndicator = (status: string) => {
};
case "pending":
return {
color: "bg-yellow-500",
color: "bg-amber-500",
tooltip: "Pending",
};
case "error":
case "failed":
return {
color: "bg-red-500",
color: "bg-destructive",
tooltip: "Error",
};
case "inactive":
return {
color: "bg-gray-500",
color: "bg-muted-foreground",
tooltip: "Inactive",
};
default:
return {
color: "bg-blue-500",
color: "bg-primary",
tooltip: status,
};
}
@@ -114,11 +114,7 @@ export default function AccountsOverview() {
};
if (accountsLoading) {
return (
<Card>
<LoadingSpinner message="Loading accounts..." />
</Card>
);
return <AccountsSkeleton />;
}
if (accountsError) {
@@ -201,8 +197,8 @@ export default function AccountsOverview() {
{uniqueBanks}
</p>
</div>
<div className="p-3 bg-purple-100 dark:bg-purple-900/20 rounded-full">
<Building2 className="h-6 w-6 text-purple-600" />
<div className="p-3 bg-muted rounded-full">
<Building2 className="h-6 w-6 text-muted-foreground" />
</div>
</div>
</CardContent>
@@ -304,7 +300,9 @@ export default function AccountsOverview() {
<div>
<div className="flex items-center space-x-2 min-w-0">
<h4 className="text-base sm:text-lg font-medium text-foreground truncate">
{account.display_name || account.name || "Unnamed Account"}
{account.display_name ||
account.name ||
"Unnamed Account"}
</h4>
<button
onClick={() => handleEditStart(account)}

View File

@@ -0,0 +1,61 @@
import { Skeleton } from "./ui/skeleton";
import { Card, CardContent, CardHeader } from "./ui/card";
export default function AccountsSkeleton() {
return (
<div className="space-y-6">
{/* Summary Cards Skeleton */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{Array.from({ length: 3 }).map((_, i) => (
<Card key={i}>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-8 w-24" />
</div>
<Skeleton className="h-12 w-12 rounded-full" />
</div>
</CardContent>
</Card>
))}
</div>
{/* Accounts List Skeleton */}
<Card>
<CardHeader>
<Skeleton className="h-6 w-32" />
<Skeleton className="h-4 w-48" />
</CardHeader>
<CardContent className="p-0">
<div className="divide-y divide-border">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
<div className="flex items-start sm:items-center space-x-3 sm:space-x-4 min-w-0 flex-1">
<Skeleton className="h-10 w-10 sm:h-12 sm:w-12 rounded-full flex-shrink-0" />
<div className="flex-1 space-y-2">
<Skeleton className="h-5 w-48" />
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-40" />
</div>
</div>
<div className="flex items-center justify-between sm:flex-col sm:items-end sm:text-right flex-shrink-0">
<div className="flex items-center space-x-2 order-1 sm:order-2">
<Skeleton className="h-3 w-3 rounded-full" />
<Skeleton className="h-4 w-20" />
</div>
<div className="flex items-center space-x-2 order-2 sm:order-1">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-5 w-24" />
</div>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,6 +1,9 @@
import { Component } from "react";
import type { ErrorInfo, ReactNode } from "react";
import { AlertTriangle, RefreshCw } from "lucide-react";
import { Card, CardContent } from "./ui/card";
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import { Button } from "./ui/button";
interface Props {
children: ReactNode;
@@ -39,46 +42,49 @@ class ErrorBoundary extends Component<Props, State> {
}
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-center text-center">
<div>
<AlertTriangle className="h-12 w-12 text-red-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
Something went wrong
</h3>
<p className="text-gray-600 mb-4">
An error occurred while rendering this component. Please try
refreshing or check the console for more details.
</p>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-center text-center">
<div>
<AlertTriangle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h3 className="text-lg font-medium text-foreground mb-2">
Something went wrong
</h3>
<p className="text-muted-foreground mb-4">
An error occurred while rendering this component. Please try
refreshing or check the console for more details.
</p>
{this.state.error && (
<div className="bg-red-50 border border-red-200 rounded-md p-3 mb-4 text-left">
<p className="text-sm font-mono text-red-800">
<strong>Error:</strong> {this.state.error.message}
</p>
{this.state.error.stack && (
<details className="mt-2">
<summary className="text-sm text-red-600 cursor-pointer">
Stack trace
</summary>
<pre className="text-xs text-red-700 mt-1 whitespace-pre-wrap">
{this.state.error.stack}
</pre>
</details>
)}
</div>
)}
{this.state.error && (
<Alert variant="destructive" className="mb-4 text-left">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error Details</AlertTitle>
<AlertDescription className="space-y-2">
<p className="text-sm font-mono">
<strong>Error:</strong> {this.state.error.message}
</p>
{this.state.error.stack && (
<details className="mt-2">
<summary className="text-sm cursor-pointer">
Stack trace
</summary>
<pre className="text-xs mt-1 whitespace-pre-wrap">
{this.state.error.stack}
</pre>
</details>
)}
</AlertDescription>
</Alert>
)}
<button
onClick={this.handleReset}
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<RefreshCw className="h-4 w-4 mr-2" />
Try Again
</button>
<Button onClick={this.handleReset}>
<RefreshCw className="h-4 w-4 mr-2" />
Try Again
</Button>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -1,29 +1,32 @@
import { Skeleton } from "./ui/skeleton";
import { Card, CardContent } from "./ui/card";
export default function FiltersSkeleton() {
return (
<div className="bg-white rounded-lg shadow animate-pulse">
<div className="px-6 py-4 border-b border-gray-200">
<Card>
<div className="px-6 py-4 border-b border-border">
<div className="flex items-center justify-between">
<div className="h-6 bg-gray-200 rounded w-32"></div>
<Skeleton className="h-6 w-32" />
<div className="flex items-center space-x-2">
<div className="h-8 bg-gray-200 rounded w-24"></div>
<div className="h-8 bg-gray-200 rounded w-20"></div>
<Skeleton className="h-8 w-24" />
<Skeleton className="h-8 w-20" />
</div>
</div>
</div>
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
<CardContent className="px-6 py-4 border-b border-border bg-muted/30">
{/* Quick Date Filters Skeleton */}
<div className="mb-6">
<div className="h-4 bg-gray-200 rounded w-32 mb-3"></div>
<Skeleton className="h-4 w-32 mb-3" />
<div className="space-y-2">
<div className="flex flex-wrap gap-2">
<div className="h-10 bg-gray-200 rounded-lg w-24"></div>
<div className="h-10 bg-gray-200 rounded-lg w-20"></div>
<div className="h-10 bg-gray-200 rounded-lg w-28"></div>
<Skeleton className="h-10 w-24 rounded-lg" />
<Skeleton className="h-10 w-20 rounded-lg" />
<Skeleton className="h-10 w-28 rounded-lg" />
</div>
<div className="flex flex-wrap gap-2">
<div className="h-10 bg-gray-200 rounded-lg w-24"></div>
<div className="h-10 bg-gray-200 rounded-lg w-20"></div>
<Skeleton className="h-10 w-24 rounded-lg" />
<Skeleton className="h-10 w-20 rounded-lg" />
</div>
</div>
</div>
@@ -31,40 +34,40 @@ export default function FiltersSkeleton() {
{/* Filter Fields Skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="sm:col-span-2 lg:col-span-1">
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div>
<div className="h-10 bg-gray-200 rounded"></div>
<Skeleton className="h-4 w-16 mb-1" />
<Skeleton className="h-10 w-full" />
</div>
<div>
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div>
<div className="h-10 bg-gray-200 rounded"></div>
<Skeleton className="h-4 w-16 mb-1" />
<Skeleton className="h-10 w-full" />
</div>
<div>
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div>
<div className="h-10 bg-gray-200 rounded"></div>
<Skeleton className="h-4 w-20 mb-1" />
<Skeleton className="h-10 w-full" />
</div>
<div>
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div>
<div className="h-10 bg-gray-200 rounded"></div>
<Skeleton className="h-4 w-16 mb-1" />
<Skeleton className="h-10 w-full" />
</div>
</div>
{/* Amount Range Filters Skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
<div>
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div>
<div className="h-10 bg-gray-200 rounded"></div>
<Skeleton className="h-4 w-20 mb-1" />
<Skeleton className="h-10 w-full" />
</div>
<div>
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div>
<div className="h-10 bg-gray-200 rounded"></div>
<Skeleton className="h-4 w-20 mb-1" />
<Skeleton className="h-10 w-full" />
</div>
</div>
</div>
</CardContent>
{/* Results Summary Skeleton */}
<div className="px-6 py-3 bg-gray-50 border-b border-gray-200">
<div className="h-4 bg-gray-200 rounded w-48"></div>
</div>
</div>
<CardContent className="px-6 py-3 bg-muted/30 border-b border-border">
<Skeleton className="h-4 w-48" />
</CardContent>
</Card>
);
}

View File

@@ -49,15 +49,15 @@ export default function Header({ setSidebarOpen }: HeaderProps) {
<div className="flex items-center space-x-1">
{healthLoading ? (
<>
<Activity className="h-4 w-4 text-yellow-500 animate-pulse" />
<Activity className="h-4 w-4 text-muted-foreground animate-pulse" />
<span className="text-sm text-muted-foreground">
Checking...
</span>
</>
) : healthError || healthStatus?.status !== "healthy" ? (
<>
<WifiOff className="h-4 w-4 text-red-500" />
<span className="text-sm text-red-500">Disconnected</span>
<WifiOff className="h-4 w-4 text-destructive" />
<span className="text-sm text-destructive">Disconnected</span>
</>
) : (
<>

View File

@@ -1,18 +0,0 @@
import { RefreshCw } from "lucide-react";
interface LoadingSpinnerProps {
message?: string;
}
export default function LoadingSpinner({
message = "Loading...",
}: LoadingSpinnerProps) {
return (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<RefreshCw className="h-8 w-8 animate-spin text-blue-600 mx-auto mb-2" />
<p className="text-gray-600 text-sm">{message}</p>
</div>
</div>
);
}

View File

@@ -12,7 +12,7 @@ import {
TestTube,
} from "lucide-react";
import { apiClient } from "../lib/api";
import LoadingSpinner from "./LoadingSpinner";
import NotificationsSkeleton from "./NotificationsSkeleton";
import {
Card,
CardContent,
@@ -24,6 +24,7 @@ import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { Badge } from "./ui/badge";
import {
Select,
SelectContent,
@@ -80,11 +81,7 @@ export default function Notifications() {
});
if (settingsLoading || servicesLoading) {
return (
<Card>
<LoadingSpinner message="Loading notifications..." />
</Card>
);
return <NotificationsSkeleton />;
}
if (settingsError || servicesError) {
@@ -233,12 +230,10 @@ export default function Notifications() {
{service.name}
</h4>
<div className="flex items-center space-x-2 mt-1">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
service.enabled
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
<Badge
variant={
service.enabled ? "default" : "destructive"
}
>
{service.enabled ? (
<CheckCircle className="h-3 w-3 mr-1" />
@@ -246,18 +241,16 @@ export default function Notifications() {
<AlertCircle className="h-3 w-3 mr-1" />
)}
{service.enabled ? "Enabled" : "Disabled"}
</span>
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
service.configured
? "bg-blue-100 text-blue-800"
: "bg-yellow-100 text-yellow-800"
}`}
</Badge>
<Badge
variant={
service.configured ? "secondary" : "outline"
}
>
{service.configured
? "Configured"
: "Not Configured"}
</span>
</Badge>
</div>
</div>
</div>

View File

@@ -0,0 +1,95 @@
import { Skeleton } from "./ui/skeleton";
import { Card, CardContent, CardHeader } from "./ui/card";
export default function NotificationsSkeleton() {
return (
<div className="space-y-6">
{/* Test Notification Section Skeleton */}
<Card>
<CardHeader>
<div className="flex items-center space-x-2">
<Skeleton className="h-5 w-5" />
<Skeleton className="h-6 w-36" />
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-10 w-full" />
</div>
</div>
<div className="mt-4">
<Skeleton className="h-10 w-48" />
</div>
</CardContent>
</Card>
{/* Notification Services Skeleton */}
<Card>
<CardHeader>
<div className="flex items-center space-x-2">
<Skeleton className="h-5 w-5" />
<Skeleton className="h-6 w-40" />
</div>
<Skeleton className="h-4 w-56" />
</CardHeader>
<CardContent className="p-0">
<div className="divide-y divide-border">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="p-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-5 w-24" />
<div className="flex items-center space-x-2">
<Skeleton className="h-5 w-16" />
<Skeleton className="h-5 w-20" />
</div>
</div>
</div>
<Skeleton className="h-8 w-8" />
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Notification Settings Skeleton */}
<Card>
<CardHeader>
<div className="flex items-center space-x-2">
<Skeleton className="h-5 w-5" />
<Skeleton className="h-6 w-40" />
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-2">
<Skeleton className="h-4 w-16" />
<div className="bg-muted rounded-md p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-2">
<Skeleton className="h-3 w-32" />
<Skeleton className="h-4 w-24" />
</div>
<div className="space-y-2">
<Skeleton className="h-3 w-28" />
<Skeleton className="h-4 w-20" />
</div>
</div>
</div>
</div>
<Skeleton className="h-12 w-full" />
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,3 +1,6 @@
import { Skeleton } from "./ui/skeleton";
import { Card } from "./ui/card";
interface TransactionSkeletonProps {
rows?: number;
view?: "table" | "mobile";
@@ -11,93 +14,89 @@ export default function TransactionSkeleton({
if (view === "mobile") {
return (
<div className="bg-white rounded-lg shadow divide-y divide-gray-200">
<Card className="divide-y divide-border">
{skeletonRows.map((_, index) => (
<div key={index} className="p-4 animate-pulse">
<div key={index} className="p-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-start space-x-3">
<div className="p-2 rounded-full bg-gray-200 flex-shrink-0">
<div className="h-4 w-4 bg-gray-300 rounded"></div>
</div>
<Skeleton className="h-10 w-10 rounded-full flex-shrink-0" />
<div className="flex-1 min-w-0 space-y-2">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<Skeleton className="h-4 w-3/4" />
<div className="space-y-1">
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
<div className="h-3 bg-gray-200 rounded w-1/3"></div>
<Skeleton className="h-3 w-1/2" />
<Skeleton className="h-3 w-2/3" />
<Skeleton className="h-3 w-1/3" />
</div>
</div>
</div>
</div>
<div className="text-right ml-3 flex-shrink-0 space-y-2">
<div className="h-6 bg-gray-200 rounded w-20"></div>
<div className="h-4 bg-gray-200 rounded w-16 ml-auto"></div>
<div className="h-6 bg-gray-200 rounded w-12 ml-auto"></div>
<Skeleton className="h-6 w-20" />
<Skeleton className="h-4 w-16 ml-auto" />
<Skeleton className="h-6 w-12 ml-auto" />
</div>
</div>
</div>
))}
</div>
</Card>
);
}
return (
<div className="bg-white rounded-lg shadow overflow-hidden">
<Card className="overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<table className="min-w-full divide-y divide-border">
<thead className="bg-muted/50">
<tr>
<th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-20 animate-pulse"></div>
<Skeleton className="h-4 w-20" />
</th>
<th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-16 animate-pulse"></div>
<Skeleton className="h-4 w-16" />
</th>
<th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-12 animate-pulse"></div>
<Skeleton className="h-4 w-12" />
</th>
<th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-8 animate-pulse"></div>
<Skeleton className="h-4 w-8" />
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-card divide-y divide-border">
{skeletonRows.map((_, index) => (
<tr key={index} className="animate-pulse">
<tr key={index}>
<td className="px-6 py-4">
<div className="flex items-start space-x-3">
<div className="p-2 rounded-full bg-gray-200 flex-shrink-0">
<div className="h-4 w-4 bg-gray-300 rounded"></div>
</div>
<Skeleton className="h-10 w-10 rounded-full flex-shrink-0" />
<div className="flex-1 space-y-2">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<Skeleton className="h-4 w-3/4" />
<div className="space-y-1">
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
<Skeleton className="h-3 w-1/2" />
<Skeleton className="h-3 w-2/3" />
</div>
</div>
</div>
</td>
<td className="px-6 py-4">
<div className="text-right">
<div className="h-6 bg-gray-200 rounded w-24 ml-auto mb-1"></div>
<Skeleton className="h-6 w-24 ml-auto mb-1" />
</div>
</td>
<td className="px-6 py-4">
<div className="space-y-1">
<div className="h-4 bg-gray-200 rounded w-20"></div>
<div className="h-3 bg-gray-200 rounded w-16"></div>
<Skeleton className="h-4 w-20" />
<Skeleton className="h-3 w-16" />
</div>
</td>
<td className="px-6 py-4">
<div className="h-6 bg-gray-200 rounded w-12"></div>
<Skeleton className="h-6 w-12" />
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</Card>
);
}

View File

@@ -192,9 +192,9 @@ export default function TransactionsTable() {
const runningBalances: { [key: string]: number } = {};
const accountBalanceMap = new Map<string, number>();
// Create a map of account current balances
// Create a map of account current balances - use interimAvailable as the most current
balances.forEach((balance) => {
if (balance.balance_type === "expected") {
if (balance.balance_type === "interimAvailable") {
accountBalanceMap.set(balance.account_id, balance.balance_amount);
}
});
@@ -211,20 +211,25 @@ export default function TransactionsTable() {
// Calculate running balance for each account
transactionsByAccount.forEach((accountTransactions, accountId) => {
const currentBalance = accountBalanceMap.get(accountId) || 0;
let runningBalance = currentBalance;
// Sort transactions by date (newest first) to work backwards
// Sort transactions by date (oldest first) for forward calculation
const sortedTransactions = [...accountTransactions].sort(
(a, b) =>
new Date(b.transaction_date).getTime() -
new Date(a.transaction_date).getTime(),
new Date(a.transaction_date).getTime() -
new Date(b.transaction_date).getTime(),
);
// Calculate running balance by working backwards from current balance
// Calculate the starting balance by working backwards from current balance
let startingBalance = currentBalance;
for (let i = sortedTransactions.length - 1; i >= 0; i--) {
startingBalance -= sortedTransactions[i].transaction_value;
}
// Now calculate running balances going forward chronologically
let runningBalance = startingBalance;
sortedTransactions.forEach((txn) => {
runningBalances[`${txn.account_id}-${txn.transaction_id}`] =
runningBalance;
runningBalance -= txn.transaction_value;
runningBalance += txn.transaction_value;
runningBalances[`${txn.account_id}-${txn.transaction_id}`] = runningBalance;
});
});

View File

@@ -17,6 +17,7 @@ interface PieDataPoint {
name: string;
value: number;
color: string;
[key: string]: string | number;
}
interface TooltipProps {

View File

@@ -38,7 +38,8 @@ export function AccountCombobox({
);
const formatAccountName = (account: Account) => {
const displayName = account.display_name || account.name || "Unnamed Account";
const displayName =
account.display_name || account.name || "Unnamed Account";
return `${displayName} (${account.institution_id})`;
};
@@ -105,7 +106,9 @@ export function AccountCombobox({
/>
<div className="flex flex-col">
<span className="font-medium">
{account.display_name || account.name || "Unnamed Account"}
{account.display_name ||
account.name ||
"Unnamed Account"}
</span>
<span className="text-xs text-gray-500">
{account.institution_id}

View File

@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {

View File

@@ -0,0 +1,26 @@
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "@/lib/utils";
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
className,
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress };

View File

@@ -0,0 +1,138 @@
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
},
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className,
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

View File

@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}
export { Skeleton };

View File

@@ -0,0 +1,27 @@
"use client";
import { Toaster as Sonner } from "sonner";
type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => {
return (
<Sonner
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
);
};
export { Toaster };

View File

@@ -42,10 +42,9 @@ export const apiClient = {
id: string,
updates: AccountUpdate,
): Promise<{ id: string; display_name?: string }> => {
const response = await api.put<ApiResponse<{ id: string; display_name?: string }>>(
`/accounts/${id}`,
updates,
);
const response = await api.put<
ApiResponse<{ id: string; display_name?: string }>
>(`/accounts/${id}`, updates);
return response.data.data;
},

View File

@@ -0,0 +1,192 @@
"""Tests for running balance calculation logic in frontend.
This test validates the running balance calculation algorithm to ensure
it correctly computes account balances after each transaction.
"""
import pytest
class TestRunningBalanceCalculation:
"""Test running balance calculation logic."""
def test_running_balance_calculation_logic(self):
"""Test the logic behind running balance calculation.
This test validates the algorithm used in the frontend TransactionsTable
component to ensure running balances are calculated correctly.
"""
# Sample test data similar to what the frontend receives
transactions = [
{
"transaction_id": "txn-1",
"account_id": "account-001",
"transaction_value": -100.00,
"transaction_date": "2025-01-01T10:00:00Z",
"description": "Initial expense"
},
{
"transaction_id": "txn-2",
"account_id": "account-001",
"transaction_value": 500.00,
"transaction_date": "2025-01-02T10:00:00Z",
"description": "Salary"
},
{
"transaction_id": "txn-3",
"account_id": "account-001",
"transaction_value": -50.00,
"transaction_date": "2025-01-03T10:00:00Z",
"description": "Shopping"
}
]
balances = [
{
"account_id": "account-001",
"balance_amount": 350.00, # Final balance after all transactions
"balance_type": "interimAvailable"
}
]
# Implement the corrected algorithm from the frontend
running_balances = self._calculate_running_balances(transactions, balances)
# Expected running balances:
# - After txn-1 (-100): balance should be -100.00
# - After txn-2 (+500): balance should be 400.00 (previous + 500)
# - After txn-3 (-50): balance should be 350.00 (previous - 50)
assert running_balances["account-001-txn-1"] == -100.00, "First transaction running balance incorrect"
assert running_balances["account-001-txn-2"] == 400.00, "Second transaction running balance incorrect"
assert running_balances["account-001-txn-3"] == 350.00, "Third transaction running balance incorrect"
# Final balance should match current account balance
final_calculated_balance = running_balances["account-001-txn-3"]
assert final_calculated_balance == balances[0]["balance_amount"], "Final balance doesn't match current account balance"
def test_running_balance_multiple_accounts(self):
"""Test running balance calculation with multiple accounts."""
transactions = [
{
"transaction_id": "txn-1",
"account_id": "account-001",
"transaction_value": -50.00,
"transaction_date": "2025-01-01T10:00:00Z",
"description": "Account 1 expense"
},
{
"transaction_id": "txn-2",
"account_id": "account-002",
"transaction_value": -25.00,
"transaction_date": "2025-01-01T11:00:00Z",
"description": "Account 2 expense"
},
{
"transaction_id": "txn-3",
"account_id": "account-001",
"transaction_value": 100.00,
"transaction_date": "2025-01-02T10:00:00Z",
"description": "Account 1 income"
}
]
balances = [
{
"account_id": "account-001",
"balance_amount": 50.00,
"balance_type": "interimAvailable"
},
{
"account_id": "account-002",
"balance_amount": 75.00,
"balance_type": "interimAvailable"
}
]
running_balances = self._calculate_running_balances(transactions, balances)
# Account 1: starts at 0, -50 = -50, +100 = 50
assert running_balances["account-001-txn-1"] == -50.00
assert running_balances["account-001-txn-3"] == 50.00
# Account 2: starts at 100, -25 = 75
assert running_balances["account-002-txn-2"] == 75.00
def test_running_balance_empty_transactions(self):
"""Test running balance calculation with no transactions."""
transactions = []
balances = [
{
"account_id": "account-001",
"balance_amount": 100.00,
"balance_type": "interimAvailable"
}
]
running_balances = self._calculate_running_balances(transactions, balances)
assert running_balances == {}
def test_running_balance_no_balances(self):
"""Test running balance calculation with no balance data."""
transactions = [
{
"transaction_id": "txn-1",
"account_id": "account-001",
"transaction_value": -50.00,
"transaction_date": "2025-01-01T10:00:00Z",
"description": "Expense"
}
]
balances = []
running_balances = self._calculate_running_balances(transactions, balances)
# When no balance data available, current balance is 0
# Working backwards: starting_balance = 0 - (-50) = 50
# Going forward: running_balance = 50 + (-50) = 0
assert running_balances["account-001-txn-1"] == 0.00
def _calculate_running_balances(self, transactions, balances):
"""
Implementation of the corrected running balance calculation algorithm.
This mirrors the logic implemented in the frontend TransactionsTable component.
"""
running_balances = {}
account_balance_map = {}
# Create a map of account current balances - use interimAvailable as the most current
for balance in balances:
if balance["balance_type"] == "interimAvailable":
account_balance_map[balance["account_id"]] = balance["balance_amount"]
# Group transactions by account
transactions_by_account = {}
for txn in transactions:
account_id = txn["account_id"]
if account_id not in transactions_by_account:
transactions_by_account[account_id] = []
transactions_by_account[account_id].append(txn)
# Calculate running balance for each account
for account_id, account_transactions in transactions_by_account.items():
current_balance = account_balance_map.get(account_id, 0)
# Sort transactions by date (oldest first) for forward calculation
sorted_transactions = sorted(
account_transactions,
key=lambda x: x["transaction_date"]
)
# Calculate the starting balance by working backwards from current balance
starting_balance = current_balance
for txn in reversed(sorted_transactions):
starting_balance -= txn["transaction_value"]
# Now calculate running balances going forward chronologically
running_balance = starting_balance
for txn in sorted_transactions:
running_balance += txn["transaction_value"]
running_balances[f"{txn['account_id']}-{txn['transaction_id']}"] = running_balance
return running_balances