added fading in tables
This commit is contained in:
parent
23b335a11d
commit
c20830a334
11 changed files with 250 additions and 35 deletions
|
@ -1,10 +1,9 @@
|
||||||
package debug_mode
|
package debug_mode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"nickiel.net/recount_server/types"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
@ -12,14 +11,6 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Transaction struct {
|
|
||||||
Id int `db:"trns_id" json:"Id"`
|
|
||||||
Amount string `db:"trns_amount" json:"Amount"`
|
|
||||||
Description sql.NullString `db:"trns_description" json:"Description"`
|
|
||||||
Account int `db:"trns_account" json:"Account"`
|
|
||||||
Bucket sql.NullInt64 `db:"trns_bucket" json:"Bucket"`
|
|
||||||
Date time.Time `db:"trns_date" json:"TransactionDate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetLogLevel(level zerolog.Level) {
|
func SetLogLevel(level zerolog.Level) {
|
||||||
zerolog.SetGlobalLevel(level)
|
zerolog.SetGlobalLevel(level)
|
||||||
|
@ -142,7 +133,7 @@ INSERT INTO transactions (trns_amount, trns_description, trns_account, trns_buck
|
||||||
"TransactionDate": "2023-11-11T00:00:00Z"
|
"TransactionDate": "2023-11-11T00:00:00Z"
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var trns Transaction = Transaction{}
|
var trns types.Transaction = types.Transaction{}
|
||||||
err = json.Unmarshal([]byte(jsonExample), &trns)
|
err = json.Unmarshal([]byte(jsonExample), &trns)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
27
types/types.go
Normal file
27
types/types.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
Id int `db:"trns_id" json:"Id"`
|
||||||
|
Amount string `db:"trns_amount" json:"Amount"`
|
||||||
|
Description sql.NullString `db:"trns_description" json:"Description"`
|
||||||
|
Account int `db:"trns_account" json:"Account"`
|
||||||
|
Bucket sql.NullInt64 `db:"trns_bucket" json:"Bucket"`
|
||||||
|
Date time.Time `db:"trns_date" json:"TransactionDate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HumanLegibleTransaction struct {
|
||||||
|
Id int `db:"trns_id" json:"Id"`
|
||||||
|
Amount string `db:"trns_amount" json:"Amount"`
|
||||||
|
Description sql.NullString `db:"trns_description" json:"Description"`
|
||||||
|
AccountName sql.NullString `db:"account_name" json:"AccountName"`
|
||||||
|
Account int `db:"trns_account" json:"Account"`
|
||||||
|
Bucket sql.NullInt64 `db:"trns_bucket" json:"Bucket"`
|
||||||
|
BucketName sql.NullString `db:"bucket_name" json:"BucketName"`
|
||||||
|
Date time.Time `db:"trns_date" json:"TransactionDate"`
|
||||||
|
}
|
|
@ -3,24 +3,55 @@ package web
|
||||||
templ dashboard() {
|
templ dashboard() {
|
||||||
<title>Dashboard</title>
|
<title>Dashboard</title>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row" style="margin-top: -20px">
|
||||||
<div class="c-crust col card mx-auto cr-all">
|
<div class="c-crust col card mx-auto cr-all">
|
||||||
<div class="c-s0 d-flex w-full cr-top">
|
<div class="c-s0 d-flex w-full cr-top">
|
||||||
<i class="my-auto 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>
|
||||||
<i class="my-auto 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 ms-auto" data-feather="arrow-right"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="c-crust col card mx-auto cr-all">
|
<div class="c-crust col card mx-auto cr-all">
|
||||||
<div class="c-s0 d-flex w-full cr-top">
|
<div class="c-s0 d-flex w-full cr-top">
|
||||||
<i class="my-auto 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>
|
||||||
<i class="my-auto 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 ms-auto" data-feather="arrow-right"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row mt-5">
|
||||||
<table>
|
<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>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
28
web/dataendpoints.templ
Normal file
28
web/dataendpoints.templ
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nickiel.net/recount_server/types"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ transaction_rows(transactions *[]types.HumanLegibleTransaction) {
|
||||||
|
for i, value := range *transactions {
|
||||||
|
<tr class="row_awaiting_processing" data-rcnt-transaction-row-pos={strconv.Itoa(i)}>
|
||||||
|
<td></td>
|
||||||
|
<td><div>{strconv.Itoa(value.Id)}</div></td>
|
||||||
|
if value.AccountName.Valid {
|
||||||
|
<td><div>{value.AccountName.String}</div></td>
|
||||||
|
} else {
|
||||||
|
<td><div>{strconv.Itoa(value.Account)}</div></td>
|
||||||
|
}
|
||||||
|
<td><div>{value.Date.Format("01/02/2006")}</div></td>
|
||||||
|
<td><div>$</div></td>
|
||||||
|
if (len(value.Amount) > 0 && value.Amount[0] == '-') {
|
||||||
|
<td class="t-e negative"><div>{value.Amount}</div></td>
|
||||||
|
} else {
|
||||||
|
<td class="t-e positive"><div>+{value.Amount}</div></td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,20 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"nickiel.net/recount_server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TemplateState struct {
|
type TemplateState struct {
|
||||||
|
@ -27,6 +31,7 @@ func SetLogLevel(level zerolog.Level) {
|
||||||
func WebRouter() http.Handler {
|
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("/hello", getHello)
|
r.Get("/hello", getHello)
|
||||||
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
|
||||||
|
@ -93,3 +98,28 @@ func getHello(w http.ResponseWriter, req *http.Request) {
|
||||||
renderFullPage(w, component, "hello");
|
renderFullPage(w, component, "hello");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_RESULT_COUNT = 20;
|
||||||
|
|
||||||
|
func getTransactions(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)
|
||||||
|
for i := 10; i > 0; i-- {
|
||||||
|
transaction := types.HumanLegibleTransaction{
|
||||||
|
Id: i,
|
||||||
|
Amount: fmt.Sprintf("%d.00", (i+1)*100),
|
||||||
|
Description: sql.NullString{String: fmt.Sprintf("Transaction %d", i+1), Valid: true},
|
||||||
|
AccountName: sql.NullString{String: "Savings", Valid: true},
|
||||||
|
Account: 123,
|
||||||
|
Bucket: sql.NullInt64{Int64: int64(i + 100), Valid: true},
|
||||||
|
BucketName: sql.NullString{String: fmt.Sprintf("Bucket %d", i+1), Valid: true},
|
||||||
|
Date: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions[10 - i] = transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
component := transaction_rows(&transactions);
|
||||||
|
component.Render(context.Background(), w);
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
border-radius: $border-radius;
|
||||||
}
|
}
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -13,6 +13,7 @@ $border-radius: 8px;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--#{$prefix}-bg);
|
background-color: var(--#{$prefix}-bg);
|
||||||
|
height: 97vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,26 +39,35 @@ body {
|
||||||
@media (min-width: 1362px) {
|
@media (min-width: 1362px) {
|
||||||
.card-table {
|
.card-table {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: clamp(300px, 80%, 450px);
|
width: 100%;
|
||||||
|
margin-left: 0%;
|
||||||
|
margin-right: 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 1362px) {
|
@media (max-width: 1362px) {
|
||||||
.card-table {
|
.card-table {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-left: 10%;
|
width: clamp(300px, 80%, 450px);
|
||||||
margin-right: 10%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div#below-header {
|
||||||
|
height: 93vh;
|
||||||
|
}
|
||||||
|
|
||||||
div#left-col {
|
div#left-col {
|
||||||
width: 15vw;
|
width: 15vw;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: var(--#{$prefix}-nav-bg);
|
background-color: var(--#{$prefix}-nav-bg);
|
||||||
|
border-radius: $border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#main-body-content {
|
div#main-body-content {
|
||||||
width: 70vw;
|
width: 70vw;
|
||||||
padding: 10px;
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0.2s ease-out;
|
transition: opacity 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
@ -73,4 +83,5 @@ div#right-col {
|
||||||
width: 15vw;
|
width: 15vw;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: var(--#{$prefix}-nav-bg);
|
background-color: var(--#{$prefix}-nav-bg);
|
||||||
|
border-radius: $border-radius;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,3 +77,58 @@ $directions: (
|
||||||
.cr-bottom {
|
.cr-bottom {
|
||||||
border-radius: 0px 0px $border-radius $border-radius;
|
border-radius: 0px 0px $border-radius $border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.t-s {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
.t-e {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,13 @@
|
||||||
--#{$prefix}-mantle: #{map-get($color, 'mantle')};
|
--#{$prefix}-mantle: #{map-get($color, 'mantle')};
|
||||||
--#{$prefix}-surface0: #{map-get($color, 'surface0')};
|
--#{$prefix}-surface0: #{map-get($color, 'surface0')};
|
||||||
--#{$prefix}-surface1: #{map-get($color, 'surface1')};
|
--#{$prefix}-surface1: #{map-get($color, 'surface1')};
|
||||||
|
--#{$prefix}-surface2: #{map-get($color, 'surface2')};
|
||||||
|
--#{$prefix}-overlay0: #{map-get($color, 'overlay0')};
|
||||||
|
--#{$prefix}-overlay1: #{map-get($color, 'overlay1')};
|
||||||
|
--#{$prefix}-overlay2: #{map-get($color, 'overlay2')};
|
||||||
--#{$prefix}-text: #{map-get($color, 'text')};
|
--#{$prefix}-text: #{map-get($color, 'text')};
|
||||||
|
--#{$prefix}-green: #{map-get($color, 'green')};
|
||||||
|
--#{$prefix}-red: #{map-get($color, 'red')};
|
||||||
|
|
||||||
--#{$prefix}-nav-bg: #{map-get($color, 'crust')};
|
--#{$prefix}-nav-bg: #{map-get($color, 'crust')};
|
||||||
--#{$prefix}-nav-color: #{map-get($color, 'text')};
|
--#{$prefix}-nav-color: #{map-get($color, 'text')};
|
||||||
|
|
|
@ -1,22 +1,51 @@
|
||||||
|
|
||||||
function say_hello() {
|
function debounce(func, delay) {
|
||||||
console.log("Hello World");
|
let timeoutId;
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
func.apply(this, args);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function register_nav_links() {
|
function register_nav_links() {
|
||||||
var navAnchors = document.querySelectorAll("nav a");
|
var navAnchors = document.querySelectorAll("nav a");
|
||||||
|
|
||||||
navAnchors.forEach(function (a_el) {
|
navAnchors.forEach(function (a_el) {
|
||||||
a_el.addEventListener("click", nav_a_click);
|
a_el.addEventListener("click", function(event) {
|
||||||
|
var active_anchor = document.querySelector("nav a.active");
|
||||||
|
active_anchor.classList.remove("active");
|
||||||
|
|
||||||
|
var clicked = event.target;
|
||||||
|
clicked.classList.add("active");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function nav_a_click(event) {
|
function register_handlers() {
|
||||||
var active_anchor = document.querySelector("nav a.active");
|
register_nav_links();
|
||||||
active_anchor.classList.remove("active");
|
|
||||||
|
|
||||||
var clicked = event.target;
|
|
||||||
clicked.classList.add("active");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {say_hello, register_nav_links};
|
|
||||||
|
function load_in_table() {
|
||||||
|
var rows = Array.from(document.querySelectorAll(".row_awaiting_processing"));
|
||||||
|
rows.sort((a, b) => b.dataset.rcntTransactionRowPos - a.dataset.rcntTransactionRowPos);
|
||||||
|
|
||||||
|
const processElement = (element, index) => {
|
||||||
|
element.classList.remove('row_awaiting_processing');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (index < rows.length - 1) {
|
||||||
|
processElement(rows[index + 1], index + 1);
|
||||||
|
}
|
||||||
|
}, 25); // 1000 milliseconds (1 second) delay, adjust as needed
|
||||||
|
};
|
||||||
|
|
||||||
|
processElement(rows[0], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trigger_table_animation = debounce(load_in_table, 100);
|
||||||
|
|
||||||
|
export {register_handlers, trigger_table_animation};
|
||||||
|
|
|
@ -60,9 +60,15 @@
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/static/index.js"></script>
|
<script type="module" src="/static/index.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {register_nav_links} from "/static/index.js";
|
import {register_handlers, trigger_table_animation} from "/static/index.js";
|
||||||
register_nav_links();
|
register_handlers();
|
||||||
feather.replace();
|
feather.replace();
|
||||||
|
htmx.onLoad(function (element) {
|
||||||
|
if (element.localName === "tr") {
|
||||||
|
console.log(element);
|
||||||
|
trigger_table_animation();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue