Compare commits
	
		
			33 Commits
		
	
	
		
			0.5.1
			...
			38e21786a7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 38e21786a7 | |||
| 971c3d3be5 | |||
| 946f14c1cc | |||
| 9ce0da0182 | |||
| 4c93e4635d | |||
| f3a50c790b | |||
| 0c5f68ed80 | |||
| 7fdd8bd935 | |||
| d4287f8aac | |||
| 6712af10d9 | |||
| 70edb382e1 | |||
| 390a042441 | |||
| e8028dae34 | |||
| fc249adc9e | |||
| c186a14644 | |||
| 8a27303670 | |||
| 44e9bb6ec0 | |||
| 42d431ba8b | |||
| 3727061065 | |||
| c7a8adb3ab | |||
| 29f534bf10 | |||
| 15381c84f6 | |||
| a0cabbf4f7 | |||
| f26ee8f472 | |||
| 4fb3c2a335 | |||
| 8899ff5772 | |||
| 347a0c9e50 | |||
| 66b8e1f69f | |||
| 5d1b49c896 | |||
| 42dc51fe9a | |||
| 1ca95f8768 | |||
| a73f7c2934 | |||
| 489aa88c4b | 
							
								
								
									
										10
									
								
								.earthignore
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.earthignore
									
									
									
									
									
								
							@@ -1,10 +0,0 @@
 | 
				
			|||||||
build/
 | 
					 | 
				
			||||||
.git/
 | 
					 | 
				
			||||||
docker-compose.yml
 | 
					 | 
				
			||||||
README.md
 | 
					 | 
				
			||||||
Earthfile
 | 
					 | 
				
			||||||
config.example.json
 | 
					 | 
				
			||||||
.gitignore
 | 
					 | 
				
			||||||
.vscode/
 | 
					 | 
				
			||||||
budgeteer
 | 
					 | 
				
			||||||
budgeteer.exe
 | 
					 | 
				
			||||||
							
								
								
									
										6
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
        "files.exclude": {
 | 
					        "files.exclude": {
 | 
				
			||||||
                "**/node_modules": true,
 | 
					                "**/node_modules": true,
 | 
				
			||||||
                "**/vendor": true
 | 
					                "**/vendor": true,
 | 
				
			||||||
 | 
					                "**/*.sql.go": true,
 | 
				
			||||||
 | 
					                ".task/": true,
 | 
				
			||||||
 | 
					                "build/": true,
 | 
				
			||||||
 | 
					                "web/dist/": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "gopls": {
 | 
					        "gopls": {
 | 
				
			||||||
                "formatting.gofumpt": true,
 | 
					                "formatting.gofumpt": true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
pipeline:
 | 
					 | 
				
			||||||
  build:
 | 
					 | 
				
			||||||
    name: Taskfile.dev
 | 
					 | 
				
			||||||
    image: hub.javil.eu/budgeteer:dev
 | 
					 | 
				
			||||||
    pull: true
 | 
					 | 
				
			||||||
    commands:
 | 
					 | 
				
			||||||
      - task ci
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  docker:
 | 
					 | 
				
			||||||
    image: plugins/docker
 | 
					 | 
				
			||||||
    secrets: [ docker_username, docker_password ]
 | 
					 | 
				
			||||||
    settings:
 | 
					 | 
				
			||||||
      registry: hub.javil.eu
 | 
					 | 
				
			||||||
      repo: hub.javil.eu/budgeteer
 | 
					 | 
				
			||||||
      context: build
 | 
					 | 
				
			||||||
      dockerfile: build/Dockerfile
 | 
					 | 
				
			||||||
      tags: 
 | 
					 | 
				
			||||||
        - latest
 | 
					 | 
				
			||||||
    when:
 | 
					 | 
				
			||||||
      event: [push, tag, deployment]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
image_pull_secrets:
 | 
					 | 
				
			||||||
- hub.javil.eu 
 | 
					 | 
				
			||||||
							
								
								
									
										21
									
								
								Earthfile
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Earthfile
									
									
									
									
									
								
							@@ -1,21 +0,0 @@
 | 
				
			|||||||
FROM golang:1.17
 | 
					 | 
				
			||||||
WORKDIR /src
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
build:
 | 
					 | 
				
			||||||
	COPY go.mod go.sum .
 | 
					 | 
				
			||||||
	RUN go mod download
 | 
					 | 
				
			||||||
	COPY . .
 | 
					 | 
				
			||||||
	RUN --mount=type=cache,target=/root/.cache/go-build go build -o build/budgeteer ./cmd/budgeteer
 | 
					 | 
				
			||||||
	SAVE ARTIFACT build/budgeteer /budgeteer AS LOCAL build/budgeteer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
docker:
 | 
					 | 
				
			||||||
	WORKDIR /app
 | 
					 | 
				
			||||||
	COPY +build/budgeteer .
 | 
					 | 
				
			||||||
	ENTRYPOINT ["/app/budgeteer"]
 | 
					 | 
				
			||||||
	SAVE IMAGE hub.javil.eu/budgeteer:latest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
run:
 | 
					 | 
				
			||||||
	LOCALLY
 | 
					 | 
				
			||||||
	WITH DOCKER --load=+docker
 | 
					 | 
				
			||||||
		RUN docker-compose up -d
 | 
					 | 
				
			||||||
	END
 | 
					 | 
				
			||||||
@@ -96,7 +96,7 @@ tasks:
 | 
				
			|||||||
      - ./docker/build.sh
 | 
					      - ./docker/build.sh
 | 
				
			||||||
      - ./web/package.json
 | 
					      - ./web/package.json
 | 
				
			||||||
    cmds:
 | 
					    cmds:
 | 
				
			||||||
      - docker build -t {{.IMAGE_NAME}}:dev . -f docker/Dockerfile
 | 
					      - docker build -t {{.IMAGE_NAME}}:dev . -f docker/Dockerfile.dev
 | 
				
			||||||
      - docker push {{.IMAGE_NAME}}:dev
 | 
					      - docker push {{.IMAGE_NAME}}:dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run:
 | 
					  run:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
FROM scratch
 | 
					 | 
				
			||||||
COPY ./budgeteer /app/budgeteer
 | 
					 | 
				
			||||||
ENTRYPOINT ["/app/budgeteer"]
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
	"DatabaseHost": "localhost", 
 | 
					 | 
				
			||||||
	"DatabaseUser": "user", 
 | 
					 | 
				
			||||||
	"DatabasePassword": "thisismypassword", 
 | 
					 | 
				
			||||||
	"DatabaseName": "budgeteer"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,17 +1,3 @@
 | 
				
			|||||||
FROM alpine as godeps
 | 
					FROM scratch
 | 
				
			||||||
RUN apk --no-cache add go
 | 
					COPY ./budgeteer /app/budgeteer
 | 
				
			||||||
RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
 | 
					ENTRYPOINT ["/app/budgeteer"]
 | 
				
			||||||
RUN go install github.com/go-task/task/v3/cmd/task@latest
 | 
					 | 
				
			||||||
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM alpine
 | 
					 | 
				
			||||||
RUN apk --no-cache add go nodejs yarn bash curl git git-perl tmux
 | 
					 | 
				
			||||||
ADD docker/build.sh /
 | 
					 | 
				
			||||||
RUN yarn global add @vue/cli
 | 
					 | 
				
			||||||
ENV PATH="/root/.yarn/bin/:${PATH}"
 | 
					 | 
				
			||||||
WORKDIR /src/web
 | 
					 | 
				
			||||||
ADD web/package.json web/yarn.lock /src/web/
 | 
					 | 
				
			||||||
RUN yarn
 | 
					 | 
				
			||||||
WORKDIR /src
 | 
					 | 
				
			||||||
COPY --from=godeps /root/go/bin/task /root/go/bin/sqlc /root/go/bin/golangci-lint /usr/local/bin/
 | 
					 | 
				
			||||||
CMD /build.sh
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								docker/Dockerfile.dev
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docker/Dockerfile.dev
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					FROM alpine as godeps
 | 
				
			||||||
 | 
					RUN apk --no-cache add go
 | 
				
			||||||
 | 
					RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
 | 
				
			||||||
 | 
					RUN go install github.com/go-task/task/v3/cmd/task@latest
 | 
				
			||||||
 | 
					RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM alpine
 | 
				
			||||||
 | 
					RUN apk --no-cache add go nodejs yarn bash curl git git-perl tmux
 | 
				
			||||||
 | 
					ADD docker/dev.sh /
 | 
				
			||||||
 | 
					RUN yarn global add @vue/cli
 | 
				
			||||||
 | 
					ENV PATH="/root/.yarn/bin/:${PATH}"
 | 
				
			||||||
 | 
					WORKDIR /src/web
 | 
				
			||||||
 | 
					ADD web/package.json web/yarn.lock /src/web/
 | 
				
			||||||
 | 
					RUN yarn
 | 
				
			||||||
 | 
					WORKDIR /src
 | 
				
			||||||
 | 
					COPY --from=godeps /root/go/bin/task /root/go/bin/sqlc /root/go/bin/golangci-lint /usr/local/bin/
 | 
				
			||||||
 | 
					CMD /dev.sh
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -11,7 +11,7 @@ require (
 | 
				
			|||||||
	golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
 | 
						golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require github.com/DATA-DOG/go-txdb v0.1.5 // indirect
 | 
					require github.com/DATA-DOG/go-txdb v0.1.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
						github.com/gin-contrib/sse v0.1.0 // indirect
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.sum
									
									
									
									
									
								
							@@ -74,6 +74,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
 | 
				
			|||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
 | 
					github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
 | 
				
			||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 | 
					github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
					github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
				
			||||||
 | 
					github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
					github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
				
			||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
					github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
				
			||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 | 
					github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,10 +12,11 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// TokenVerifier verifies Tokens.
 | 
					// TokenVerifier verifies Tokens.
 | 
				
			||||||
type TokenVerifier struct {
 | 
					type TokenVerifier struct {
 | 
				
			||||||
 | 
						Expiration time.Duration
 | 
				
			||||||
	secret     string
 | 
						secret     string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ErrEmptySecret = fmt.Errorf("secret is required")
 | 
					const DefaultExpiration = time.Hour * time.Duration(72)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewTokenVerifier(secret string) (*TokenVerifier, error) {
 | 
					func NewTokenVerifier(secret string) (*TokenVerifier, error) {
 | 
				
			||||||
	if secret == "" {
 | 
						if secret == "" {
 | 
				
			||||||
@@ -23,20 +24,16 @@ func NewTokenVerifier(secret string) (*TokenVerifier, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &TokenVerifier{
 | 
						return &TokenVerifier{
 | 
				
			||||||
 | 
							Expiration: DefaultExpiration,
 | 
				
			||||||
		secret:     secret,
 | 
							secret:     secret,
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Token contains everything to authenticate a user.
 | 
					var (
 | 
				
			||||||
type Token struct {
 | 
						ErrUnexpectedSigningMethod = fmt.Errorf("unexpected signing method")
 | 
				
			||||||
	username string
 | 
						ErrInvalidToken            = fmt.Errorf("token is invalid")
 | 
				
			||||||
	name     string
 | 
						ErrTokenExpired            = fmt.Errorf("token has expired")
 | 
				
			||||||
	expiry   float64
 | 
						ErrEmptySecret             = fmt.Errorf("secret is required")
 | 
				
			||||||
	id       uuid.UUID
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	expiration = 72
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateToken creates a new token from username and name.
 | 
					// CreateToken creates a new token from username and name.
 | 
				
			||||||
@@ -48,7 +45,7 @@ func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) {
 | 
				
			|||||||
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 | 
						token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 | 
				
			||||||
		"usr":  user.Email,
 | 
							"usr":  user.Email,
 | 
				
			||||||
		"name": user.Name,
 | 
							"name": user.Name,
 | 
				
			||||||
		"exp":  time.Now().Add(time.Hour * expiration).Unix(),
 | 
							"exp":  time.Now().Add(tv.Expiration).Unix(),
 | 
				
			||||||
		"id":   user.ID,
 | 
							"id":   user.ID,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,13 +58,7 @@ func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) {
 | 
				
			|||||||
	return t, nil
 | 
						return t, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					// VerifyToken verifies a given string-token.
 | 
				
			||||||
	ErrUnexpectedSigningMethod = fmt.Errorf("unexpected signing method")
 | 
					 | 
				
			||||||
	ErrInvalidToken            = fmt.Errorf("token is invalid")
 | 
					 | 
				
			||||||
	ErrTokenExpired            = fmt.Errorf("token has expired")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// VerifyToken verifys a given string-token.
 | 
					 | 
				
			||||||
func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error) { //nolint:ireturn
 | 
					func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error) { //nolint:ireturn
 | 
				
			||||||
	if tv.secret == "" {
 | 
						if tv.secret == "" {
 | 
				
			||||||
		return nil, ErrEmptySecret
 | 
							return nil, ErrEmptySecret
 | 
				
			||||||
@@ -96,36 +87,3 @@ func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return tkn, nil
 | 
						return tkn, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func verifyToken(token *jwt.Token) (jwt.MapClaims, error) {
 | 
					 | 
				
			||||||
	if !token.Valid {
 | 
					 | 
				
			||||||
		return nil, ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	claims, ok := token.Claims.(jwt.MapClaims)
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		return nil, ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
 | 
					 | 
				
			||||||
		return nil, ErrTokenExpired
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return claims, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (t *Token) GetName() string {
 | 
					 | 
				
			||||||
	return t.name
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (t *Token) GetUsername() string {
 | 
					 | 
				
			||||||
	return t.username
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (t *Token) GetExpiry() float64 {
 | 
					 | 
				
			||||||
	return t.expiry
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (t *Token) GetID() uuid.UUID {
 | 
					 | 
				
			||||||
	return t.id
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										49
									
								
								jwt/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								jwt/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					package jwt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/dgrijalva/jwt-go"
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Token contains everything to authenticate a user.
 | 
				
			||||||
 | 
					type Token struct {
 | 
				
			||||||
 | 
						username string
 | 
				
			||||||
 | 
						name     string
 | 
				
			||||||
 | 
						expiry   float64
 | 
				
			||||||
 | 
						id       uuid.UUID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func verifyToken(token *jwt.Token) (jwt.MapClaims, error) {
 | 
				
			||||||
 | 
						if !token.Valid {
 | 
				
			||||||
 | 
							return nil, ErrInvalidToken
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						claims, ok := token.Claims.(jwt.MapClaims)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, ErrInvalidToken
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
 | 
				
			||||||
 | 
							return nil, ErrTokenExpired
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return claims, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Token) GetName() string {
 | 
				
			||||||
 | 
						return t.name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Token) GetUsername() string {
 | 
				
			||||||
 | 
						return t.username
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Token) GetExpiry() float64 {
 | 
				
			||||||
 | 
						return t.expiry
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Token) GetID() uuid.UUID {
 | 
				
			||||||
 | 
						return t.id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,7 +15,7 @@ const createAccount = `-- name: CreateAccount :one
 | 
				
			|||||||
INSERT INTO accounts
 | 
					INSERT INTO accounts
 | 
				
			||||||
(name, budget_id)
 | 
					(name, budget_id)
 | 
				
			||||||
VALUES ($1, $2)
 | 
					VALUES ($1, $2)
 | 
				
			||||||
RETURNING id, budget_id, name, on_budget
 | 
					RETURNING id, budget_id, name, on_budget, is_open
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CreateAccountParams struct {
 | 
					type CreateAccountParams struct {
 | 
				
			||||||
@@ -31,12 +31,13 @@ func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (A
 | 
				
			|||||||
		&i.BudgetID,
 | 
							&i.BudgetID,
 | 
				
			||||||
		&i.Name,
 | 
							&i.Name,
 | 
				
			||||||
		&i.OnBudget,
 | 
							&i.OnBudget,
 | 
				
			||||||
 | 
							&i.IsOpen,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	return i, err
 | 
						return i, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAccount = `-- name: GetAccount :one
 | 
					const getAccount = `-- name: GetAccount :one
 | 
				
			||||||
SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget FROM accounts
 | 
					SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget, accounts.is_open FROM accounts
 | 
				
			||||||
WHERE accounts.id = $1
 | 
					WHERE accounts.id = $1
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,13 +49,15 @@ func (q *Queries) GetAccount(ctx context.Context, id uuid.UUID) (Account, error)
 | 
				
			|||||||
		&i.BudgetID,
 | 
							&i.BudgetID,
 | 
				
			||||||
		&i.Name,
 | 
							&i.Name,
 | 
				
			||||||
		&i.OnBudget,
 | 
							&i.OnBudget,
 | 
				
			||||||
 | 
							&i.IsOpen,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	return i, err
 | 
						return i, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAccounts = `-- name: GetAccounts :many
 | 
					const getAccounts = `-- name: GetAccounts :many
 | 
				
			||||||
SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget FROM accounts
 | 
					SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget, accounts.is_open FROM accounts
 | 
				
			||||||
WHERE accounts.budget_id = $1
 | 
					WHERE accounts.budget_id = $1
 | 
				
			||||||
 | 
					AND accounts.is_open = TRUE
 | 
				
			||||||
ORDER BY accounts.name
 | 
					ORDER BY accounts.name
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,6 +75,7 @@ func (q *Queries) GetAccounts(ctx context.Context, budgetID uuid.UUID) ([]Accoun
 | 
				
			|||||||
			&i.BudgetID,
 | 
								&i.BudgetID,
 | 
				
			||||||
			&i.Name,
 | 
								&i.Name,
 | 
				
			||||||
			&i.OnBudget,
 | 
								&i.OnBudget,
 | 
				
			||||||
 | 
								&i.IsOpen,
 | 
				
			||||||
		); err != nil {
 | 
							); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -87,13 +91,14 @@ func (q *Queries) GetAccounts(ctx context.Context, budgetID uuid.UUID) ([]Accoun
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAccountsWithBalance = `-- name: GetAccountsWithBalance :many
 | 
					const getAccountsWithBalance = `-- name: GetAccountsWithBalance :many
 | 
				
			||||||
SELECT accounts.id, accounts.name, accounts.on_budget, 
 | 
					SELECT accounts.id, accounts.name, accounts.on_budget, accounts.is_open,
 | 
				
			||||||
        (SELECT MAX(transactions.date) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.status = 'Reconciled')::date as last_reconciled,
 | 
					        (SELECT MAX(transactions.date) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.status = 'Reconciled')::date as last_reconciled,
 | 
				
			||||||
        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW())::decimal(12,2) as working_balance,
 | 
					        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW())::decimal(12,2) as working_balance,
 | 
				
			||||||
        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status IN ('Cleared', 'Reconciled'))::decimal(12,2) as cleared_balance,
 | 
					        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status IN ('Cleared', 'Reconciled'))::decimal(12,2) as cleared_balance,
 | 
				
			||||||
        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status = 'Reconciled')::decimal(12,2) as reconciled_balance
 | 
					        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status = 'Reconciled')::decimal(12,2) as reconciled_balance
 | 
				
			||||||
FROM accounts
 | 
					FROM accounts
 | 
				
			||||||
WHERE accounts.budget_id = $1
 | 
					WHERE accounts.budget_id = $1
 | 
				
			||||||
 | 
					AND accounts.is_open = TRUE
 | 
				
			||||||
ORDER BY accounts.name
 | 
					ORDER BY accounts.name
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -101,6 +106,7 @@ type GetAccountsWithBalanceRow struct {
 | 
				
			|||||||
	ID                uuid.UUID
 | 
						ID                uuid.UUID
 | 
				
			||||||
	Name              string
 | 
						Name              string
 | 
				
			||||||
	OnBudget          bool
 | 
						OnBudget          bool
 | 
				
			||||||
 | 
						IsOpen            bool
 | 
				
			||||||
	LastReconciled    time.Time
 | 
						LastReconciled    time.Time
 | 
				
			||||||
	WorkingBalance    numeric.Numeric
 | 
						WorkingBalance    numeric.Numeric
 | 
				
			||||||
	ClearedBalance    numeric.Numeric
 | 
						ClearedBalance    numeric.Numeric
 | 
				
			||||||
@@ -120,6 +126,7 @@ func (q *Queries) GetAccountsWithBalance(ctx context.Context, budgetID uuid.UUID
 | 
				
			|||||||
			&i.ID,
 | 
								&i.ID,
 | 
				
			||||||
			&i.Name,
 | 
								&i.Name,
 | 
				
			||||||
			&i.OnBudget,
 | 
								&i.OnBudget,
 | 
				
			||||||
 | 
								&i.IsOpen,
 | 
				
			||||||
			&i.LastReconciled,
 | 
								&i.LastReconciled,
 | 
				
			||||||
			&i.WorkingBalance,
 | 
								&i.WorkingBalance,
 | 
				
			||||||
			&i.ClearedBalance,
 | 
								&i.ClearedBalance,
 | 
				
			||||||
@@ -141,6 +148,7 @@ func (q *Queries) GetAccountsWithBalance(ctx context.Context, budgetID uuid.UUID
 | 
				
			|||||||
const searchAccounts = `-- name: SearchAccounts :many
 | 
					const searchAccounts = `-- name: SearchAccounts :many
 | 
				
			||||||
SELECT accounts.id, accounts.budget_id, accounts.name, 'account' as type FROM accounts
 | 
					SELECT accounts.id, accounts.budget_id, accounts.name, 'account' as type FROM accounts
 | 
				
			||||||
WHERE accounts.budget_id = $1
 | 
					WHERE accounts.budget_id = $1
 | 
				
			||||||
 | 
					AND accounts.is_open = TRUE
 | 
				
			||||||
AND accounts.name LIKE $2
 | 
					AND accounts.name LIKE $2
 | 
				
			||||||
ORDER BY accounts.name
 | 
					ORDER BY accounts.name
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
@@ -188,25 +196,33 @@ func (q *Queries) SearchAccounts(ctx context.Context, arg SearchAccountsParams)
 | 
				
			|||||||
const updateAccount = `-- name: UpdateAccount :one
 | 
					const updateAccount = `-- name: UpdateAccount :one
 | 
				
			||||||
UPDATE accounts
 | 
					UPDATE accounts
 | 
				
			||||||
SET name = $1,
 | 
					SET name = $1,
 | 
				
			||||||
    on_budget = $2
 | 
					    on_budget = $2,
 | 
				
			||||||
WHERE accounts.id = $3
 | 
					    is_open = $3
 | 
				
			||||||
RETURNING id, budget_id, name, on_budget
 | 
					WHERE accounts.id = $4
 | 
				
			||||||
 | 
					RETURNING id, budget_id, name, on_budget, is_open
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UpdateAccountParams struct {
 | 
					type UpdateAccountParams struct {
 | 
				
			||||||
	Name     string
 | 
						Name     string
 | 
				
			||||||
	OnBudget bool
 | 
						OnBudget bool
 | 
				
			||||||
 | 
						IsOpen   bool
 | 
				
			||||||
	ID       uuid.UUID
 | 
						ID       uuid.UUID
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error) {
 | 
					func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error) {
 | 
				
			||||||
	row := q.db.QueryRowContext(ctx, updateAccount, arg.Name, arg.OnBudget, arg.ID)
 | 
						row := q.db.QueryRowContext(ctx, updateAccount,
 | 
				
			||||||
 | 
							arg.Name,
 | 
				
			||||||
 | 
							arg.OnBudget,
 | 
				
			||||||
 | 
							arg.IsOpen,
 | 
				
			||||||
 | 
							arg.ID,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	var i Account
 | 
						var i Account
 | 
				
			||||||
	err := row.Scan(
 | 
						err := row.Scan(
 | 
				
			||||||
		&i.ID,
 | 
							&i.ID,
 | 
				
			||||||
		&i.BudgetID,
 | 
							&i.BudgetID,
 | 
				
			||||||
		&i.Name,
 | 
							&i.Name,
 | 
				
			||||||
		&i.OnBudget,
 | 
							&i.OnBudget,
 | 
				
			||||||
 | 
							&i.IsOpen,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	return i, err
 | 
						return i, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,7 @@ type Account struct {
 | 
				
			|||||||
	BudgetID uuid.UUID
 | 
						BudgetID uuid.UUID
 | 
				
			||||||
	Name     string
 | 
						Name     string
 | 
				
			||||||
	OnBudget bool
 | 
						OnBudget bool
 | 
				
			||||||
 | 
						IsOpen   bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Assignment struct {
 | 
					type Assignment struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,27 +11,31 @@ WHERE accounts.id = $1;
 | 
				
			|||||||
-- name: GetAccounts :many
 | 
					-- name: GetAccounts :many
 | 
				
			||||||
SELECT accounts.* FROM accounts
 | 
					SELECT accounts.* FROM accounts
 | 
				
			||||||
WHERE accounts.budget_id = $1
 | 
					WHERE accounts.budget_id = $1
 | 
				
			||||||
 | 
					AND accounts.is_open = TRUE
 | 
				
			||||||
ORDER BY accounts.name;
 | 
					ORDER BY accounts.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- name: GetAccountsWithBalance :many
 | 
					-- name: GetAccountsWithBalance :many
 | 
				
			||||||
SELECT accounts.id, accounts.name, accounts.on_budget, 
 | 
					SELECT accounts.id, accounts.name, accounts.on_budget, accounts.is_open,
 | 
				
			||||||
        (SELECT MAX(transactions.date) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.status = 'Reconciled')::date as last_reconciled,
 | 
					        (SELECT MAX(transactions.date) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.status = 'Reconciled')::date as last_reconciled,
 | 
				
			||||||
        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW())::decimal(12,2) as working_balance,
 | 
					        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW())::decimal(12,2) as working_balance,
 | 
				
			||||||
        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status IN ('Cleared', 'Reconciled'))::decimal(12,2) as cleared_balance,
 | 
					        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status IN ('Cleared', 'Reconciled'))::decimal(12,2) as cleared_balance,
 | 
				
			||||||
        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status = 'Reconciled')::decimal(12,2) as reconciled_balance
 | 
					        (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status = 'Reconciled')::decimal(12,2) as reconciled_balance
 | 
				
			||||||
FROM accounts
 | 
					FROM accounts
 | 
				
			||||||
WHERE accounts.budget_id = $1
 | 
					WHERE accounts.budget_id = $1
 | 
				
			||||||
 | 
					AND accounts.is_open = TRUE
 | 
				
			||||||
ORDER BY accounts.name;
 | 
					ORDER BY accounts.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- name: SearchAccounts :many
 | 
					-- name: SearchAccounts :many
 | 
				
			||||||
SELECT accounts.id, accounts.budget_id, accounts.name, 'account' as type FROM accounts
 | 
					SELECT accounts.id, accounts.budget_id, accounts.name, 'account' as type FROM accounts
 | 
				
			||||||
WHERE accounts.budget_id = @budget_id
 | 
					WHERE accounts.budget_id = @budget_id
 | 
				
			||||||
 | 
					AND accounts.is_open = TRUE
 | 
				
			||||||
AND accounts.name LIKE @search
 | 
					AND accounts.name LIKE @search
 | 
				
			||||||
ORDER BY accounts.name;
 | 
					ORDER BY accounts.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- name: UpdateAccount :one
 | 
					-- name: UpdateAccount :one
 | 
				
			||||||
UPDATE accounts
 | 
					UPDATE accounts
 | 
				
			||||||
SET name = $1,
 | 
					SET name = $1,
 | 
				
			||||||
    on_budget = $2
 | 
					    on_budget = $2,
 | 
				
			||||||
WHERE accounts.id = $3
 | 
					    is_open = $3
 | 
				
			||||||
 | 
					WHERE accounts.id = $4
 | 
				
			||||||
RETURNING *;
 | 
					RETURNING *;
 | 
				
			||||||
							
								
								
									
										5
									
								
								postgres/schema/0016_closed-accounts.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								postgres/schema/0016_closed-accounts.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					-- +goose Up
 | 
				
			||||||
 | 
					ALTER TABLE accounts ADD COLUMN is_open BOOLEAN NOT NULL DEFAULT TRUE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- +goose Down
 | 
				
			||||||
 | 
					ALTER TABLE accounts DROP COLUMN is_open;
 | 
				
			||||||
@@ -39,6 +39,7 @@ type TransactionsResponse struct {
 | 
				
			|||||||
type EditAccountRequest struct {
 | 
					type EditAccountRequest struct {
 | 
				
			||||||
	Name     string `json:"name"`
 | 
						Name     string `json:"name"`
 | 
				
			||||||
	OnBudget bool   `json:"onBudget"`
 | 
						OnBudget bool   `json:"onBudget"`
 | 
				
			||||||
 | 
						IsOpen   bool   `json:"isOpen"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Handler) editAccount(c *gin.Context) {
 | 
					func (h *Handler) editAccount(c *gin.Context) {
 | 
				
			||||||
@@ -59,6 +60,7 @@ func (h *Handler) editAccount(c *gin.Context) {
 | 
				
			|||||||
	updateParams := postgres.UpdateAccountParams{
 | 
						updateParams := postgres.UpdateAccountParams{
 | 
				
			||||||
		Name:     request.Name,
 | 
							Name:     request.Name,
 | 
				
			||||||
		OnBudget: request.OnBudget,
 | 
							OnBudget: request.OnBudget,
 | 
				
			||||||
 | 
							IsOpen:   request.IsOpen,
 | 
				
			||||||
		ID:       accountUUID,
 | 
							ID:       accountUUID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	account, err := h.Service.UpdateAccount(c.Request.Context(), updateParams)
 | 
						account, err := h.Service.UpdateAccount(c.Request.Context(), updateParams)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
    <title>Vite App</title>
 | 
					    <title>Vite App</title>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body class="bg-slate-200 text-slate-800 dark:bg-slate-800 dark:text-slate-200">
 | 
					  <body class="bg-slate-200 text-slate-800 dark:bg-slate-800 dark:text-slate-200 box-border w-full">
 | 
				
			||||||
    <div id="app"></div>
 | 
					    <div id="app"></div>
 | 
				
			||||||
    <script type="module" src="/src/main.ts"></script>
 | 
					    <script type="module" src="/src/main.ts"></script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,9 +27,20 @@ export default defineComponent({
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="box-border w-full">
 | 
					    <div class="flex flex-col md:flex-row flex-1">
 | 
				
			||||||
        <div class="flex bg-gray-400 dark:bg-gray-600 p-4 m-2 rounded-lg">
 | 
					        <div
 | 
				
			||||||
            <span class="flex-1 font-bold text-5xl -my-3 hidden md:inline" @click="toggleMenuSize">≡</span>
 | 
					            :class="[Menu.Expand ? 'md:w-72' : 'md:w-36', Menu.Show ? '' : 'hidden']"
 | 
				
			||||||
 | 
					            class="md:block flex-shrink-0 w-full bg-gray-500 border-r-4 border-black"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <router-view name="sidebar"></router-view>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="flex-1">
 | 
				
			||||||
 | 
					            <div class="flex bg-gray-400 dark:bg-gray-600 p-4 fixed md:static top-0 left-0 w-full h-14">
 | 
				
			||||||
 | 
					                <span
 | 
				
			||||||
 | 
					                    class="flex-1 font-bold text-5xl -my-3 hidden md:inline"
 | 
				
			||||||
 | 
					                    @click="toggleMenuSize"
 | 
				
			||||||
 | 
					                >≡</span>
 | 
				
			||||||
                <span class="flex-1 font-bold text-5xl -my-3 md:hidden" @click="toggleMenu">≡</span>
 | 
					                <span class="flex-1 font-bold text-5xl -my-3 md:hidden" @click="toggleMenu">≡</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <span class="flex-1">{{ CurrentBudgetName }}</span>
 | 
					                <span class="flex-1">{{ CurrentBudgetName }}</span>
 | 
				
			||||||
@@ -41,25 +52,9 @@ export default defineComponent({
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="flex flex-col md:flex-row flex-1">
 | 
					            <div class="p-3 pl-6">
 | 
				
			||||||
            <div
 | 
					 | 
				
			||||||
                :class="[Menu.Expand ? 'md:w-72' : 'md:w-36', Menu.Show ? '' : 'hidden']"
 | 
					 | 
				
			||||||
                class="md:block flex-shrink-0 w-full"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                <router-view name="sidebar"></router-view>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <div class="flex-1 p-6">
 | 
					 | 
				
			||||||
                <router-view></router-view>
 | 
					                <router-view></router-view>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
#app {
 | 
					 | 
				
			||||||
    font-family: Avenir, Helvetica, Arial, sans-serif;
 | 
					 | 
				
			||||||
    -webkit-font-smoothing: antialiased;
 | 
					 | 
				
			||||||
    -moz-osx-font-smoothing: grayscale;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								web/src/components/Checkbox.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								web/src/components/Checkbox.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					const props = defineProps(["modelValue"]);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <input 
 | 
				
			||||||
 | 
					        type="checkbox"
 | 
				
			||||||
 | 
					        :checked="modelValue"
 | 
				
			||||||
 | 
					        @change="$emit('update:modelValue', ($event.target as HTMLInputElement)?.checked)"
 | 
				
			||||||
 | 
					        class="dark:bg-slate-900">
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
@@ -4,7 +4,7 @@ const props = defineProps(["modelValue"]);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <input 
 | 
					    <input 
 | 
				
			||||||
        v-model="modelValue"
 | 
					        :value="modelValue"
 | 
				
			||||||
        @input="$emit('update:modelValue', ($event.target as HTMLInputElement)?.value)"
 | 
					        @input="$emit('update:modelValue', ($event.target as HTMLInputElement)?.value)"
 | 
				
			||||||
        class="dark:bg-slate-900">
 | 
					        class="dark:bg-slate-900">
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -7,7 +7,7 @@ const props = defineProps<{
 | 
				
			|||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits<{
 | 
					const emit = defineEmits<{
 | 
				
			||||||
    (e: 'submit'): void,
 | 
					    (e: 'submit', event : {cancel:boolean}): boolean,
 | 
				
			||||||
    (e: 'open'): void,
 | 
					    (e: 'open'): void,
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,8 +20,12 @@ function openDialog() {
 | 
				
			|||||||
    visible.value = true;
 | 
					    visible.value = true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
function submitDialog() {
 | 
					function submitDialog() {
 | 
				
			||||||
 | 
					    const e = {cancel: false};
 | 
				
			||||||
 | 
					    emit("submit", e);
 | 
				
			||||||
 | 
					    if(e.cancel)
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    visible.value = false;
 | 
					    visible.value = false;
 | 
				
			||||||
    emit("submit");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,9 +42,9 @@ function submitDialog() {
 | 
				
			|||||||
        v-if="visible"
 | 
					        v-if="visible"
 | 
				
			||||||
        class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full"
 | 
					        class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
 | 
					        <div class="relative top-20 mx-auto p-5 w-96 shadow-lg rounded-md bg-white dark:bg-black">
 | 
				
			||||||
            <div class="mt-3 text-center">
 | 
					            <div class="mt-3 text-center">
 | 
				
			||||||
                <h3 class="mt-3 text-lg leading-6 font-medium text-gray-900">{{ buttonText }}</h3>
 | 
					                <h3 class="mt-3 text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">{{ buttonText }}</h3>
 | 
				
			||||||
                <slot></slot>
 | 
					                <slot></slot>
 | 
				
			||||||
                <div class="grid grid-cols-2 gap-6">
 | 
					                <div class="grid grid-cols-2 gap-6">
 | 
				
			||||||
                    <button
 | 
					                    <button
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import TransactionEditRow from "./TransactionEditRow.vue";
 | 
				
			|||||||
import { formatDate } from "../date";
 | 
					import { formatDate } from "../date";
 | 
				
			||||||
import { useAccountStore } from "../stores/budget-account";
 | 
					import { useAccountStore } from "../stores/budget-account";
 | 
				
			||||||
import Input from "./Input.vue";
 | 
					import Input from "./Input.vue";
 | 
				
			||||||
 | 
					import Checkbox from "./Checkbox.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
    transactionid: string,
 | 
					    transactionid: string,
 | 
				
			||||||
@@ -70,7 +71,7 @@ function getStatusSymbol() {
 | 
				
			|||||||
            {{ TX.GroupID ? "☀" : "" }}
 | 
					            {{ TX.GroupID ? "☀" : "" }}
 | 
				
			||||||
            {{ getStatusSymbol() }}
 | 
					            {{ getStatusSymbol() }}
 | 
				
			||||||
            <a @click="edit = true;">✎</a>
 | 
					            <a @click="edit = true;">✎</a>
 | 
				
			||||||
            <Input v-if="Reconciling && TX.Status != 'Reconciled'" type="checkbox" v-model="TX.Reconciled" />
 | 
					            <Checkbox v-if="Reconciling && TX.Status != 'Reconciled'" v-model="TX.Reconciled" />
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    <TransactionEditRow v-if="edit" :transactionid="TX.ID" @save="edit = false" />
 | 
					    <TransactionEditRow v-if="edit" :transactionid="TX.ID" @save="edit = false" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,29 +3,50 @@ import { computed, ref } from 'vue';
 | 
				
			|||||||
import Modal from '../components/Modal.vue';
 | 
					import Modal from '../components/Modal.vue';
 | 
				
			||||||
import { useAccountStore } from '../stores/budget-account';
 | 
					import { useAccountStore } from '../stores/budget-account';
 | 
				
			||||||
import Input from '../components/Input.vue';
 | 
					import Input from '../components/Input.vue';
 | 
				
			||||||
 | 
					import Checkbox from '../components/Checkbox.vue';
 | 
				
			||||||
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
 | 
					import { useBudgetsStore } from '../stores/budget';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const router = useRouter();
 | 
				
			||||||
const accountStore = useAccountStore();
 | 
					const accountStore = useAccountStore();
 | 
				
			||||||
const CurrentAccount = computed(() => accountStore.CurrentAccount);
 | 
					const CurrentAccount = computed(() => accountStore.CurrentAccount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const accountName = ref("");
 | 
					const accountName = ref("");
 | 
				
			||||||
const accountOnBudget = ref(true);
 | 
					const accountOnBudget = ref(true);
 | 
				
			||||||
 | 
					const accountOpen = ref(true);
 | 
				
			||||||
 | 
					const error = ref("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function editAccount(e : any) {
 | 
					function editAccount(e : {cancel:boolean}) : boolean {
 | 
				
			||||||
    accountStore.EditAccount(CurrentAccount.value?.ID ?? "", accountName.value, accountOnBudget.value);
 | 
					    if(CurrentAccount.value?.ClearedBalance != 0 && !accountOpen.value){
 | 
				
			||||||
 | 
					        e.cancel = true;
 | 
				
			||||||
 | 
					        error.value = "Cannot close account with balance";
 | 
				
			||||||
 | 
					        return false; 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    error.value = "";
 | 
				
			||||||
 | 
					    accountStore.EditAccount(CurrentAccount.value?.ID ?? "", accountName.value, accountOnBudget.value, accountOpen.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // account closed, move to Budget
 | 
				
			||||||
 | 
					    if(!accountOpen.value){
 | 
				
			||||||
 | 
					        const currentBudgetID = useBudgetsStore().CurrentBudgetID;
 | 
				
			||||||
 | 
					        router.replace('/budget/'+currentBudgetID+'/budgeting');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function openEditAccount(e : any) {
 | 
					function openEditAccount(e : any) {
 | 
				
			||||||
    accountName.value = CurrentAccount.value?.Name ?? "";
 | 
					    accountName.value = CurrentAccount.value?.Name ?? "";
 | 
				
			||||||
    accountOnBudget.value = CurrentAccount.value?.OnBudget ?? true;
 | 
					    accountOnBudget.value = CurrentAccount.value?.OnBudget ?? true;
 | 
				
			||||||
 | 
					    accountOpen.value = CurrentAccount.value?.IsOpen ?? true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <Modal button-text="Edit Account" @open="openEditAccount" @submit="editAccount">
 | 
					    <Modal button-text="Edit Account" @open="openEditAccount" @submit="editAccount">
 | 
				
			||||||
        <template v-slot:placeholder>✎</template>
 | 
					        <template v-slot:placeholder><span class="ml-2">✎</span></template>
 | 
				
			||||||
        <div class="mt-2 px-7 py-3">
 | 
					        <div class="mt-2 px-7 py-3">
 | 
				
			||||||
            <Input
 | 
					            <Input
 | 
				
			||||||
                class="border-2"
 | 
					                class="border-2 dark:border-gray-700"
 | 
				
			||||||
                type="text"
 | 
					                type="text"
 | 
				
			||||||
                v-model="accountName"
 | 
					                v-model="accountName"
 | 
				
			||||||
                placeholder="Account name"
 | 
					                placeholder="Account name"
 | 
				
			||||||
@@ -33,13 +54,23 @@ function openEditAccount(e : any) {
 | 
				
			|||||||
            />
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="mt-2 px-7 py-3">
 | 
					        <div class="mt-2 px-7 py-3">
 | 
				
			||||||
            <Input
 | 
					            <Checkbox
 | 
				
			||||||
                class="border-2"
 | 
					                class="border-2"
 | 
				
			||||||
                type="checkbox"
 | 
					 | 
				
			||||||
                v-model="accountOnBudget"
 | 
					                v-model="accountOnBudget"
 | 
				
			||||||
                required
 | 
					                required
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <label>On Budget</label>
 | 
					            <label>On Budget</label>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mt-2 px-7 py-3">
 | 
				
			||||||
 | 
					            <Checkbox
 | 
				
			||||||
 | 
					                class="border-2"
 | 
				
			||||||
 | 
					                v-model="accountOpen"
 | 
				
			||||||
 | 
					                required
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <label>Open</label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div v-if="error != ''" class="dark:text-red-300 text-red-700">
 | 
				
			||||||
 | 
					            {{ error }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
    </Modal>
 | 
					    </Modal>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -9,3 +9,9 @@ h1 {
 | 
				
			|||||||
a {
 | 
					a {
 | 
				
			||||||
  text-decoration: underline;
 | 
					  text-decoration: underline;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#app {
 | 
				
			||||||
 | 
					    font-family: Avenir, Helvetica, Arial, sans-serif;
 | 
				
			||||||
 | 
					    -webkit-font-smoothing: antialiased;
 | 
				
			||||||
 | 
					    -moz-osx-font-smoothing: grayscale;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -43,48 +43,57 @@ function createReconcilationTransaction() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div class="grid grid-cols-[1fr_auto]">
 | 
					    <div class="grid grid-cols-[1fr_auto]">
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
            <h1 class="inline">
 | 
					            <h1 class="inline">
 | 
				
			||||||
                {{ accounts.CurrentAccount?.Name }}
 | 
					                {{ accounts.CurrentAccount?.Name }}
 | 
				
			||||||
            <EditAccount />
 | 
					 | 
				
			||||||
            </h1>
 | 
					            </h1>
 | 
				
			||||||
 | 
					            <EditAccount />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="text-right flex flex-wrap flex-col md:flex-row justify-end gap-2 max-w-sm">
 | 
					        <div class="text-right flex flex-wrap flex-col md:flex-row justify-end gap-2 max-w-sm">
 | 
				
			||||||
            <span class="border-2 rounded-lg p-1 whitespace-nowrap flex-1">
 | 
					            <span class="rounded-lg p-1 whitespace-nowrap flex-1">
 | 
				
			||||||
                Working:
 | 
					                Working:
 | 
				
			||||||
                <Currency :value="accounts.CurrentAccount?.WorkingBalance" />
 | 
					                <Currency :value="accounts.CurrentAccount?.WorkingBalance" />
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <span class="border-2 rounded-lg p-1 whitespace-nowrap flex-1">
 | 
					            <span class="rounded-lg p-1 whitespace-nowrap flex-1">
 | 
				
			||||||
                Cleared:
 | 
					                Cleared:
 | 
				
			||||||
                <Currency :value="accounts.CurrentAccount?.ClearedBalance" />
 | 
					                <Currency :value="accounts.CurrentAccount?.ClearedBalance" />
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <span
 | 
					            <span
 | 
				
			||||||
                class="border-2 border-blue-500 rounded-lg bg-blue-500 p-1 whitespace-nowrap flex-1"
 | 
					                class="rounded-lg bg-blue-500 p-1 whitespace-nowrap flex-1"
 | 
				
			||||||
                v-if="!transactions.Reconciling"
 | 
					                v-if="!transactions.Reconciling"
 | 
				
			||||||
                @click="transactions.Reconciling = true"
 | 
					                @click="transactions.Reconciling = true"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                Reconciled:
 | 
					                Reconciled:
 | 
				
			||||||
                <Currency :value="accounts.CurrentAccount?.ReconciledBalance" />
 | 
					                <Currency :value="accounts.CurrentAccount?.ReconciledBalance" />
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <span v-if="transactions.Reconciling" class="border-2 block bg-gray-200 dark:bg-gray-800 rounded-lg p-2">
 | 
					            <span v-if="transactions.Reconciling" class="contents">
 | 
				
			||||||
            Is
 | 
					                <Button @click="submitReconcilation"
 | 
				
			||||||
            <Currency :value="transactions.ReconcilingBalance" />your current balance?
 | 
					                    class="bg-blue-500 p-1 whitespace-nowrap flex-1">
 | 
				
			||||||
            <Button class="bg-blue-500 mx-3 py-2" @click="submitReconcilation">Yes!</Button>
 | 
					                    My current balance is 
 | 
				
			||||||
            <br />No, it's:
 | 
					                    <Currency :value="transactions.ReconcilingBalance" />
 | 
				
			||||||
            <Input class="text-right" type="number" v-model="TargetReconcilingBalance" />
 | 
					                </Button>
 | 
				
			||||||
            Difference:
 | 
					
 | 
				
			||||||
            <Currency :value="transactions.ReconcilingBalance - TargetReconcilingBalance" />
 | 
					                <Button @click="createReconcilationTransaction"
 | 
				
			||||||
            <Button
 | 
					                    class="bg-orange-500 p-1 whitespace-nowrap flex-1">
 | 
				
			||||||
                class="bg-orange-500 mx-3 py-2"
 | 
					                    No, it's:
 | 
				
			||||||
                v-if="Math.abs(transactions.ReconcilingBalance - TargetReconcilingBalance) > 0.01"
 | 
					                    <Input
 | 
				
			||||||
                @click="createReconcilationTransaction"
 | 
					                        class="text-right w-20 bg-transparent dark:bg-transparent border-b-2"
 | 
				
			||||||
            >Create reconciling Transaction</Button>
 | 
					                        type="number"
 | 
				
			||||||
            <Button class="bg-red-500 mx-3 py-2" @click="cancelReconcilation">Cancel</Button>
 | 
					                        v-model="TargetReconcilingBalance"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    (Difference
 | 
				
			||||||
 | 
					                    <Currency
 | 
				
			||||||
 | 
					                        :value="transactions.ReconcilingBalance - TargetReconcilingBalance"
 | 
				
			||||||
 | 
					                    />)
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					                <Button class="bg-red-500 p-1 flex-1" @click="cancelReconcilation">Cancel</Button>
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <table>
 | 
					    <table>
 | 
				
			||||||
        <tr class="font-bold">
 | 
					        <tr class="font-bold">
 | 
				
			||||||
@@ -97,7 +106,11 @@ function createReconcilationTransaction() {
 | 
				
			|||||||
                <Input v-if="transactions.Reconciling" type="checkbox" @input="setReconciled" />
 | 
					                <Input v-if="transactions.Reconciling" type="checkbox" @input="setReconciled" />
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
        <TransactionInputRow class="hidden md:table-row" :budgetid="budgetid" :accountid="accountid" />
 | 
					        <TransactionInputRow
 | 
				
			||||||
 | 
					            class="hidden md:table-row"
 | 
				
			||||||
 | 
					            :budgetid="budgetid"
 | 
				
			||||||
 | 
					            :accountid="accountid"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
        <TransactionRow
 | 
					        <TransactionRow
 | 
				
			||||||
            v-for="(transaction, index) in transactions.TransactionsList"
 | 
					            v-for="(transaction, index) in transactions.TransactionsList"
 | 
				
			||||||
            :key="transaction.ID"
 | 
					            :key="transaction.ID"
 | 
				
			||||||
@@ -110,7 +123,11 @@ function createReconcilationTransaction() {
 | 
				
			|||||||
            <template v-slot:placeholder>
 | 
					            <template v-slot:placeholder>
 | 
				
			||||||
                <Button class="fixed right-4 bottom-4 font-bold text-lg bg-blue-500 py-2">+</Button>
 | 
					                <Button class="fixed right-4 bottom-4 font-bold text-lg bg-blue-500 py-2">+</Button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
            <TransactionInputRow class="grid grid-cols-2" :budgetid="budgetid" :accountid="accountid" />
 | 
					            <TransactionInputRow
 | 
				
			||||||
 | 
					                class="grid grid-cols-2"
 | 
				
			||||||
 | 
					                :budgetid="budgetid"
 | 
				
			||||||
 | 
					                :accountid="accountid"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
        </Modal>
 | 
					        </Modal>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ const OffBudgetAccountsBalance = computed(() => accountStore.OffBudgetAccountsBa
 | 
				
			|||||||
function isRecentlyReconciled(account : Account) {
 | 
					function isRecentlyReconciled(account : Account) {
 | 
				
			||||||
  const now = new Date().getTime();
 | 
					  const now = new Date().getTime();
 | 
				
			||||||
  const recently = 7 * 24 * 60 * 60 * 1000;
 | 
					  const recently = 7 * 24 * 60 * 60 * 1000;
 | 
				
			||||||
  console.log(account.Name, account.LastReconciled, now, recently, new Date(now-recently));
 | 
					 | 
				
			||||||
  return new Date(now - recently).getTime() < account.LastReconciled.getTime();
 | 
					  return new Date(now - recently).getTime() < account.LastReconciled.getTime();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,17 +30,17 @@ function getAccountName(account : Account) {
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="flex flex-col">
 | 
					  <div class="flex flex-col mt-14 md:mt-0">
 | 
				
			||||||
    <span class="m-1 p-1 px-3 text-xl">
 | 
					    <span class="m-2 p-1 px-3 h-10 overflow-hidden" :class="[ExpandMenu ? 'text-2xl' : 'text-md']">
 | 
				
			||||||
      <router-link to="/dashboard">⌂</router-link>
 | 
					      <router-link to="/dashboard" style="font-size:150%">⌂</router-link>
 | 
				
			||||||
      {{CurrentBudgetName}}
 | 
					      {{CurrentBudgetName}}
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
    <span class="bg-orange-200 dark:bg-orange-700 rounded-lg m-1 p-1 px-3 flex flex-col">
 | 
					    <span class="bg-gray-100 dark:bg-gray-700 p-2 px-3 flex flex-col">
 | 
				
			||||||
      <router-link :to="'/budget/'+CurrentBudgetID+'/budgeting'">Budget</router-link><br />
 | 
					      <router-link :to="'/budget/'+CurrentBudgetID+'/budgeting'">Budget</router-link><br />
 | 
				
			||||||
      <!--<router-link :to="'/budget/'+CurrentBudgetID+'/reports'">Reports</router-link>-->
 | 
					      <!--<router-link :to="'/budget/'+CurrentBudgetID+'/reports'">Reports</router-link>-->
 | 
				
			||||||
      <!--<router-link :to="'/budget/'+CurrentBudgetID+'/all-accounts'">All Accounts</router-link>-->
 | 
					      <!--<router-link :to="'/budget/'+CurrentBudgetID+'/all-accounts'">All Accounts</router-link>-->
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
    <li class="bg-orange-200 dark:bg-orange-700 rounded-lg m-1 p-1 px-3">
 | 
					    <li class="bg-slate-200 dark:bg-slate-700 my-2 p-2 px-3">
 | 
				
			||||||
      <div class="flex flex-row justify-between font-bold">
 | 
					      <div class="flex flex-row justify-between font-bold">
 | 
				
			||||||
        <span>On-Budget Accounts</span>
 | 
					        <span>On-Budget Accounts</span>
 | 
				
			||||||
        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="OnBudgetAccountsBalance" />
 | 
					        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="OnBudgetAccountsBalance" />
 | 
				
			||||||
@@ -51,7 +50,7 @@ function getAccountName(account : Account) {
 | 
				
			|||||||
        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="account.ClearedBalance" />
 | 
					        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="account.ClearedBalance" />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <li class="bg-red-200 dark:bg-red-800 rounded-lg m-1 p-1 px-3">
 | 
					    <li class="bg-slate-200 dark:bg-slate-700 my-2 p-2 px-3">
 | 
				
			||||||
      <div class="flex flex-row justify-between font-bold">
 | 
					      <div class="flex flex-row justify-between font-bold">
 | 
				
			||||||
        <span>Off-Budget Accounts</span>
 | 
					        <span>Off-Budget Accounts</span>
 | 
				
			||||||
        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="OffBudgetAccountsBalance" />
 | 
					        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="OffBudgetAccountsBalance" />
 | 
				
			||||||
@@ -61,18 +60,18 @@ function getAccountName(account : Account) {
 | 
				
			|||||||
        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="account.ClearedBalance" />
 | 
					        <Currency :class="ExpandMenu?'md:inline':'md:hidden'" :value="account.ClearedBalance" />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <li class="bg-red-200 dark:bg-red-800 rounded-lg m-1 p-1 px-3">
 | 
					    <!--
 | 
				
			||||||
 | 
					    <li class="bg-slate-100 dark:bg-slate-800 my-2 p-2 px-3">
 | 
				
			||||||
      <div class="flex flex-row justify-between font-bold">
 | 
					      <div class="flex flex-row justify-between font-bold">
 | 
				
			||||||
        <span>Closed Accounts</span>
 | 
					        <span>Closed Accounts</span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      + Add Account
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
    <!--<li>
 | 
					    <!--<li>
 | 
				
			||||||
      <router-link :to="'/budget/'+CurrentBudgetID+'/accounts'">Edit accounts</router-link>
 | 
					      <router-link :to="'/budget/'+CurrentBudgetID+'/accounts'">Edit accounts</router-link>
 | 
				
			||||||
    </li>-->
 | 
					    </li>-->
 | 
				
			||||||
    <li class="bg-red-200 dark:bg-red-800 rounded-lg m-1 p-1 px-3">
 | 
					    <li class="bg-red-100 dark:bg-slate-600 my-2 p-2 px-3">
 | 
				
			||||||
      + Add Account
 | 
					 | 
				
			||||||
    </li>
 | 
					 | 
				
			||||||
    <li class="bg-red-200 dark:bg-red-800 rounded-lg m-1 p-1 px-3">
 | 
					 | 
				
			||||||
      <router-link :to="'/budget/'+CurrentBudgetID+'/settings'">Budget-Settings</router-link>
 | 
					      <router-link :to="'/budget/'+CurrentBudgetID+'/settings'">Budget-Settings</router-link>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <!--<li><router-link to="/admin">Admin</router-link></li>-->
 | 
					    <!--<li><router-link to="/admin">Admin</router-link></li>-->
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,6 @@ onMounted(() => {
 | 
				
			|||||||
const expandedGroups = ref<Map<string, boolean>>(new Map<string, boolean>())
 | 
					const expandedGroups = ref<Map<string, boolean>>(new Map<string, boolean>())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleGroup(group: { Name: string, Expand: boolean }) {
 | 
					function toggleGroup(group: { Name: string, Expand: boolean }) {
 | 
				
			||||||
    console.log(expandedGroups.value);
 | 
					 | 
				
			||||||
    expandedGroups.value.set(group.Name, !(expandedGroups.value.get(group.Name) ?? group.Expand))
 | 
					    expandedGroups.value.set(group.Name, !(expandedGroups.value.get(group.Name) ?? group.Expand))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ export interface Account {
 | 
				
			|||||||
    ID: string
 | 
					    ID: string
 | 
				
			||||||
    Name: string
 | 
					    Name: string
 | 
				
			||||||
    OnBudget: boolean
 | 
					    OnBudget: boolean
 | 
				
			||||||
 | 
					    IsOpen: boolean
 | 
				
			||||||
    ClearedBalance: number
 | 
					    ClearedBalance: number
 | 
				
			||||||
    WorkingBalance: number
 | 
					    WorkingBalance: number
 | 
				
			||||||
    ReconciledBalance: number
 | 
					    ReconciledBalance: number
 | 
				
			||||||
@@ -123,10 +124,14 @@ export const useAccountStore = defineStore("budget/account", {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            this.addCategoriesForMonth(year, month, response.Categories);
 | 
					            this.addCategoriesForMonth(year, month, response.Categories);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        async EditAccount(accountid: string, name: string, onBudget: boolean) {
 | 
					        async EditAccount(accountid: string, name: string, onBudget: boolean, isOpen: boolean) {
 | 
				
			||||||
            const result = await POST("/account/" + accountid, JSON.stringify({ name: name, onBudget: onBudget }));
 | 
					            const result = await POST("/account/" + accountid, JSON.stringify({ name: name, onBudget: onBudget, isOpen: isOpen }));
 | 
				
			||||||
            const response = await result.json();
 | 
					            const response = await result.json();
 | 
				
			||||||
            useBudgetsStore().MergeBudgetingData(response);
 | 
					            useBudgetsStore().MergeBudgetingData(response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(!isOpen) {
 | 
				
			||||||
 | 
					                this.Accounts.delete(accountid);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        addCategoriesForMonth(year: number, month: number, categories: Category[]): void {
 | 
					        addCategoriesForMonth(year: number, month: number, categories: Category[]): void {
 | 
				
			||||||
            this.$patch((state) => {
 | 
					            this.$patch((state) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,7 +84,6 @@ export const useTransactionsStore = defineStore("budget/transactions", {
 | 
				
			|||||||
                this.AddTransactions([recTrans]);
 | 
					                this.AddTransactions([recTrans]);
 | 
				
			||||||
                account.Transactions.unshift(recTrans.ID);
 | 
					                account.Transactions.unshift(recTrans.ID);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            console.log("Reconcile: " + response.message);
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        logout() {
 | 
					        logout() {
 | 
				
			||||||
            this.$reset()
 | 
					            this.$reset()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user