Date: Sat, 28 Sep 2024 11:40:53 +0200
Subject: [PATCH 11/24] unify spinners
---
templates/admin/charts_page.html | 47 +++++++++++---------------------
1 file changed, 16 insertions(+), 31 deletions(-)
diff --git a/templates/admin/charts_page.html b/templates/admin/charts_page.html
index 562c7d0..3bfdf1d 100644
--- a/templates/admin/charts_page.html
+++ b/templates/admin/charts_page.html
@@ -17,22 +17,14 @@ Game Statistics
hx-swap="outerHTML"
class="flex items-center justify-center"
>
-
-
+
+
+
Loading chart...
@@ -51,21 +43,14 @@ Game Statistics
hx-swap="outerHTML"
class="flex items-center justify-center"
>
-
+
+
+
Loading chart...
From 7e8f8327abdd5fcc03da2f4010e20f34529b040e Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 12:18:31 +0200
Subject: [PATCH 12/24] fix: the title of page was replaced by go-echarts
content
---
internal/api_handlers.go | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/internal/api_handlers.go b/internal/api_handlers.go
index d95ba64..2f0aed0 100644
--- a/internal/api_handlers.go
+++ b/internal/api_handlers.go
@@ -7,6 +7,7 @@ import (
"math/rand"
"minesweeper/internal/db"
"net/http"
+ "strings"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
@@ -45,7 +46,16 @@ func renderToHtml(c interface{}) (template.HTML, error) {
}
- return template.HTML(buf.String()), nil
+ htmlContent := buf.String()
+
+ // Remove the , , ", "")
+
+ return template.HTML(htmlContent), nil
}
func (h *ApiHandler) PieWinsLossesIncompleteChart(w http.ResponseWriter, r *http.Request) {
From 8102c60d7b6e93340ff7ec31f604b918060ee0ba Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 21:11:31 +0200
Subject: [PATCH 13/24] ID => Id
---
db/sqlc.yaml | 1 +
internal/handlers.go | 2 +-
internal/models/models.go | 4 ++--
templates/admin/index_games_page.html | 2 +-
4 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/db/sqlc.yaml b/db/sqlc.yaml
index 74b3327..ea8e024 100644
--- a/db/sqlc.yaml
+++ b/db/sqlc.yaml
@@ -5,5 +5,6 @@ sql:
schema: "./migrations"
gen:
go:
+ initialisms: []
package: "db"
out: "../internal/db"
\ No newline at end of file
diff --git a/internal/handlers.go b/internal/handlers.go
index 7578afd..dc7307c 100644
--- a/internal/handlers.go
+++ b/internal/handlers.go
@@ -225,7 +225,7 @@ func (h *Handler) HandleGridAction(w http.ResponseWriter, r *http.Request) {
GameFailed: game.GameFailed,
GameWon: game.GameWon,
GridState: encodedGridState,
- ID: game.ID,
+ Id: game.Id,
})
if err != nil {
log.Printf("Failed to update game state in database: %v", err)
diff --git a/internal/models/models.go b/internal/models/models.go
index fda1a87..f8b41fa 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -15,7 +15,7 @@ type Cell struct {
}
type Game struct {
- ID int64
+ Id int64
Uuid string
GridSize int
MinesAmount int
@@ -276,7 +276,7 @@ func FromDbGame(dbGame *db.Game) (*Game, error) {
decodedGameGrid := DecodeGameGrid(dbGame.GridState, int(dbGame.GridSize))
return &Game{
- ID: dbGame.ID,
+ Id: dbGame.Id,
Uuid: dbGame.Uuid,
GridSize: int(dbGame.GridSize),
MinesAmount: int(dbGame.MinesAmount),
diff --git a/templates/admin/index_games_page.html b/templates/admin/index_games_page.html
index a72cb5d..e7048a7 100644
--- a/templates/admin/index_games_page.html
+++ b/templates/admin/index_games_page.html
@@ -62,7 +62,7 @@
{{ range .Games }}
- {{ .ID }}
+ {{ .Id }}
|
{{ .Uuid }}
From 4ff58e23af6f098cb9e1c640abc530488a2b6420 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 22:31:37 +0200
Subject: [PATCH 14/24] add GetGamesByMonthYearGroupedByDay query
---
db/queries/queries.sql | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/db/queries/queries.sql b/db/queries/queries.sql
index aeff188..864ae10 100644
--- a/db/queries/queries.sql
+++ b/db/queries/queries.sql
@@ -68,4 +68,13 @@ SELECT
FROM
moves
WHERE
- game_id = ?
\ No newline at end of file
+ game_id = ?;
+
+-- name: GetGamesByMonthYearGroupedByDay :many
+SELECT
+ strftime('%d', created_at) AS day,
+ COUNT(*) AS games_played
+FROM games
+WHERE created_at >= ? AND created_at < ?
+GROUP BY day
+ORDER BY day;
\ No newline at end of file
From 3e24eea90210be40af763c2ea964eaf278a5cf22 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 22:32:00 +0200
Subject: [PATCH 15/24] add missing endpoint to routing
---
main.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/main.go b/main.go
index 1dd7770..a46dd80 100644
--- a/main.go
+++ b/main.go
@@ -121,6 +121,8 @@ func main() {
mux.HandleFunc("/api/charts/pie/wins-losses-incomplete", apiHandler.PieWinsLossesIncompleteChart)
mux.HandleFunc("/api/charts/bar/grid-size", apiHandler.GridSizeBar)
+ mux.HandleFunc("/api/charts/bar/mines-amount", apiHandler.MinesAmountBarChart)
+ mux.HandleFunc("/api/charts/bar/games-played", apiHandler.PlayedGamesInMonthBarChart)
port := cmp.Or(os.Getenv("APP_PORT"), "8080")
From 1b6506fc5e34d454f93d6299ac264f670e36a34b Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 22:32:26 +0200
Subject: [PATCH 16/24] add slider and stop displaying the legend
---
internal/api_handlers.go | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/internal/api_handlers.go b/internal/api_handlers.go
index 2f0aed0..5423a6e 100644
--- a/internal/api_handlers.go
+++ b/internal/api_handlers.go
@@ -101,14 +101,18 @@ func (h *ApiHandler) GridSizeBar(w http.ResponseWriter, r *http.Request) {
gamesPlayed := []int{15, 40, 25, 10, 5}
bar := charts.NewBar()
- bar.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
- Title: "Grid Size Popularity",
- Subtitle: "Games played per grid size",
- }), charts.WithDataZoomOpts(opts.DataZoom{
- Type: "slider",
- Start: 10,
- End: 50,
- }),
+ bar.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: "Grid Size Popularity",
+ Subtitle: "Games played per grid size",
+ }), charts.WithDataZoomOpts(opts.DataZoom{
+ Type: "slider",
+ Start: 0,
+ End: 100,
+ }),
+ charts.WithLegendOpts(opts.Legend{
+ Show: opts.Bool(false),
+ }),
)
items := make([]opts.BarData, 0)
From 22aebc76a351070eeae88c6c7f0efaea74734702 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 22:33:31 +0200
Subject: [PATCH 17/24] ad handler PlayedGamesInMonthBarChart grouped by days
---
internal/api_handlers.go | 74 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/internal/api_handlers.go b/internal/api_handlers.go
index 5423a6e..9da017b 100644
--- a/internal/api_handlers.go
+++ b/internal/api_handlers.go
@@ -2,6 +2,7 @@ package internal
import (
"bytes"
+ "database/sql"
"fmt"
"html/template"
"math/rand"
@@ -135,3 +136,76 @@ func (h *ApiHandler) GridSizeBar(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(htmlBarSnippet))
}
+func (h *ApiHandler) PlayedGamesInMonthBarChart(w http.ResponseWriter, r *http.Request) {
+ pickedDate := r.URL.Query().Get("picked-date-range")
+
+ if pickedDate == "" {
+ now := time.Now()
+ pickedDate = now.Format("2006-01")
+ }
+
+ parsedDate, err := time.Parse("2006-01", pickedDate)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Invalid date format: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ startOfMonth := parsedDate
+ endOfMonth := startOfMonth.AddDate(0, 1, 0)
+
+ // Create sql.NullTime for start and end of the month
+ startTime := sql.NullTime{
+ Time: startOfMonth,
+ Valid: true,
+ }
+
+ endTime := sql.NullTime{
+ Time: endOfMonth,
+ Valid: true,
+ }
+
+ gamesPerDay, err := h.Queries.GetGamesByMonthYearGroupedByDay(r.Context(), db.GetGamesByMonthYearGroupedByDayParams{
+ CreatedAt: startTime,
+ CreatedAt_2: endTime,
+ })
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error fetching games: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ days := []string{}
+ gamesPlayed := []opts.BarData{}
+
+ for _, gameDay := range gamesPerDay {
+ days = append(days, fmt.Sprintf("%v", gameDay.Day))
+ gamesPlayed = append(gamesPlayed, opts.BarData{Value: gameDay.GamesPlayed})
+ }
+
+ bar := charts.NewBar()
+ bar.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: fmt.Sprintf("Games Per Day in %v", parsedDate.Format("January 2006")),
+ Subtitle: "Amount of games played per day",
+ }),
+ charts.WithDataZoomOpts(opts.DataZoom{
+ Type: "slider",
+ Start: 0,
+ End: 100,
+ }),
+ charts.WithLegendOpts(opts.Legend{
+ Show: opts.Bool(false),
+ }),
+ )
+
+ bar.SetXAxis(days).
+ AddSeries("Games Played", gamesPlayed).
+ SetSeriesOptions(charts.WithLabelOpts(opts.Label{Show: opts.Bool(true)}))
+
+ htmlBarSnippet, err := renderToHtml(bar)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error rendering chart: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ w.Write([]byte(htmlBarSnippet))
+}
From f0a35c4c3295cf2db909c201d7c16ed048349a53 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sat, 28 Sep 2024 22:34:27 +0200
Subject: [PATCH 18/24] put all charts containers in it's places + month picker
---
templates/admin/charts_page.html | 76 ++++++++++++++++++++++++++++++--
1 file changed, 73 insertions(+), 3 deletions(-)
diff --git a/templates/admin/charts_page.html b/templates/admin/charts_page.html
index 3bfdf1d..4b913e2 100644
--- a/templates/admin/charts_page.html
+++ b/templates/admin/charts_page.html
@@ -7,14 +7,14 @@ Game Statistics
@@ -31,6 +31,50 @@ Game Statistics
+
+
+
+
+
+
+
+
+
+
+
+ Loading chart...
+
+
+
+
+
+
Game Statistics
hx-get="/api/charts/bar/grid-size"
hx-trigger="load"
hx-indicator="#grid-size-bar-chart-spinner"
- hx-swap="outerHTML"
+ hx-swap="innerHTML"
class="flex items-center justify-center"
>
@@ -57,6 +101,32 @@ Game Statistics
+
+
+
+
+
+
+
+ Loading chart...
+
+
+
{{ end }}
From 0029a55c17d6ae7241277792b16db523e7c0f5b4 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sun, 29 Sep 2024 11:13:53 +0200
Subject: [PATCH 19/24] rm placeholder data from pie chart
---
db/queries/queries.sql | 9 +++++++++
internal/api_handlers.go | 26 ++++++++++++++------------
2 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/db/queries/queries.sql b/db/queries/queries.sql
index 864ae10..7d9fcc6 100644
--- a/db/queries/queries.sql
+++ b/db/queries/queries.sql
@@ -62,6 +62,15 @@ FROM
WHERE
uuid IN (sqlc.slice('uuids'));
+-- name: GetGamesInfo :one
+SELECT
+ COUNT(*) AS total_games,
+ COUNT(*) FILTER (WHERE game_won = TRUE) AS won_games,
+ COUNT(*) FILTER (WHERE game_failed = TRUE AND game_won = FALSE) AS lost_games,
+ COUNT(*) FILTER (WHERE game_failed = FALSE AND game_won = FALSE) AS not_finished_games
+FROM
+ games;
+
-- name: GetMovesByGameId :many
SELECT
*
diff --git a/internal/api_handlers.go b/internal/api_handlers.go
index 9da017b..9f84a52 100644
--- a/internal/api_handlers.go
+++ b/internal/api_handlers.go
@@ -5,10 +5,10 @@ import (
"database/sql"
"fmt"
"html/template"
- "math/rand"
"minesweeper/internal/db"
"net/http"
"strings"
+ "time"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
@@ -60,26 +60,28 @@ func renderToHtml(c interface{}) (template.HTML, error) {
}
func (h *ApiHandler) PieWinsLossesIncompleteChart(w http.ResponseWriter, r *http.Request) {
- var (
- itemCntPie = 3
- options = []string{"Wins", "Losses", "Incomplete"}
- colors = []string{"#28a745", "#dc3545", "#ffc107"}
- )
+ rawData, err := h.Queries.GetGamesInfo(r.Context())
- // TODO remove this placeholder data
- items := make([]opts.PieData, 0)
- for i := 0; i < itemCntPie; i++ {
- items = append(items, opts.PieData{Name: options[i], Value: rand.Intn(100), ItemStyle: &opts.ItemStyle{Color: colors[i]}})
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error fetching DB information: %v", err), http.StatusInternalServerError)
+ return
}
+ parsedData := make([]opts.PieData, 0)
+ var colors = []string{"#28a745", "#dc3545", "#ffc107"}
+
+ parsedData = append(parsedData, opts.PieData{Name: "Wins", Value: rawData.WonGames, ItemStyle: &opts.ItemStyle{Color: colors[0]}})
+ parsedData = append(parsedData, opts.PieData{Name: "Losses", Value: rawData.LostGames, ItemStyle: &opts.ItemStyle{Color: colors[1]}})
+ parsedData = append(parsedData, opts.PieData{Name: "Incomplete", Value: rawData.NotFinishedGames, ItemStyle: &opts.ItemStyle{Color: colors[2]}})
+
pie := charts.NewPie()
pie.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
Title: "Wins vs Losses vs Incomplete",
- Subtitle: "Minesweeper Global Statistics",
+ Subtitle: fmt.Sprintf("Total games: %v", rawData.TotalGames),
}))
- pie.AddSeries("Game Status", items).
+ pie.AddSeries("Game Status", parsedData).
SetSeriesOptions(
charts.WithLabelOpts(opts.Label{
Formatter: "{b}: {d}%", // Label formatter to show percentage
From 2fb96c3e8534c86a1156f892063047b8278aafd8 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sun, 29 Sep 2024 11:51:35 +0200
Subject: [PATCH 20/24] rm placeholder data with real one
---
db/queries/queries.sql | 14 +++++-
internal/api_handlers.go | 99 ++++++++++++++++++++++++++++++++++++----
2 files changed, 104 insertions(+), 9 deletions(-)
diff --git a/db/queries/queries.sql b/db/queries/queries.sql
index 7d9fcc6..f339fa5 100644
--- a/db/queries/queries.sql
+++ b/db/queries/queries.sql
@@ -86,4 +86,16 @@ SELECT
FROM games
WHERE created_at >= ? AND created_at < ?
GROUP BY day
-ORDER BY day;
\ No newline at end of file
+ORDER BY day;
+
+-- name: GetGamesPlayedPerGridSize :many
+SELECT grid_size, COUNT(*) AS games_played
+FROM games
+GROUP BY grid_size
+ORDER BY grid_size;
+
+-- name: GetMinesPopularity :many
+SELECT mines_amount, COUNT(*) AS mines_count
+FROM games
+GROUP BY mines_amount
+ORDER BY mines_amount;
\ No newline at end of file
diff --git a/internal/api_handlers.go b/internal/api_handlers.go
index 9f84a52..5749adc 100644
--- a/internal/api_handlers.go
+++ b/internal/api_handlers.go
@@ -7,6 +7,7 @@ import (
"html/template"
"minesweeper/internal/db"
"net/http"
+ "strconv"
"strings"
"time"
@@ -99,9 +100,34 @@ func (h *ApiHandler) PieWinsLossesIncompleteChart(w http.ResponseWriter, r *http
}
func (h *ApiHandler) GridSizeBar(w http.ResponseWriter, r *http.Request) {
- // TODO remove this placeholder data
- gridSizes := []string{"3x3", "4x4", "5x5", "6x6", "7x7"}
- gamesPlayed := []int{15, 40, 25, 10, 5}
+
+ rawData, err := h.Queries.GetGamesPlayedPerGridSize(r.Context())
+
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error fetching grid size data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ gridSizes := make([]string, len(rawData))
+ parsedBarData := make([]opts.BarData, len(rawData))
+
+ // find the most popular grid size
+ maxGamesPlayed := int64(0)
+ for _, dbData := range rawData {
+ if dbData.GamesPlayed > maxGamesPlayed {
+ maxGamesPlayed = dbData.GamesPlayed
+ }
+ }
+
+ for i, dbData := range rawData {
+ gridSizes[i] = fmt.Sprintf("%vx%v", dbData.GridSize, dbData.GridSize)
+
+ if dbData.GamesPlayed == maxGamesPlayed {
+ parsedBarData[i] = opts.BarData{Value: dbData.GamesPlayed, ItemStyle: &opts.ItemStyle{Color: "#ffa500"}}
+ } else {
+ parsedBarData[i] = opts.BarData{Value: dbData.GamesPlayed}
+ }
+ }
bar := charts.NewBar()
bar.SetGlobalOptions(
@@ -118,13 +144,69 @@ func (h *ApiHandler) GridSizeBar(w http.ResponseWriter, r *http.Request) {
}),
)
- items := make([]opts.BarData, 0)
- for _, v := range gamesPlayed {
- items = append(items, opts.BarData{Value: v})
+ bar.SetXAxis(gridSizes).
+ AddSeries("Games Played", parsedBarData).
+ SetSeriesOptions(
+ charts.WithLabelOpts(opts.Label{Show: opts.Bool(true)}),
+ )
+
+ htmlBarSnippet, err := renderToHtml(bar)
+
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error rendering chart: %v", err), http.StatusInternalServerError)
+ return
}
- bar.SetXAxis(gridSizes).
- AddSeries("Games Played", items).
+ w.Write([]byte(htmlBarSnippet))
+}
+
+func (h *ApiHandler) MinesAmountBarChart(w http.ResponseWriter, r *http.Request) {
+ rawDbData, err := h.Queries.GetMinesPopularity(r.Context())
+
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error fetching mines popularity data: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // find the most popular amount of mines
+ maxAmount := int64(0)
+ for _, dbData := range rawDbData {
+ if dbData.MinesCount > maxAmount {
+ maxAmount = dbData.MinesCount
+ }
+ }
+
+ minesPopularity := make([]string, len(rawDbData))
+ parsedBarData := make([]opts.BarData, len(rawDbData))
+
+ for i, dbData := range rawDbData {
+ minesPopularity[i] = strconv.FormatInt(dbData.MinesAmount, 10)
+
+ if dbData.MinesCount == maxAmount {
+ parsedBarData[i] = opts.BarData{Value: dbData.MinesCount, ItemStyle: &opts.ItemStyle{Color: "#ffa500"}}
+ } else {
+ parsedBarData[i] = opts.BarData{Value: dbData.MinesCount}
+ }
+ }
+
+ bar := charts.NewBar()
+ bar.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: "Amount of Mines Popularity",
+ Subtitle: "Games with particular amount of mines",
+ }),
+ charts.WithDataZoomOpts(opts.DataZoom{
+ Type: "slider",
+ Start: 10,
+ End: 75,
+ }),
+ charts.WithLegendOpts(opts.Legend{
+ Show: opts.Bool(false),
+ }),
+ )
+
+ bar.SetXAxis(minesPopularity).
+ AddSeries("Mines Amount", parsedBarData).
SetSeriesOptions(
charts.WithLabelOpts(opts.Label{Show: opts.Bool(true)}),
)
@@ -138,6 +220,7 @@ func (h *ApiHandler) GridSizeBar(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(htmlBarSnippet))
}
+
func (h *ApiHandler) PlayedGamesInMonthBarChart(w http.ResponseWriter, r *http.Request) {
pickedDate := r.URL.Query().Get("picked-date-range")
From 66307736279c5f950c70f7af54e00882fc3af829 Mon Sep 17 00:00:00 2001
From: Oskarowski
Date: Sun, 29 Sep 2024 19:21:47 +0200
Subject: [PATCH 21/24] total home page UI refactor
---
templates/game/game_settings_form.html | 4 +-
templates/game/session_games_info.html | 6 +-
templates/index.html | 114 ++++++++++++++++++-------
3 files changed, 89 insertions(+), 35 deletions(-)
diff --git a/templates/game/game_settings_form.html b/templates/game/game_settings_form.html
index a48b3fd..20e4fe5 100644
--- a/templates/game/game_settings_form.html
+++ b/templates/game/game_settings_form.html
@@ -2,11 +2,11 @@
|