Add budget graphs
On the budget page, add a pie chart of expenses, and a bar chart comparing expenses to revenue.
This commit is contained in:
parent
8cce90f83b
commit
9c0d7122f6
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Society Self-Service
|
Society Self-Service
|
||||||
Copyright © 2018–2020 Yingtong Li (RunasSudo)
|
Copyright © 2018–2023 Yingtong Li (RunasSudo)
|
||||||
|
Copyright © 2023 MUMUS Inc.
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -21,7 +22,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="ui stackable grid">
|
<div class="ui stackable grid">
|
||||||
{# side menu #}
|
{# side menu #}
|
||||||
<div class="four wide column">
|
<div class="four wide column" id="sidebar">
|
||||||
<div class="ui vertical fluid menu">
|
<div class="ui vertical fluid menu">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
Budgets
|
Budgets
|
||||||
@ -43,8 +44,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% block aftersidebar %}
|
||||||
<div class="twelve wide column">
|
<div class="twelve wide column">
|
||||||
{% block maincontent %}{% endblock %}
|
{% block maincontent %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Society Self-Service
|
Society Self-Service
|
||||||
Copyright © 2018–2020 Yingtong Li (RunasSudo)
|
Copyright © 2018–2023 Yingtong Li (RunasSudo)
|
||||||
|
Copyright © 2023 MUMUS Inc.
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -20,7 +21,8 @@
|
|||||||
|
|
||||||
{% block title %}{{ revision.name }}{% endblock %}
|
{% block title %}{{ revision.name }}{% endblock %}
|
||||||
|
|
||||||
{% block maincontent %}
|
{% block aftersidebar %}
|
||||||
|
<div class="eight wide column">
|
||||||
<h1>{{ revision.name }}</h1>
|
<h1>{{ revision.name }}</h1>
|
||||||
|
|
||||||
<form class="ui form" action="{{ url('budget_action', kwargs={'id': revision.budget.id}) }}" method="POST">
|
<form class="ui form" action="{{ url('budget_action', kwargs={'id': revision.budget.id}) }}" method="POST">
|
||||||
@ -301,6 +303,15 @@
|
|||||||
<div class="ui cancel button">Cancel</div>
|
<div class="ui cancel button">Cancel</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="four wide column">
|
||||||
|
<p class="ui header">Expenses</p>
|
||||||
|
<canvas id="chartExpenses"></canvas>
|
||||||
|
|
||||||
|
<p class="ui header">Revenue and expenses</p>
|
||||||
|
<canvas id="chartRevExp"></canvas>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
@ -327,6 +338,15 @@
|
|||||||
.jsgrid-header-row .jsgrid-header-cell {
|
.jsgrid-header-row .jsgrid-header-cell {
|
||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Full screen width for graphs */
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
.ui.container {
|
||||||
|
width: auto;
|
||||||
|
margin-left: 64px !important;
|
||||||
|
margin-right: 64px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -355,6 +375,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jsgrid@1.5.3/dist/jsgrid.min.js" integrity="sha256-lzjMTpg04xOdI+MJdjBst98bVI6qHToLyVodu3EywFU=" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jsgrid@1.5.3/dist/jsgrid.min.js" integrity="sha256-lzjMTpg04xOdI+MJdjBst98bVI6qHToLyVodu3EywFU=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.js" integrity="sha256-5dsP1lVzcWPn5aOwu+zs+G+TqAu9oT8NUNM0C4n3H+4=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script src="{{ static('sstreasury/budget.js') }}"></script>
|
<script src="{{ static('sstreasury/budget.js') }}"></script>
|
||||||
|
|
||||||
@ -363,5 +384,6 @@
|
|||||||
var expense_data = JSON.parse({{ import('json').dumps(import('json').dumps(revision.expense))|safe }});
|
var expense_data = JSON.parse({{ import('json').dumps(import('json').dumps(revision.expense))|safe }});
|
||||||
var editing = false;
|
var editing = false;
|
||||||
makeGrid();
|
makeGrid();
|
||||||
|
makeCharts();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Society Self-Service
|
Society Self-Service
|
||||||
Copyright © 2018-2019 Yingtong Li (RunasSudo)
|
Copyright © 2018-2023 Yingtong Li (RunasSudo)
|
||||||
|
Copyright © 2023 MUMUS Inc.
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,22 +19,27 @@
|
|||||||
|
|
||||||
$('.ui.accordion').accordion();
|
$('.ui.accordion').accordion();
|
||||||
|
|
||||||
|
var revTotal = 0;
|
||||||
|
var revTotalIWT = 0;
|
||||||
|
var expTotal = 0;
|
||||||
|
var emergency_fund_mult = 0.05;
|
||||||
|
|
||||||
function recalcRevTotal(args) {
|
function recalcRevTotal(args) {
|
||||||
var total = 0;
|
revTotal = 0;
|
||||||
var totalIWT = 0;
|
revTotalIWT = 0;
|
||||||
for (var row of args.grid.data) {
|
for (var row of args.grid.data) {
|
||||||
total += row['Unit price'] * row['Units'];
|
revTotal += row['Unit price'] * row['Units'];
|
||||||
if (row['Unit price'] > 0 && row['IWT']) {
|
if (row['Unit price'] > 0 && row['IWT']) {
|
||||||
totalIWT += (row['Unit price'] - (row['Unit price'] - 0.8133) / 1.01884) * row['Units'];
|
revTotalIWT += (row['Unit price'] - (row['Unit price'] - 0.8133) / 1.01884) * row['Units'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(args.grid._body).find('.totalrow').remove();
|
$(args.grid._body).find('.totalrow').remove();
|
||||||
|
|
||||||
if (totalIWT > 0) {
|
if (revTotalIWT > 0) {
|
||||||
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
|
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
|
||||||
totalrow.append($('<td class="jsgrid-cell">Less IWT fees:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
totalrow.append($('<td class="jsgrid-cell">Less IWT fees:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
||||||
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('($' + totalIWT.toFixed(2) + ')'));
|
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('($' + revTotalIWT.toFixed(2) + ')'));
|
||||||
if (editing) {
|
if (editing) {
|
||||||
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
||||||
}
|
}
|
||||||
@ -42,7 +48,7 @@ function recalcRevTotal(args) {
|
|||||||
|
|
||||||
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-weight: bold;"></tr>');
|
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-weight: bold;"></tr>');
|
||||||
totalrow.append($('<td class="jsgrid-cell">Total:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
totalrow.append($('<td class="jsgrid-cell">Total:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
||||||
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (total - totalIWT).toFixed(2)));
|
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (revTotal - revTotalIWT).toFixed(2)));
|
||||||
if (editing) {
|
if (editing) {
|
||||||
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
||||||
}
|
}
|
||||||
@ -50,21 +56,21 @@ function recalcRevTotal(args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recalcExpTotal(args) {
|
function recalcExpTotal(args) {
|
||||||
var total = 0;
|
expTotal = 0;
|
||||||
for (var row of args.grid.data) {
|
for (var row of args.grid.data) {
|
||||||
total += row['Unit price'] * row['Units'];
|
expTotal += row['Unit price'] * row['Units'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$(args.grid._body).find('.totalrow').remove();
|
$(args.grid._body).find('.totalrow').remove();
|
||||||
|
|
||||||
var emergency_fund_mult = 0.05;
|
emergency_fund_mult = 0.05;
|
||||||
if ($('#expense_no_emergency_fund').length > 0 && $('#expense_no_emergency_fund').prop('checked')) {
|
if ($('#expense_no_emergency_fund').length > 0 && $('#expense_no_emergency_fund').prop('checked')) {
|
||||||
emergency_fund_mult = 0;
|
emergency_fund_mult = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
|
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
|
||||||
totalrow.append($('<td class="jsgrid-cell">Plus emergency fund:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
totalrow.append($('<td class="jsgrid-cell">Plus emergency fund:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
||||||
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (total * emergency_fund_mult).toFixed(2)));
|
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (expTotal * emergency_fund_mult).toFixed(2)));
|
||||||
if (editing) {
|
if (editing) {
|
||||||
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
||||||
}
|
}
|
||||||
@ -72,7 +78,7 @@ function recalcExpTotal(args) {
|
|||||||
|
|
||||||
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-weight: bold;"></tr>');
|
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-weight: bold;"></tr>');
|
||||||
totalrow.append($('<td class="jsgrid-cell">Total:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
totalrow.append($('<td class="jsgrid-cell">Total:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
|
||||||
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (total * (1 + emergency_fund_mult)).toFixed(2)));
|
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (expTotal * (1 + emergency_fund_mult)).toFixed(2)));
|
||||||
if (editing) {
|
if (editing) {
|
||||||
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
totalrow.append($('<td class="jsgrid-cell"></td>'));
|
||||||
}
|
}
|
||||||
@ -146,3 +152,56 @@ function makeGrid() {
|
|||||||
$('.jsgrid-filter-row, .jsgrid-insert-row').attr('style', 'display: none !important;'); /* Override Semantic UI */
|
$('.jsgrid-filter-row, .jsgrid-insert-row').attr('style', 'display: none !important;'); /* Override Semantic UI */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeCharts() {
|
||||||
|
// Display expense, revenue charts on budget view page
|
||||||
|
|
||||||
|
new Chart(document.getElementById('chartExpenses'), {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: expense_data.map(e => e['Description']),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Expenses',
|
||||||
|
data: expense_data.map(e => e['Unit price'] * e['Units'])
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: i => '$' + i.parsed.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(document.getElementById('chartRevExp'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Revenue', 'Expenses'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Budget',
|
||||||
|
data: [revTotal - revTotalIWT, (expTotal * (1 + emergency_fund_mult))],
|
||||||
|
backgroundColor: ['#36a2eb', '#ff6384']
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: i => '$' + i.parsed.y.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user