wip
This commit is contained in:
35
web/components/stats/info_card.go
Normal file
35
web/components/stats/info_card.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
type InfoCardData struct {
|
||||
Title string
|
||||
Size int64
|
||||
Link string
|
||||
}
|
||||
|
||||
func InfoCard(d InfoCardData) g.Node {
|
||||
cardContent := h.Div(
|
||||
g.Attr("class", "flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"),
|
||||
h.Div(
|
||||
g.Attr("class", "flex flex-col justify-around w-full text-sm"),
|
||||
h.P(g.Attr("class", "text-2xl font-bold"), g.Text(fmt.Sprint(d.Size))),
|
||||
h.P(g.Attr("class", "text-sm text-gray-400"), g.Text(d.Title)),
|
||||
),
|
||||
)
|
||||
|
||||
if d.Link == "" {
|
||||
return h.Div(g.Attr("class", "w-full"), cardContent)
|
||||
}
|
||||
|
||||
return h.A(
|
||||
g.Attr("class", "w-full"),
|
||||
h.Href(d.Link),
|
||||
cardContent,
|
||||
)
|
||||
}
|
||||
130
web/components/stats/leaderboard_card.go
Normal file
130
web/components/stats/leaderboard_card.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
type LeaderboardItem struct {
|
||||
UserID string
|
||||
Value string
|
||||
}
|
||||
|
||||
type LeaderboardData struct {
|
||||
Name string
|
||||
All []LeaderboardItem
|
||||
Year []LeaderboardItem
|
||||
Month []LeaderboardItem
|
||||
Week []LeaderboardItem
|
||||
}
|
||||
|
||||
func LeaderboardCard(l LeaderboardData) g.Node {
|
||||
orderedItems := map[string][]LeaderboardItem{
|
||||
"All": l.All,
|
||||
"Year": l.Year,
|
||||
"Month": l.Month,
|
||||
"Week": l.Week,
|
||||
}
|
||||
|
||||
var allNodes []g.Node
|
||||
for key, items := range orderedItems {
|
||||
// Get Top Reader Nodes
|
||||
topReaders := items[:min(len(items), 3)]
|
||||
var topReaderNodes []g.Node
|
||||
for idx, reader := range topReaders {
|
||||
border := ""
|
||||
if idx > 0 {
|
||||
border = " border-t border-gray-200"
|
||||
}
|
||||
topReaderNodes = append(topReaderNodes, h.Div(
|
||||
g.Attr("class", "flex items-center justify-between pt-2 pb-2 text-sm"+border),
|
||||
h.Div(h.P(g.Text(reader.UserID))),
|
||||
h.Div(g.Attr("class", "flex items-end font-bold"), g.Text(reader.Value)),
|
||||
))
|
||||
}
|
||||
|
||||
allNodes = append(allNodes, g.Group([]g.Node{
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-end my-6 space-x-2 hidden peer-checked/"+key+":block"),
|
||||
g.If(len(items) == 0,
|
||||
h.P(g.Attr("class", "text-5xl font-bold text-black dark:text-white"), g.Text("N/A")),
|
||||
),
|
||||
g.If(len(items) > 0,
|
||||
h.P(g.Attr("class", "text-5xl font-bold text-black dark:text-white"), g.Text(items[0].UserID)),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "hidden dark:text-white peer-checked/"+key+":block"),
|
||||
g.Group(topReaderNodes),
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
return h.Div(
|
||||
g.Attr("class", "w-full"),
|
||||
h.Div(
|
||||
g.Attr("class", "flex flex-col justify-between h-full w-full px-4 py-6 bg-white shadow-lg dark:bg-gray-700 rounded"),
|
||||
h.Div(
|
||||
h.Div(
|
||||
g.Attr("class", "flex justify-between"),
|
||||
h.P(
|
||||
g.Attr("class", "text-sm font-semibold text-gray-700 border-b border-gray-200 w-max dark:text-white dark:border-gray-500"),
|
||||
g.Textf("%s Leaderboard", l.Name),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex gap-2 text-xs text-gray-400 items-center"),
|
||||
h.Label(
|
||||
g.Attr("for", fmt.Sprintf("all-%s", l.Name)),
|
||||
g.Attr("class", "cursor-pointer hover:text-black dark:hover:text-white"),
|
||||
g.Text("all"),
|
||||
),
|
||||
h.Label(
|
||||
g.Attr("for", fmt.Sprintf("year-%s", l.Name)),
|
||||
g.Attr("class", "cursor-pointer hover:text-black dark:hover:text-white"),
|
||||
g.Text("year"),
|
||||
),
|
||||
h.Label(
|
||||
g.Attr("for", fmt.Sprintf("month-%s", l.Name)),
|
||||
g.Attr("class", "cursor-pointer hover:text-black dark:hover:text-white"),
|
||||
g.Text("month"),
|
||||
),
|
||||
h.Label(
|
||||
g.Attr("for", fmt.Sprintf("week-%s", l.Name)),
|
||||
g.Attr("class", "cursor-pointer hover:text-black dark:hover:text-white"),
|
||||
g.Text("week"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
h.Input(
|
||||
g.Attr("type", "radio"),
|
||||
g.Attr("name", fmt.Sprintf("options-%s", l.Name)),
|
||||
g.Attr("id", fmt.Sprintf("all-%s", l.Name)),
|
||||
g.Attr("class", "hidden peer/All"),
|
||||
g.Attr("checked", ""),
|
||||
),
|
||||
h.Input(
|
||||
g.Attr("type", "radio"),
|
||||
g.Attr("name", fmt.Sprintf("options-%s", l.Name)),
|
||||
g.Attr("id", fmt.Sprintf("year-%s", l.Name)),
|
||||
g.Attr("class", "hidden peer/Year"),
|
||||
),
|
||||
h.Input(
|
||||
g.Attr("type", "radio"),
|
||||
g.Attr("name", fmt.Sprintf("options-%s", l.Name)),
|
||||
g.Attr("id", fmt.Sprintf("month-%s", l.Name)),
|
||||
g.Attr("class", "hidden peer/Month"),
|
||||
),
|
||||
h.Input(
|
||||
g.Attr("type", "radio"),
|
||||
g.Attr("name", fmt.Sprintf("options-%s", l.Name)),
|
||||
g.Attr("id", fmt.Sprintf("week-%s", l.Name)),
|
||||
g.Attr("class", "hidden peer/Week"),
|
||||
),
|
||||
g.Group(allNodes),
|
||||
),
|
||||
)
|
||||
}
|
||||
61
web/components/stats/monthly_chart.go
Normal file
61
web/components/stats/monthly_chart.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/graph"
|
||||
)
|
||||
|
||||
func MonthlyChart(dailyStats []database.GetDailyReadStatsRow) g.Node {
|
||||
graphData := buildSVGGraphData(dailyStats, 800, 70)
|
||||
return h.Div(
|
||||
g.Attr("class", "relative"),
|
||||
h.SVG(
|
||||
g.Attr("viewBox", fmt.Sprintf("26 0 755 %d", graphData.Height)),
|
||||
g.Attr("preserveAspectRatio", "none"),
|
||||
g.Attr("width", "100%"),
|
||||
g.Attr("height", "6em"),
|
||||
g.El("path",
|
||||
g.Attr("fill", "#316BBE"),
|
||||
g.Attr("fill-opacity", "0.5"),
|
||||
g.Attr("stroke", "none"),
|
||||
g.Attr("d", graphData.BezierPath+" "+graphData.BezierFill),
|
||||
),
|
||||
g.El("path",
|
||||
g.Attr("fill", "none"),
|
||||
g.Attr("stroke", "#316BBE"),
|
||||
g.Attr("d", graphData.BezierPath),
|
||||
),
|
||||
),
|
||||
|
||||
h.Div(
|
||||
g.Attr("class", "flex absolute w-full h-full top-0"),
|
||||
g.Attr("style", "width: calc(100%*31/30); transform: translateX(-50%); left: 50%"),
|
||||
g.Group(g.Map(dailyStats, func(d database.GetDailyReadStatsRow) g.Node {
|
||||
return h.Div(
|
||||
g.Attr("onclick", ""),
|
||||
g.Attr("class", "opacity-0 hover:opacity-100 w-full"),
|
||||
g.Attr("style", "background: linear-gradient(rgba(128, 128, 128, 0.5), rgba(128, 128, 128, 0.5)) no-repeat center/2px 100%"),
|
||||
h.Div(
|
||||
g.Attr("class", "flex flex-col items-center p-2 rounded absolute top-3 dark:text-white text-xs pointer-events-none"),
|
||||
g.Attr("style", "transform: translateX(-50%); background-color: rgba(128, 128, 128, 0.2); left: 50%"),
|
||||
h.Span(g.Text(d.Date)),
|
||||
h.Span(g.Textf("%d minutes", d.MinutesRead)),
|
||||
),
|
||||
)
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// buildSVGGraphData builds SVGGraphData from the provided stats, width and height.
|
||||
func buildSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) graph.SVGGraphData {
|
||||
var intData []int64
|
||||
for _, item := range inputData {
|
||||
intData = append(intData, item.MinutesRead)
|
||||
}
|
||||
return graph.GetSVGGraphData(intData, svgWidth, svgHeight)
|
||||
}
|
||||
65
web/components/stats/streak_card.go
Normal file
65
web/components/stats/streak_card.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/database"
|
||||
)
|
||||
|
||||
func StreakCard(s database.UserStreak) g.Node {
|
||||
return h.Div(
|
||||
g.Attr("class", "w-full"),
|
||||
h.Div(
|
||||
g.Attr("class", "relative w-full px-4 py-6 bg-white shadow-lg dark:bg-gray-700 rounded"),
|
||||
h.P(
|
||||
g.Attr("class", "text-sm font-semibold text-gray-700 border-b border-gray-200 w-max dark:text-white dark:border-gray-500"),
|
||||
g.If(s.Window == "WEEK", g.Text("Weekly Read Streak")),
|
||||
g.If(s.Window != "WEEK", g.Text("Daily Read Streak")),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-end my-6 space-x-2"),
|
||||
h.P(
|
||||
g.Attr("class", "text-5xl font-bold text-black dark:text-white"),
|
||||
g.Textf("%d", s.CurrentStreak),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "dark:text-white"),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-center justify-between pb-2 mb-2 text-sm border-b border-gray-200"),
|
||||
h.Div(
|
||||
h.P(
|
||||
g.If(s.Window == "WEEK", g.Text("Current Weekly Streak")),
|
||||
g.If(s.Window != "WEEK", g.Text("Current Daily Streak")),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-end text-sm text-gray-400"),
|
||||
g.Textf("%s ➞ %s", s.CurrentStreakStartDate, s.CurrentStreakEndDate),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-end font-bold"),
|
||||
g.Textf("%d", s.CurrentStreak),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-center justify-between pb-2 mb-2 text-sm"),
|
||||
h.Div(
|
||||
h.P(
|
||||
g.If(s.Window == "WEEK", g.Text("Best Weekly Streak")),
|
||||
g.If(s.Window != "WEEK", g.Text("Best Daily Streak")),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-end text-sm text-gray-400"),
|
||||
g.Textf("%s ➞ %s", s.MaxStreakStartDate, s.MaxStreakEndDate),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-end font-bold"),
|
||||
g.Textf("%d", s.MaxStreak),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user