added fading in tables

This commit is contained in:
Nickiel12 2024-01-15 19:26:51 -08:00
parent 23b335a11d
commit c20830a334
11 changed files with 250 additions and 35 deletions

View file

@ -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
View 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"`
}

View file

@ -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
View 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>
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;
} }

View file

@ -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);
}
}

View file

@ -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')};

View file

@ -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};

View file

@ -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>