
11 changed files with 21073 additions and 6 deletions
@ -0,0 +1,22 @@ |
|||
# http://releases.llvm.org/6.0.0/tools/clang/docs/ClangFormatStyleOptions.html |
|||
BasedOnStyle: Google |
|||
AccessModifierOffset: -4 |
|||
AllowShortBlocksOnASingleLine: false |
|||
AllowShortFunctionsOnASingleLine: None |
|||
AllowShortIfStatementsOnASingleLine: false |
|||
AllowShortLoopsOnASingleLine: false |
|||
BreakBeforeBraces: Allman |
|||
BraceWrapping: |
|||
AfterNamespace: false |
|||
BreakBeforeTernaryOperators: false |
|||
ColumnLimit: 100 |
|||
ConstructorInitializerIndentWidth: 4 |
|||
ContinuationIndentWidth: 4 |
|||
IndentWidth: 4 |
|||
Standard: Cpp11 |
|||
TabWidth: 4 |
|||
UseTab: ForIndentation |
|||
DerivePointerAlignment: false |
|||
PointerAlignment: Left |
|||
NamespaceIndentation: None |
|||
IndentCaseLabels: true |
@ -0,0 +1,47 @@ |
|||
/* |
|||
* DOM element rendering detection |
|||
* https://davidwalsh.name/detect-node-insertion |
|||
*/ |
|||
@keyframes chartjs-render-animation { |
|||
from { opacity: 0.99; } |
|||
to { opacity: 1; } |
|||
} |
|||
|
|||
.chartjs-render-monitor { |
|||
animation: chartjs-render-animation 0.001s; |
|||
} |
|||
|
|||
/* |
|||
* DOM element resizing detection |
|||
* https://github.com/marcj/css-element-queries |
|||
*/ |
|||
.chartjs-size-monitor, |
|||
.chartjs-size-monitor-expand, |
|||
.chartjs-size-monitor-shrink { |
|||
position: absolute; |
|||
direction: ltr; |
|||
left: 0; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
overflow: hidden; |
|||
pointer-events: none; |
|||
visibility: hidden; |
|||
z-index: -1; |
|||
} |
|||
|
|||
.chartjs-size-monitor-expand > div { |
|||
position: absolute; |
|||
width: 1000000px; |
|||
height: 1000000px; |
|||
left: 0; |
|||
top: 0; |
|||
} |
|||
|
|||
.chartjs-size-monitor-shrink > div { |
|||
position: absolute; |
|||
width: 200%; |
|||
height: 200%; |
|||
left: 0; |
|||
top: 0; |
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,44 @@ |
|||
// Thanks https://www.html5rocks.com/en/tutorials/file/dndfiles/
|
|||
function handleFileSelect(evt) |
|||
{ |
|||
var files = evt.target.files; // FileList object
|
|||
|
|||
// files is a FileList of File objects. List some properties.
|
|||
var output = []; |
|||
for (var i = 0, file; file = files[i]; i++) |
|||
{ |
|||
/* output.push( |
|||
'<li><strong>', escape(file.name), '</strong> (', file.type || 'n/a', ') - ', file.size, |
|||
' bytes, last modified: ', |
|||
file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() : 'n/a', |
|||
!file.type.match('text.csv') ? ' (unsupported format)' : ' (CSV supported)', '</li>'); |
|||
*/ |
|||
var reader = new FileReader(); |
|||
reader.onload = function() { |
|||
var transactionTable = document.getElementById('transactionTable'); |
|||
transactionTable.innerHTML = '<thead></thead><tbody>'; |
|||
|
|||
var text = reader.result; |
|||
var lines = text.split('\n'); |
|||
// Note: Skip row 0, which has header labels
|
|||
for (var lineIndex = 1; lineIndex < lines.length; ++lineIndex) |
|||
{ |
|||
line = lines[lineIndex]; |
|||
var logEntry = makeTransactionLogEntryFromCSV(line); |
|||
/* console.log(logEntry); */ |
|||
if (logEntry) |
|||
{ |
|||
gTransactionData.push(logEntry); |
|||
} |
|||
} |
|||
|
|||
processTransactionData(); |
|||
updateVisualizations(); |
|||
|
|||
transactionTable.innerHTML += '</tbody>'; |
|||
}; |
|||
reader.readAsText(file); |
|||
} |
|||
} |
|||
|
|||
document.getElementById('filesUpload').addEventListener('change', handleFileSelect, false); |
File diff suppressed because it is too large
@ -0,0 +1,83 @@ |
|||
var gTransactionData = []; |
|||
var gBalanceOverTime = []; |
|||
var gTransactionsByCategory = {}; |
|||
|
|||
// Recalculate charts now that transaction data changed
|
|||
function processTransactionData() |
|||
{ |
|||
gBalanceOverTime = []; |
|||
for (var i = 0; i < gTransactionData.length; ++i) |
|||
{ |
|||
gBalanceOverTime.push({x: gTransactionData[i].date, y: gTransactionData[i].balance}); |
|||
|
|||
// Separate by category
|
|||
if (gTransactionData[i].userCategories) { |
|||
for (var categoryIndex = 0; categoryIndex < gTransactionData[i].userCategories.length; ++categoryIndex) { |
|||
categoryName = gTransactionData[i].userCategories[categoryIndex]; |
|||
if (categoryName in gTransactionsByCategory) { |
|||
gTransactionsByCategory[categoryName].push(gTransactionData[i]); |
|||
} |
|||
else { |
|||
gTransactionsByCategory[categoryName] = [gTransactionData[i]]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
let dateRegex = /(.*)\/(.*)\/(.*)/ |
|||
|
|||
// Assume format account,date,amount,balance,category,description,memo,notes\r
|
|||
function makeTransactionLogEntryFromCSV(csvLine) |
|||
{ |
|||
splitLine = csvLine.trim().split(','); |
|||
if (splitLine.length != 8) |
|||
{ |
|||
console.log('Error: CSV line has unexpected formatting: \n' + csvLine); |
|||
return null; |
|||
} |
|||
|
|||
dateMatch = splitLine[1].match(dateRegex); |
|||
|
|||
var newEntry = { |
|||
account: splitLine[0], |
|||
// E.g. 12/17/2019
|
|||
// Month is zero indexed in JS
|
|||
date: new Date(dateMatch[3], dateMatch[1] - 1, dateMatch[2], 1, 1, 1, 1), |
|||
amount: splitLine[2], |
|||
balance: parseFloat(splitLine[3].replace("$", "")), |
|||
bankCategory: splitLine[4], |
|||
description: splitLine[5], |
|||
memo: splitLine[6], |
|||
notes: splitLine[7], |
|||
userCategories: [] |
|||
}; |
|||
|
|||
categorizeEntry(newEntry); |
|||
|
|||
return newEntry; |
|||
} |
|||
|
|||
function addUserCategory(entry, newCategory) { |
|||
if (entry.userCategories.indexOf(newCategory) === -1) { |
|||
entry.userCategories.push(newCategory); |
|||
} |
|||
} |
|||
|
|||
|
|||
function categorizeEntry(entry) |
|||
{ |
|||
// These are provided by my bank. Not sure I'm happy they're analyzing my transactions... :(
|
|||
if (entry.bankCategory == 'DiningOut') { |
|||
addUserCategory(entry, 'Food'); |
|||
} |
|||
else if (entry.bankCategory == 'Travel') { |
|||
addUserCategory(entry, 'Travel'); |
|||
} |
|||
else if (entry.bankCategory == 'Entertainment') { |
|||
addUserCategory(entry, 'Entertainment'); |
|||
} |
|||
else if (entry.bankCategory == 'Automobile') { |
|||
addUserCategory(entry, 'Gas'); |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
<html> |
|||
<head> |
|||
<title>Upload Transactions</title> |
|||
|
|||
<meta charset="utf-8"> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<!-- For mobile: set scale to native --> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
|
|||
<link rel="stylesheet" type="text/css" href="/webInterfaceNoAuth/index.css"> |
|||
</head> |
|||
<body> |
|||
<h1>Upload Transactions</h1> |
|||
|
|||
<a href="/">Back to Homepage</a><br /><br /> |
|||
|
|||
<p>Lazy Budget cannot access your bank account automatically. |
|||
If you want this functionality, I recommend using <a href="https://www.mint.com/">Mint</a>, though you may not have your privacy respected with that service.</p> |
|||
<h2>Supported Formats</h2> |
|||
<p>Lazy Budget supports the following formats:</p> |
|||
<ul> |
|||
<li>CSV</li> |
|||
</ul> |
|||
</body> |
|||
</html> |
@ -0,0 +1,37 @@ |
|||
// This script uses Chart.js
|
|||
// See https://www.chartjs.org/docs/latest/
|
|||
|
|||
var ctx = document.getElementById('myChart').getContext('2d'); |
|||
// Bar graph
|
|||
/* var myChart = new Chart(ctx, { |
|||
* type: 'bar', |
|||
* data: { |
|||
* labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], |
|||
* datasets: [{ |
|||
* label: '# of Votes', |
|||
* data: [12, 19, 3, 5, 2, 3], |
|||
* backgroundColor: [ |
|||
* 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', |
|||
* 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' |
|||
* ], |
|||
* borderColor: [ |
|||
* 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', |
|||
* 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' |
|||
* ], |
|||
* borderWidth: 1 |
|||
* }] |
|||
* }, |
|||
* options: {scales: {yAxes: [{ticks: {beginAtZero: true}}]}} |
|||
* }); */ |
|||
|
|||
var gBalanceOverTimeChart = new Chart(ctx, { |
|||
type: 'line', |
|||
data: {datasets: [{label: 'Balance over time', data: gBalanceOverTime, backgroundColor: 'rgba(200, 30, 30, 0.2)'}]}, |
|||
options: {scales: {xAxes: [{type: 'time'}]}} |
|||
}); |
|||
|
|||
function updateVisualizations() |
|||
{ |
|||
gBalanceOverTimeChart.data.datasets[0].data = gBalanceOverTime; |
|||
gBalanceOverTimeChart.update(); |
|||
} |
@ -0,0 +1,52 @@ |
|||
<html> |
|||
<head> |
|||
<title>Lazy Budget</title> |
|||
|
|||
<meta charset="utf-8"> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<!-- For mobile: set scale to native --> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
|
|||
<link rel="stylesheet" type="text/css" href="/webInterfaceNoAuth/index.css"> |
|||
</head> |
|||
<body> |
|||
<h1>Lazy Budget</h1> |
|||
|
|||
<script type="text/javascript" src="/webInterface/TransactionData.js"></script> |
|||
|
|||
<!-- |
|||
Visualization |
|||
--> |
|||
<!-- Note that this size will be changed automatically by Chart.js --> |
|||
<canvas id="myChart" width="400" height="400"></canvas><br /> |
|||
<!-- Moment: Required by Chart for time support; https://momentjs.com/ --> |
|||
<script type="text/javascript" src="/webInterface/Moment.js"></script> |
|||
<!-- From https://github.com/chartjs/Chart.js --> |
|||
<script type="text/javascript" src="/webInterface/Chart.js"></script> |
|||
<!-- Code using Chart.js for Lazy Budget --> |
|||
<script type="text/javascript" src="/webInterface/Visualization.js"></script> |
|||
|
|||
<!-- File upload --> |
|||
<input type="file" id="filesUpload" name="files[]" multiple /> |
|||
<output id="output"></output> |
|||
<script type="text/javascript" src="/webInterface/Files.js"></script> |
|||
<!-- Raw data --> |
|||
<table id="transactionTable"> |
|||
</table> |
|||
|
|||
<nav> |
|||
<a href="/webInterface/Upload.html">Upload Transactions</a><br /> |
|||
<!-- <a href="/settings">Settings</a><br /><br /> --> |
|||
|
|||
<!-- Less frequent options example --> |
|||
<!-- <a class="small" href="/unsupportedSubmissions">Content which failed to download</a><br /> |
|||
<br /> |
|||
<a class="small" href="/logout">Sign Out</a><br /> --> |
|||
</nav> |
|||
|
|||
<footer> |
|||
<a href="https://github.com/makuto/Lazy-Budget">See the code</a> |
|||
<a href="mailto:macoy@macoy.me">Give feedback</a> |
|||
</footer> |
|||
</body> |
|||
</html> |
Loading…
Reference in new issue