Compare commits
2 commits
d0e334e842
...
00a697d1a6
Author | SHA1 | Date | |
---|---|---|---|
|
00a697d1a6 | ||
|
9610ddf731 |
10 changed files with 267 additions and 216 deletions
2
api.go
2
api.go
|
@ -41,7 +41,7 @@ func getTransactions(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
transactions := []types.Transaction{}
|
transactions := []types.Transaction{}
|
||||||
|
|
||||||
err := db.GetTransactions(&transactions, input.ResultCount, input.PageNum);
|
err := db.GetTransactions(&transactions, input.ResultCount, input.PageNum)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
|
|
103
db/db.go
103
db/db.go
|
@ -1,8 +1,8 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
@ -11,14 +11,14 @@ import (
|
||||||
"nickiel.net/recount_server/types"
|
"nickiel.net/recount_server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB_TYPE string;
|
var DB_TYPE string
|
||||||
var DB_SCHEMA string;
|
var DB_SCHEMA string
|
||||||
var DB_CONNECTION_STRING string;
|
var DB_CONNECTION_STRING string
|
||||||
|
|
||||||
func SetConstants(db_type string, db_schema string, db_conn_string string) {
|
func SetConstants(db_type string, db_schema string, db_conn_string string) {
|
||||||
DB_TYPE = db_type;
|
DB_TYPE = db_type
|
||||||
DB_SCHEMA = db_schema;
|
DB_SCHEMA = db_schema
|
||||||
DB_CONNECTION_STRING = db_conn_string;
|
DB_CONNECTION_STRING = db_conn_string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTransactions(transactions *[]types.Transaction, resultCount int, pageNum int) error {
|
func GetTransactions(transactions *[]types.Transaction, resultCount int, pageNum int) error {
|
||||||
|
@ -69,62 +69,61 @@ func NewTransaction(transaction types.Transaction) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserAccounts(userID int) ([]types.Account, error) {
|
func GetUserAccounts(userID int) ([]types.Account, error) {
|
||||||
user_accounts := make([]types.Account, 4);
|
user_accounts := make([]types.Account, 4)
|
||||||
|
|
||||||
user_accounts[0] = types.Account {
|
user_accounts[0] = types.Account{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
DisplayName: sql.NullString {
|
DisplayName: sql.NullString{
|
||||||
String: "Savings",
|
String: "Savings",
|
||||||
Valid: true,
|
Valid: true,
|
||||||
} ,
|
},
|
||||||
Description: sql.NullString {
|
Description: sql.NullString{
|
||||||
String: "BECU Saving Account",
|
String: "BECU Saving Account",
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
user_accounts[1] = types.Account {
|
user_accounts[1] = types.Account{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
DisplayName: sql.NullString {
|
DisplayName: sql.NullString{
|
||||||
String: "BECU Credit Card",
|
String: "BECU Credit Card",
|
||||||
Valid: true,
|
Valid: true,
|
||||||
} ,
|
},
|
||||||
Description: sql.NullString {
|
Description: sql.NullString{
|
||||||
String: "BECU Saving Account",
|
String: "BECU Saving Account",
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
return user_accounts, nil;
|
return user_accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserBuckets(userID int) ([]types.Bucket, error) {
|
func GetUserBuckets(userID int) ([]types.Bucket, error) {
|
||||||
bucket1 := types.Bucket{
|
bucket1 := types.Bucket{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
DisplayCode: sql.NullString{String: "SVNG", Valid: true},
|
DisplayCode: sql.NullString{String: "SVNG", Valid: true},
|
||||||
DisplayName: sql.NullString{String: "Savings", Valid: true},
|
DisplayName: sql.NullString{String: "Savings", Valid: true},
|
||||||
Description: sql.NullString{String: "The Savings Bucket", Valid: true},
|
Description: sql.NullString{String: "The Savings Bucket", Valid: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket2 := types.Bucket{
|
bucket2 := types.Bucket{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
DisplayCode: sql.NullString{String: "SPND", Valid: true},
|
DisplayCode: sql.NullString{String: "SPND", Valid: true},
|
||||||
DisplayName: sql.NullString{String: "Spending", Valid: true},
|
DisplayName: sql.NullString{String: "Spending", Valid: true},
|
||||||
Description: sql.NullString{String: "The Spending Bucket", Valid: true},
|
Description: sql.NullString{String: "The Spending Bucket", Valid: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating a slice of length two and populating it with the two structs
|
// Creating a slice of length two and populating it with the two structs
|
||||||
buckets := []types.Bucket{bucket1, bucket2}
|
buckets := []types.Bucket{bucket1, bucket2}
|
||||||
|
|
||||||
return buckets, nil;
|
return buckets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTransPaneEntries(userID int) ([]types.QuickTransactionType, error) {
|
func GetTransPaneEntries(userID int) ([]types.QuickTransactionType, error) {
|
||||||
transaction_types := make([]types.QuickTransactionType, 1);
|
transaction_types := make([]types.QuickTransactionType, 1)
|
||||||
|
|
||||||
transaction_types[0] = types.QuickTransactionType {
|
transaction_types[0] = types.QuickTransactionType{
|
||||||
DisplayName: "Manual",
|
DisplayName: "Manual",
|
||||||
};
|
}
|
||||||
|
|
||||||
return transaction_types, nil;
|
return transaction_types, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"nickiel.net/recount_server/db"
|
||||||
"nickiel.net/recount_server/tests"
|
"nickiel.net/recount_server/tests"
|
||||||
"nickiel.net/recount_server/db"
|
|
||||||
"nickiel.net/recount_server/web"
|
"nickiel.net/recount_server/web"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -60,7 +60,7 @@ func main() {
|
||||||
debug_mode.Init_testdb(DB_TYPE, DB_CONNECTION_STRING)
|
debug_mode.Init_testdb(DB_TYPE, DB_CONNECTION_STRING)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.SetConstants(DB_TYPE, DB_SCHEMA, DB_CONNECTION_STRING);
|
db.SetConstants(DB_TYPE, DB_SCHEMA, DB_CONNECTION_STRING)
|
||||||
|
|
||||||
debug_mode.SetLogLevel(zerolog.GlobalLevel())
|
debug_mode.SetLogLevel(zerolog.GlobalLevel())
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package debug_mode
|
package debug_mode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"nickiel.net/recount_server/types"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"nickiel.net/recount_server/types"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@ -11,32 +11,31 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func SetLogLevel(level zerolog.Level) {
|
func SetLogLevel(level zerolog.Level) {
|
||||||
zerolog.SetGlobalLevel(level)
|
zerolog.SetGlobalLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init_testdb(DB_TYPE string, DB_CONNECTION_STRING string) {
|
func Init_testdb(DB_TYPE string, DB_CONNECTION_STRING string) {
|
||||||
|
|
||||||
cwd, err := os.Getwd();
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Could not get current working directory")
|
log.Fatal().Err(err).Msg("Could not get current working directory")
|
||||||
} else {
|
} else {
|
||||||
cwd = cwd + "/";
|
cwd = cwd + "/"
|
||||||
log.Trace().Msgf("Currect working directory is: %s", cwd)
|
log.Trace().Msgf("Currect working directory is: %s", cwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = os.Stat(cwd + DB_CONNECTION_STRING)
|
_, err = os.Stat(cwd + DB_CONNECTION_STRING)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Msg("No existing test.db file found")
|
log.Debug().Msg("No existing test.db file found")
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Msg("Found existing test.db file. Attempting to delete")
|
log.Debug().Msg("Found existing test.db file. Attempting to delete")
|
||||||
err = os.Remove(cwd + DB_CONNECTION_STRING)
|
err = os.Remove(cwd + DB_CONNECTION_STRING)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Failed to delete testing db. Continueing anyways")
|
log.Warn().Err(err).Msg("Failed to delete testing db. Continueing anyways")
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Msg("Deleted test.db file successfully")
|
log.Debug().Msg("Deleted test.db file successfully")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sqlx.Connect(DB_TYPE, DB_CONNECTION_STRING)
|
db, err := sqlx.Connect(DB_TYPE, DB_CONNECTION_STRING)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package types
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
|
@ -22,33 +22,33 @@ type Account struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bucket struct {
|
type Bucket struct {
|
||||||
Id int `db:"bkt_id" json:"id"`
|
Id int `db:"bkt_id" json:"id"`
|
||||||
DisplayCode sql.NullString `db:"bkt_display_code" json:"DisplayCode"`
|
DisplayCode sql.NullString `db:"bkt_display_code" json:"DisplayCode"`
|
||||||
DisplayName sql.NullString `db:"bkt_display_name" json:"DisplayName"`
|
DisplayName sql.NullString `db:"bkt_display_name" json:"DisplayName"`
|
||||||
Description sql.NullString `db:"bkt_description" json:"Description"`
|
Description sql.NullString `db:"bkt_description" json:"Description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanLegibleTransaction struct {
|
type HumanLegibleTransaction struct {
|
||||||
Id int `db:"trns_id" json:"Id"`
|
Id int `db:"trns_id" json:"Id"`
|
||||||
Amount string `db:"trns_amount" json:"Amount"`
|
Amount string `db:"trns_amount" json:"Amount"`
|
||||||
Description sql.NullString `db:"trns_description" json:"Description"`
|
Description sql.NullString `db:"trns_description" json:"Description"`
|
||||||
AccountName sql.NullString `db:"account_name" json:"AccountName"`
|
AccountName sql.NullString `db:"account_name" json:"AccountName"`
|
||||||
Account int `db:"trns_account" json:"Account"`
|
Account int `db:"trns_account" json:"Account"`
|
||||||
Bucket sql.NullInt64 `db:"trns_bucket" json:"Bucket"`
|
Bucket sql.NullInt64 `db:"trns_bucket" json:"Bucket"`
|
||||||
BucketName sql.NullString `db:"bucket_name" json:"BucketName"`
|
BucketName sql.NullString `db:"bucket_name" json:"BucketName"`
|
||||||
Date time.Time `db:"trns_date" json:"TransactionDate"`
|
Date time.Time `db:"trns_date" json:"TransactionDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QuickTransactionType struct {
|
type QuickTransactionType struct {
|
||||||
DisplayName string
|
DisplayName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChartjsData struct {
|
type ChartjsData struct {
|
||||||
Labels []string `json:"labels"`
|
Labels []string `json:"labels"`
|
||||||
Data []int `json:"data"`
|
Data []int `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TwoIntsItem struct {
|
type TwoIntsItem struct {
|
||||||
Item1 int
|
Item1 int
|
||||||
Item2 int
|
Item2 int
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"nickiel.net/recount_server/web/templates"
|
"nickiel.net/recount_server/web/templates"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
@ -15,11 +15,10 @@ import (
|
||||||
"nickiel.net/recount_server/types"
|
"nickiel.net/recount_server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DEFAULT_RESULT_COUNT = 20
|
||||||
const DEFAULT_RESULT_COUNT = 20;
|
|
||||||
|
|
||||||
func GetTransactionsRows(w http.ResponseWriter, req *http.Request) {
|
func GetTransactionsRows(w http.ResponseWriter, req *http.Request) {
|
||||||
transactions := make([]types.HumanLegibleTransaction, 10)
|
transactions := make([]types.HumanLegibleTransaction, 10)
|
||||||
|
|
||||||
// Populate the slice with dummy data (you can replace this with your actual data)
|
// Populate the slice with dummy data (you can replace this with your actual data)
|
||||||
for i := 10; i > 0; i-- {
|
for i := 10; i > 0; i-- {
|
||||||
|
@ -34,15 +33,15 @@ func GetTransactionsRows(w http.ResponseWriter, req *http.Request) {
|
||||||
Date: time.Now(),
|
Date: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions[10 - i] = transaction
|
transactions[10-i] = transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
component := templates.TransactionRows(&transactions);
|
component := templates.TransactionRows(&transactions)
|
||||||
component.Render(context.Background(), w);
|
component.Render(context.Background(), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTransactionQuickAccessEntries(w http.ResponseWriter, req *http.Request) {
|
func GetTransactionQuickAccessEntries(w http.ResponseWriter, req *http.Request) {
|
||||||
transactions := make([]types.HumanLegibleTransaction, 20)
|
transactions := make([]types.HumanLegibleTransaction, 20)
|
||||||
|
|
||||||
// Populate the slice with dummy data (you can replace this with your actual data)
|
// Populate the slice with dummy data (you can replace this with your actual data)
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
|
@ -59,50 +58,50 @@ func GetTransactionQuickAccessEntries(w http.ResponseWriter, req *http.Request)
|
||||||
|
|
||||||
transactions[i] = transaction
|
transactions[i] = transaction
|
||||||
}
|
}
|
||||||
component := templates.TransactionQuickAccessEntries(&transactions);
|
component := templates.TransactionQuickAccessEntries(&transactions)
|
||||||
component.Render(context.Background(), w);
|
component.Render(context.Background(), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetExpenditureChart(w http.ResponseWriter, req *http.Request) {
|
func GetExpenditureChart(w http.ResponseWriter, req *http.Request) {
|
||||||
data_package := struct{
|
data_package := struct {
|
||||||
Labels []string `json:"labels"`
|
Labels []string `json:"labels"`
|
||||||
IncomeDataset []int `json:"income_data"`
|
IncomeDataset []int `json:"income_data"`
|
||||||
ExpensesDataset []int `json:"expenses_data"`
|
ExpensesDataset []int `json:"expenses_data"`
|
||||||
} {
|
}{
|
||||||
Labels: []string{"Income", "Expenses"},
|
Labels: []string{"Income", "Expenses"},
|
||||||
IncomeDataset: []int {300, 250},
|
IncomeDataset: []int{300, 250},
|
||||||
ExpensesDataset: []int {200, 100},
|
ExpensesDataset: []int{200, 100},
|
||||||
};
|
}
|
||||||
|
|
||||||
json_data, err := json.Marshal(data_package);
|
json_data, err := json.Marshal(data_package)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Could not jsonify data_package");
|
log.Fatal().Err(err).Msg("Could not jsonify data_package")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write(json_data);
|
w.Write(json_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccountSummaries(w http.ResponseWriter, req *http.Request) {
|
func GetAccountSummaries(w http.ResponseWriter, req *http.Request) {
|
||||||
accounts := make([]types.TwoIntsItem, 20)
|
accounts := make([]types.TwoIntsItem, 20)
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
accounts[i] = types.TwoIntsItem {Item1: i*100, Item2: i+5}
|
accounts[i] = types.TwoIntsItem{Item1: i * 100, Item2: i + 5}
|
||||||
}
|
}
|
||||||
component := templates.AccountSummaryRows(&accounts)
|
component := templates.AccountSummaryRows(&accounts)
|
||||||
component.Render(context.Background(), w)
|
component.Render(context.Background(), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccountSummaryChart(w http.ResponseWriter, req *http.Request) {
|
func GetAccountSummaryChart(w http.ResponseWriter, req *http.Request) {
|
||||||
accountID := chi.URLParam(req, "accountID")
|
accountID := chi.URLParam(req, "accountID")
|
||||||
|
|
||||||
data_package := types.ChartjsData {
|
|
||||||
Labels: []string {accountID, "1/10", "1/17", "1/24"},
|
|
||||||
Data: []int {100, 0, -50, 25},
|
|
||||||
}
|
|
||||||
|
|
||||||
json_data, err := json.Marshal(data_package);
|
data_package := types.ChartjsData{
|
||||||
if err != nil {
|
Labels: []string{accountID, "1/10", "1/17", "1/24"},
|
||||||
log.Fatal().Err(err).Msg("Could not jsonify data_package");
|
Data: []int{100, 0, -50, 25},
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write(json_data);
|
json_data, err := json.Marshal(data_package)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not jsonify data_package")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(json_data)
|
||||||
}
|
}
|
||||||
|
|
165
web/router.go
165
web/router.go
|
@ -18,10 +18,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexTemplateModel struct {
|
type IndexTemplateModel struct {
|
||||||
InnerHtml template.HTML
|
InnerHtml template.HTML
|
||||||
ActivePage string
|
ActivePage string
|
||||||
NewTransactionPane template.HTML
|
NewTransactionPane template.HTML
|
||||||
QuickAccessPane template.HTML
|
QuickAccessPane template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
const TemplateDir = "./web/templates/"
|
const TemplateDir = "./web/templates/"
|
||||||
|
@ -33,111 +33,110 @@ func SetLogLevel(level zerolog.Level) {
|
||||||
func WebRouter() http.Handler {
|
func WebRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getDashboard)
|
r.Get("/", getDashboard)
|
||||||
r.Get("/transactions", getTransactionsPage)
|
r.Get("/transactions", getTransactionsPage)
|
||||||
|
|
||||||
r.Get("/components/account_summaries", GetAccountSummaries)
|
r.Get("/components/account_summaries", GetAccountSummaries)
|
||||||
|
|
||||||
r.Get("/components/data/transaction_table_rows", GetTransactionsRows)
|
r.Get("/components/data/transaction_table_rows", GetTransactionsRows)
|
||||||
r.Get("/components/data/expenditure_chart", GetExpenditureChart)
|
r.Get("/components/data/expenditure_chart", GetExpenditureChart)
|
||||||
r.Get("/components/data/account_summary/{accountID}", GetAccountSummaryChart)
|
r.Get("/components/data/account_summary/{accountID}", GetAccountSummaryChart)
|
||||||
r.Get("/components/data/transaction_quick_access", GetTransactionQuickAccessEntries)
|
r.Get("/components/data/transaction_quick_access", GetTransactionQuickAccessEntries)
|
||||||
|
|
||||||
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static/"))))
|
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static/"))))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
//for name, values := range req.Header {
|
//for name, values := range req.Header {
|
||||||
// Loop over all values for the name.
|
// Loop over all values for the name.
|
||||||
// for _, value := range values {
|
// for _, value := range values {
|
||||||
// log.Debug().Msg(name + " " + value);
|
// log.Debug().Msg(name + " " + value);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func renderFullPage(w http.ResponseWriter, c templ.Component, pageName string) {
|
func renderFullPage(w http.ResponseWriter, c templ.Component, pageName string) {
|
||||||
var main_component bytes.Buffer
|
var main_component bytes.Buffer
|
||||||
|
|
||||||
// Render the provided templ component
|
// Render the provided templ component
|
||||||
err := c.Render(context.Background(), &main_component)
|
err := c.Render(context.Background(), &main_component)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Fatal error reading hello template");
|
log.Fatal().Err(err).Msg("Fatal error reading hello template")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the index template
|
// get the index template
|
||||||
index, err := template.ParseFiles(TemplateDir + "index.html")
|
index, err := template.ParseFiles(TemplateDir + "index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().
|
log.Fatal().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Fatal error reading index template")
|
Msg("Fatal error reading index template")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var new_tp bytes.Buffer
|
||||||
|
|
||||||
var new_tp bytes.Buffer;
|
quick_trans_types, err := db.GetTransPaneEntries(0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not get transaction pane entries for user")
|
||||||
|
}
|
||||||
|
user_acnts, err := db.GetUserAccounts(0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not get accounts for user")
|
||||||
|
}
|
||||||
|
user_bkts, err := db.GetUserBuckets(0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not get buckets for user")
|
||||||
|
}
|
||||||
|
|
||||||
quick_trans_types, err := db.GetTransPaneEntries(0);
|
err = templates.NewTransactionPane(&quick_trans_types, &user_acnts, &user_bkts).Render(context.Background(), &new_tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Could not get transaction pane entries for user");
|
log.Fatal().Err(err).Msg("Could not render new transaction pane for index")
|
||||||
}
|
}
|
||||||
user_acnts, err := db.GetUserAccounts(0);
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Could not get accounts for user");
|
|
||||||
}
|
|
||||||
user_bkts, err := db.GetUserBuckets(0);
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Could not get buckets for user");
|
|
||||||
}
|
|
||||||
|
|
||||||
err = templates.NewTransactionPane(&quick_trans_types, &user_acnts, &user_bkts).Render(context.Background(), &new_tp);
|
var quick_ap bytes.Buffer
|
||||||
if err != nil {
|
err = templates.QuickAccessPane().Render(context.Background(), &quick_ap)
|
||||||
log.Fatal().Err(err).Msg("Could not render new transaction pane for index");
|
if err != nil {
|
||||||
}
|
log.Fatal().Err(err).Msg("Could not render quick access pane for index")
|
||||||
|
}
|
||||||
|
|
||||||
var quick_ap bytes.Buffer;
|
// Inject the templ component html into the index template
|
||||||
err = templates.QuickAccessPane().Render(context.Background(), &quick_ap);
|
err = index.Execute(w,
|
||||||
if err != nil {
|
IndexTemplateModel{
|
||||||
log.Fatal().Err(err).Msg("Could not render quick access pane for index");
|
InnerHtml: template.HTML(main_component.String()),
|
||||||
}
|
ActivePage: pageName,
|
||||||
|
NewTransactionPane: template.HTML(new_tp.String()),
|
||||||
// Inject the templ component html into the index template
|
QuickAccessPane: template.HTML(quick_ap.String()),
|
||||||
err = index.Execute(w,
|
})
|
||||||
IndexTemplateModel{
|
if err != nil {
|
||||||
InnerHtml: template.HTML(main_component.String()),
|
log.Fatal().Err(err).Msg("Fatal error reading hello template")
|
||||||
ActivePage: pageName,
|
}
|
||||||
NewTransactionPane: template.HTML(new_tp.String()),
|
|
||||||
QuickAccessPane: template.HTML(quick_ap.String()),
|
|
||||||
});
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Fatal error reading hello template");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDashboard(w http.ResponseWriter, req *http.Request) {
|
func getDashboard(w http.ResponseWriter, req *http.Request) {
|
||||||
component := templates.Dashboard();
|
component := templates.Dashboard()
|
||||||
|
|
||||||
_, ok := req.Header["Hx-Request"]
|
_, ok := req.Header["Hx-Request"]
|
||||||
if ok {
|
if ok {
|
||||||
err := component.Render(context.Background(), w);
|
err := component.Render(context.Background(), w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Couldn't render dashboard templ template");
|
log.Fatal().Err(err).Msg("Couldn't render dashboard templ template")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
renderFullPage(w, component, "Dashboard");
|
renderFullPage(w, component, "Dashboard")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTransactionsPage(w http.ResponseWriter, req *http.Request) {
|
func getTransactionsPage(w http.ResponseWriter, req *http.Request) {
|
||||||
accounts := make([]string, 10);
|
accounts := make([]string, 10)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
accounts[i] = strconv.Itoa(i) + " Account";
|
accounts[i] = strconv.Itoa(i) + " Account"
|
||||||
}
|
}
|
||||||
|
|
||||||
component := templates.TransactionsPage(1, &accounts);
|
component := templates.TransactionsPage(1, &accounts)
|
||||||
_, ok := req.Header["Hx-Request"]
|
_, ok := req.Header["Hx-Request"]
|
||||||
if ok {
|
if ok {
|
||||||
err := component.Render(context.Background(), w);
|
err := component.Render(context.Background(), w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Couldn't render transactions templ template");
|
log.Fatal().Err(err).Msg("Couldn't render transactions templ template")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
renderFullPage(w, component, "Transactions")
|
renderFullPage(w, component, "Transactions")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {register_dropdowns} from "./dropdowns.js";
|
||||||
import {
|
import {
|
||||||
open_new_transaction_pane
|
open_new_transaction_pane
|
||||||
, close_new_transaction_pane
|
, close_new_transaction_pane
|
||||||
, add_new_breakdown
|
, register_breakdown_handlers
|
||||||
} from "./new_transactions.js";
|
} from "./new_transactions.js";
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,12 +60,12 @@ function register_handlers() {
|
||||||
a_el.addEventListener("click", switch_nav);
|
a_el.addEventListener("click", switch_nav);
|
||||||
});
|
});
|
||||||
register_dropdowns();
|
register_dropdowns();
|
||||||
|
register_breakdown_handlers();
|
||||||
document.querySelector("#open-draft").addEventListener("click", open_drafts);
|
document.querySelector("#open-draft").addEventListener("click", open_drafts);
|
||||||
document.querySelector("#close-transaction-pane").addEventListener("click", close_drafts);
|
document.querySelector("#close-transaction-pane").addEventListener("click", close_drafts);
|
||||||
document.querySelector(".input-sizer > input").addEventListener("input", (event) => {
|
document.querySelector(".input-sizer > input").addEventListener("input", (event) => {
|
||||||
event.target.parentNode.dataset.value = event.target.value
|
event.target.parentNode.dataset.value = event.target.value
|
||||||
});
|
});
|
||||||
document.getElementById("new-breakdown-btn").addEventListener("click", add_new_breakdown);
|
|
||||||
close_new_transaction_pane();
|
close_new_transaction_pane();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,15 +48,68 @@ function close_new_transaction_pane() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
function on_input(event) {
|
||||||
|
if (event.target.id === "bottom-most-breakdown-input"
|
||||||
|
|| document.querySelectorAll("#breakdown-tbody input[type='number']:not(#tax-input)").length === 0
|
||||||
|
) {
|
||||||
|
add_new_breakdown();
|
||||||
|
}
|
||||||
|
let total_input = document.getElementById("amount-entry");
|
||||||
|
|
||||||
|
if (event.target.id === "amount-entry") {
|
||||||
|
let tax_entry = document.getElementById("tax-input");
|
||||||
|
let tax_rate = document.getElementById("new-transaction-pane").dataset.rcntTaxrate;
|
||||||
|
if (tax_entry) {
|
||||||
|
tax_entry.value = (parseFloat(total_input.value) - (parseFloat(total_input.value) / (1 + parseFloat(tax_rate)))).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let breakdown_sum = Array.from(document
|
||||||
|
.querySelectorAll("#breakdown-tbody input[type='number']:not(#bottom-most-breakdown-input)"
|
||||||
|
)).reduce(function(total, el) {
|
||||||
|
return total + parseFloat(el.value || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (total_input.value) {
|
||||||
|
let value_total = parseFloat(total_input.value);
|
||||||
|
|
||||||
|
document.getElementById("bottom-most-breakdown-input").value = (value_total - breakdown_sum).toFixed(2);
|
||||||
|
} else {
|
||||||
|
total_input.placeholder = breakdown_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function add_new_breakdown() {
|
function add_new_breakdown() {
|
||||||
let source = document.querySelector("#breakdown-template-row tr").cloneNode(true);
|
let input_latest_old = document.getElementById("bottom-most-breakdown-input");
|
||||||
source.style.removeProperty("display");
|
if (input_latest_old) {
|
||||||
console.log(source.querySelector("input[type='number']"));
|
input_latest_old.id = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = document.importNode(document.getElementById("breakdown-template-row").content, true);
|
||||||
|
let input_latest = source.querySelector("input[type='number']");
|
||||||
|
input_latest.addEventListener("input", on_input);
|
||||||
|
input_latest.id = "bottom-most-breakdown-input";
|
||||||
document.getElementById("breakdown-tbody").appendChild(source);
|
document.getElementById("breakdown-tbody").appendChild(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_breakdown_amount() {
|
function insert_tax() {
|
||||||
|
if (document.getElementById("tax-input")) return;
|
||||||
|
let source = document.importNode(document.getElementById("breakdown-template-row").content, true);
|
||||||
|
source.querySelector("input[type='text']").value = "Tax";
|
||||||
|
source.querySelector("input[type='number']").id = "tax-input";
|
||||||
|
|
||||||
|
let table_body = document.getElementById("breakdown-tbody");
|
||||||
|
table_body.insertBefore(source, table_body.firstChild);
|
||||||
|
// on_input();
|
||||||
}
|
}
|
||||||
|
|
||||||
export {open_new_transaction_pane, close_new_transaction_pane, add_new_breakdown}
|
function register_breakdown_handlers() {
|
||||||
|
document.getElementById("amount-entry").addEventListener("input", on_input);
|
||||||
|
document.getElementById("new-breakdown-btn").addEventListener("click", add_new_breakdown);
|
||||||
|
document.getElementById("tax-breakdown-btn").addEventListener("click", insert_tax);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {open_new_transaction_pane, close_new_transaction_pane, register_breakdown_handlers}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import "strconv"
|
||||||
|
|
||||||
|
|
||||||
templ NewTransactionPane(entry_types *[]types.QuickTransactionType, acnts *[]types.Account, buckets *[]types.Bucket) {
|
templ NewTransactionPane(entry_types *[]types.QuickTransactionType, acnts *[]types.Account, buckets *[]types.Bucket) {
|
||||||
<div style="display: none" id="breakdown-template-row">
|
<template id="breakdown-template-row">
|
||||||
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="my-2">
|
<td class="my-2">
|
||||||
<select class="ms-2 select border" id="acnt-entry-selection">
|
<select class="ms-2 select border" id="acnt-entry-selection">
|
||||||
|
@ -16,8 +17,9 @@ templ NewTransactionPane(entry_types *[]types.QuickTransactionType, acnts *[]typ
|
||||||
</td>
|
</td>
|
||||||
<td><input type="text" /></td>
|
<td><input type="text" /></td>
|
||||||
<td><input type="number" step="any" /></td>
|
<td><input type="number" step="any" /></td>
|
||||||
</div>
|
</tr>
|
||||||
<div id="new-transaction-pane" style="opacity: 0;" class="cr-all c-base d-flex-col">
|
</template>
|
||||||
|
<div id="new-transaction-pane" style="opacity: 0;" class="cr-all c-base d-flex-col" data-rcnt-taxrate="0.095">
|
||||||
<div class="my-2 d-flex">
|
<div class="my-2 d-flex">
|
||||||
<h2 class="ms-5">New Transaction</h2>
|
<h2 class="ms-5">New Transaction</h2>
|
||||||
<button class="ms-auto me-4 my-auto exit-btn" id="close-transaction-pane">
|
<button class="ms-auto me-4 my-auto exit-btn" id="close-transaction-pane">
|
||||||
|
@ -84,7 +86,7 @@ templ NewTransactionPane(entry_types *[]types.QuickTransactionType, acnts *[]typ
|
||||||
<i class="" data-feather="plus"></i>
|
<i class="" data-feather="plus"></i>
|
||||||
<span class="my-auto pe-2">New</span>
|
<span class="my-auto pe-2">New</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-sm ms-5 my-auto c-green invert">
|
<button class="btn-sm ms-5 my-auto c-green invert" id="tax-breakdown-btn">
|
||||||
<i class="" data-feather="plus"></i>
|
<i class="" data-feather="plus"></i>
|
||||||
<span class="my-auto pe-2">Tax</span>
|
<span class="my-auto pe-2">Tax</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
Loading…
Reference in a new issue