Archived
Template
1
0

Added a user sign up chart to admin dashboard

This commit is contained in:
uberswe
2022-02-14 05:41:50 +01:00
parent 5e28422425
commit 1a8e99b59c
3 changed files with 219 additions and 1 deletions

View File

@ -3,7 +3,12 @@
<main class="flex-shrink-0">
<div class="container">
<h1 class="mt-5">{{ call .Trans "Admin Dashboard" }}</h1>
{{ template "messages.html" . }}
<p>{{ call .Trans "You now have an authenticated session, feel free to log out using the link in the navbar above." }}</p>
<p>{{ call .Trans "Below is a chart showing the number of user sign ups to this website per month." }}</p>
{{ template "chart.html" .Chart }}
</div>
</main>

54
dist/templates/chart.html vendored Normal file
View File

@ -0,0 +1,54 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" class="graph" aria-labelledby="title" role="img" viewBox="0 0 800 500"
preserveAspectRatio="xMinYMin meet">
<title id="title">{{ .Title }}</title>
<g class="grid x-grid" id="xGrid">
<line x1="90" x2="90" y1="5" y2="371"></line>
</g>
<g class="grid y-grid" id="yGrid">
<line x1="90" x2="705" y1="370" y2="370"></line>
</g>
<g class="labels x-labels">
{{ range $xvalue := .XValues }}
<text x="{{ $xvalue.X }}" y="{{ $xvalue.Y }}">{{ $xvalue.Value }}</text>
{{ end }}
<text x="400" y="420" class="label-title">{{ .XLabel }}</text>
</g>
<g class="labels y-labels">
{{ range $yvalue := .YValues }}
<text x="{{ $yvalue.X }}" y="{{ $yvalue.Y }}">{{ $yvalue.Value }}</text>
{{ end }}
<text x="50" y="200" class="label-title">{{ .YLabel }}</text>
</g>
<polyline fill="none" stroke="#0074d9" stroke-width="2" points="{{ .Points }}"/>
</svg>
<style>
.graph .labels.x-labels {
text-anchor: middle;
}
.graph .labels.y-labels {
text-anchor: end;
}
.graph {
width: 100%;
height: auto;
}
.graph .grid {
stroke: #ccc;
stroke-dasharray: 0;
stroke-width: 1;
}
.labels {
font-size: 10px;
}
.label-title {
font-weight: bold;
text-transform: uppercase;
font-size: 12px;
fill: black;
}
</style>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,13 +1,172 @@
package routes
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/uberswe/golang-base-project/models"
"gorm.io/gorm"
"log"
"math"
"net/http"
"sort"
"time"
)
type AdminData struct {
PageData
Chart Chart
}
type Chart struct {
Title string
XLabel string
YLabel string
XValues []ChartValue
YValues []ChartValue
Points string
}
type ChartValue struct {
Value string
X int
Y int
}
type MonthlySignUps struct {
Year int
Month int
Count int
}
// Admin renders the admin dashboard
func (controller Controller) Admin(c *gin.Context) {
pd := controller.DefaultPageData(c)
pd.Title = pd.Trans("Admin")
c.HTML(http.StatusOK, "admin.html", pd)
ad := AdminData{
PageData: pd,
Chart: Chart{
Title: "Monthly User Sign Ups",
XLabel: "Month",
YLabel: "Users",
},
}
var msu []MonthlySignUps
res := controller.db.Model(&models.User{}).
Select("YEAR(created_at) as year, MONTH(created_at) AS month, COUNT(*) AS count").
Where("created_at >= CURDATE() - INTERVAL 1 YEAR").
Group("YEAR(created_at), MONTH(created_at)").
Find(&msu)
if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
pd.Messages = append(pd.Messages, Message{
Type: "error",
Content: "Something went wrong while fetching user data",
})
log.Println(res.Error)
c.HTML(http.StatusInternalServerError, "admin.html", ad)
return
}
// TODO make this into a graph package or find a go package to replace this manual code
// Add 0 values
y, m, _ := time.Now().AddDate(-1, 0, 0).Date()
ny, nm, _ := time.Now().Date()
for y < ny || m < nm {
found := false
for _, ms := range msu {
if ms.Year == y && ms.Month == int(m) {
found = true
}
}
if !found {
msu = append(msu, MonthlySignUps{
Year: y,
Month: int(m),
Count: 0,
})
}
m++
if m > 12 {
m = 1
y++
}
}
// Sort our values so that the graph shows left to right going from earliest to latest date
sort.Slice(msu, func(i, j int) bool {
if msu[i].Year < msu[j].Year {
return true
} else if msu[i].Year == msu[j].Year && msu[i].Month < msu[j].Month {
return true
}
return false
})
rightAdjust := 50
base := 350
lowest := math.MaxInt32
highest := 0
count := len(msu)
for _, m := range msu {
if lowest > m.Count {
lowest = m.Count
}
if highest < m.Count {
highest = m.Count
}
}
diff := highest - lowest
if diff < 5 {
diff = 5
}
// We multiply counts by this ratio after subtracting our lowest
ratio := base / diff
// y line is 90 x to 705 x at 370 y
maxX := 705
minX := 90
seg := (maxX - minX) / count
var yValues []ChartValue
for i, m := range msu {
yValues = append(yValues, ChartValue{
Value: fmt.Sprintf("%d-%d", m.Year, m.Month),
X: rightAdjust + 20 + minX + (i * seg),
Y: base + 40,
})
}
// x line is 5 y to 371 y on 90 x
var xValues []ChartValue
seg2 := diff / 5
for i := 0; i <= 5; i++ {
xValues = append(xValues, ChartValue{
Value: fmt.Sprintf("%d", lowest+(seg2*i)+1),
X: minX - 20,
Y: (base - 10) - (i * seg2 * ratio),
})
}
points := ""
for i, m := range msu {
if i > 0 {
points += " "
}
points += fmt.Sprintf("%d,%d", rightAdjust+minX+(i*seg), (base+20)-(m.Count*ratio))
}
ad.Chart.XValues = xValues
ad.Chart.YValues = yValues
ad.Chart.Points = points
// The chart is inverted so we need to subtract the base value to our calculated values
c.HTML(http.StatusOK, "admin.html", ad)
}