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 { type ChartjsData struct {
Labels []string `json:"labels"` Labels []string `json:"labels"`
DataSets []DataSet `json:"datasets"`
}
type DataSet struct {
Data []int `json:"data"` 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"> <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> <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> <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>
<div class="h-100"> <div class="d-flex" style="height: 88%">
<div class="w-50 h-100"> <div class="w-50">
<canvas <canvas
class="chartjs-chart" class="chartjs-chart"
data-chart-endpoint="/web/dashboard/expenditure_chart" data-chart-endpoint="/web/dashboard/expenditure_chart"
data-chart-type="bar" data-chart-type="historical_vs_current"
id="IncomeVsExpenditureChart" id="IncomeVsExpenditureChart"
></canvas> ></canvas>
</div> </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>
</div> </div>

View file

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"nickiel.net/recount_server/types" "nickiel.net/recount_server/types"
) )
@ -56,3 +57,28 @@ func getExpenditureChart(w http.ResponseWriter, req *http.Request) {
w.Write(json_data); 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> </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 := chi.NewRouter()
r.Get("/", getIndex) r.Get("/", getIndex)
r.Get("/web/transaction_table_rows", getTransactions) r.Get("/web/transaction_table_rows", getTransactions)
r.Get("/web/account_summaries", getAccountSummaries)
r.Get("/web/dashboard/expenditure_chart", getExpenditureChart) r.Get("/web/dashboard/expenditure_chart", getExpenditureChart)
r.Get("/web/dashboard/account_summary/{accountID}", getAccountSummaryChart)
r.Get("/hello", getHello) r.Get("/hello", getHello)
r.Handle("/chart.js/*", http.StripPrefix("/chart.js/", http.FileServer(http.Dir("web/node_modules/chart.js/")))) 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/")))) r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static/"))))

View file

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

View file

@ -27,24 +27,13 @@ function createDiagonalPattern(color = '#ffffff') {
return c.createPattern(shape, 'repeat') return c.createPattern(shape, 'repeat')
} }
function fill_charts() { function historical_vs_current_chart(jsonData, element) {
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
const red_color = style.getPropertyValue("--pf-red"); const red_color = style.getPropertyValue("--pf-red");
const green_color = style.getPropertyValue("--pf-green"); const green_color = style.getPropertyValue("--pf-green");
document.querySelectorAll(".chartjs-chart").forEach(function (el) { const legend_bg = style.getPropertyValue("--pf-overlay2");
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 config = { const config = {
type: type, type: "bar",
data: { data: {
labels: jsonData.labels, labels: jsonData.labels,
datasets: [{ datasets: [{
@ -88,9 +77,9 @@ function fill_charts() {
var labels = Chart.defaults.plugins.legend.labels.generateLabels(chart); var labels = Chart.defaults.plugins.legend.labels.generateLabels(chart);
for (var key in labels) { for (var key in labels) {
if (labels[key].text == "Historical") { if (labels[key].text == "Historical") {
labels[key].fillStyle = createDiagonalPattern("#888888"); labels[key].fillStyle = createDiagonalPattern(legend_bg);
} else { } else {
labels[key].fillStyle = "#88888840" labels[key].fillStyle = legend_bg;
} }
labels[key].strokeStyle = "rgba(33, 44, 22, 0.7)"; 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 => { }).catch(error => {
console.error("Unable to set up chart: ", 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); 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 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" src="/static/index.js"></script>
<script type="module"> <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(); register_handlers();
feather.replace(); feather.replace();
htmx.onLoad(function (element) { htmx.onLoad(function (element) {
if (element.localName === "tr") { if (element.localName === "tr") {
trigger_table_animation(); trigger_table_animation();
} else { } else {
fill_charts(); fill_all_charts();
} }
}); });
</script> </script>