Compare commits

37 Commits

Author SHA1 Message Date
061d63a8ad feat(ui): #111 draft for (unfinished) mobile transaction-ui
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 6m43s
2025-08-24 15:35:00 +02:00
d0faee2950 chore(deps): update golang:1.25.0 docker digest to 4859242
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m24s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m39s
2025-08-22 21:05:43 +00:00
01d459e913 fix: add "?" to validateString
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m52s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m36s
2025-08-20 04:41:29 +02:00
cee533694c chore(deps): update golang:1.25.0 docker digest to 91e2cd4
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m40s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m40s
2025-08-18 12:05:26 +00:00
f6283c6ab3 fix(deps): update module github.com/a-h/templ to v0.3.943
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m51s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m33s
2025-08-15 16:05:55 +00:00
e48d11b818 chore(deps): update node.js to v24.6.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m38s
2025-08-14 22:06:46 +00:00
b9150334ee chore(deps): update node.js to 3266bc9
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m47s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m41s
2025-08-14 16:06:16 +00:00
d20981beaa fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.32
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m0s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m34s
2025-08-14 15:06:41 +00:00
3c95abe59c fix(deps): update module github.com/a-h/templ to v0.3.937
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m6s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m30s
2025-08-14 14:10:03 +00:00
fad2bd3928 chore(deps): update tailwindcss monorepo to v4.1.12
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m34s
2025-08-14 13:06:29 +00:00
c75b99ea9d chore(deps): update node.js to 73297e2
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m46s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m31s
2025-08-14 07:05:57 +00:00
56737a4156 chore(deps): update golang:1.25.0 docker digest to 9e56f0d
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m58s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m46s
2025-08-14 06:09:06 +00:00
ab425d759c chore(deps): update node.js to 58a2604
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m42s
2025-08-14 01:16:40 +00:00
283679fc4f chore(deps): update golang:1.25.0 docker digest to 10a15b9
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m49s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m38s
2025-08-14 00:06:49 +00:00
e0802cf232 chore(deps): update dependency go to v1.25.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m19s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m43s
2025-08-13 23:10:18 +00:00
6577dbb297 chore(deps): update golang docker tag to v1.25.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m49s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m41s
2025-08-13 22:11:10 +00:00
7e37c24b07 chore(deps): update golang:1.24.6 docker digest to 746a0e9
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m30s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m41s
2025-08-13 21:06:28 +00:00
3f3edbb8ad chore(deps): update golang:1.24.6 docker digest to 86a999d
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m37s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m22s
2025-08-13 18:06:20 +00:00
16429f1950 chore(deps): update debian docker tag to v13
All checks were successful
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m55s
2025-08-13 15:25:14 +00:00
82a9fd8220 chore(deps): update golang:1.24.6 docker digest to 370491a
Some checks failed
Build Docker Image / Build-Docker-Image (push) Successful in 4m50s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Has been cancelled
2025-08-13 15:08:39 +00:00
192e6b7f50 chore(deps): update node.js to 5cc5271
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m37s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m33s
2025-08-13 02:06:22 +00:00
57377f9c27 chore(deps): update debian:12.11 docker digest to 731dd13
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m8s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m58s
2025-08-13 01:16:08 +00:00
66227c5818 chore(deps): update golang:1.24.6 docker digest to 0348485
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m32s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m44s
2025-08-13 00:08:49 +00:00
6e51e3c8b3 chore(deps): update debian:12.11 docker digest to 5626655
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m57s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m39s
2025-08-12 22:06:39 +00:00
6c916aecb4 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.31
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m0s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m39s
2025-08-11 15:06:14 +00:00
8575fbf56e chore(deps): update actions/checkout digest to 08eba0b
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m46s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m38s
2025-08-11 11:05:53 +00:00
f820fcdfeb fix(deps): update module golang.org/x/net to v0.43.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m15s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m8s
2025-08-09 20:35:22 +00:00
f6e58b7afc fix(deps): update module golang.org/x/crypto to v0.41.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m44s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m2s
2025-08-09 02:06:26 +00:00
93e669b038 chore(deps): update golang docker tag to v1.24.6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m28s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m55s
2025-08-09 01:10:39 +00:00
d037317aab chore(deps): update dependency go to v1.24.6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m27s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m50s
2025-08-08 23:09:13 +00:00
0517e7ec89 feat(transaction): #243 add pagination to transactions
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 2m28s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m34s
2025-08-09 00:36:50 +02:00
867c0ca1cd chore(deps): update node.js to 3218f0d
Some checks failed
Build Docker Image / Build-Docker-Image (push) Successful in 4m3s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 2m50s
2025-08-04 16:07:53 +00:00
ddcbfaa075 chore(deps): update node.js to 0d98a9f
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m44s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m41s
2025-08-04 13:04:59 +00:00
4583c0a70e chore(deps): update node.js to v22.18.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m14s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m43s
2025-08-04 10:05:10 +00:00
380854272a chore(deps): update dependency echarts to v6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m9s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m48s
2025-08-01 22:42:20 +02:00
6bc9e0666b feat(layout): #211 optimize the overall layout for mobile
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m10s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m18s
move navigation to aside
proper mobile handling
update logo.svg
remove pirata-one/only use it for the logo
2025-08-01 22:26:17 +02:00
9fa554c60a chore(deps): update node.js to v24.5.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m48s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m37s
2025-07-31 22:08:19 +00:00
17 changed files with 482 additions and 425 deletions

View File

@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- run: docker build . -t spend-sparrow-test
- run: docker rmi spend-sparrow-test

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- run: docker login git.wundenbergs.de -u tim -p ${{ secrets.DOCKER_GITEA_TOKEN }}
- run: docker build . -t git.wundenbergs.de/x/spend-sparrow:latest -t git.wundenbergs.de/x/spend-sparrow:$GITHUB_SHA
- run: docker push git.wundenbergs.de/x/spend-sparrow:latest

2
.nvmrc
View File

@@ -1 +1 @@
24.4.1
24.6.0

View File

@@ -1,4 +1,4 @@
FROM golang:1.24.5@sha256:ef5b4be1f94b36c90385abd9b6b4f201723ae28e71acacb76d00687333c17282 AS builder_go
FROM golang:1.25.0@sha256:4859242e2c392ddc9d3225fd41181c00a443d9cc005b8e5131ce164106fbc676 AS builder_go
WORKDIR /spend-sparrow
RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
RUN go install github.com/a-h/templ/cmd/templ@latest
@@ -13,7 +13,7 @@ RUN golangci-lint run ./...
RUN go build -o /spend-sparrow/spend-sparrow .
FROM node:22.17.1@sha256:37ff334612f77d8f999c10af8797727b731629c26f2e83caa6af390998bdc49c AS builder_node
FROM node:22.18.0@sha256:3266bc9e8bee1acc8a77386eefaf574987d2729b8c5ec35b0dbd6ddbc40b0ce2 AS builder_node
WORKDIR /spend-sparrow
COPY package.json package-lock.json ./
RUN npm clean-install
@@ -21,7 +21,7 @@ COPY . ./
RUN npm run build
FROM debian:12.11@sha256:b6507e340c43553136f5078284c8c68d86ec8262b1724dde73c325e8d3dcdeba
FROM debian:13.0@sha256:6d87375016340817ac2391e670971725a9981cfc24e221c47734681ed0f6c0f5
WORKDIR /spend-sparrow
RUN apt-get update && apt-get install -y ca-certificates && echo "" > .env
COPY migration ./migration

14
go.mod
View File

@@ -2,15 +2,15 @@ module spend-sparrow
go 1.23.0
toolchain go1.24.5
toolchain go1.25.0
require (
github.com/a-h/templ v0.3.924
github.com/a-h/templ v0.3.943
github.com/golang-migrate/migrate/v4 v4.18.3
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
github.com/joho/godotenv v1.5.1
github.com/mattn/go-sqlite3 v1.14.30
github.com/mattn/go-sqlite3 v1.14.32
github.com/stretchr/testify v1.10.0
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2
github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2
@@ -25,8 +25,8 @@ require (
go.opentelemetry.io/otel/sdk/log v0.13.0
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
golang.org/x/crypto v0.40.0
golang.org/x/net v0.42.0
golang.org/x/crypto v0.41.0
golang.org/x/net v0.43.0
)
require (
@@ -45,8 +45,8 @@ require (
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect

24
go.sum
View File

@@ -1,7 +1,7 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/a-h/templ v0.3.924 h1:t5gZqTneXqvehpNZsgtnlOscnBboNh9aASBH2MgV/0k=
github.com/a-h/templ v0.3.924/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY=
github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -41,8 +41,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY=
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@@ -91,14 +91,14 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=

View File

@@ -15,7 +15,6 @@ import (
type migrationLogger struct{}
func (l migrationLogger) Printf(format string, v ...any) {
//nolint:noctx
slog.Info(format, v...)
}
func (l migrationLogger) Verbose() bool {

View File

@@ -58,6 +58,7 @@ func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
AccountId: r.URL.Query().Get("account-id"),
TreasureChestId: r.URL.Query().Get("treasure-chest-id"),
Error: r.URL.Query().Get("error"),
Page: r.URL.Query().Get("page"),
}
transactions, err := h.s.GetAll(r.Context(), user, filter)

View File

@@ -10,7 +10,7 @@ const (
)
var (
safeInputRegex = regexp.MustCompile(`^[a-zA-Z0-9ÄÖÜäöüß,&'". -]+$`)
safeInputRegex = regexp.MustCompile(`^[a-zA-Z0-9ÄÖÜäöüß,&'". \-\?]+$`)
)
func validateString(value string, fieldName string) error {

View File

@@ -7,12 +7,15 @@ import (
"log/slog"
"spend-sparrow/internal/db"
"spend-sparrow/internal/types"
"strconv"
"time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
const page_size = 25
type Transaction interface {
Add(ctx context.Context, tx *sqlx.Tx, user *types.User, transaction types.Transaction) (*types.Transaction, error)
Update(ctx context.Context, user *types.User, transaction types.Transaction) (*types.Transaction, error)
@@ -231,22 +234,41 @@ func (s TransactionImpl) GetAll(ctx context.Context, user *types.User, filter ty
return nil, ErrUnauthorized
}
var (
page int64
offset int64
err error
)
if filter.Page != "" {
page, err = strconv.ParseInt(filter.Page, 10, 64)
if err != nil {
offset = 0
} else {
offset = page - 1
offset *= page_size
}
}
transactions := make([]*types.Transaction, 0)
err := s.db.SelectContext(ctx, &transactions, `
err = s.db.SelectContext(ctx, &transactions, `
SELECT *
FROM "transaction"
WHERE user_id = ?
AND (? = '' OR account_id = ?)
AND (? = '' OR treasure_chest_id = ?)
AND (? = ''
OR (? = "true" AND error IS NOT NULL)
OR (? = "false" AND error IS NULL)
AND ($1 = '' OR account_id = $1)
AND ($2 = '' OR treasure_chest_id = $2)
AND ($3 = ''
OR ($3 = "true" AND error IS NOT NULL)
OR ($3 = "false" AND error IS NULL)
)
ORDER BY timestamp DESC, created_at DESC`,
ORDER BY timestamp DESC, created_at DESC
LIMIT $4 OFFSET $5
`,
user.Id,
filter.AccountId, filter.AccountId,
filter.TreasureChestId, filter.TreasureChestId,
filter.Error, filter.Error, filter.Error)
filter.AccountId,
filter.TreasureChestId,
filter.Error,
page_size,
offset)
err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err)
if err != nil {
return nil, err

View File

@@ -31,6 +31,7 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
<script src="/static/js/htmx.min.js"></script>
<script src="/static/js/toast.js"></script>
<script src="/static/js/layout.js"></script>
<script src="/static/js/transaction.js"></script>
<script src="/static/js/time.js"></script>
<script src="/static/js/echarts.min.js"></script>
<script src="/static/js/dashboard.js" defer></script>

View File

@@ -1,74 +1,97 @@
package transaction
import "fmt"
import "time"
import "spend-sparrow/internal/template/svg"
import "spend-sparrow/internal/types"
import "github.com/google/uuid"
templ Transaction(items templ.Component, filter types.TransactionItemsFilter, accounts []*types.Account, treasureChests []*types.TreasureChest) {
<div class="max-w-6xl mt-10 mx-auto">
<div class="flex items-center gap-4">
<form
hx-get="/transaction"
hx-target="#transaction-items"
hx-push-url="true"
hx-trigger="change"
>
<select name="account-id" class="bg-white input">
<option value="">- Filter Acount -</option>
for _, account := range accounts {
<option
value={ account.Id.String() }
selected?={ filter.AccountId == account.Id.String() }
>{ account.Name }</option>
}
</select>
<select name="treasure-chest-id" class="bg-white input">
<option value="">- Filter Treasure Chest -</option>
for _, parent := range treasureChests {
if parent.ParentId == nil {
<optgroup label={ parent.Name }>
for _, child := range treasureChests {
if child.ParentId != nil && *child.ParentId == parent.Id {
<option
value={ child.Id.String() }
selected?={ filter.TreasureChestId == child.Id.String() }
>{ child.Name }</option>
}
}
</optgroup>
}
}
</select>
<select name="error" class="bg-white input">
<option value="">- Filter Error -</option>
<option
value="true"
selected?={ filter.Error == "true" }
>Has Errors</option>
<option
value="false"
selected?={ filter.Error == "false" }
>Has no Errors</option>
</select>
</form>
<button
hx-get="/transaction/new"
hx-target="#transaction-items"
hx-swap="afterbegin"
class="button button-primary ml-auto px-2 flex items-center gap-2 justify-center"
>
@svg.Plus()
<p>New Transaction</p>
</button>
templ Transaction(
items templ.Component,
filter types.TransactionItemsFilter,
accounts []*types.Account,
treasureChests []*types.TreasureChest) {
<div class="">
<div class="">
<!-- <form -->
<!-- id="transactionFilterForm" -->
<!-- hx-get="/transaction" -->
<!-- hx-target="#transaction-items" -->
<!-- hx-push-url="true" -->
<!-- hx-trigger="change" -->
<!-- > -->
<!-- <select name="account-id" class=""> -->
<!-- <option value="">- Filter Acount -</option> -->
<!-- for _, account := range accounts { -->
<!-- <option -->
<!-- value={ account.Id.String() } -->
<!-- selected?={ filter.AccountId == account.Id.String() } -->
<!-- >{ account.Name }</option> -->
<!-- } -->
<!-- </select> -->
<!-- <select name="treasure-chest-id" class=""> -->
<!-- <option value="">- Filter Treasure Chest -</option> -->
<!-- for _, parent := range treasureChests { -->
<!-- if parent.ParentId == nil { -->
<!-- <optgroup label={ parent.Name }> -->
<!-- for _, child := range treasureChests { -->
<!-- if child.ParentId != nil && *child.ParentId == parent.Id { -->
<!-- <option -->
<!-- value={ child.Id.String() } -->
<!-- selected?={ filter.TreasureChestId == child.Id.String() } -->
<!-- >{ child.Name }</option> -->
<!-- } -->
<!-- } -->
<!-- </optgroup> -->
<!-- } -->
<!-- } -->
<!-- </select> -->
<!-- <select name="error" class=""> -->
<!-- <option value="">- Filter Error -</option> -->
<!-- <option -->
<!-- value="true" -->
<!-- selected?={ filter.Error == "true" } -->
<!-- >Has Errors</option> -->
<!-- <option -->
<!-- value="false" -->
<!-- selected?={ filter.Error == "false" } -->
<!-- >Has no Errors</option> -->
<!-- </select> -->
<!-- <input id="page" name="page" type="hidden" value={ filter.Page }/> -->
<!-- </form> -->
<!-- <button -->
<!-- hx-get="/transaction/new" -->
<!-- hx-target="#transaction-items" -->
<!-- hx-swap="afterbegin" -->
<!-- class="" -->
<!-- > -->
<!-- @svg.Plus() -->
<!-- <p>New Transaction</p> -->
<!-- </button> -->
</div>
<!-- <div class=""> -->
<!-- <button id="pagePrev1" class=""> -->
<!-- &lt; -->
<!-- </button> -->
<!-- <span class="">Page: <span class="" id="page1">{ getPageNumber(filter.Page) }</span></span> -->
<!-- <button id="pageNext1" class=""> -->
<!-- &gt; -->
<!-- </button> -->
<!-- </div> -->
@items
<!-- <div class=""> -->
<!-- <button id="pagePrev2" class=""> -->
<!-- &lt; -->
<!-- </button> -->
<!-- <span class="">Page: <span class="" id="page2">{ getPageNumber(filter.Page) }</span></span> -->
<!-- <button id="pageNext2" class=""> -->
<!-- &gt; -->
<!-- </button> -->
<!-- </div> -->
</div>
}
templ TransactionItems(transactions []*types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
<div id="transaction-items" class="my-6">
<div id="transaction-items" class="flex flex-col gap-8">
for _, transaction := range transactions {
@TransactionItem(transaction, accounts, treasureChests)
}
@@ -76,209 +99,186 @@ templ TransactionItems(transactions []*types.Transaction, accounts, treasureChes
}
templ EditTransaction(transaction *types.Transaction, accounts []*types.Account, treasureChests []*types.TreasureChest) {
{{
var (
timestamp time.Time
id string
cancelUrl string
)
party := ""
description := ""
accountId := ""
value := "0.00"
treasureChestId := ""
if transaction == nil {
timestamp = time.Now().UTC().Truncate(time.Minute)
id = "new"
cancelUrl = "/empty"
} else {
timestamp = transaction.Timestamp.UTC().Truncate(time.Minute)
party = transaction.Party
description = transaction.Description
if transaction.AccountId != nil {
accountId = transaction.AccountId.String()
}
if transaction.TreasureChestId != nil {
treasureChestId = transaction.TreasureChestId.String()
}
value = formatFloat(transaction.Value)
id = transaction.Id.String()
cancelUrl = "/transaction/" + id
}
}}
<div id="transaction" class="border-1 border-gray-300 w-full my-4 p-4 bg-gray-50 rounded-lg">
<form
hx-post={ "/transaction/" + id }
hx-target="closest #transaction"
hx-swap="outerHTML"
class="text-xl flex justify-end gap-4 items-center"
>
<div class="grid grid-cols-[auto_auto] items-center gap-4 mr-auto">
<label for="timestamp" class="text-sm text-gray-500">Transaction Date</label>
<input
autofocus
name="timestamp"
type="date"
value={ timestamp.String() }
class="bg-white input datetime"
/>
<label for="party" class="text-sm text-gray-500">Party</label>
<input
name="party"
type="text"
value={ party }
class="mr-auto bg-white input"
/>
<label for="description" class="text-sm text-gray-500">Description</label>
<input
name="description"
type="text"
value={ description }
class="mr-auto bg-white input"
/>
<label for="value" class="text-sm text-gray-500">Value (€)</label>
<input
name="value"
step="0.01"
type="number"
value={ value }
class="bg-white input"
/>
<label for="account-id" class="text-sm text-gray-500">Account</label>
<select
name="account-id"
class="bg-white input"
>
<option value="">-</option>
for _, account := range accounts {
<option selected?={ account.Id.String() == accountId } value={ account.Id.String() }>{ account.Name }</option>
}
</select>
<label for="treasure-chest-id" class="text-sm text-gray-500">Treasure Chest</label>
<select name="treasure-chest-id" class="bg-white input">
<option value="">- Filter Treasure Chest -</option>
for _, parent := range treasureChests {
if parent.ParentId == nil {
<optgroup label={ parent.Name }>
for _, child := range treasureChests {
if child.ParentId != nil && *child.ParentId == parent.Id {
<option
value={ child.Id.String() }
selected?={ treasureChestId == child.Id.String() }
>{ child.Name }</option>
}
}
</optgroup>
}
}
</select>
</div>
<button type="submit" class="button button-neglect px-1 flex items-center gap-2">
@svg.Save()
<span>
Save
</span>
</button>
<button
hx-get={ cancelUrl }
hx-target="closest #transaction"
hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2"
>
<span class="h-4 w-4">
@svg.Cancel()
</span>
<span>
Cancel
</span>
</button>
</form>
</div>
// {{
// var (
// timestamp time.Time
//
// id string
// cancelUrl string
// )
// party := ""
// description := ""
// accountId := ""
// value := "0.00"
// treasureChestId := ""
// if transaction == nil {
// timestamp = time.Now().UTC().Truncate(time.Minute)
//
// id = "new"
// cancelUrl = "/empty"
// } else {
// timestamp = transaction.Timestamp.UTC().Truncate(time.Minute)
// party = transaction.Party
// description = transaction.Description
// if transaction.AccountId != nil {
// accountId = transaction.AccountId.String()
// }
// if transaction.TreasureChestId != nil {
// treasureChestId = transaction.TreasureChestId.String()
// }
// value = formatFloat(transaction.Value)
//
// id = transaction.Id.String()
// cancelUrl = "/transaction/" + id
// }
// }}
// <div id="transaction" class="">
// <form
// hx-post={ "/transaction/" + id }
// hx-target="closest #transaction"
// hx-swap="outerHTML"
// class=""
// >
// <div class="">
// <label for="timestamp" class="">Transaction Date</label>
// <input
// autofocus
// name="timestamp"
// type="date"
// value={ timestamp.String() }
// class=""
// />
// <label for="party" class="">Party</label>
// <input
// name="party"
// type="text"
// value={ party }
// class=""
// />
// <label for="description" class="">Description</label>
// <input
// name="description"
// type="text"
// value={ description }
// class=""
// />
// <label for="value" class="">Value (€)</label>
// <input
// name="value"
// step="0.01"
// type="number"
// value={ value }
// class=""
// />
// <label for="account-id" class="">Account</label>
// <select
// name="account-id"
// class=""
// >
// <option value="">-</option>
// for _, account := range accounts {
// <option selected?={ account.Id.String() == accountId } value={ account.Id.String() }>{ account.Name }</option>
// }
// </select>
// <label for="treasure-chest-id" class="">Treasure Chest</label>
// <select name="treasure-chest-id" class="">
// <option value="">- Filter Treasure Chest -</option>
// for _, parent := range treasureChests {
// if parent.ParentId == nil {
// <optgroup label={ parent.Name }>
// for _, child := range treasureChests {
// if child.ParentId != nil && *child.ParentId == parent.Id {
// <option
// value={ child.Id.String() }
// selected?={ treasureChestId == child.Id.String() }
// >{ child.Name }</option>
// }
// }
// </optgroup>
// }
// }
// </select>
// </div>
// <button type="submit" class="">
// @svg.Save()
// <span>
// Save
// </span>
// </button>
// <button
// hx-get={ cancelUrl }
// hx-target="closest #transaction"
// hx-swap="outerHTML"
// class=""
// >
// <span class="">
// @svg.Cancel()
// </span>
// <span>
// Cancel
// </span>
// </button>
// </form>
// </div>
}
templ TransactionItem(transaction *types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
{{
background := "bg-gray-50"
if transaction.Error != nil {
background = "bg-yellow-50"
}
}}
<div
id="transaction"
class={ "mt-4 border-1 grid grid-cols-[auto_auto_1fr_1fr_auto_auto_auto_auto] gap-4 items-center text-xl border-gray-300 w-full p-4 rounded-lg " + background }
class="grid grid-cols-[1fr_auto]"
if transaction.Error != nil {
title={ *transaction.Error }
}
>
<p class="mr-auto datetime">{ transaction.Timestamp.String() }</p>
<div class="w-6">
if transaction.Error != nil {
@svg.Info()
<p class="datetime">{ transaction.Timestamp.String() }</p>
<p class="text-2xl flex items-center col-start-2 row-start-1 row-end-3 align-center">
if transaction.Value < 0 {
<span class="text-red-700">- { types.FormatEuros(transaction.Value) }</span>
} else {
<span class="text-green-700">+ { types.FormatEuros(transaction.Value) }</span>
}
</div>
<div>
<p class="text-sm text-gray-500">
if transaction.AccountId != nil {
{ accounts[*transaction.AccountId] }
} else {
&nbsp;
}
</p>
<!-- if transaction.AccountId != nil { -->
<!-- <p class="col-start-1"> -->
<!-- { accounts[*transaction.AccountId] } -->
<!-- </p> -->
<!-- } -->
if transaction.TreasureChestId != nil {
<p class="col-start-1">
{ treasureChests[*transaction.TreasureChestId] }
</p>
<p class="text-sm text-gray-500">
if transaction.TreasureChestId != nil {
{ treasureChests[*transaction.TreasureChestId] }
} else {
&nbsp;
}
</p>
</div>
<div>
<p class="text-sm text-gray-500">
if transaction.Party != "" {
{ transaction.Party }
} else {
&nbsp;
}
</p>
<p class="text-sm text-gray-500">
if transaction.Description != "" {
{ transaction.Description }
} else {
&nbsp;
}
</p>
</div>
if transaction.Value < 0 {
<p class="mr-8 min-w-22 text-right text-red-700">{ types.FormatEuros(transaction.Value) }</p>
} else {
<p class="mr-8 w-22 text-right text-green-700">{ types.FormatEuros(transaction.Value) }</p>
}
<button
hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" }
hx-target="closest #transaction"
hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2"
>
@svg.Edit()
<span>
Edit
</span>
</button>
<button
hx-delete={ "/transaction/" + transaction.Id.String() }
hx-target="closest #transaction"
hx-swap="outerHTML"
hx-confirm="Are you sure you want to delete this transaction?"
class="button button-neglect px-1 flex items-center gap-2"
>
@svg.Delete()
<span>
Delete
</span>
</button>
<!-- if transaction.Party != "" { -->
<!-- <p class="col-start-1"> -->
<!-- { transaction.Party } -->
<!-- </p> -->
<!-- } -->
<!-- if transaction.Description != "" { -->
<!-- <p class="col-start-1"> -->
<!-- { transaction.Description } -->
<!-- </p> -->
<!-- } -->
<!-- <div class="col-start-2 col-end-3 flex gap-10 justify-end"> -->
<!-- <button -->
<!-- hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" } -->
<!-- hx-target="closest #transaction" -->
<!-- hx-swap="outerHTML" -->
<!-- class="flex items-center gap-2" -->
<!-- > -->
<!-- @svg.Edit() -->
<!-- Edit -->
<!-- </button> -->
<!-- <button -->
<!-- hx-delete={ "/transaction/" + transaction.Id.String() } -->
<!-- hx-target="closest #transaction" -->
<!-- hx-swap="outerHTML" -->
<!-- hx-confirm="Are you sure you want to delete this transaction?" -->
<!-- class="flex items-center gap-2" -->
<!-- > -->
<!-- @svg.Delete() -->
<!-- Delete -->
<!-- </button> -->
<!-- </div> -->
</div>
}
@@ -287,3 +287,11 @@ func formatFloat(balance int64) string {
euros := float64(balance) / 100
return fmt.Sprintf("%.2f", euros)
}
func getPageNumber(page string) string {
if page == "" {
return "1"
} else {
return page
}
}

View File

@@ -51,4 +51,5 @@ type TransactionItemsFilter struct {
AccountId string
TreasureChestId string
Error string
Page string
}

251
package-lock.json generated
View File

@@ -9,24 +9,10 @@
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@tailwindcss/cli": "4.1.11",
"echarts": "5.6.0",
"@tailwindcss/cli": "4.1.12",
"echarts": "6.0.0",
"htmx.org": "2.0.6",
"tailwindcss": "4.1.11"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
"tailwindcss": "4.1.12"
}
},
"node_modules/@isaacs/fs-minipass": {
@@ -43,18 +29,25 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -67,16 +60,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -85,9 +68,9 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -405,44 +388,44 @@
}
},
"node_modules/@tailwindcss/cli": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz",
"integrity": "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.12.tgz",
"integrity": "sha512-2PyJ5MGh/6JPS+cEaAq6MGDx3UemkX/mJt+/phm7/VOpycpecwNnHuFZbbgx6TNK/aIjvFOhhTVlappM7tmqvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@parcel/watcher": "^2.5.1",
"@tailwindcss/node": "4.1.11",
"@tailwindcss/oxide": "4.1.11",
"enhanced-resolve": "^5.18.1",
"@tailwindcss/node": "4.1.12",
"@tailwindcss/oxide": "4.1.12",
"enhanced-resolve": "^5.18.3",
"mri": "^1.2.0",
"picocolors": "^1.1.1",
"tailwindcss": "4.1.11"
"tailwindcss": "4.1.12"
},
"bin": {
"tailwindcss": "dist/index.mjs"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
"integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz",
"integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"enhanced-resolve": "^5.18.1",
"jiti": "^2.4.2",
"@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.11"
"tailwindcss": "4.1.12"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
"integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz",
"integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -454,24 +437,24 @@
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-x64": "4.1.11",
"@tailwindcss/oxide-freebsd-x64": "4.1.11",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-x64-musl": "4.1.11",
"@tailwindcss/oxide-wasm32-wasi": "4.1.11",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
"@tailwindcss/oxide-android-arm64": "4.1.12",
"@tailwindcss/oxide-darwin-arm64": "4.1.12",
"@tailwindcss/oxide-darwin-x64": "4.1.12",
"@tailwindcss/oxide-freebsd-x64": "4.1.12",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.12",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.12",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.12",
"@tailwindcss/oxide-linux-x64-musl": "4.1.12",
"@tailwindcss/oxide-wasm32-wasi": "4.1.12",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.12",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.12"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
"integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz",
"integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==",
"cpu": [
"arm64"
],
@@ -486,9 +469,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
"integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz",
"integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==",
"cpu": [
"arm64"
],
@@ -503,9 +486,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
"integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz",
"integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==",
"cpu": [
"x64"
],
@@ -520,9 +503,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
"integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz",
"integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==",
"cpu": [
"x64"
],
@@ -537,9 +520,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
"integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz",
"integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==",
"cpu": [
"arm"
],
@@ -554,9 +537,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
"integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz",
"integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==",
"cpu": [
"arm64"
],
@@ -571,9 +554,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
"integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz",
"integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==",
"cpu": [
"arm64"
],
@@ -588,9 +571,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
"integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz",
"integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==",
"cpu": [
"x64"
],
@@ -605,9 +588,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
"integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz",
"integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==",
"cpu": [
"x64"
],
@@ -622,9 +605,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
"integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz",
"integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -640,11 +623,11 @@
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2",
"@napi-rs/wasm-runtime": "^0.2.11",
"@tybys/wasm-util": "^0.9.0",
"@emnapi/core": "^1.4.5",
"@emnapi/runtime": "^1.4.5",
"@emnapi/wasi-threads": "^1.0.4",
"@napi-rs/wasm-runtime": "^0.2.12",
"@tybys/wasm-util": "^0.10.0",
"tslib": "^2.8.0"
},
"engines": {
@@ -652,18 +635,18 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.3",
"version": "1.4.5",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.2",
"@emnapi/wasi-threads": "1.0.4",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.3",
"version": "1.4.5",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -673,7 +656,7 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.2",
"version": "1.0.4",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -683,7 +666,7 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.11",
"version": "0.2.12",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -691,11 +674,11 @@
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.9.0"
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"version": "0.10.0",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -712,9 +695,9 @@
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
"integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
"integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==",
"cpu": [
"arm64"
],
@@ -729,9 +712,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
"integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz",
"integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==",
"cpu": [
"x64"
],
@@ -792,20 +775,20 @@
}
},
"node_modules/echarts": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
"zrender": "6.0.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -877,9 +860,9 @@
}
},
"node_modules/jiti": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"dev": true,
"license": "MIT",
"bin": {
@@ -1246,16 +1229,16 @@
}
},
"node_modules/tailwindcss": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
"integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1311,9 +1294,9 @@
}
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {

View File

@@ -11,9 +11,9 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@tailwindcss/cli": "4.1.11",
"@tailwindcss/cli": "4.1.12",
"htmx.org": "2.0.6",
"tailwindcss": "4.1.11",
"echarts": "5.6.0"
"tailwindcss": "4.1.12",
"echarts": "6.0.0"
}
}

43
static/js/transaction.js Normal file
View File

@@ -0,0 +1,43 @@
// document.addEventListener("DOMContentLoaded", () => {
// if (!page || !page1 || !pagePrev1 || !pageNext1 || !page2 || !pagePrev2 || !pageNext2 || !transactionFilterForm) {
// return;
// }
//
//
// const scrollToTop = function() {
// window.scrollTo(0, 0);
// };
// const incPage = function() {
// const currPage = Number(page.value);
// var nextPage = currPage
// if (currPage > 1) {
// nextPage -= 1;
// page.value = nextPage;
// transactionFilterForm.dispatchEvent(new Event('change'));
// }
// page1.textContent = nextPage;
// page2.textContent = nextPage;
// scrollToTop();
// };
// const decPage = function() {
// const currPage = Number(page.value);
// var nextPage = currPage + 1;
// page.value = nextPage;
// transactionFilterForm.dispatchEvent(new Event('change'));
// page1.textContent = nextPage;
// page2.textContent = nextPage;
// scrollToTop();
// };
//
//
//
// pagePrev1.addEventListener("click", incPage);
// pagePrev2.addEventListener("click", incPage);
//
// pageNext1.addEventListener("click", decPage);
// pageNext2.addEventListener("click", decPage);
//
// console.log("initialized pagination");
// })

View File

@@ -1862,7 +1862,6 @@ func TestIntegrationAccount(t *testing.T) {
">": 400,
"/": 400,
"\\": 400,
"?": 400,
":": 400,
"*": 400,
"|": 400,