add edit pages for bookmakers and accounts
Marco Andronaco andronacomarco@gmail.com
Sun, 06 Oct 2024 12:46:05 +0200
14 files changed,
368 insertions(+),
18 deletions(-)
jump to
M
src/api/api.go
→
src/api/api.go
@@ -20,6 +20,24 @@
jsonResponse(w, bookmakers) } +func getBookmakersId(w http.ResponseWriter, r *http.Request) { + id, err := getId(r) + if err != nil { + new400Error(w, err) + return + } + + var bookmaker app.Bookmaker + err = app.DB.First(&bookmaker, id).Error + if err != nil { + log.Println("could not get bookmaker: " + err.Error()) + new500Error(w, err) + return + } + + jsonResponse(w, bookmaker) +} + func postBookmakers(w http.ResponseWriter, r *http.Request) { var bookmaker app.Bookmaker err := json.NewDecoder(r.Body).Decode(&bookmaker)@@ -48,6 +66,24 @@ return
} jsonResponse(w, accounts) +} + +func getAccountsId(w http.ResponseWriter, r *http.Request) { + id, err := getId(r) + if err != nil { + new400Error(w, err) + return + } + + var account app.Account + err = app.DB.First(&account, id).Error + if err != nil { + log.Println("could not get account: " + err.Error()) + new500Error(w, err) + return + } + + jsonResponse(w, account) } func postAccounts(w http.ResponseWriter, r *http.Request) {
M
src/api/routes.go
→
src/api/routes.go
@@ -15,9 +15,11 @@
http.Handle("GET /", http.FileServer(http.Dir("static"))) http.HandleFunc("GET /api/bookmakers", getBookmakers) + http.HandleFunc("GET /api/bookmakers/{id}", getBookmakersId) http.HandleFunc("POST /api/bookmakers", postBookmakers) http.HandleFunc("GET /api/accounts", getAccounts) + http.HandleFunc("GET /api/accounts/{id}", getAccountsId) http.HandleFunc("POST /api/accounts", postAccounts) http.HandleFunc("GET /api/records", getRecords)
M
src/app/models.go
→
src/app/models.go
@@ -17,6 +17,7 @@
type Account struct { ID uint `json:"id"` CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` Name string `json:"name" gorm:"not null"` }
A
static/accounts/edit/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Piggy - Accounts</title> + <link rel="stylesheet" href="/css/styles.css"> +</head> + +<body> + <header> + <nav></nav> + </header> + <main> + <h1>Accounts > Edit</h1> + + <div id="main-container"> + </div> + + <button id="save">Save</button> + </main> + <script src="/js/common.js"></script> + <script src="/js/accounts-edit.js"></script> +</body> + +</html>
M
static/accounts/index.html
→
static/accounts/index.html
@@ -16,10 +16,17 @@ <main>
<h1>Accounts</h1> <div id="main-container"> - Accounts will be shown here. + <table> + <thead id="accounts-header"></thead> + <tbody id="accounts-table"></tbody> + <tfoot></tfoot> + </table> </div> + + <button id="new-account">New Account</button> </main> <script src="/js/common.js"></script> + <script src="/js/accounts.js"></script> </body> </html>
A
static/bookmakers/edit/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Piggy - Bookmakers</title> + <link rel="stylesheet" href="/css/styles.css"> +</head> + +<body> + <header> + <nav></nav> + </header> + <main> + <h1>Bookmakers > Edit</h1> + + <div id="main-container"> + </div> + + <button id="save">Save</button> + </main> + <script src="/js/common.js"></script> + <script src="/js/bookmakers-edit.js"></script> +</body> + +</html>
M
static/bookmakers/index.html
→
static/bookmakers/index.html
@@ -16,10 +16,17 @@ <main>
<h1>Bookmakers</h1> <div id="main-container"> - Bookmakers will be shown here. + <table> + <thead id="bookmakers-header"></thead> + <tbody id="bookmakers-table"></tbody> + <tfoot></tfoot> + </table> </div> + + <button id="new-bookmaker">New Bookmaker</button> </main> <script src="/js/common.js"></script> + <script src="/js/bookmakers.js"></script> </body> </html>
A
static/js/accounts-edit.js
@@ -0,0 +1,45 @@
+document.addEventListener('DOMContentLoaded', function () { + handleID(); + + document.getElementById('save').addEventListener('click', submit); +}); + +let id; + +async function handleID() { + id = getQueryStringID(); + const record = id === 0 ? null : await getAccount(id); + document.getElementById("main-container").appendChild(loadAccount(record)); +} + +function loadAccount(account) { + const div = document.createElement("div"); + div.setAttribute("data-type", "account"); + div.setAttribute("data-id", id); + div.classList.add("account"); + + // account.name + div.appendChild(newInputText("Name", account?.name, "account-name")); + + return div; +} + +function getInputValueFromNode(node, name) { + const element = node.getElementsByClassName(name)[0]; + return element.type === "checkbox" ? element.checked : element.value; +} + +function buildAccountObject() { + const node = document.getElementsByClassName("account")[0]; + return { + id: +node.getAttribute("data-id"), + name: getInputValueFromNode(node, "account-name"), + } +} + +async function submit() { + if (await saveAccount(buildAccountObject())) { + location.href = "/accounts" + } +} +
A
static/js/accounts.js
@@ -0,0 +1,53 @@
+document.addEventListener('DOMContentLoaded', function () { + loadAccounts(); + + document.getElementById('new-account').addEventListener('click', editAccount); +}); + +function editAccount() { + let out = "/accounts/edit/"; + const id = this.getAttribute("data-id"); + if (id) { + out += "?id=" + id; + } + location.href = out; +} + +function loadAccounts() { + getAccounts().then(accounts => { + const header = document.getElementById('accounts-header'); + const table = document.getElementById('accounts-table'); + header.innerHTML = ''; + table.innerHTML = ''; + + const tr = document.createElement('tr'); + const headers = ["ID", "Created", "Updated", "Name"]; + + for (const header of headers) { + const td = document.createElement('td'); + td.innerText = header; + tr.appendChild(td); + } + header.appendChild(tr); + + for (const account of accounts) { + const tr = document.createElement('tr'); + tr.setAttribute("data-id", account.id); + tr.onclick = editAccount; + + const fields = [ + account.id, + formatDate(account.created_at), + formatDate(account.updated_at), + account.name, + ]; + + for (const field of fields) { + const td = document.createElement('td'); + td.innerHTML = field; + tr.appendChild(td); + } + table.appendChild(tr); + } + }); +}
A
static/js/bookmakers-edit.js
@@ -0,0 +1,53 @@
+document.addEventListener('DOMContentLoaded', function () { + handleID(); + + document.getElementById('save').addEventListener('click', submit); +}); + +let id; + +async function handleID() { + id = getQueryStringID(); + const record = id === 0 ? null : await getBookmaker(id); + document.getElementById("main-container").appendChild(loadBookmaker(record)); +} + +function loadBookmaker(bookmaker) { + const div = document.createElement("div"); + div.setAttribute("data-type", "bookmaker"); + div.setAttribute("data-id", id); + div.classList.add("bookmaker"); + + // bookmaker.name + div.appendChild(newInputText("Name", bookmaker?.name, "bookmaker-name")); + + // bookmaker.exchange + div.appendChild(newInputCheckbox("Exchange", bookmaker?.exchange, "bookmaker-exchange")); + + // bookmaker.default_commission + div.appendChild(newInputText("Commission", bookmaker?.default_commission, "bookmaker-default_commission")); + + return div; +} + +function getInputValueFromNode(node, name) { + const element = node.getElementsByClassName(name)[0]; + return element.type === "checkbox" ? element.checked : element.value; +} + +function buildBookmakerObject() { + const node = document.getElementsByClassName("bookmaker")[0]; + return { + id: +node.getAttribute("data-id"), + name: getInputValueFromNode(node, "bookmaker-name"), + exchange: getInputValueFromNode(node, "bookmaker-exchange"), + default_commission: +getInputValueFromNode(node, "bookmaker-default_commission"), + } +} + +async function submit() { + if (await saveBookmaker(buildBookmakerObject())) { + location.href = "/bookmakers" + } +} +
A
static/js/bookmakers.js
@@ -0,0 +1,55 @@
+document.addEventListener('DOMContentLoaded', function () { + loadBookmakers(); + + document.getElementById('new-bookmaker').addEventListener('click', editBookmaker); +}); + +function editBookmaker() { + let out = "/bookmakers/edit/"; + const id = this.getAttribute("data-id"); + if (id) { + out += "?id=" + id; + } + location.href = out; +} + +function loadBookmakers() { + getBookmakers().then(bookmakers => { + const header = document.getElementById('bookmakers-header'); + const table = document.getElementById('bookmakers-table'); + header.innerHTML = ''; + table.innerHTML = ''; + + const tr = document.createElement('tr'); + const headers = ["ID", "Created", "Updated", "Name", "Exchange", "Commission"]; + + for (const header of headers) { + const td = document.createElement('td'); + td.innerText = header; + tr.appendChild(td); + } + header.appendChild(tr); + + for (const bookmaker of bookmakers) { + const tr = document.createElement('tr'); + tr.setAttribute("data-id", bookmaker.id); + tr.onclick = editBookmaker; + + const fields = [ + bookmaker.id, + formatDate(bookmaker.created_at), + formatDate(bookmaker.updated_at), + bookmaker.name, + formatBoolean(bookmaker.exchange), + bookmaker.default_commission, + ]; + + for (const field of fields) { + const td = document.createElement('td'); + td.innerHTML = field; + tr.appendChild(td); + } + table.appendChild(tr); + } + }); +}
M
static/js/common.js
→
static/js/common.js
@@ -8,6 +8,7 @@ navObject.appendChild(a)
} }); +// Global constants const navPages = [ { name: "Home", href: "/" }, { name: "Bookmakers", href: "/bookmakers" },@@ -18,6 +19,7 @@
const currency = "€"; const locale = "it-IT"; +// Cell formatters function formatValue(v) { return (v / 100).toFixed(2); }@@ -30,16 +32,17 @@ function formatDate(dateString) {
return (new Date(dateString)).toLocaleString(locale); } -function formatDone(value, id) { +function formatBoolean(value, id) { const input = document.createElement("input"); input.type = "checkbox"; - input.checked = value; + if (value) input.setAttribute("checked", ""); input.disabled = true; //input.setAttribute("data-id", id); //input.onchange = undefined; return input.outerHTML; } +// Input components function newInputText(label, value, name) { const l = document.createElement("label"); const input = document.createElement("input");@@ -57,12 +60,17 @@ const l = document.createElement("label");
const input = document.createElement("input"); input.className = name; input.type = "checkbox"; - input.checked = value ?? false; + if (value) input.setAttribute("checked", ""); l.appendChild(input); l.innerHTML += label; return l; } +// Functions +function getQueryStringID() { + return Number(new URLSearchParams(window.location.search).get("id") ?? 0); +} + async function handleFetchResult(res) { if (!res.ok) { console.error(await res.text())@@ -86,6 +94,39 @@ });
return await handleFetchResult(res); } +// API calls +async function getBookmakers() { + return await myFetch('/api/bookmakers'); +} + +async function getAccounts() { + return await myFetch('/api/accounts'); +} + async function getRecords() { return await myFetch('/api/records'); } + +async function getBookmaker(id) { + return await myFetch(`/api/bookmakers/${id}`); +} + +async function getAccount(id) { + return await myFetch(`/api/accounts/${id}`); +} + +async function getRecord(id) { + return await myFetch(`/api/records/${id}`); +} + +async function saveBookmaker(payload) { + return await myFetchPOST("/api/bookmakers", payload); +} + +async function saveAccount(payload) { + return await myFetchPOST("/api/accounts", payload); +} + +async function saveRecord(payload) { + return await myFetchPOST("/api/records", payload); +}
M
static/js/records-edit.js
→
static/js/records-edit.js
@@ -1,18 +1,15 @@
document.addEventListener('DOMContentLoaded', function () { handleID(); - document.getElementById('save').addEventListener('click', saveRecord); + document.getElementById('save').addEventListener('click', submit); }); let id; async function handleID() { - id = Number(new URLSearchParams(window.location.search).get("id") ?? 0); - const record = id === 0 ? null : await myFetch(`/api/records/${id}`); - - document.getElementById("main-container").appendChild(loadRecord(record)) - - console.log(record); + id = getQueryStringID(); + const record = id === 0 ? null : await getRecord(id); + document.getElementById("main-container").appendChild(loadRecord(record)); } function loadRecord(record) {@@ -153,9 +150,8 @@ }
return result; } -async function saveRecord() { - const result = await myFetchPOST("/api/records", buildRecordObject()); - if (result) { - alert("Done"); +async function submit() { + if (await saveRecord(buildRecordObject())) { + location.href = "/records" } }
M
static/js/records.js
→
static/js/records.js
@@ -5,7 +5,7 @@ document.getElementById('new-record').addEventListener('click', editRecord);
}); function editRecord() { - const out = "/records/edit/"; + let out = "/records/edit/"; const id = this.getAttribute("data-id"); if (id) { out += "?id=" + id;@@ -37,7 +37,7 @@ tr.onclick = editRecord;
const fields = [ formatDate(record.created_at), - formatDone(record.done, record.id), + formatBoolean(record.done, record.id), record.type, record.description, formatDate(record.date),