feat(dashboard): #192 include treemap of treasure chests
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled

This commit is contained in:
2025-06-20 21:28:47 +02:00
parent 3120c19669
commit a201b818fc
6 changed files with 138 additions and 14 deletions

View File

@@ -29,7 +29,8 @@ func NewDashboard(r *Render, d *service.Dashboard) Dashboard {
func (handler DashboardImpl) Handle(router *http.ServeMux) { func (handler DashboardImpl) Handle(router *http.ServeMux) {
router.Handle("GET /dashboard", handler.handleDashboard()) router.Handle("GET /dashboard", handler.handleDashboard())
router.Handle("GET /dashboard/dataset", handler.handleDashboardDataset()) router.Handle("GET /dashboard/main-chart", handler.handleDashboardMainChart())
router.Handle("GET /dashboard/treasure-chests", handler.handleDashboardTreasureChests())
} }
func (handler DashboardImpl) handleDashboard() http.HandlerFunc { func (handler DashboardImpl) handleDashboard() http.HandlerFunc {
@@ -47,7 +48,7 @@ func (handler DashboardImpl) handleDashboard() http.HandlerFunc {
} }
} }
func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc { func (handler DashboardImpl) handleDashboardMainChart() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
updateSpan(r) updateSpan(r)
@@ -105,3 +106,56 @@ func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc {
} }
} }
} }
func (handler DashboardImpl) handleDashboardTreasureChests() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
updateSpan(r)
user := middleware.GetUser(r)
treeList, err := handler.d.TreasureChests(r.Context(), user)
if err != nil {
handleError(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
data := ""
for _, item := range treeList {
children := ""
for _, child := range item.Children {
if child.Value < 0 {
children += fmt.Sprintf(`{"name":"%s\n%.2f €","value":%d},`, child.Name, float64(child.Value)/100, -child.Value)
} else {
children += fmt.Sprintf(`{"name":"%s\n%.2f €","value":%d},`, child.Name, float64(child.Value)/100, child.Value)
}
}
children = children[:len(children)-1]
data += fmt.Sprintf(`{"name":"%s","children":[%s]},`, item.Name, children)
}
data = data[:len(data)-1]
_, err = fmt.Fprintf(w, `
{
"aria": {
"enabled": true,
"decal": {
"show": false
}
},
"series": [
{
"data": [%s],
"type": "treemap",
"name": "Savings"
}
]
}
`, data)
if err != nil {
slog.InfoContext(r.Context(), "could not write response", "err", err)
}
}
}

View File

@@ -75,3 +75,40 @@ func (s Dashboard) MainChart(
return timeEntries, nil return timeEntries, nil
} }
func (s Dashboard) TreasureChests(
ctx context.Context,
user *types.User,
) ([]*types.DashboardTreasureChest, error) {
if user == nil {
return nil, ErrUnauthorized
}
treasureChests := make([]*types.TreasureChest, 0)
err := s.db.SelectContext(ctx, &treasureChests, `SELECT * FROM treasure_chest WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError(ctx, "dashboard TreasureChests", nil, err)
if err != nil {
return nil, err
}
treasureChests = sortTreasureChests(treasureChests)
result := make([]*types.DashboardTreasureChest, 0)
for _, t := range treasureChests {
if t.ParentId == nil {
result = append(result, &types.DashboardTreasureChest{
Name: t.Name,
Value: t.CurrentBalance,
Children: make([]types.DashboardTreasureChest, 0),
})
} else {
result[len(result)-1].Children = append(result[len(result)-1].Children, types.DashboardTreasureChest{
Name: t.Name,
Value: t.CurrentBalance,
Children: make([]types.DashboardTreasureChest, 0),
})
}
}
return result, nil
}

View File

@@ -205,7 +205,7 @@ func (s TreasureChestImpl) GetAll(ctx context.Context, user *types.User) ([]*typ
return nil, err return nil, err
} }
return sortTree(treasureChests), nil return sortTreasureChests(treasureChests), nil
} }
func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr string) error { func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr string) error {
@@ -277,7 +277,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
return nil return nil
} }
func sortTree(nodes []*types.TreasureChest) []*types.TreasureChest { func sortTreasureChests(nodes []*types.TreasureChest) []*types.TreasureChest {
var ( var (
roots []*types.TreasureChest roots []*types.TreasureChest
) )

View File

@@ -2,6 +2,7 @@ package dashboard
templ Dashboard() { templ Dashboard() {
<div class="mt-10 h-full"> <div class="mt-10 h-full">
<div id="graph" class="h-96"></div> <div id="main-chart" class="h-96"></div>
<div id="treasure-chests" class="h-96"></div>
</div> </div>
} }

View File

@@ -22,3 +22,9 @@ type DashboardMainChartEntry struct {
Value int64 Value int64
Savings int64 Savings int64
} }
type DashboardTreasureChest struct {
Name string
Value int64
Children []DashboardTreasureChest
}

View File

@@ -1,7 +1,7 @@
// Initialize the echarts instance based on the prepared dom // Initialize the echarts instance based on the prepared dom
async function init() { async function initMainChart() {
const element = document.getElementById('graph') const element = document.getElementById('main-chart')
if (element === null) { if (element === null) {
return; return;
} }
@@ -9,7 +9,7 @@ async function init() {
var myChart = echarts.init(element); var myChart = echarts.init(element);
try { try {
const response = await fetch("/dashboard/dataset"); const response = await fetch("/dashboard/main-chart");
if (!response.ok) { if (!response.ok) {
throw new Error(`Response status: ${response.status}`); throw new Error(`Response status: ${response.status}`);
} }
@@ -27,12 +27,38 @@ async function init() {
myChart.resize(); myChart.resize();
}); });
console.log("initialized charts"); console.log("initialized main-chart");
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);
} }
// Display the chart using the configuration items and data just specified.
} }
init(); async function initTreasureChests() {
const element = document.getElementById('treasure-chests')
if (element === null) {
return;
}
var myChart = echarts.init(element);
try {
const response = await fetch("/dashboard/treasure-chests");
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const option = await response.json();
const chart = myChart.setOption(option);
window.addEventListener('resize', function() {
myChart.resize();
});
console.log("initialized treasure-chests");
} catch (error) {
console.error(error.message);
}
}
initMainChart();
initTreasureChests();