added transaction page

This commit is contained in:
Nickiel12 2024-01-20 15:05:06 -08:00
parent eb339feb6b
commit 48470e905a
11 changed files with 402 additions and 48 deletions

195
2 Normal file
View file

@ -0,0 +1,195 @@
$sizes: (
"auto": auto,
"0": 0px,
"1": 2px,
"2": 5px,
"3": 8px,
"4": 10px,
"5": 15px
);
$directions: (
"s": "left",
"e": "right",
"t": "top",
"b": "bottom"
);
@each $size, $val in $sizes {
.m-#{$size} {
margin: $val;
}
.p-#{$size} {
padding: $val;
}
@each $dir, $dir-val in $directions {
.m#{$dir}-#{$size} {
margin-#{$dir-val}: $val;
}
.p#{$dir}-#{$size} {
padding-#{$dir-val}: $val;
}
}
.mx-#{$size} {
margin-left: $val;
margin-right: $val;
}
.my-#{$size} {
margin-top: $val;
margin-bottom: $val;
}
.px-#{$size} {
padding-left: $val;
padding-right: $val;
}
.py-#{$size} {
padding-top: $val;
padding-bottom: $val;
}
}
.m-auto {
margin: auto;
}
.my-auto {
margin-top: auto;
margin-bottom: auto;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
$w_h_sizes: (
'25': 25%,
'50': 50%,
'75': 75%,
'80': 80%,
'100': 100%,
'auto': auto,
);
// Loop through the sizes and generate classes
@each $name, $value in $w_h_sizes {
.h-#{$name} {
height: $value;
}
.w-#{$name} {
width: $value;
}
}
.d-flex {
display: flex;
}
.d-flex-col {
display: flex;
flex-direction: column;
}
.d-block {
display: block;
}
.cr-all {
border-radius: $border-radius;
}
.cr-top {
border-radius: $border-radius $border-radius 0px 0px;
}
.cr-right {
border-radius: 0px $border-radius $border-radius 0px;
}
.cr-left {
border-radius: $border-radius 0px 0px $border-radius;
}
.cr-bottom {
border-radius: 0px 0px $border-radius $border-radius;
}
.t-s {
text-align: start;
}
.t-e {
text-align: end;
}
.t-m {
text-align: center;
}
.borderless-btn {
display: flex;
color: var(--#{$prefix}-text);
font-size: 1em;
border: none;
padding: 10px 15px 10px 15px;
border-radius: 20px;
background-color: var(--#{$prefix}-surface2);
transition: all 0.1s linear;
}
.borderless-btn:hover {
background-color: var(--#{$prefix}-overlay1);
color: var(--#{$prefix}-nav-active-color);
}
table.table {
color: var(--#{$prefix}-text);
td {
padding-right: 10px;
}
}
table.table-striped {
border-collapse: collapse;
overflow: hidden;
thead {
background-color: var(--#{$prefix}-surface0);
}
tbody {
tr {
&.row_awaiting_processing {
background-color: var(--#{$prefix}-bg) !important;
max-width: 0px;
div {
opacity: 0;
max-height: 0px;
}
}
div {
display: block;
overflow: hidden;
max-height: 50px;
transition: max-height 0.6s cubic-bezier(0.02, 0.15, 0.84, 0.98),
opacity 0.5s ease-in;
}
max-width: 100%;
transition: all 0.5s ease-in;
&:nth-child(odd) {
background-color: var(--#{$prefix}-crust);
}
&:nth-child(even) {
background-color: var(--#{$prefix}-mantle);
}
}
}
.positive {
color: var(--#{$prefix}-green);
}
.negative {
color: var(--#{$prefix}-red);
}
}
.popup-menu {
width: max-content;
height: max-content;
position: absolute;
top: 0;
left: 0;
font-size: 1em;
background-color: var(--#{$prefix}-overlay2);
color: var(--#{$prefix}-bg);
border-radius: 0px 0px $border-radius $border-radius;
transition: all 0.2s linear;
overflow: hidden;
}

View file

@ -16,7 +16,7 @@ import (
const DEFAULT_RESULT_COUNT = 20; const DEFAULT_RESULT_COUNT = 20;
func getTransactions(w http.ResponseWriter, req *http.Request) { func getTransactionsRows(w http.ResponseWriter, req *http.Request) {
transactions := make([]types.HumanLegibleTransaction, 10) transactions := make([]types.HumanLegibleTransaction, 10)
// Populate the slice with dummy data (you can replace this with your actual data) // Populate the slice with dummy data (you can replace this with your actual data)

View file

@ -1,7 +0,0 @@
package web
templ hello(name string) {
<title>Hello</title>
<div>Hello { name } </div>
<h1>SUPRISE!!!</h1>
}

View file

@ -26,12 +26,12 @@ func SetLogLevel(level zerolog.Level) {
func WebRouter() http.Handler { func WebRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/", getIndex) r.Get("/", getDashboard)
r.Get("/web/transaction_table_rows", getTransactions) r.Get("/transactions", getTransactionsPage)
r.Get("/web/transaction_table_rows", getTransactionsRows)
r.Get("/web/account_summaries", getAccountSummaries) 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("/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("/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/"))))
return r return r
@ -72,7 +72,7 @@ func renderFullPage(w http.ResponseWriter, c templ.Component, pageName string) {
} }
} }
func getIndex(w http.ResponseWriter, req *http.Request) { func getDashboard(w http.ResponseWriter, req *http.Request) {
component := dashboard(); component := dashboard();
_, ok := req.Header["Hx-Request"] _, ok := req.Header["Hx-Request"]
@ -82,20 +82,21 @@ func getIndex(w http.ResponseWriter, req *http.Request) {
log.Fatal().Err(err).Msg("Couldn't render dashboard templ template"); log.Fatal().Err(err).Msg("Couldn't render dashboard templ template");
} }
} else { } else {
renderFullPage(w, component, "index"); renderFullPage(w, component, "Dashboard");
} }
} }
func getHello(w http.ResponseWriter, req *http.Request) { func getTransactionsPage(w http.ResponseWriter, req *http.Request) {
component := hello("Nick") component := transactions_page(1);
_, ok := req.Header["Hx-Request"] _, ok := req.Header["Hx-Request"]
if ok { if ok {
err := component.Render(context.Background(), w); err := component.Render(context.Background(), w);
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Couldn't render dashboard templ template"); log.Fatal().Err(err).Msg("Couldn't render transactions templ template");
} }
}else { } else {
renderFullPage(w, component, "hello"); renderFullPage(w, component, "Transactions")
} }
}
}

View file

@ -16,13 +16,6 @@ body {
height: 97vh; height: 97vh;
} }
.container {
width: 100%;
margin-right: auto;
margin-left: auto;
}
.row { .row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View file

@ -117,6 +117,21 @@ $w_h_sizes: (
text-align: center; text-align: center;
} }
.borderless-btn {
display: flex;
color: var(--#{$prefix}-text);
font-size: 1em;
border: none;
padding: 10px 15px 10px 15px;
border-radius: 20px;
background-color: var(--#{$prefix}-surface2);
transition: all 0.1s linear;
}
.borderless-btn:hover {
background-color: var(--#{$prefix}-overlay1);
color: var(--#{$prefix}-nav-active-color);
}
table.table { table.table {
color: var(--#{$prefix}-text); color: var(--#{$prefix}-text);
td { td {
@ -164,3 +179,17 @@ table.table-striped {
color: var(--#{$prefix}-red); color: var(--#{$prefix}-red);
} }
} }
.popup-menu {
width: max-content;
height: max-content;
position: absolute;
top: 0;
left: 0;
font-size: 1em;
background-color: var(--#{$prefix}-overlay2);
color: var(--#{$prefix}-bg);
border-radius: 0px 0px $border-radius $border-radius;
transition: all 0.2s linear;
overflow: hidden;
}

View file

@ -99,14 +99,19 @@ function sparkline_summary_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");
const legend_bg = style.getPropertyValue("--pf-overlay2"); const line_color = style.getPropertyValue("--pf-overlay2");
const config = { const config = {
type: "line", type: "line",
data: { data: {
labels: jsonData.labels, labels: jsonData.labels,
datasets: [{ datasets: [{
label: "Historical", label: "Historical",
data: jsonData.data data: jsonData.data,
borderColor: line_color,
backgroundColor: function(context) {
var value = context.dataset.data[context.dataIndex];
return value < 0 ? red_color : green_color;
},
}] }]
}, },
options: { options: {

70
web/static/dropdowns.js Normal file
View file

@ -0,0 +1,70 @@
import {
computePosition,
flip,
shift,
} from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.5.4/+esm';
function update_position(button, dropdown_div) {
const placement = button.dataset.dropdownDirection;
computePosition(button, dropdown_div, {
placement: placement,
middleware: [
flip(),
shift()
],
}).then(({x, y}) => {
Object.assign(dropdown_div.style, {
left: `${x}px`,
top: `${y}px`,
});
})
}
function setup_dropdown(button) {
const dropdown_div = document.querySelector("#" + button.dataset.dropdownTarget);
update_position(button, dropdown_div);
return function () {
if (dropdown_div.style.display == "none") {
dropdown_div.style.removeProperty("display");
if (button.dataset.dropdownMotion == "expand-down") {
dropdown_div.style.display = "block";
dropdown_div.style.maxHeight = "0px";
button.style.borderBottomLeftRadius = "0px";
button.style.borderBottomRightRadius = "0px";
}
update_position(button, dropdown_div);
setTimeout(function () {
if (button.dataset.dropdownMotion == "expand-down") {
dropdown_div.style.maxHeight = "100px";
} else {
dropdown_div.style.opacity = 1;
}
}, 110);
} else {
if (button.dataset.dropdownMotion == "expand-down") {
dropdown_div.style.maxHeight = "0px";
} else {
dropdown_div.style.opacity = 0;
}
setTimeout(function () {
if (button.dataset.dropdownMotion == "expand-down") {
button.style.removeProperty("border-bottom-left-radius");
button.style.removeProperty("border-bottom-right-radius");
}
dropdown_div.style.display = "none";
}, 200);
}
};
}
function register_dropdowns() {
document.querySelectorAll(".dropdown-button").forEach(function (el) {
el.addEventListener("click", setup_dropdown(el));
});
}
export {register_dropdowns}

View file

@ -1,4 +1,5 @@
import {fill_charts} from "./chart_functions.js"; import {fill_charts} from "./chart_functions.js";
import {register_dropdowns} from "./dropdowns.js";
function debounce(func, delay) { function debounce(func, delay) {
@ -13,25 +14,29 @@ function debounce(func, delay) {
}; };
} }
function register_nav_links() { function switch_nav(event) {
var navAnchors = document.querySelectorAll("nav a");
navAnchors.forEach(function (a_el) {
a_el.addEventListener("click", function(event) {
var active_anchor = document.querySelector("nav a.active"); var active_anchor = document.querySelector("nav a.active");
active_anchor.classList.remove("active"); active_anchor.classList.remove("active");
var clicked = event.target; var clicked = event.target;
clicked.classList.add("active"); clicked.classList.add("active");
}); }
function register_nav_links() {
var navAnchors = document.querySelectorAll("nav a");
navAnchors.forEach(function (a_el) {
a_el.removeEventListener("click", switch_nav);
a_el.addEventListener("click", switch_nav);
}); });
} }
function register_handlers() { function register_handlers() {
register_nav_links(); register_nav_links();
fill_charts();
register_dropdowns();
} }
function load_in_table() { function load_in_table() {
var rows = Array.from(document.querySelectorAll(".row_awaiting_processing")); var rows = Array.from(document.querySelectorAll(".row_awaiting_processing"));
rows.sort((a, b) => b.dataset.rcntTransactionRowPos - a.dataset.rcntTransactionRowPos); rows.sort((a, b) => b.dataset.rcntTransactionRowPos - a.dataset.rcntTransactionRowPos);
@ -50,6 +55,6 @@ function load_in_table() {
const trigger_table_animation = debounce(load_in_table, 100); const trigger_table_animation = debounce(load_in_table, 100);
const fill_all_charts = debounce(fill_charts, 500); const trigger_reset_handlers = debounce(register_handlers, 200);
export {register_handlers, trigger_table_animation, fill_all_charts}; export {trigger_reset_handlers, trigger_table_animation};

View file

@ -6,6 +6,7 @@
<link rel="stylesheet" href="/static/site.css"> <link rel="stylesheet" href="/static/site.css">
<script src="/static/htmx.min.js"></script> <script src="/static/htmx.min.js"></script>
<script src="/static/feather.min.js"></script> <script src="/static/feather.min.js"></script>
<title>{{ .ActivePage }}</title>
</head> </head>
<body class="latte"> <body class="latte">
<header> <header>
@ -26,26 +27,26 @@
hx-push-url="true" hx-push-url="true"
hx-boost="true" hx-boost="true"
hx-target="#main-body-content" hx-target="#main-body-content"
{{ if (eq .ActivePage "index") }} {{ if (eq .ActivePage "Dashboard") }}
class="active" class="active"
{{ end }} {{ end }}
> >
Home! Dashboard
</a> </a>
</li> </li>
<li> <li>
<a <a
hx-get="/hello" hx-get="/transactions"
hx-swap="innerHtml swap:0.2s settle:0.2s" hx-swap="innerHtml swap:0.2s settle:0.2s"
hx-push-url="true" hx-push-url="true"
hx-boost="true" hx-boost="true"
hx-target="#main-body-content" hx-target="#main-body-content"
{{ if (eq .ActivePage "hello") }} {{ if (eq .ActivePage "Transactions") }}
class="active" class="active"
{{ end }} {{ end }}
> >
Not home! Transactions
</a> </a>
</li> </li>
</ul> </ul>
@ -61,14 +62,13 @@
<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_all_charts, trigger_table_animation} from "/static/index.js"; import {trigger_reset_handlers, trigger_table_animation} from "/static/index.js";
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_all_charts(); trigger_reset_handlers();
} }
}); });
</script> </script>

63
web/transactions.templ Normal file
View file

@ -0,0 +1,63 @@
package web
templ transactions_page(userID int) {
<title>Transactions</title>
<div class="d-flex-col mx-4 h-100">
<div class="c-crust row cr-all p-5" style="height: fit-content">
<button
class="borderless-btn dropdown-button"
aria-describedby="filter-popup"
data-dropdown-target="filter-popup"
data-dropdown-direction="bottom-start"
data-dropdown-motion="expand-down"
>
<i class="" data-feather="filter"></i>
<span class="ms-3 my-auto">Add Filter</span>
</button>
<div class="popup-menu" id="filter-popup" role="tooltip" style="display: none; max-height: 0px;">
<p>Hey there</p>
</div>
<button class="ms-5 borderless-btn">
<span class="my-auto mx-3">No Filters</span>
</button>
</div>
<div class="row mt-5">
<table class="card-table table table-striped cr-all">
<colgroup>
<col style="width: 2%"></col>
<col style="width: 15%"></col>
<col style="width: 30%"></col>
<col style="width: 15%"></col>
<col style="width: 3%"></col>
<col style="width: 1%"></col>
</colgroup>
<thead>
<tr>
<td>
</td>
<td>
<div class="d-flex">
<span class="my-auto me-0">
ID
</span>
<i class="my-auto ms-0 me-auto" data-feather="chevron-up"></i>
</div>
</td>
<td>Account</td>
<td>Date</td>
<td></td>
<td class="t-e" style="width:auto">Amount</td>
</tr>
</thead>
<tbody
hx-trigger="load delay:0.25s"
hx-get="/web/transaction_table_rows"
hx-params=""
>
</tbody>
</table>
</div>
</div>
}