added transaction page
This commit is contained in:
parent
eb339feb6b
commit
48470e905a
11 changed files with 402 additions and 48 deletions
195
2
Normal file
195
2
Normal 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;
|
||||
}
|
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
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)
|
||||
|
||||
// Populate the slice with dummy data (you can replace this with your actual data)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package web
|
||||
|
||||
templ hello(name string) {
|
||||
<title>Hello</title>
|
||||
<div>Hello { name } </div>
|
||||
<h1>SUPRISE!!!</h1>
|
||||
}
|
|
@ -26,12 +26,12 @@ func SetLogLevel(level zerolog.Level) {
|
|||
|
||||
func WebRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getIndex)
|
||||
r.Get("/web/transaction_table_rows", getTransactions)
|
||||
r.Get("/", getDashboard)
|
||||
r.Get("/transactions", getTransactionsPage)
|
||||
r.Get("/web/transaction_table_rows", getTransactionsRows)
|
||||
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/"))))
|
||||
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();
|
||||
|
||||
_, 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");
|
||||
}
|
||||
} else {
|
||||
renderFullPage(w, component, "index");
|
||||
renderFullPage(w, component, "Dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
func getHello(w http.ResponseWriter, req *http.Request) {
|
||||
component := hello("Nick")
|
||||
func getTransactionsPage(w http.ResponseWriter, req *http.Request) {
|
||||
component := transactions_page(1);
|
||||
|
||||
_, ok := req.Header["Hx-Request"]
|
||||
if ok {
|
||||
err := component.Render(context.Background(), w);
|
||||
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 {
|
||||
renderFullPage(w, component, "hello");
|
||||
}
|
||||
renderFullPage(w, component, "Transactions")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,13 +16,6 @@ body {
|
|||
height: 97vh;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -117,6 +117,21 @@ $w_h_sizes: (
|
|||
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 {
|
||||
|
@ -164,3 +179,17 @@ table.table-striped {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -99,14 +99,19 @@ 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 line_color = style.getPropertyValue("--pf-overlay2");
|
||||
const config = {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: jsonData.labels,
|
||||
datasets: [{
|
||||
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: {
|
||||
|
|
70
web/static/dropdowns.js
Normal file
70
web/static/dropdowns.js
Normal 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}
|
|
@ -1,4 +1,5 @@
|
|||
import {fill_charts} from "./chart_functions.js";
|
||||
import {register_dropdowns} from "./dropdowns.js";
|
||||
|
||||
|
||||
function debounce(func, delay) {
|
||||
|
@ -13,25 +14,29 @@ function debounce(func, delay) {
|
|||
};
|
||||
}
|
||||
|
||||
function register_nav_links() {
|
||||
var navAnchors = document.querySelectorAll("nav a");
|
||||
|
||||
navAnchors.forEach(function (a_el) {
|
||||
a_el.addEventListener("click", function(event) {
|
||||
function switch_nav(event) {
|
||||
var active_anchor = document.querySelector("nav a.active");
|
||||
active_anchor.classList.remove("active");
|
||||
|
||||
var clicked = event.target;
|
||||
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() {
|
||||
register_nav_links();
|
||||
fill_charts();
|
||||
register_dropdowns();
|
||||
}
|
||||
|
||||
|
||||
function load_in_table() {
|
||||
var rows = Array.from(document.querySelectorAll(".row_awaiting_processing"));
|
||||
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 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};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<link rel="stylesheet" href="/static/site.css">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/feather.min.js"></script>
|
||||
<title>{{ .ActivePage }}</title>
|
||||
</head>
|
||||
<body class="latte">
|
||||
<header>
|
||||
|
@ -26,26 +27,26 @@
|
|||
hx-push-url="true"
|
||||
hx-boost="true"
|
||||
hx-target="#main-body-content"
|
||||
{{ if (eq .ActivePage "index") }}
|
||||
{{ if (eq .ActivePage "Dashboard") }}
|
||||
class="active"
|
||||
{{ end }}
|
||||
>
|
||||
Home!
|
||||
Dashboard
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
hx-get="/hello"
|
||||
hx-get="/transactions"
|
||||
hx-swap="innerHtml swap:0.2s settle:0.2s"
|
||||
hx-push-url="true"
|
||||
hx-boost="true"
|
||||
hx-target="#main-body-content"
|
||||
{{ if (eq .ActivePage "hello") }}
|
||||
{{ if (eq .ActivePage "Transactions") }}
|
||||
class="active"
|
||||
{{ end }}
|
||||
>
|
||||
Not home!
|
||||
Transactions
|
||||
</a>
|
||||
</li>
|
||||
</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 type="module" src="/static/index.js"></script>
|
||||
<script type="module">
|
||||
import {register_handlers, fill_all_charts, trigger_table_animation} from "/static/index.js";
|
||||
register_handlers();
|
||||
import {trigger_reset_handlers, trigger_table_animation} from "/static/index.js";
|
||||
feather.replace();
|
||||
htmx.onLoad(function (element) {
|
||||
if (element.localName === "tr") {
|
||||
trigger_table_animation();
|
||||
} else {
|
||||
fill_all_charts();
|
||||
trigger_reset_handlers();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
63
web/transactions.templ
Normal file
63
web/transactions.templ
Normal 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>
|
||||
}
|
Loading…
Reference in a new issue