feat(dashboard): #192 include treemap of treasure chests
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,9 @@ type DashboardMainChartEntry struct {
|
|||||||
Value int64
|
Value int64
|
||||||
Savings 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
|
// 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}`);
|
||||||
}
|
}
|
||||||
@@ -24,15 +24,41 @@ async function init() {
|
|||||||
|
|
||||||
const chart = myChart.setOption(option);
|
const chart = myChart.setOption(option);
|
||||||
window.addEventListener('resize', function() {
|
window.addEventListener('resize', function() {
|
||||||
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user