summaryrefslogtreecommitdiff
path: root/pages
diff options
context:
space:
mode:
Diffstat (limited to 'pages')
-rw-r--r--pages/metadata.inc5
-rw-r--r--pages/money.inc588
2 files changed, 593 insertions, 0 deletions
diff --git a/pages/metadata.inc b/pages/metadata.inc
index ffe2397..f3e48d7 100644
--- a/pages/metadata.inc
+++ b/pages/metadata.inc
@@ -67,6 +67,10 @@ if ($member === null) {
$toUpdate["bitset"] = (int)$_GET["bitset"];
}
+ if (isset($_GET["interest"])) {
+ $toUpdate["interest"] = strip_tags($_GET["interest"]);
+ }
+
if (isset($_GET["marefriends"])) {
$toUpdate["marefriends"] = array_values(array_filter(array_map(function ($i) { return trim($i); }, explode(",", $_GET["marefriends"])), function ($i) {
return !!preg_match("/^(ynmuc|gdapd)\/[a-z]{5}$/m", $i);
@@ -126,6 +130,7 @@ if ($member === null) {
"heat" => $metadata["heat"] ?? null,
"birth" => $metadata["birth"] ?? ["age" => 0, "date" => "01-01", "year" => null],
"code" => $metadata["code"] ?? null,
+ "interest" => $metadata["interest"] ?? null
];
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json", json_encode($metadata));
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" ?>&nbsp;<span class="<?= $transaction["amount"] < 0 ? "text-danger" : "text-success" ?>"><?= $account["currency"] === "gbp" ? "£" : "€" ?><?= number_format(abs($transaction["amount"]), 2, '.', ',') ?>&nbsp;</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'; ?>