Add more statistics with graphs
This commit is contained in:
parent
602387df58
commit
190c052963
20
scripts/libs/chart.js
Normal file
20
scripts/libs/chart.js
Normal file
File diff suppressed because one or more lines are too long
21
scripts/stats.js
Normal file
21
scripts/stats.js
Normal file
@ -0,0 +1,21 @@
|
||||
function loadGraph(container, dataPoints, symbol, run) {
|
||||
if (run) {
|
||||
var ctx = document.getElementById(container).getContext('2d');
|
||||
|
||||
var chart = new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: dataPoints.map(point => point.y),
|
||||
}],
|
||||
labels: dataPoints.map(point => `${point.label} (${point.y}${symbol})`),
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
animateRotate: true,
|
||||
animateScale: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -540,6 +540,15 @@
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Chart.js:
|
||||
<span>
|
||||
https://www.chartjs.org/
|
||||
<a href="https://www.chartjs.org/" target="_blank" title="Visit external url">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
106
stats.php
106
stats.php
@ -51,7 +51,18 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$members[$memberId] = $row;
|
||||
$memberCost[$memberId]['cost'] = 0;
|
||||
$memberCost[$memberId]['name'] = $row['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get categories
|
||||
$categories = array();
|
||||
$query = "SELECT * FROM categories";
|
||||
$result = $db->query($query);
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$categoryId = $row['id'];
|
||||
$categories[$categoryId] = $row;
|
||||
$categoryCost[$categoryId]['cost'] = 0;
|
||||
$categoryCost[$categoryId]['name'] = $row['name'];
|
||||
}
|
||||
|
||||
// Get symbol of main currency to display on statistics
|
||||
$query = "SELECT c.symbol
|
||||
@ -76,7 +87,8 @@ $activeSubscriptions = $row['active_subscriptions'];
|
||||
$mostExpensiveSubscription = 0;
|
||||
$amountDueThisMonth = 0;
|
||||
$totalCostPerMonth = 0;
|
||||
$query = "SELECT name, price, frequency, cycle, currency_id, next_payment, payer_user_id FROM subscriptions";
|
||||
|
||||
$query = "SELECT name, price, frequency, cycle, currency_id, next_payment, payer_user_id, category_id FROM subscriptions";
|
||||
$result = $db->query($query);
|
||||
if ($result) {
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
@ -91,10 +103,12 @@ if ($result) {
|
||||
$currency = $subscription['currency_id'];
|
||||
$next_payment = $subscription['next_payment'];
|
||||
$payerId = $subscription['payer_user_id'];
|
||||
$categoryId = $subscription['category_id'];
|
||||
$originalSubscriptionPrice = getPriceConverted($price, $currency, $db);
|
||||
$price = getPricePerMonth($cycle, $frequency, $originalSubscriptionPrice);
|
||||
$totalCostPerMonth += $price;
|
||||
$memberCost[$payerId]['cost'] += $price;
|
||||
$categoryCost[$categoryId]['cost'] += $price;
|
||||
if ($price > $mostExpensiveSubscription) {
|
||||
$mostExpensiveSubscription = $price;
|
||||
}
|
||||
@ -103,7 +117,13 @@ if ($result) {
|
||||
if ((int)$memberCost[$payerId]['cost'] == $memberCost[$payerId]['cost']) {
|
||||
$memberCost[$payerId]['cost'] = (int)$memberCost[$payerId]['cost'];
|
||||
}
|
||||
|
||||
$categoryCost[$categoryId]['cost'] = number_format($categoryCost[$categoryId]['cost'], 2, ".", "");
|
||||
if ((int)$categoryCost[$categoryId]['cost'] == $categoryCost[$categoryId]['cost']) {
|
||||
$categoryCost[$categoryId]['cost'] = (int)$categoryCost[$categoryId]['cost'];
|
||||
}
|
||||
|
||||
// Calculate ammount due this month
|
||||
$nextPaymentDate = DateTime::createFromFormat('Y-m-d', trim($next_payment));
|
||||
$tomorrow = new DateTime('tomorrow');
|
||||
$endOfMonth = new DateTime('last day of this month');
|
||||
@ -122,6 +142,7 @@ if ($result) {
|
||||
}
|
||||
$amountDueThisMonth += $originalSubscriptionPrice * $timesToPay;
|
||||
}
|
||||
|
||||
}
|
||||
$mostExpensiveSubscription = number_format($mostExpensiveSubscription, 2, ".", "");
|
||||
|
||||
@ -151,6 +172,7 @@ if ($result) {
|
||||
|
||||
?>
|
||||
<section class="contain">
|
||||
<h2>General Statistics</h2>
|
||||
<div class="statistics">
|
||||
<div class="statistic">
|
||||
<span><?= $activeSubscriptions ?></span>
|
||||
@ -178,23 +200,81 @@ if ($result) {
|
||||
</div>
|
||||
<?php
|
||||
$numberOfElements = 6;
|
||||
foreach($memberCost as $member) {
|
||||
?>
|
||||
<div class="statistic">
|
||||
<span><?= $member['cost'] ?><?= $symbol ?></span>
|
||||
<div class="title"><?= $member['name'] ?>'s monthly costs</div>
|
||||
</div>
|
||||
<?php
|
||||
$numberOfElements++;
|
||||
}
|
||||
if (($numberOfElements + 1) % 3 == 0) {
|
||||
?>
|
||||
<div class="statistic empty"><div>
|
||||
<div class="statistic empty"></div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
?>
|
||||
</div>
|
||||
<h2>Split Views</h2>
|
||||
<div class="graphs">
|
||||
<?php
|
||||
|
||||
foreach ($categoryCost as $category) {
|
||||
if ($category['cost'] != 0) {
|
||||
$categoryDataPoints[] = [
|
||||
"label" => $category['name'],
|
||||
"y" => $category["cost"],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$showCategoryCostGraph = count($categoryCost) > 1;
|
||||
|
||||
foreach ($memberCost as $member) {
|
||||
if ($member['cost'] != 0) {
|
||||
$memberDataPoints[] = [
|
||||
"label" => $member['name'],
|
||||
"y" => $member["cost"],
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$showMemberCostGraph = count($memberCost) > 1;
|
||||
|
||||
if ($showMemberCostGraph) {
|
||||
?>
|
||||
<section class="graph">
|
||||
<header>
|
||||
Household Split
|
||||
<div class="sub-header">(Monthly cost)</div>
|
||||
</header>
|
||||
<canvas id="memberSplitChart"></canvas>
|
||||
</section>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ($showCategoryCostGraph) {
|
||||
?>
|
||||
<section class="graph">
|
||||
<header>
|
||||
Category Split
|
||||
<div class="sub-header">(Monthly cost)</div>
|
||||
</header>
|
||||
<canvas id="categorySplitChart" style="height: 370px; width: 100%;"></canvas>
|
||||
</section>
|
||||
<?php
|
||||
}
|
||||
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
if ($showCategoryCostGraph || $showMemberCostGraph || $showCostPerMonthGraph) {
|
||||
?>
|
||||
<script src="scripts/libs/chart.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
loadGraph("categorySplitChart", <?php echo json_encode($categoryDataPoints, JSON_NUMERIC_CHECK); ?>, "<?= $symbol ?>", <?= $showCategoryCostGraph ?>);
|
||||
loadGraph("memberSplitChart", <?php echo json_encode($memberDataPoints, JSON_NUMERIC_CHECK); ?>, "<?= $symbol ?>", <?= $showMemberCostGraph ?>);
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<script src="scripts/stats.js"></script>
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
@ -18,7 +18,8 @@ header .logo .logo-image {
|
||||
.logo-search,
|
||||
.dropdown-content,
|
||||
.sort-options,
|
||||
.statistic {
|
||||
.statistic,
|
||||
.graph {
|
||||
background-color: #222;
|
||||
border: 1px solid #333;
|
||||
box-shadow: 0 2px 5px rgba(120, 120, 120, 0.1);
|
||||
|
||||
@ -21,7 +21,7 @@ body, html {
|
||||
max-width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #eee;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
@ -80,7 +80,7 @@ select {
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ input[type="submit"] {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ input[type="checkbox"] {
|
||||
margin: 0px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ main > .contain {
|
||||
background-color: #007bff;
|
||||
padding: 12px 30px;
|
||||
font-size: 1rem;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -190,7 +190,7 @@ main > .contain {
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 12px 15px;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.subscription > span {
|
||||
@ -295,7 +295,7 @@ main > .contain {
|
||||
border: 1px solid #eee;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.account-section header h2 {
|
||||
@ -497,6 +497,7 @@ main > .contain {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
line-break: anywhere;
|
||||
}
|
||||
|
||||
.credits-list > p {
|
||||
@ -586,7 +587,7 @@ select {
|
||||
font-size: 16px;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
color: #202020;
|
||||
box-sizing: border-box;
|
||||
@ -624,7 +625,7 @@ input[type="button"] {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid #007bff;
|
||||
@ -674,7 +675,7 @@ input[type="checkbox"] {
|
||||
margin: 0px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
@ -697,7 +698,7 @@ input[type="checkbox"] {
|
||||
overflow-x: hidden;
|
||||
padding: 10px;
|
||||
border: 1px solid #EEEEEE;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
background-color: white;
|
||||
box-sizing: border-box;
|
||||
@ -758,7 +759,7 @@ input[type="checkbox"] {
|
||||
background-color: #FFFFFF;
|
||||
padding: 22px;
|
||||
border: 1px solid #EEEEEE;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
@ -852,7 +853,7 @@ input[type="checkbox"] {
|
||||
font-size: 16px;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EEEEEE;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
top: 48px;
|
||||
@ -1040,7 +1041,7 @@ input[type="checkbox"] {
|
||||
.statistic {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EEEEEE;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px 24px 30px;
|
||||
display: flex;
|
||||
@ -1074,4 +1075,45 @@ input[type="checkbox"] {
|
||||
|
||||
.statistic > .title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graphs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.graph {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EEEEEE;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
flex-basis: 48%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.graph > header {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graph > header > .sub-header {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.graph {
|
||||
flex-basis: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user