mirror of
https://github.com/jcreek/BankStatementAnalyser.git
synced 2026-07-13 02:53:49 +00:00
feat(*): Add basic graphing
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
export const transactions: Array<BankTransaction> = [];
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { transactions } from "./bankData";
|
||||||
|
import Chart from 'chart.js/auto';
|
||||||
|
|
||||||
|
export default function generateCharts(): void {
|
||||||
|
// const filter: Filter = { year: 2022, category: 'Eating out', excludeIncome: true};
|
||||||
|
const filter: Filter = { year: 2022, excludeIncome: true};
|
||||||
|
// const filter: Filter = { year: 2022, month: 1, excludeIncome: true};
|
||||||
|
const chartData = getChartData(transactions, filter);
|
||||||
|
console.log('chartData', chartData);
|
||||||
|
|
||||||
|
const ctx = document.getElementById('myChart') as HTMLCanvasElement;
|
||||||
|
const myChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: chartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomColor(): string {
|
||||||
|
// Generate a random color in hexadecimal format
|
||||||
|
const color = Math.floor(Math.random() * 0x1000000).toString(16);
|
||||||
|
return '#' + ('000000' + color).slice(-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChartData(transactions: BankTransaction[], filters: { year?: number, month?: number, category?: string, excludeIncome?: boolean } | null | undefined): ChartData {
|
||||||
|
// Return an empty chart data object if no filters are provided
|
||||||
|
if (!filters) {
|
||||||
|
return {
|
||||||
|
labels: [],
|
||||||
|
datasets: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the transactions based on the specified filters
|
||||||
|
const filteredTransactions = transactions.filter(transaction => {
|
||||||
|
if (filters.year && transaction.dateYear !== filters.year) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (filters.month && transaction.dateMonth !== filters.month) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (filters.category && transaction.category !== filters.category) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Group the transactions by year or month, depending on the filters
|
||||||
|
const groupedTransactions: { [key: string]: BankTransaction[] } = {};
|
||||||
|
if (filters.year && !filters.month) {
|
||||||
|
// Group the transactions by month
|
||||||
|
for (const transaction of filteredTransactions) {
|
||||||
|
if (filters.excludeIncome && transaction.amount < 0) {
|
||||||
|
continue; // Skip transactions with a positive amount if excludeIncome is true
|
||||||
|
}
|
||||||
|
const month = `${transaction.dateYear}-${transaction.dateMonth}`;
|
||||||
|
if (!groupedTransactions[month]) {
|
||||||
|
groupedTransactions[month] = [];
|
||||||
|
}
|
||||||
|
groupedTransactions[month].push(transaction);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Group the transactions by year
|
||||||
|
for (const transaction of filteredTransactions) {
|
||||||
|
if (filters.excludeIncome && transaction.amount < 0) {
|
||||||
|
continue; // Skip transactions with a positive amount if excludeIncome is true
|
||||||
|
}
|
||||||
|
const year = transaction.dateYear.toString();
|
||||||
|
if (!groupedTransactions[year]) {
|
||||||
|
groupedTransactions[year] = [];
|
||||||
|
}
|
||||||
|
groupedTransactions[year].push(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the chart data object
|
||||||
|
const chartData: ChartData = {
|
||||||
|
labels: [],
|
||||||
|
datasets: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a dataset for each category
|
||||||
|
const categories: { [key: string]: string } = {};
|
||||||
|
for (const year in groupedTransactions) {
|
||||||
|
for (let index = 0; index < groupedTransactions[year].length; index++) {
|
||||||
|
const transaction = groupedTransactions[year][index];
|
||||||
|
if (!categories[transaction.category]) {
|
||||||
|
categories[transaction.category] = getRandomColor();
|
||||||
|
chartData.datasets.push({
|
||||||
|
label: transaction.category,
|
||||||
|
data: [],
|
||||||
|
backgroundColor: categories[transaction.category]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('here');
|
||||||
|
|
||||||
|
// Populate the chart data with the transaction amounts
|
||||||
|
for (const year in groupedTransactions) {
|
||||||
|
chartData.labels.push(year);
|
||||||
|
for (let i = 0; i < chartData.datasets.length; i++) {
|
||||||
|
chartData.datasets[i].data.push(0);
|
||||||
|
}
|
||||||
|
for (const transaction of groupedTransactions[year]) {
|
||||||
|
for (let i = 0; i < chartData.datasets.length; i++) {
|
||||||
|
if (chartData.datasets[i].label === transaction.category) {
|
||||||
|
chartData.datasets[i].data[chartData.labels.length - 1] += transaction.amount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return chartData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+7
-20
@@ -1,5 +1,6 @@
|
|||||||
import loadServiceWorker from './loadServiceWorker';
|
import loadServiceWorker from './loadServiceWorker';
|
||||||
import Chart from 'chart.js/auto';
|
import loadBankData from './loadBankData';
|
||||||
|
import generateCharts from './generateCharts';
|
||||||
|
|
||||||
require('./assets/favicon.ico');
|
require('./assets/favicon.ico');
|
||||||
require('./assets/android-chrome-192x192.png');
|
require('./assets/android-chrome-192x192.png');
|
||||||
@@ -12,26 +13,12 @@ require('./styles/buttons.scss');
|
|||||||
require('./styles/modal.scss');
|
require('./styles/modal.scss');
|
||||||
require('./styles/spinner.scss');
|
require('./styles/spinner.scss');
|
||||||
|
|
||||||
const ctx: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById('myChart');
|
if (localStorage.hasOwnProperty('bankData')) {
|
||||||
|
loadBankData(JSON.parse(localStorage.getItem('bankData')).values);
|
||||||
|
generateCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
|
||||||
datasets: [{
|
|
||||||
label: '# of Votes',
|
|
||||||
data: [12, 19, 3, 5, 2, 3],
|
|
||||||
borderWidth: 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
loadServiceWorker();
|
loadServiceWorker();
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { transactions } from "./bankData";
|
||||||
|
|
||||||
|
export default function loadBankData(data: Array<any>):void {
|
||||||
|
// Set bankData array to be whatever this new data is
|
||||||
|
for (let index = 0; index < data.length; index++) {
|
||||||
|
const dateParts: Array<string> = data[index][0].split("/");
|
||||||
|
const date: Date = new Date(Number(dateParts[2]), Number(dateParts[1])-1, Number(dateParts[0])); // JS months are zero-indexed
|
||||||
|
const year: number = date.getFullYear();
|
||||||
|
const month: number = date.getMonth();
|
||||||
|
const day: number = date.getDay();
|
||||||
|
|
||||||
|
const transaction: BankTransaction = {
|
||||||
|
date: data[index][0],
|
||||||
|
dateYear: year,
|
||||||
|
dateMonth: month + 1, // JS months are zero-indexed
|
||||||
|
dateDay: day,
|
||||||
|
time: data[index][1],
|
||||||
|
type: data[index][2],
|
||||||
|
name: data[index][3],
|
||||||
|
category: data[index][5],
|
||||||
|
amount: Number(data[index][6]) * -1, // Invert money out being negative and money in being positive
|
||||||
|
currency: data[index][7],
|
||||||
|
localAmount: Number(data[index][8]) * -1, // Invert money out being negative and money in being positive
|
||||||
|
localCurrency: data[index][9],
|
||||||
|
};
|
||||||
|
|
||||||
|
transactions.push(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-12
@@ -45,11 +45,6 @@
|
|||||||
<canvas id="myChart"></canvas>
|
<canvas id="myChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<footer class="footer-container">
|
<footer class="footer-container">
|
||||||
<hr>
|
<hr>
|
||||||
<span class="footer-items">Copyright © 2022 Josh Creek</span>
|
<span class="footer-items">Copyright © 2022 Josh Creek</span>
|
||||||
@@ -118,7 +113,7 @@
|
|||||||
* Enables user interaction after all libraries are loaded.
|
* Enables user interaction after all libraries are loaded.
|
||||||
*/
|
*/
|
||||||
function maybeEnableButtons() {
|
function maybeEnableButtons() {
|
||||||
if (gapiInited && gisInited) {
|
if (gapiInited && gisInited && !localStorage.hasOwnProperty('bankData')) {
|
||||||
document.getElementById('authorize_button').style.visibility = 'visible';
|
document.getElementById('authorize_button').style.visibility = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +127,7 @@
|
|||||||
throw (resp);
|
throw (resp);
|
||||||
}
|
}
|
||||||
document.getElementById('signout_button').style.visibility = 'visible';
|
document.getElementById('signout_button').style.visibility = 'visible';
|
||||||
document.getElementById('authorize_button').innerText = 'Refresh';
|
document.getElementById('authorize_button').innerText = 'Refresh Google Sheets data';
|
||||||
await listMajors();
|
await listMajors();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,15 +172,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const range = response.result;
|
const range = response.result;
|
||||||
|
localStorage.setItem('bankData', JSON.stringify(range));
|
||||||
if (!range || !range.values || range.values.length == 0) {
|
if (!range || !range.values || range.values.length == 0) {
|
||||||
document.getElementById('content').innerText = 'No values found.';
|
document.getElementById('content').innerText = 'No values found.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Flatten to string to display
|
|
||||||
const output = range.values.reduce(
|
|
||||||
(str, row) => `${str}${row[0]}, ${row[4]}\n`,
|
|
||||||
'Name, Major:\n');
|
|
||||||
document.getElementById('content').innerText = output;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
|
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
type BankTransaction = {
|
||||||
|
date: string;
|
||||||
|
dateYear: number;
|
||||||
|
dateMonth: number;
|
||||||
|
dateDay: number;
|
||||||
|
time: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
// emoji here
|
||||||
|
category: string;
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
localAmount: number;
|
||||||
|
localCurrency: string; // Actually the foreign currency
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
type ChartData = {
|
||||||
|
labels: string[];
|
||||||
|
datasets: {
|
||||||
|
label: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
data: number[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
type Filter = {
|
||||||
|
year?: number;
|
||||||
|
month?: number;
|
||||||
|
category?: string;
|
||||||
|
excludeIncome?: boolean;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user