202 lines
7.0 KiB
HTML
Executable File
202 lines
7.0 KiB
HTML
Executable File
<html>
|
|
<header>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css">
|
|
<style>
|
|
body > div {
|
|
width: 100%;
|
|
}
|
|
.center {
|
|
text-align: center;
|
|
}
|
|
.right {
|
|
text-align: right;
|
|
}
|
|
#transactions > tbody > tr > td:first-child input {
|
|
padding: 1ch;
|
|
display: inline-block;
|
|
}
|
|
#transactions td[key="Amount"]:before {
|
|
content: "$";
|
|
}
|
|
#transactions table tr td:first-child {
|
|
display: none;
|
|
}
|
|
#transactions table tr td:nth-child(2) {
|
|
width: 12ch;
|
|
}
|
|
#transactions table tr td:last-child {
|
|
width: 12ch;
|
|
}
|
|
</style>
|
|
<script>
|
|
function init() {
|
|
http("get", "/api/transactions", (body, status) => {
|
|
body = JSON.parse(body)
|
|
loadTransactions(body.Transactions)
|
|
}, null)
|
|
http("get", "/api/balances", (body, status) => {
|
|
body = JSON.parse(body)
|
|
loadBalances(body.Balances)
|
|
}, null)
|
|
}
|
|
function prettyMoney(money) {
|
|
try {
|
|
var f = parseFloat(money)
|
|
if (f) {
|
|
return Math.round(100.0*f) / 100.0
|
|
}
|
|
} catch { }
|
|
return money;
|
|
}
|
|
function loadBalances(balances) {
|
|
var innerHTML = ""
|
|
innerHTML += "<table>"
|
|
|
|
for(var k in balances) {
|
|
if(k.startsWith("Asset"))
|
|
innerHTML += `<tr><td>${k}</td><td>${prettyMoney(balances[k])}</td></tr>`
|
|
}
|
|
|
|
innerHTML += "</table>"
|
|
|
|
document.getElementById("balances").innerHTML = innerHTML
|
|
}
|
|
function loadTransactions(transactions) {
|
|
if (transactions.length < 1) {
|
|
return
|
|
}
|
|
var innerHTML = ""
|
|
|
|
for(var i in transactions) {
|
|
const transaction = transactions[i]
|
|
var one = "<tr>"
|
|
one += "<td style=\"width: 20%;\">"
|
|
for(var foo of ["saveTransaction", "zachsPayment", "belsPayment", "zachsCharge", "belsCharge"]) {
|
|
one += `<input value="${foo}" type="button" onclick="${foo}(this.parentNode.parentNode)"/><br>`
|
|
}
|
|
one += "</td>"
|
|
|
|
one += "<td><table>"
|
|
one += " <tr>"
|
|
one += ` <td key="idx" disabled readonly>${i}</td>`
|
|
for(var key of ["Date", "Description"])
|
|
one += ` <td contenteditable key=${JSON.stringify(key)}>${transaction[key]}</td>`
|
|
one += " <td></td>"
|
|
one += " </tr>"
|
|
one += " <tr>"
|
|
one += " <td></td><td></td>"
|
|
for(var key of ["Payee", "Amount"])
|
|
one += ` <td contenteditable key=${JSON.stringify(key)}>${prettyMoney(transaction[key])}</td>`
|
|
one += " </tr>"
|
|
one += " <tr>"
|
|
one += " <td></td><td></td>"
|
|
for(var key of ["Payer"])
|
|
one += ` <td contenteditable key=${JSON.stringify(key)}>${transaction[key]}</td>`
|
|
one += " <td></td>"
|
|
one += " </tr>"
|
|
one += "</table></td>"
|
|
|
|
one += "</tr>\n"
|
|
innerHTML = one + innerHTML
|
|
}
|
|
|
|
document.getElementById("transactions").innerHTML = innerHTML
|
|
}
|
|
function newTransaction() {
|
|
var today = new Date()
|
|
var year = today.getFullYear()
|
|
var month = today.getMonth()+1
|
|
var day = today.getDate()
|
|
if (day < 10) {
|
|
day = `0${day}`
|
|
}
|
|
if (month < 10) {
|
|
month = `0${month}`
|
|
}
|
|
http("post", "/api/transactions", () => {init()}, JSON.stringify({
|
|
Date: `${year}-${month}-${day}`,
|
|
Description: "?",
|
|
Payer: "?",
|
|
Payee: "?",
|
|
Amount: 0,
|
|
}))
|
|
}
|
|
function saveTransaction(row) {
|
|
const inputs = row.getElementsByTagName("td")
|
|
var kvs = {}
|
|
for (var i = 0; i < inputs.length; i++) {
|
|
const key = inputs[i].getAttribute("key")
|
|
if (!key) {
|
|
continue
|
|
}
|
|
const value = (inputs[i].textContent || inputs[i].innerText).replaceAll("\n", " ").trim()
|
|
kvs[key] = value
|
|
if (!isNaN(value))
|
|
kvs[key] = parseFloat(value)
|
|
}
|
|
http("put", "/api/transactions", () => {init()}, JSON.stringify(kvs))
|
|
}
|
|
function getRowKeyValue(row, wantkey) {
|
|
const inputs = row.getElementsByTagName("td")
|
|
for (var i = 0; i < inputs.length; i++) {
|
|
const key = inputs[i].getAttribute("key")
|
|
if (key == wantkey) {
|
|
return inputs[i].innerText + ""
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
function setRowKeyValues(row, wantKeysWantValues) {
|
|
const inputs = row.getElementsByTagName("td")
|
|
var kvs = {}
|
|
for (var i = 0; i < inputs.length; i++) {
|
|
const key = inputs[i].getAttribute("key")
|
|
if (key in wantKeysWantValues) {
|
|
inputs[i].innerHTML = wantKeysWantValues[key]
|
|
}
|
|
}
|
|
saveTransaction(row)
|
|
}
|
|
zachsPayment = (x) => {
|
|
setRowKeyValues(x, {
|
|
"Payee": getRowKeyValue(x, "Payer"),
|
|
"Payer": "AssetAccount:Zach",
|
|
})
|
|
}
|
|
belsPayment = (x) => {
|
|
setRowKeyValues(x, {
|
|
"Payee": getRowKeyValue(x, "Payer"),
|
|
"Payer": "AssetAccount:Bel",
|
|
})
|
|
}
|
|
zachsCharge = (x) => setRowKeyValues(x, {"Payee": "AssetAccount:Zach"})
|
|
belsCharge = (x) => setRowKeyValues(x, {"Payee": "AssetAccount:Bel"})
|
|
function http(method, remote, callback, body) {
|
|
var xmlhttp = new XMLHttpRequest();
|
|
xmlhttp.onreadystatechange = function() {
|
|
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
|
|
callback(xmlhttp.responseText, xmlhttp.status)
|
|
}
|
|
};
|
|
xmlhttp.open(method, remote, true);
|
|
if (typeof body == "undefined") {
|
|
body = null
|
|
}
|
|
xmlhttp.send(body);
|
|
}
|
|
</script>
|
|
</header>
|
|
<body onload="init()">
|
|
<h1>Shared Expenses</h1>
|
|
<details>
|
|
<summary>Balances</summary>
|
|
<div id="balances"></div>
|
|
</details>
|
|
<div class="right"><button onclick="newTransaction();">New Transaction</button></div>
|
|
<table id="transactions"></table>
|
|
</body>
|
|
<footer>
|
|
</footer>
|
|
</html>
|