added sparkline at-a-glaces

This commit is contained in:
Nickiel12 2024-01-19 21:05:08 -08:00
parent 6b2f5b577b
commit eb339feb6b
9 changed files with 200 additions and 73 deletions

View file

@ -28,9 +28,10 @@ type HumanLegibleTransaction struct {
type ChartjsData struct {
Labels []string `json:"labels"`
DataSets []DataSet `json:"datasets"`
}
type DataSet struct {
Data []int `json:"data"`
}
type TwoIntsItem struct {
Item1 int
Item2 int
}

View file

@ -8,18 +8,23 @@ templ dashboard() {
<div class="c-s0 d-flex w-100 cr-top">
<i class="my-auto c-text py-3 ps-3 ms-1" data-feather="arrow-left"></i>
<span class="mx-auto my-auto c-text">Income/Expenses</span>
<i class="my-auto c-text py-3 pe-3 me-1 ms-auto" data-feather="arrow-right"></i>
<i class="my-auto c-text py-3 pe-3 me-1" data-feather="arrow-right"></i>
</div>
<div class="h-100">
<div class="w-50 h-100">
<div class="d-flex" style="height: 88%">
<div class="w-50">
<canvas
class="chartjs-chart"
data-chart-endpoint="/web/dashboard/expenditure_chart"
data-chart-type="bar"
data-chart-type="historical_vs_current"
id="IncomeVsExpenditureChart"
></canvas>
</div>
<div class="c-mantle">
<div class="w-50 c-s1" style="overflow-y: scroll">
<div class="m-4 my-5"
hx-trigger="load delay:0.25s"
hx-get="web/account_summaries"
>
</div>
</div>
</div>

View file

@ -8,6 +8,7 @@ import (
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"nickiel.net/recount_server/types"
)
@ -56,3 +57,28 @@ func getExpenditureChart(w http.ResponseWriter, req *http.Request) {
w.Write(json_data);
}
func getAccountSummaries(w http.ResponseWriter, req *http.Request) {
accounts := make([]types.TwoIntsItem, 20)
for i := 0; i < 20; i++ {
accounts[i] = types.TwoIntsItem {Item1: i*100, Item2: i+5}
}
component := account_summary_rows(&accounts)
component.Render(context.Background(), w)
}
func getAccountSummaryChart(w http.ResponseWriter, req *http.Request) {
accountID := chi.URLParam(req, "accountID")
data_package := types.ChartjsData {
Labels: []string {accountID, "1/10", "1/17", "1/24"},
Data: []int {100, 0, -50, 25},
}
json_data, err := json.Marshal(data_package);
if err != nil {
log.Fatal().Err(err).Msg("Could not jsonify data_package");
}
w.Write(json_data);
}

View file

@ -26,3 +26,33 @@ templ transaction_rows(transactions *[]types.HumanLegibleTransaction) {
</tr>
}
}
templ account_summary_rows(accounts *[]types.TwoIntsItem){
for _, value := range *accounts {
<div class="c-crust m-2" style="height: 90px">
<div class="w-100 d-flex" style="height:15px">
<span class="mx-auto">Account: {strconv.Itoa(value.Item2)}</span>
</div>
<div class="d-flex w-100">
<div class="w-75" style="height:75px">
<canvas
class="chartjs-chart sparkline-chart"
data-chart-endpoint={"/web/dashboard/account_summary/" + strconv.Itoa(value.Item2)}
data-chart-type="sparkline_summary"
></canvas>
</div>
<div class="w-25 d-flex">
if value.Item1 > 0 {
<span class="my-auto w-100 t-m cr-all c-mantle" style="color: var(--pf-green)">
-${strconv.Itoa(value.Item1)}
</span>
} else {
<span class="my-auto w-100 t-m cr-all c-mantle" style="color: var(--pf-red)">
+${strconv.Itoa(value.Item1)}
</span>
}
</div>
</div>
</div>
}
}

View file

@ -28,7 +28,9 @@ func WebRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getIndex)
r.Get("/web/transaction_table_rows", getTransactions)
r.Get("/web/account_summaries", getAccountSummaries)
r.Get("/web/dashboard/expenditure_chart", getExpenditureChart)
r.Get("/web/dashboard/account_summary/{accountID}", getAccountSummaryChart)
r.Get("/hello", getHello)
r.Handle("/chart.js/*", http.StripPrefix("/chart.js/", http.FileServer(http.Dir("web/node_modules/chart.js/"))))
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static/"))))

View file

@ -20,6 +20,9 @@ $directions: (
.m-#{$size} {
margin: $val;
}
.p-#{$size} {
padding: $val;
}
@each $dir, $dir-val in $directions {
.m#{$dir}-#{$size} {
margin-#{$dir-val}: $val;
@ -35,7 +38,7 @@ $directions: (
}
.my-#{$size} {
margin-top: $val;
margin-right: $val;
margin-bottom: $val;
}
.px-#{$size} {
padding-left: $val;
@ -46,6 +49,9 @@ $directions: (
padding-bottom: $val;
}
}
.m-auto {
margin: auto;
}
.my-auto {
margin-top: auto;
margin-bottom: auto;
@ -107,6 +113,9 @@ $w_h_sizes: (
.t-e {
text-align: end;
}
.t-m {
text-align: center;
}
table.table {
color: var(--#{$prefix}-text);

View file

@ -27,24 +27,13 @@ function createDiagonalPattern(color = '#ffffff') {
return c.createPattern(shape, 'repeat')
}
function fill_charts() {
function historical_vs_current_chart(jsonData, element) {
const style = getComputedStyle(document.body);
const red_color = style.getPropertyValue("--pf-red");
const green_color = style.getPropertyValue("--pf-green");
document.querySelectorAll(".chartjs-chart").forEach(function (el) {
var url = el.dataset.chartEndpoint;
var type = el.dataset.chartType;
fetch(url)
.then(response => {
if (!response.ok) {
console.log(response);
throw new Error("Fetch response was not ok!")
}
return response.json();
}).then(jsonData => {
const legend_bg = style.getPropertyValue("--pf-overlay2");
const config = {
type: type,
type: "bar",
data: {
labels: jsonData.labels,
datasets: [{
@ -88,9 +77,9 @@ function fill_charts() {
var labels = Chart.defaults.plugins.legend.labels.generateLabels(chart);
for (var key in labels) {
if (labels[key].text == "Historical") {
labels[key].fillStyle = createDiagonalPattern("#888888");
labels[key].fillStyle = createDiagonalPattern(legend_bg);
} else {
labels[key].fillStyle = "#88888840"
labels[key].fillStyle = legend_bg;
}
labels[key].strokeStyle = "rgba(33, 44, 22, 0.7)";
@ -102,7 +91,70 @@ function fill_charts() {
}
}
};
new Chart(el, config);
new Chart(element, config);
}
function sparkline_summary_chart(jsonData, element) {
const style = getComputedStyle(document.body);
const red_color = style.getPropertyValue("--pf-red");
const green_color = style.getPropertyValue("--pf-green");
const legend_bg = style.getPropertyValue("--pf-overlay2");
const config = {
type: "line",
data: {
labels: jsonData.labels,
datasets: [{
label: "Historical",
data: jsonData.data
}]
},
options: {
maintainAspectRatio: false,
responsive: true,
scales: {
y: {
ticks: {
display: true,
callback: function(value, index, values) {
if (value === 0) {
return value;
} else {
return null;
}
},
},
suggestedMin: 5
}
},
plugins: {
legend: {
display: false
}
}
},
};
new Chart(element, config);
}
function fill_charts() {
document.querySelectorAll(".chartjs-chart").forEach(function (el) {
var url = el.dataset.chartEndpoint;
var type = el.dataset.chartType;
fetch(url)
.then(response => {
if (!response.ok) {
console.log(response);
throw new Error("Fetch response was not ok!")
}
return response.json();
}).then(jsonData => {
if (type == "historical_vs_current") {
historical_vs_current_chart(jsonData, el);
} else if (type == "sparkline_summary") {
sparkline_summary_chart(jsonData, el);
}
}).catch(error => {
console.error("Unable to set up chart: ", error)
});

View file

@ -50,4 +50,6 @@ function load_in_table() {
const trigger_table_animation = debounce(load_in_table, 100);
export {register_handlers, trigger_table_animation, fill_charts};
const fill_all_charts = debounce(fill_charts, 500);
export {register_handlers, trigger_table_animation, fill_all_charts};

View file

@ -61,14 +61,14 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" integrity="sha512-CQBWl4fJHWbryGE+Pc7UAxWMUMNMWzWxF4SQo9CgkJIN1kx6djDQZjh3Y8SZ1d+6I+1zze6Z7kHXO7q3UyZAWw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="module" src="/static/index.js"></script>
<script type="module">
import {register_handlers, fill_charts, trigger_table_animation} from "/static/index.js";
import {register_handlers, fill_all_charts, trigger_table_animation} from "/static/index.js";
register_handlers();
feather.replace();
htmx.onLoad(function (element) {
if (element.localName === "tr") {
trigger_table_animation();
} else {
fill_charts();
fill_all_charts();
}
});
</script>