feat(dashboard): #192 include treemap of treasure chests
This commit was merged in pull request #195.
This commit is contained in:
@@ -29,7 +29,8 @@ func NewDashboard(r *Render, d *service.Dashboard) Dashboard {
|
||||
|
||||
func (handler DashboardImpl) Handle(router *http.ServeMux) {
|
||||
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 {
|
||||
@@ -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) {
|
||||
updateSpan(r)
|
||||
|
||||
@@ -74,6 +75,9 @@ func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc {
|
||||
|
||||
_, err = fmt.Fprintf(w, `
|
||||
{
|
||||
"aria": {
|
||||
"enabled": true
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "axis",
|
||||
"formatter": "<updated by client>"
|
||||
@@ -105,3 +109,53 @@ 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
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"data": [%s],
|
||||
"type": "treemap",
|
||||
"name": "Savings"
|
||||
}
|
||||
]
|
||||
}
|
||||
`, data)
|
||||
if err != nil {
|
||||
slog.InfoContext(r.Context(), "could not write response", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,3 +75,40 @@ func (s Dashboard) MainChart(
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ func (s TreasureChestImpl) GetAll(ctx context.Context, user *types.User) ([]*typ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sortTree(treasureChests), nil
|
||||
return sortTreasureChests(treasureChests), nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func sortTree(nodes []*types.TreasureChest) []*types.TreasureChest {
|
||||
func sortTreasureChests(nodes []*types.TreasureChest) []*types.TreasureChest {
|
||||
var (
|
||||
roots []*types.TreasureChest
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package dashboard
|
||||
|
||||
templ Dashboard() {
|
||||
<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>
|
||||
}
|
||||
|
||||
@@ -22,3 +22,9 @@ type DashboardMainChartEntry struct {
|
||||
Value int64
|
||||
Savings int64
|
||||
}
|
||||
|
||||
type DashboardTreasureChest struct {
|
||||
Name string
|
||||
Value int64
|
||||
Children []DashboardTreasureChest
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Initialize the echarts instance based on the prepared dom
|
||||
|
||||
async function init() {
|
||||
const element = document.getElementById('graph')
|
||||
async function initMainChart() {
|
||||
const element = document.getElementById('main-chart')
|
||||
if (element === null) {
|
||||
return;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ async function init() {
|
||||
var myChart = echarts.init(element);
|
||||
|
||||
try {
|
||||
const response = await fetch("/dashboard/dataset");
|
||||
const response = await fetch("/dashboard/main-chart");
|
||||
if (!response.ok) {
|
||||
throw new Error(`Response status: ${response.status}`);
|
||||
}
|
||||
@@ -27,12 +27,38 @@ async function init() {
|
||||
myChart.resize();
|
||||
});
|
||||
|
||||
console.log("initialized charts");
|
||||
console.log("initialized main-chart");
|
||||
} catch (error) {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user