diff options
Diffstat (limited to 'pages/money.inc')
-rw-r--r-- | pages/money.inc | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/pages/money.inc b/pages/money.inc new file mode 100644 index 0000000..9c98613 --- /dev/null +++ b/pages/money.inc @@ -0,0 +1,588 @@ +<?php + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; global $title; global $isLoggedIn; global $lang; global $pages; global $_PROFILE; +$parts = explode("/", $_GET["_"]); + +$accounts = array_map(function ($i) { + $name = substr($i, 0, -5); + $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/money/" . $i), true); + $data["_name"] = $name; + return $data; +}, array_values(array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/money"), function ($i) { return !str_starts_with($i, "."); }))); + +function calculateFullAmount($account, $asNumber = false, $gbpOnly = false): string|float|int { + global $rate; + $total = 0; + + foreach ($account["transactions"] as $transaction) { + $total += $transaction["amount"]; + } + + if ($asNumber) { + if ($gbpOnly && $account["currency"] === "eur") { + return $total * $rate; + } else { + return $total; + } + } else { + return number_format($total, 2, '.', ','); + } +} + +global $parts; +if (isset($parts[2])) { + if (str_contains($parts[2], "/") || !file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/money/" . $parts[2] . ".json")) { + header("Location: /-/money"); + die(); + } +} + +$fronters = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . ($_PROFILE["login"] === "cloudburst" ? "ynmuc" : "gdapd") . "/fronters.json"), true)["members"]; + +if (count($fronters) > 0) { + $myId = $fronters[0]["id"]; +} else { + $myId = "zdtsg"; +} + +if ((isset($_GET["create"]) || isset($_GET["delete"])) && isset($parts[2])) { + $account = array_values(array_filter($accounts, function ($i) use ($parts) { return $i["_name"] === $parts[2]; }))[0]; + + header("Content-Type: text/plain"); + //var_dump($_GET); + + if (isset($_GET["create"])) { + if (!(isset($_GET["amount"]) && is_numeric($_GET["amount"]) && (float)$_GET["amount"] < 9999 && (float)$_GET["amount"] > -9999)) { + header("Location: /-/money/" . $parts[2]); + die(); + } + if (!isset($_GET["description"])) $_GET["description"] = ""; + $_GET["description"] = substr($_GET["description"], 0, 150); + + array_unshift($account["transactions"], [ + "author" => $myId, + "description" => $_GET["description"], + "amount" => (float)$_GET["amount"], + "date" => date('c') + ]); + + $name = $account["_name"]; + unset($account["_name"]); + + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/money/" . $name . ".json", json_encode($account, JSON_PRETTY_PRINT)); + } else { + if (isset($_GET["id"]) && is_numeric($_GET["id"]) && isset($account["transactions"][(int)$_GET["id"]])) { + unset($account["transactions"][(int)$_GET["id"]]); + + $name = $account["_name"]; + unset($account["_name"]); + + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/money/" . $name . ".json", json_encode($account, JSON_PRETTY_PRINT)); + } + } + + //die(); + + header("Location: /-/money/" . $parts[2]); + die(); +} + +$rate = (float)trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/exchange.txt")); + +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc'; + +?> + +<script src="/assets/editor/chart.js"></script> + +<style> + a:hover > .card { + opacity: .75; + } + + a:active > .card, a:focus > .card { + opacity: .5; + } + + .list-group-item { + color: #fff; + background-color: #222; + border: 1px solid rgba(255, 255, 255, .125); + } + + .list-group-item.disabled { + color: #fff; + background-color: #222; + border-color: rgba(255, 255, 255, .125); + opacity: .75; + } + + .list-group-item-action:hover { + background-color: #252525; + color: #ddd; + } + + .list-group-item-action:active, .list-group-item-action:focus { + background-color: #272727; + color: #bbb; + } + + .modal-header { + border-bottom: 1px solid #353738; + } + + .modal-content { + border: 1px solid rgba(255, 255, 255, .2); + background-color: #111; + } + + .btn-close { + filter: invert(1); + } + + .text-danger, .text-success { + filter: invert(1) brightness(150%) hue-rotate(180deg); + } +</style> + +<br> +<div class="container"> + <?php if (count($parts) < 3): ?> + <div> + <h2>Money tracker</h2> + + <hr> + + <?php + + $allAccounts = array_reduce(array_map(function ($i) { + return calculateFullAmount($i, true, true); + }, $accounts), function ($a, $b) { + return $a + $b; + }); + + ?> + <h2>£<?= number_format($allAccounts, 2, '.', ',') ?> · €<?= number_format($allAccounts * (1 / $rate), 2, '.', ',') ?></h2> + <p>As of <?= date('j F Y') ?> · €1 = £<?= $rate ?></p> + + <canvas id="history" style="margin-top: 10px; width: 100%; height: 200px; max-height: 100%;"></canvas> + <?php + + $balance = 0; + $history = []; + $dates = []; + + $transactions1 = array_reduce(array_values(array_filter($accounts, function ($i) { return $i["owner"] === "raindrops"; })), function ($a, $b) { + return [...$a, ...$b["transactions"]]; + }, []); + + $transactions2 = array_reduce(array_values(array_filter($accounts, function ($i) { return $i["owner"] === "cloudburst"; })), function ($a, $b) { + return [...$a, ...$b["transactions"]]; + }, []); + $transactions2 = array_map(function ($i) use ($rate) { + $i["amount"] = $i["amount"] * (1/$rate); + return $i; + }, $transactions2); + + $transactions = [...$transactions1, ...$transactions2]; + usort($transactions, function ($a, $b) { + return strtotime($a["date"]) - strtotime($b["date"]); + }); + + foreach ($transactions as $transaction) { + $balance += $transaction["amount"]; + $history[] = $balance; + $dates[] = date('j M Y, H:i', strtotime($transaction["date"])); + } + + ?> + <script> + const ctx3 = document.getElementById('history').getContext('2d'); + const graph3 = new Chart(ctx3, { + type: 'line', + data: { + labels: JSON.parse(`<?= json_encode($dates) ?>`), + datasets: [{ + label: 'Balance', + data: JSON.parse(`<?= json_encode($history) ?>`), + borderColor: '#b9f1ef', + backgroundColor: '#B9F1EF77' + }] + }, + options: { + animation: { + duration: 0 + }, + scales: { + y: { + ticks: { + callback: function(label) { + return '€' + Math.round(label); + } + }, + grid: { + color: "rgba(255,255,255,0.25)" + } + } + }, + elements: { + point: { + radius: 5 + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(tooltipItem) { + return '€' + tooltipItem.raw.toFixed(2); + } + }, + intersect: false + } + } + } + }) + </script> + + <hr> + + <h4>Cloudburst System</h4> + <div style="display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 20px;"> + <?php foreach ($accounts as $index => $account): if ($account["owner"] === "cloudburst"): ?> + <a style="color: white; text-decoration: none;" href="/-/money/<?= $account["_name"] ?>"> + <div class="card"> + <div class="card-body"> + <h4 class="card-title"><?= $account["currency"] === "gbp" ? "£" : "€" ?><?= calculateFullAmount($account); ?></h4> + <?= $account["name"] ?><?php if ($account["default"]): ?> <span class="badge bg-success rounded-pill">Default</span><?php endif; ?><?php if (isset($account["interests"])): ?> · <?= $account["interests"] * 100 ?>% interests<?php endif; ?> + <?php if (isset($account["max"])): ?><br><span class="text-muted">Max balance: <?= $account["currency"] === "gbp" ? "£" : "€" ?><?= number_format($account["max"], 2, '.', ',') ?> (<?= round((calculateFullAmount($account, true) / $account["max"]) * 100, 2) ?>% used)</span><?php endif; ?> + </div> + </div> + </a> + <?php endif; endforeach; ?> + </div> + <canvas id="history-cloudburst" style="margin-top: 10px; width: 100%; height: 200px; max-height: 100%;"></canvas> + <?php + + $balance = 0; + $history = []; + $dates = []; + + $transactions = array_reduce(array_values(array_filter($accounts, function ($i) { return $i["owner"] === "cloudburst"; })), function ($a, $b) { + return [...$a, ...$b["transactions"]]; + }, []); + usort($transactions, function ($a, $b) { + return strtotime($a["date"]) - strtotime($b["date"]); + }); + + foreach ($transactions as $transaction) { + $balance += $transaction["amount"]; + $history[] = $balance; + $dates[] = date('j M Y, H:i', strtotime($transaction["date"])); + } + + ?> + <script> + const ctx = document.getElementById('history-cloudburst').getContext('2d'); + const graph = new Chart(ctx, { + type: 'line', + data: { + labels: JSON.parse(`<?= json_encode($dates) ?>`), + datasets: [{ + label: 'Balance', + data: JSON.parse(`<?= json_encode($history) ?>`), + borderColor: '#ff6ae6', + backgroundColor: '#ff6ae677' + }] + }, + options: { + animation: { + duration: 0 + }, + scales: { + y: { + ticks: { + callback: function(label) { + return '£' + Math.round(label); + } + }, + grid: { + color: "rgba(255,255,255,0.25)" + } + } + }, + elements: { + point: { + radius: 5 + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(tooltipItem) { + return '£' + tooltipItem.raw.toFixed(2); + } + }, + intersect: false + } + } + } + }) + </script> + + <h4 style="margin-top: 20px;">Raindrops System</h4> + <div style="display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 20px;"> + <?php foreach ($accounts as $index => $account): if ($account["owner"] === "raindrops"): ?> + <a style="color: white; text-decoration: none;" href="/-/money/<?= $account["_name"] ?>"> + <div class="card"> + <div class="card-body"> + <h4 class="card-title"><?= $account["currency"] === "gbp" ? "£" : "€" ?><?= calculateFullAmount($account); ?></h4> + <?= $account["name"] ?><?php if ($account["default"]): ?> <span class="badge bg-success rounded-pill">Default</span><?php endif; ?><?php if (isset($account["interests"])): ?> · <?= $account["interests"] * 100 ?>% interests<?php endif; ?> + <?php if (isset($account["max"])): ?><br><span class="text-muted">Max balance: <?= $account["currency"] === "gbp" ? "£" : "€" ?><?= number_format($account["max"], 2, '.', ',') ?> (<?= round((calculateFullAmount($account, true) / $account["max"]) * 100, 2) ?>% used)</span><?php endif; ?> + </div> + </div> + </a> + <?php endif; endforeach; ?> + </div> + <canvas id="history-raindrops" style="margin-top: 10px; width: 100%; height: 200px; max-height: 100%;"></canvas> + <?php + + $balance = 0; + $history = []; + $dates = []; + + $transactions = array_reduce(array_values(array_filter($accounts, function ($i) { return $i["owner"] === "raindrops"; })), function ($a, $b) { + return [...$a, ...$b["transactions"]]; + }, []); + usort($transactions, function ($a, $b) { + return strtotime($a["date"]) - strtotime($b["date"]); + }); + + foreach ($transactions as $transaction) { + $balance += $transaction["amount"]; + $history[] = $balance; + $dates[] = date('j M Y, H:i', strtotime($transaction["date"])); + } + + ?> + <script> + const ctx2 = document.getElementById('history-raindrops').getContext('2d'); + const graph2 = new Chart(ctx2, { + type: 'line', + data: { + labels: JSON.parse(`<?= json_encode($dates) ?>`), + datasets: [{ + label: 'Balance', + data: JSON.parse(`<?= json_encode($history) ?>`), + borderColor: '#d7e1ff', + backgroundColor: '#d7e1ff77' + }] + }, + options: { + animation: { + duration: 0 + }, + scales: { + y: { + ticks: { + callback: function(label) { + return '€' + Math.round(label); + } + }, + grid: { + color: "rgba(255,255,255,0.25)" + } + } + }, + elements: { + point: { + radius: 5 + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(tooltipItem) { + return '€' + tooltipItem.raw.toFixed(2); + } + }, + intersect: false + } + } + } + }) + </script> + </div> + <?php else: $account = array_values(array_filter($accounts, function ($i) use ($parts) { return $i["_name"] === $parts[2]; }))[0]; ?> + <h2> + <?= $account["owner"] === "cloudburst" ? "Cloudburst System" : "Raindrops System" ?> · <?= $account["name"] ?><?php if ($account["default"]): ?><span class="text-muted"> (default)</span><?php endif; ?> + <a href="/-/money" class="small btn btn-outline-light" style="float:right;margin-top:5px;vertical-align:middle;opacity:1 !important; color:white;">Back</a> + </h2> + <div style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 15px;"> + <h3 style="margin-bottom: 0; display: flex; align-items: center; justify-content: center;"><?= $account["currency"] === "gbp" ? "£" : "€" ?><?= calculateFullAmount($account) ?></h3> + <div> + <div><b>Maximum balance:</b> <?php if (isset($account["max"])): ?><?= $account["currency"] === "gbp" ? "£" : "€" ?><?= number_format($account["max"], 2, '.', ',') ?> (<?= round((calculateFullAmount($account, true) / $account["max"]) * 100, 2) ?>% used)<?php else: ?>No maximum balance<?php endif; ?></div> + <div><b>Interests:</b> <?php if (isset($account["interests"])): ?><?= $account["interests"] * 100 ?>% interests<?php else: ?>None<?php endif; ?></div> + </div> + </div> + <canvas id="history" style="margin-top: 10px; width: 100%; height: 200px; max-height: 100%;"></canvas> + <?php + + $balance = 0; + $history = []; + $dates = []; + + $transactions = $account["transactions"]; + usort($transactions, function ($a, $b) { + return strtotime($a["date"]) - strtotime($b["date"]); + }); + + foreach ($transactions as $transaction) { + $balance += $transaction["amount"]; + $history[] = $balance; + $dates[] = date('j M Y, H:i', strtotime($transaction["date"])); + } + + ?> + <script> + const ctx = document.getElementById('history').getContext('2d'); + const graph = new Chart(ctx, { + type: 'line', + data: { + labels: JSON.parse(`<?= json_encode($dates) ?>`), + datasets: [{ + label: 'Balance', + data: JSON.parse(`<?= json_encode($history) ?>`), + borderColor: '#<?= $account["owner"] === "cloudburst" ? "ff6ae6" : "d7e1ff" ?>', + backgroundColor: '#<?= $account["owner"] === "cloudburst" ? "ff6ae6" : "d7e1ff" ?>77' + }] + }, + options: { + animation: { + duration: 0 + }, + scales: { + y: { + ticks: { + callback: function(label) { + return '<?= $account["currency"] === "gbp" ? "£" : "€" ?>' + Math.round(label); + } + }, + grid: { + color: "rgba(255,255,255,0.25)" + } + } + }, + elements: { + point: { + radius: 5 + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(tooltipItem) { + return '<?= $account["currency"] === "gbp" ? "£" : "€" ?>' + tooltipItem.raw.toFixed(2); + } + }, + intersect: false + } + } + } + }) + </script> + + <hr> + + <div class="list-group"> + <a href="#" data-bs-toggle="modal" data-bs-target="#create" class="list-group-item list-group-item-action" style="display: grid; grid-template-columns: max-content 1fr;"><img src="/assets/icons/add.svg" style="filter: invert(1); margin-right: 10px; height: 32px; width: 32px;"><div style="display: flex; align-items: center;"><b>Add new transaction</b></div></a> + <?php foreach ($account["transactions"] as $index => $transaction): $member = getMemberWithoutSystem($transaction["author"]) ?? getMemberWithoutSystem("zdtsg"); ?> + <a id="transaction-<?= $index ?>" onclick="deleteTransaction(<?= $index ?>)" class="list-group-item list-group-item-action" style="display: grid; grid-template-columns: max-content 1fr;cursor:pointer;"><div style="display: flex; align-items: center;"><img src="<?= getAsset($member["_system"], $member["id"]) ?>" style="border-radius: 999px; width: 32px; height: 32px; margin-right: 10px;"></div><div style="display: flex; align-items: center;"><div><b><?= $member["display_name"] ?? $member["name"] ?></b><?= $transaction["amount"] < 0 ? " removed" : " added" ?> <span class="<?= $transaction["amount"] < 0 ? "text-danger" : "text-success" ?>"><?= $account["currency"] === "gbp" ? "£" : "€" ?><?= number_format(abs($transaction["amount"]), 2, '.', ',') ?> </span><?= timeAgo($transaction["date"]) ?><?php if (isset($transaction["description"]) && trim($transaction["description"]) !== ""): ?>: <?= strip_tags(trim($transaction["description"])) ?><?php endif; ?></div></div></a> + <?php endforeach; ?> + </div> + + <script> + window.transactions = JSON.parse(atob("<?= base64_encode(json_encode($account["transactions"])) ?>")); + + function deleteTransaction(index) { + document.getElementById("delete-item").innerHTML = document.getElementById("transaction-" + index).innerHTML; + document.getElementById("delete-link").href = location.pathname + "/?delete&id=" + index; + new bootstrap.Modal(document.getElementById("delete")).show(); + } + </script> + + <div class="modal fade" id="delete"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">Delete transaction?</h4> + <button type="button" class="btn-close" data-bs-dismiss="modal"></button> + </div> + + <div class="modal-body"> + <p>Are you sure you want to delete the following transaction?</p> + + <p> + <div class="list-group"> + <div class="list-group-item" id="delete-item" style="display: grid; grid-template-columns: max-content 1fr;">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab eum facere minima officiis quas. Architecto ea eius impedit incidunt ipsa itaque laudantium pariatur quae repudiandae sunt unde vel, veritatis, voluptates.</div> + </div> + </p> + + <p>Deleting a transaction takes effect immediately and cannot be undone.</p> + <a href="#" id="delete-link" class="btn btn-danger">Delete</a> + </div> + </div> + </div> + </div> + + <div class="modal fade" id="create"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">Create new transaction</h4> + <button type="button" class="btn-close" data-bs-dismiss="modal"></button> + </div> + + <div class="modal-body"> + <form> + <input type="hidden" name="create"> + + <label style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px;"> + <span style="align-items: center; justify-content: right; display: flex;">Amount (<?= $account["currency"] === "gbp" ? "£" : "€" ?>):</span> + <input required type="text" pattern="^(-|)\d{1,4}([,.]\d{0,2}|)$" name="amount" class="form-control" placeholder="Amount" style="filter: invert(1) hue-rotate(180deg);" maxlength="7" minlength="1"> + </label> + + <label style="margin-top: 10px;display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px;"> + <span style="align-items: center; justify-content: right; display: flex;">Description:</span> + <input maxlength="150" type="text" name="description" class="form-control" placeholder="Description" style="filter: invert(1) hue-rotate(180deg);"> + </label> + + <p> + <label style="margin-top: 10px;display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px;"> + <span style="align-items: center; justify-content: right; display: flex;">Author:</span> + <input type="text" disabled class="form-control" placeholder="Description" style="filter: invert(1) hue-rotate(180deg);" value="<?= getMemberWithoutSystem($myId)["display_name"] ?? getMemberWithoutSystem($myId)["name"] ?>"> + </label> + </p> + + <p>Adding a transaction takes effect immediately and cannot be modified afterwards.</p> + <button id="delete-link" class="btn btn-success">Create</button> + </form> + </div> + </div> + </div> + </div> + <?php endif; ?> +</div> + +<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.inc'; ?> |