diff --git a/package-lock.json b/package-lock.json
index b4a73c2..7e4cdc6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1171,6 +1171,11 @@
"semver-intersect": "1.4.0"
}
},
+ "@types/chart.js": {
+ "version": "2.7.52",
+ "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.52.tgz",
+ "integrity": "sha512-h4Md9c0FYPoqHyPeo3sG+wBIDFGz6GubulKUopsmFkSSW2ieyI2phjlj+FjqzTwhrWwR9dbw/HlCW3axj+tWug=="
+ },
"@types/jasmine": {
"version": "2.8.16",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.16.tgz",
@@ -2626,6 +2631,39 @@
"integrity": "sha1-5upnvSR+EHESmDt6sEee02KAAIE=",
"dev": true
},
+ "chart.js": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz",
+ "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==",
+ "requires": {
+ "chartjs-color": "2.3.0",
+ "moment": "2.24.0"
+ }
+ },
+ "chartjs-color": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz",
+ "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==",
+ "requires": {
+ "chartjs-color-string": "0.6.0",
+ "color-convert": "0.5.3"
+ },
+ "dependencies": {
+ "color-convert": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
+ "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
+ }
+ }
+ },
+ "chartjs-color-string": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
+ "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
+ "requires": {
+ "color-name": "1.1.1"
+ }
+ },
"chokidar": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
@@ -2862,8 +2900,7 @@
"color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz",
- "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=",
- "dev": true
+ "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok="
},
"colors": {
"version": "1.1.2",
@@ -7989,8 +8026,7 @@
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
- "dev": true
+ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"lodash._isnative": {
"version": "2.4.1",
@@ -8572,6 +8608,11 @@
"minimist": "0.0.8"
}
},
+ "moment": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+ },
"morgan": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
@@ -8687,6 +8728,16 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
},
+ "ng2-charts": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-2.2.3.tgz",
+ "integrity": "sha512-Kxj2bewn537xGFVkR7AgDmfqV+YH4hIL4R36EjlUI9WCWnphzY+VKZGX+D+usXd8e+znuqly+sbGHjddLxupUA==",
+ "requires": {
+ "@types/chart.js": "2.7.52",
+ "lodash": "4.17.11",
+ "tslib": "1.9.3"
+ }
+ },
"ng2-currency-mask": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ng2-currency-mask/-/ng2-currency-mask-5.3.1.tgz",
diff --git a/package.json b/package.json
index 21b0034..6d3a2ff 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
- "start": "ng serve",
+ "start": "ng serve --host '0.0.0.0'",
"build": "ng build",
"publish": "ng build --prod --service-worker && firebase deploy",
"test": "ng test",
@@ -25,10 +25,12 @@
"@angular/pwa": "^0.7.5",
"@angular/router": "^7.2.14",
"@angular/service-worker": "^7.2.14",
+ "chart.js": "^2.8.0",
"core-js": "^2.6.5",
"dexie": "^2.0.4",
"firebase": "^5.11.1",
"hammerjs": "^2.0.8",
+ "ng2-charts": "^2.2.3",
"ng2-currency-mask": "^5.3.1",
"rxjs": "^6.5.1",
"zone.js": "^0.8.29"
diff --git a/src/app/accounts/account-details/account-details.component.html b/src/app/accounts/account-details/account-details.component.html
index 44a0fec..4ea2079 100644
--- a/src/app/accounts/account-details/account-details.component.html
+++ b/src/app/accounts/account-details/account-details.component.html
@@ -5,33 +5,41 @@
0, 'expense': getBalance() < 0}">{{ getBalance() / 100 | currency }}
+
+
-
+
add
\ No newline at end of file
diff --git a/src/app/accounts/account-details/account-details.component.ts b/src/app/accounts/account-details/account-details.component.ts
index e631487..904d4ac 100644
--- a/src/app/accounts/account-details/account-details.component.ts
+++ b/src/app/accounts/account-details/account-details.component.ts
@@ -9,6 +9,8 @@ import { Observable } from 'rxjs';
import { TransactionType } from 'src/app/transactions/transaction.type';
import { TRANSACTION_SERVICE, TransactionService } from 'src/app/transactions/transaction.service';
import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service';
+import { Label } from 'ng2-charts';
+import { ChartDataSets } from 'chart.js';
@Component({
selector: 'app-account-details',
@@ -22,6 +24,15 @@ export class AccountDetailsComponent implements OnInit {
public expenses: Category[] = [];
public income: Category[] = [];
categoryBalances: Map;
+ expectedIncome = 0;
+ actualIncome = 0;
+ expectedExpenses = 0;
+ actualExpenses = 0;
+ barChartLabels: Label[] = ['Income', 'Expenses'];
+ barChartData: ChartDataSets[] = [
+ { data: [0, 0], label: 'Expected' },
+ { data: [0, 0], label: 'Actual' },
+ ];
constructor(
private app: AppComponent,
@@ -49,6 +60,27 @@ export class AccountDetailsComponent implements OnInit {
});
}
+ updateBarChart() {
+ const color = [0, 188, 212];
+ this.barChartData = [
+ {
+ data: [this.expectedIncome / 100, this.expectedExpenses / 100],
+ label: 'Expected',
+ backgroundColor: 'rgba(241, 241, 241, 0.8)',
+ borderColor: 'rgba(241, 241, 241, 0.9)',
+ hoverBackgroundColor: 'rgba(241, 241, 241, 1)',
+ hoverBorderColor: 'rgba(241, 241, 241, 1)',
+ },
+ {
+ data: [this.actualIncome / 100, this.actualExpenses / 100],
+ label: 'Actual',
+ backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.8)`,
+ borderColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.9)`,
+ hoverBackgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`,
+ hoverBorderColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`
+ }
+ ];
+ }
getBalance(): number {
let totalBalance = 0;
@@ -68,13 +100,38 @@ export class AccountDetailsComponent implements OnInit {
getCategories(): void {
this.categoryService.getCategories(this.account.id).subscribe(categories => {
+ const categoryBalances = new Map();
+ let categoryBalancesCount = 0;
for (const category of categories) {
if (category.isExpense) {
this.expenses.push(category);
+ this.expectedExpenses += category.amount;
} else {
this.income.push(category);
+ this.expectedIncome += category.amount;
}
- this.getCategoryBalance(category.id).subscribe(balance => this.categoryBalances.set(category.id, balance));
+ this.getCategoryBalance(category.id).subscribe(
+ balance => {
+ console.log(balance);
+ if (category.isExpense) {
+ this.actualExpenses += balance * -1;
+ } else {
+ this.actualIncome += balance;
+ }
+ categoryBalances.set(category.id, balance);
+ categoryBalancesCount++;
+ },
+ error => { categoryBalancesCount++; },
+ () => {
+ // This weird workaround is to force the OnChanges callback to be fired.
+ // Angular needs the reference to the object to change in order for it to
+ // work.
+ if (categoryBalancesCount === categories.length) {
+ this.categoryBalances = categoryBalances;
+ this.updateBarChart();
+ }
+ }
+ );
}
});
}
@@ -91,6 +148,7 @@ export class AccountDetailsComponent implements OnInit {
}
}
subscriber.next(balance);
+ subscriber.complete();
});
});
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 934dd8c..2fa9511 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -48,6 +48,8 @@ import { ACCOUNT_SERVICE } from './accounts/account.service';
import { FirestoreAccountService } from './accounts/account.service.firestore';
import { USER_SERVICE } from './users/user.service';
import { FirestoreUserService } from './users/user.service.firestore';
+import { CategoryBreakdownComponent } from './categories/category-breakdown/category-breakdown.component';
+import { ChartsModule } from 'ng2-charts';
export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
align: 'left',
@@ -79,6 +81,7 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
NewAccountComponent,
AccountDetailsComponent,
AccountsComponent,
+ CategoryBreakdownComponent,
],
imports: [
BrowserModule,
@@ -99,6 +102,7 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
HttpClientModule,
CurrencyMaskModule,
+ ChartsModule,
],
providers: [
{ provide: CURRENCY_MASK_CONFIG, useValue: CustomCurrencyMaskConfig },
diff --git a/src/app/categories/category-breakdown/category-breakdown.component.css b/src/app/categories/category-breakdown/category-breakdown.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/categories/category-breakdown/category-breakdown.component.html b/src/app/categories/category-breakdown/category-breakdown.component.html
new file mode 100644
index 0000000..bb9be6b
--- /dev/null
+++ b/src/app/categories/category-breakdown/category-breakdown.component.html
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/app/categories/category-breakdown/category-breakdown.component.spec.ts b/src/app/categories/category-breakdown/category-breakdown.component.spec.ts
new file mode 100644
index 0000000..104d707
--- /dev/null
+++ b/src/app/categories/category-breakdown/category-breakdown.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CategoryBreakdownComponent } from './category-breakdown.component';
+
+describe('CategoryBreakdownComponent', () => {
+ let component: CategoryBreakdownComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ CategoryBreakdownComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CategoryBreakdownComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/categories/category-breakdown/category-breakdown.component.ts b/src/app/categories/category-breakdown/category-breakdown.component.ts
new file mode 100644
index 0000000..c58b6dd
--- /dev/null
+++ b/src/app/categories/category-breakdown/category-breakdown.component.ts
@@ -0,0 +1,45 @@
+import { Component, OnInit, Input, OnChanges, SimpleChanges, SimpleChange, ViewChild } from '@angular/core';
+import { Category } from '../category';
+import { CategoriesComponent } from '../categories.component';
+import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
+import { BaseChartDirective, Label } from 'ng2-charts';
+
+@Component({
+ selector: 'app-category-breakdown',
+ templateUrl: './category-breakdown.component.html',
+ styleUrls: ['./category-breakdown.component.css']
+})
+export class CategoryBreakdownComponent implements OnInit, OnChanges {
+ barChartOptions: ChartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ xAxes: [{
+ ticks: {
+ beginAtZero: true
+ }
+ }], yAxes: [{}]
+ },
+ };
+ @Input() barChartLabels: Label[];
+ @Input() barChartData: ChartDataSets[] = [
+ { data: [0, 0, 0, 0], label: '' },
+ ];
+ barChartType: ChartType = 'horizontalBar';
+ barChartLegend = true;
+ @ViewChild(BaseChartDirective) chart: BaseChartDirective;
+
+ constructor() { }
+
+ ngOnInit() { }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ console.log(changes);
+ if (changes.barChartLabels) {
+ this.barChartLabels = changes.barChartLabels.currentValue;
+ }
+ if (changes.barChartData) {
+ this.barChartData = changes.barChartData.currentValue;
+ }
+ }
+}
diff --git a/src/app/transactions/add-edit-transaction/add-edit-transaction.component.html b/src/app/transactions/add-edit-transaction/add-edit-transaction.component.html
index ebe3553..33bc786 100644
--- a/src/app/transactions/add-edit-transaction/add-edit-transaction.component.html
+++ b/src/app/transactions/add-edit-transaction/add-edit-transaction.component.html
@@ -12,7 +12,7 @@
-
+
diff --git a/src/app/transactions/transaction.ts b/src/app/transactions/transaction.ts
index 8ee42e1..cef2fa3 100644
--- a/src/app/transactions/transaction.ts
+++ b/src/app/transactions/transaction.ts
@@ -7,7 +7,7 @@ export class Transaction {
accountId: string;
title: string;
description: string = null;
- amount: number;
+ amount = 0;
date: Date = new Date();
categoryId: string;
type: TransactionType = TransactionType.EXPENSE;