Skip to content

Commit

Permalink
added payers report
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyharrington committed May 15, 2020
1 parent 699245e commit 18dffd8
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 7 deletions.
21 changes: 17 additions & 4 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ def index():

# User reached route via GET
if request.method == "GET":
# TODO reduce or completely remove the redundant use of javascript code in dashboard.js and reports.js

# Initialize metrics to None to render the appropriate UX if data does not exist yet for the user
expenses_year = None
expenses_month = None
Expand Down Expand Up @@ -198,15 +200,15 @@ def index():
spending_month = tendie_dashboard.getMonthlySpending(
session["user_id"])

# TODO consider passing additional vars to the template that has strings formatted for the charts. E.g. javascript needs months/data in an array,
# but due to jinja looping it makes the HTML doc render really messy with a lot of spaces. Might be better to just pass a single string for those charts.

# Get spending trends for the user
spending_trends = tendie_dashboard.getSpendingTrends(
session["user_id"])

# Get payer spending for the user
payersChart = tendie_reports.generatePayersReport(session["user_id"])

return render_template("index.html", categories=categories, payers=payers, date=date, income=income, expenses_year=expenses_year, expenses_month=expenses_month, expenses_week=expenses_week, expenses_last5=expenses_last5,
budgets=budgets, spending_week=spending_week, spending_month=spending_month, spending_trends=spending_trends)
budgets=budgets, spending_week=spending_week, spending_month=spending_month, spending_trends=spending_trends, payersChart=payersChart)

# User reached route via POST
else:
Expand Down Expand Up @@ -792,6 +794,17 @@ def updateaccount():
return render_template("account.html", username=user["name"], income=user["income"], payers=user["payers"], stats=user["stats"], newIncome=None, addPayer=None, renamedPayer=None, deletedPayer=None, updatedPassword=None)


@app.route("/payersreport", methods=["GET"])
@login_required
def payersreport():
"""View payers spending report"""

# Generate a data structure that combines the users payers and expense data for chart and table
payersReport = tendie_reports.generatePayersReport(session["user_id"])

return render_template("payersreport.html", payers=payersReport)


# Handle errors by rendering apology
def errorhandler(e):
"""Handle error"""
Expand Down
78 changes: 78 additions & 0 deletions static/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ function loadTrendsData(trendsData) {
loadSpendingTrendsChart(spendingTrends);
}

// Loads *payers* data from Flask/Jinja that is passed from the request
function loadPayersData(payersData) {
payersSpending = JSON.parse(payersData);
loadPayersSpendingChart(payersSpending);
}

// After the modal is fully rendered, focus input into the new 'description' field
$('#quickExpenseModal').on('shown.bs.modal', function () {
$('#description').trigger('focus')
Expand Down Expand Up @@ -245,4 +251,76 @@ function loadSpendingTrendsChart(spendingTrends) {
}
});
}
}

function loadPayersSpendingChart(payersSpending) {
if (payersSpending == null) {
return;
}
else {
// Loop through the payers object and build the labels
payerNames = []
for (i = 0; i < payersSpending.length; i++) {
// If the payer represents less than 1% of overall expenses do not include
if (payersSpending[i].percentAmount < 1) {
continue;
}
else {
payerNames.push(payersSpending[i].name)
}
}

// Loop through the payers object and build the amounts dataset
payerAmounts = []
for (i = 0; i < payersSpending.length; i++) {
// If the payer represents less than 1% of overall expenses do not include
if (payersSpending[i].percentAmount < 1) {
continue;
}
else {
payerAmounts.push(payersSpending[i].amount)
}
}

// Build the chart
chartElement = document.getElementById('payersChart').getContext('2d');
budgetCharts = new Chart(chartElement, {
type: 'pie',
data: {
labels: payerNames,
datasets: [{
data: payerAmounts,
// Note: the color scheme for this is hard-coded with an assumption of 6 total payers including the default 'Self' payer.
// If max payer count changes in the future, the background/border colors will need to as well
backgroundColor: [
'rgba(240, 173, 78, 1)',
'rgba(2, 184, 117, 1)',
'rgba(69, 130, 236)',
'rgba(23, 162, 184)',
'rgba(202, 207, 212)',
'rgba(217, 83, 79)'
],
borderColor: [
'rgba(192, 138, 62, 1)',
'rgba(1, 147, 93, 1)',
'rgba(64, 121, 220)',
'rgba(21, 151, 171)',
'rgba(173, 181, 189)',
'rgba(195, 74, 71)'
],
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
legend: {
labels: {
fontColor: 'black'
}
}
}
});

}
}
78 changes: 78 additions & 0 deletions static/js/reports.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ function loadTrendsData(trendsData_chart, trendsData_table) {
loadSpendingTrendsTable(spendingTrends_table);
}

// Loads *payers* data from Flask/Jinja that is passed from the request
function loadPayersData(payersData) {
payersSpending = JSON.parse(payersData);
loadPayersSpendingChart(payersSpending);
}

function loadBudgetCharts(budgets) {
if (budgets == null) {
return;
Expand Down Expand Up @@ -255,4 +261,76 @@ function loadSpendingTrendsTable(spendingTrends_table) {
buttons: ['copy', 'csv', 'excel', 'colvis']
});
}
}

function loadPayersSpendingChart(payersSpending) {
if (payersSpending == null) {
return;
}
else {
// Loop through the payers object and build the labels
payerNames = []
for (i = 0; i < payersSpending.length; i++) {
// If the payer represents less than 1% of overall expenses do not include
if (payersSpending[i].percentAmount < 1) {
continue;
}
else {
payerNames.push(payersSpending[i].name)
}
}

// Loop through the payers object and build the amounts dataset
payerAmounts = []
for (i = 0; i < payersSpending.length; i++) {
// If the payer represents less than 1% of overall expenses do not include
if (payersSpending[i].percentAmount < 1) {
continue;
}
else {
payerAmounts.push(payersSpending[i].amount)
}
}

// Build the chart
chartElement = document.getElementById('payersChart').getContext('2d');
budgetCharts = new Chart(chartElement, {
type: 'pie',
data: {
labels: payerNames,
datasets: [{
data: payerAmounts,
// Note: the color scheme for this is hard-coded with an assumption of 6 total payers including the default 'Self' payer.
// If max payer count changes in the future, the background/border colors will need to as well
backgroundColor: [
'rgba(240, 173, 78, 1)',
'rgba(2, 184, 117, 1)',
'rgba(69, 130, 236)',
'rgba(23, 162, 184)',
'rgba(202, 207, 212)',
'rgba(217, 83, 79)'
],
borderColor: [
'rgba(192, 138, 62, 1)',
'rgba(1, 147, 93, 1)',
'rgba(64, 121, 220)',
'rgba(21, 151, 171)',
'rgba(173, 181, 189)',
'rgba(195, 74, 71)'
],
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
legend: {
labels: {
fontColor: 'black'
}
}
}
});

}
}
36 changes: 36 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ <h4 class="card-title">{{ expenses_week | usd }}</h4>
<div class="row">
<div class="col-12">
<h4 style="text-align:left">Last 5 Expenses</h4>
<p class="small" style="text-align:left"><a href="/expensehistory">(view all expense history)</a></p>
{% if expenses_last5 != None %}
<div class="table-responsive">
<table class="table table-hover table-striped table-sm">
Expand Down Expand Up @@ -180,6 +181,7 @@ <h4 style="text-align:left">Last 5 Expenses</h4>
<div class="row">
<div class="col-12">
<h2 style="text-align:left">Your Budgets</h2>
<p class="small" style="text-align:left"><a href="/budgets">(view all budgets)</a></p>
</div>
</div>
<div class="row">
Expand Down Expand Up @@ -237,6 +239,7 @@ <h2 style="text-align:left">Weekly Spending</h2>
<div class="row">
<div class="col-12">
<h2 style="text-align:left">Monthly Spending</h2>
<p class="small" style="text-align:left"><a href="/monthlyreport">(view full monthly report)</a></p>
</div>
</div>
<div class="row">
Expand All @@ -260,6 +263,7 @@ <h2 style="text-align:left">Monthly Spending</h2>
<div class="row">
<div class="col-12">
<h2 style="text-align:left">Spending Habits</h2>
<p class="small" style="text-align:left"><a href="/spendingreport">(view full spending report)</a></p>
</div>
</div>
<div class="row">
Expand All @@ -279,6 +283,31 @@ <h2 style="text-align:left">Spending Habits</h2>
{% endif %}
</div>
<!-- End Spending Habits Chart -->

<!-- Begin Payers Chart -->
<br>
<div class="row">
<div class="col-12">
<h2 style="text-align:left">Payer Spending</h2>
<p class="small" style="text-align:left"><a href="/payersreport">(view full payers report)</a></p>
</div>
</div>
<div class="row">
{% if payersChart %}
<div class="col-12">
<div class="chart-container" style="position: relative; height:40vh">
<canvas id="payersChart" width="400" height="400"></canvas>
</div>
<p><small class="text-muted">Chart note: does not include payers that represent less than 1% of overall spending</small></p>
</div>
{% else %}
<div class="col">
<p style="text-align:left"><small class="text-muted">Chart will not display until you record at least 1
expense.</small></p>
</div>
{% endif %}
</div>
<!-- End payers Chart -->
</div>


Expand Down Expand Up @@ -312,4 +341,11 @@ <h2 style="text-align:left">Spending Habits</h2>
</script>
{% endif %}

{% if payersChart %}
<script>
var payersData = JSON.stringify({{ payersChart | tojson }});
loadPayersData(payersData);
</script>
{% endif %}

{% endblock %}
59 changes: 59 additions & 0 deletions templates/payersreport.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{% extends "layout.html" %}

{% block scripts %}
<!--Chart.js-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/Chart.min.js"></script>
{% endblock %}

{% block title %}
Reports | Payers Spending
{% endblock %}

{% block main %}
<h1>Payers Spending Report</h1>
<br>

<!--Charts courtesty of Chart.js: https://www.chartjs.org/-->
<div class="chart-container" style="position: relative; height:40vh">
<canvas id="payersChart" width="400" height="400"></canvas>
</div>
<p><small class="text-muted">Chart note: does not include payers that represent less than 1% of overall spending</small></p>
<br>

<h3>Total Paid Per Payer</h3>
<div class="table-responsive">
<table class="table table-hover table-striped table-sm">
<thead>
<tr>
<th scope="col">Payer</th>
<th scope="col">Amount</th>
<th scope="col">% of total</th>
</tr>
</thead>
<tbody>
{% for payer in payers %}
<tr>
<td>{{ payer["name"] }}</td>
<td>{{ payer["amount"] | usd }}</td>
<td>{{ payer["percentAmount"] }}%</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<th>Total</th>
<th>{{ payers | sum(attribute="amount") | usd }}</th>
<th>{{ payers | sum(attribute="percentAmount")}}%</th>
</tr>
</tfoot>
</table>
</div>

<script src="/static/js/reports.js"></script>
<script>
var payersData = JSON.stringify({{ payers | tojson }});
loadPayersData(payersData);
</script>

{% endblock %}

2 changes: 1 addition & 1 deletion templates/reports.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ <h5 class="card-title">Spending Categories</h5>
<div class="card-body">
<h5 class="card-title">Payer</h5>
<p>View how spending is managed between payers.</p>
<a href="#" class="btn btn-success">View Report</a>
<a href="/payersreport" class="btn btn-success">View Report</a>
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion tendie_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def getPayers(userID):
# Add a payer to the users account
def addPayer(name, userID):

# Make sure the user has no more than 5 payers (note: this max amount is arbitrary, 5 sounded good ¯\_(ツ)_/¯)
# Make sure the user has no more than 5 payers (6 w/ default 'Self') (note: this max amount is arbitrary, 5 sounded good ¯\_(ツ)_/¯.
# Also note that the payers report charts have 5 hardcoded color pallettes that will need to be updated if the max number of payers is changed in the future)
if getTotalPayers(userID) >= 5:
return {"apology": "You have the maximum number of payers. Try deleting one you aren't using or contact the admin."}

Expand Down
Loading

0 comments on commit 18dffd8

Please sign in to comment.