diff --git a/.gitignore b/.gitignore index 4f99426..36484f4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ web/*_templ.go web/static/*.css web/static/*.css.map .sass-cache/* +*/node_modules/* # --> nix .direnv/ diff --git a/flake.nix b/flake.nix index d0d10a9..363d7fd 100644 --- a/flake.nix +++ b/flake.nix @@ -75,6 +75,7 @@ default = pkgs.mkShell { buildInputs = with pkgs; [ air # live go app reloading + corepack # npm install for the web folder dart-sass go gopls diff --git a/types/types.go b/types/types.go index 339ae08..a95953d 100644 --- a/types/types.go +++ b/types/types.go @@ -25,3 +25,8 @@ type HumanLegibleTransaction struct { BucketName sql.NullString `db:"bucket_name" json:"BucketName"` Date time.Time `db:"trns_date" json:"TransactionDate"` } + +type ChartjsData struct { + Labels []string `json:"labels"` + Data []int `json:"data"` +} diff --git a/web/dashboard.templ b/web/dashboard.templ index df67906..33e4ffa 100644 --- a/web/dashboard.templ +++ b/web/dashboard.templ @@ -9,6 +9,18 @@ templ dashboard() { +
+
+ +
+
+
+
diff --git a/web/data_endpoints.go b/web/data_endpoints.go new file mode 100644 index 0000000..eb6af3c --- /dev/null +++ b/web/data_endpoints.go @@ -0,0 +1,53 @@ +package web + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/rs/zerolog/log" + "nickiel.net/recount_server/types" +) + + +const DEFAULT_RESULT_COUNT = 20; + +func getTransactions(w http.ResponseWriter, req *http.Request) { + transactions := make([]types.HumanLegibleTransaction, 10) + + // Populate the slice with dummy data (you can replace this with your actual data) + for i := 10; i > 0; i-- { + transaction := types.HumanLegibleTransaction{ + Id: i, + Amount: fmt.Sprintf("%d.00", (i+1)*100), + Description: sql.NullString{String: fmt.Sprintf("Transaction %d", i+1), Valid: true}, + AccountName: sql.NullString{String: "Savings", Valid: true}, + Account: 123, + Bucket: sql.NullInt64{Int64: int64(i + 100), Valid: true}, + BucketName: sql.NullString{String: fmt.Sprintf("Bucket %d", i+1), Valid: true}, + Date: time.Now(), + } + + transactions[10 - i] = transaction + } + + component := transaction_rows(&transactions); + component.Render(context.Background(), w); +} + +func getExpenditureChart(w http.ResponseWriter, req *http.Request) { + data_package := types.ChartjsData { + Labels: []string{"Income", "Expenses"}, + Data: []int{500, 200}, + }; + + json_data, err := json.Marshal(data_package); + if err != nil { + log.Fatal().Err(err).Msg("Could not jsonify data_package"); + } + + w.Write(json_data); +} diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..8de758d --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "web", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "chart.js": "^4.4.1" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..3df43b7 --- /dev/null +++ b/web/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "chart.js": "^4.4.1" + } +} diff --git a/web/router.go b/web/router.go index 9b6485c..6958c82 100644 --- a/web/router.go +++ b/web/router.go @@ -1,20 +1,16 @@ package web import ( - "database/sql" "html/template" "net/http" "bytes" "context" - "fmt" - "time" "github.com/a-h/templ" "github.com/go-chi/chi/v5" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "nickiel.net/recount_server/types" ) type TemplateState struct { @@ -32,6 +28,7 @@ func WebRouter() http.Handler { r := chi.NewRouter() r.Get("/", getIndex) r.Get("/web/transaction_table_rows", getTransactions) + r.Get("/web/dashboard/expenditure_chart", getExpenditureChart) r.Get("/hello", getHello) r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static/")))) return r @@ -99,27 +96,3 @@ func getHello(w http.ResponseWriter, req *http.Request) { } } -const DEFAULT_RESULT_COUNT = 20; - -func getTransactions(w http.ResponseWriter, req *http.Request) { - transactions := make([]types.HumanLegibleTransaction, 10) - - // Populate the slice with dummy data (you can replace this with your actual data) - for i := 10; i > 0; i-- { - transaction := types.HumanLegibleTransaction{ - Id: i, - Amount: fmt.Sprintf("%d.00", (i+1)*100), - Description: sql.NullString{String: fmt.Sprintf("Transaction %d", i+1), Valid: true}, - AccountName: sql.NullString{String: "Savings", Valid: true}, - Account: 123, - Bucket: sql.NullInt64{Int64: int64(i + 100), Valid: true}, - BucketName: sql.NullString{String: fmt.Sprintf("Bucket %d", i+1), Valid: true}, - Date: time.Now(), - } - - transactions[10 - i] = transaction - } - - component := transaction_rows(&transactions); - component.Render(context.Background(), w); -} diff --git a/web/sass/utility-classes.scss b/web/sass/utility-classes.scss index 23def63..b83e651 100644 --- a/web/sass/utility-classes.scss +++ b/web/sass/utility-classes.scss @@ -56,6 +56,9 @@ $directions: ( .w-full { width: 100%; } +.w-75 { + width: 75%; +} .w-50 { width: 50%; } diff --git a/web/static/chart_functions.js b/web/static/chart_functions.js new file mode 100644 index 0000000..e2366af --- /dev/null +++ b/web/static/chart_functions.js @@ -0,0 +1,39 @@ + +function fill_charts() { + document.querySelectorAll(".chartjs-chart").forEach(function (el) { + var url = el.dataset.chartEndpoint; + var type = el.dataset.chartType; + + fetch(url) + .then(response => { + if (!response.ok) { + console.log(response); + throw new Error("Fetch response was not ok!") + } + return response.json(); + }).then(jsonData => { + const config = { + type: type, + data: { + labels: jsonData.labels, + datasets: [{ + data: jsonData.data + }] + }, + options: { + scales: { + y: { + beginAtZero: true + } + } + } + }; + new Chart(el, config)(); + }).catch(error => { + console.error("Unable to set up chart: ", error) + }); + + }); +} + +export {fill_charts} diff --git a/web/static/index.js b/web/static/index.js index 7384b62..c973658 100644 --- a/web/static/index.js +++ b/web/static/index.js @@ -1,3 +1,5 @@ +import {fill_charts} from "./chart_functions.js"; + function debounce(func, delay) { let timeoutId; @@ -48,4 +50,4 @@ function load_in_table() { const trigger_table_animation = debounce(load_in_table, 100); -export {register_handlers, trigger_table_animation}; +export {register_handlers, trigger_table_animation, fill_charts}; diff --git a/web/templates/index.html b/web/templates/index.html index 07d1042..6ed9920 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -60,13 +60,15 @@