WIP: Integrate with API
This commit is contained in:
parent
09e32d1cd1
commit
da7a7c94f6
26 changed files with 276 additions and 122 deletions
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -5829,6 +5829,11 @@
|
|||
"integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==",
|
||||
"dev": true
|
||||
},
|
||||
"ng2-currency-mask": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ng2-currency-mask/-/ng2-currency-mask-5.3.1.tgz",
|
||||
"integrity": "sha1-1z4nv2DqQj38AEAQbI6hcxbQeqs="
|
||||
},
|
||||
"no-case": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"core-js": "^2.5.4",
|
||||
"dexie": "^2.0.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
"ng2-currency-mask": "^5.3.1",
|
||||
"rxjs": "^6.0.0",
|
||||
"zone.js": "~0.8.26"
|
||||
},
|
||||
|
|
15
src/app/account.service.spec.ts
Normal file
15
src/app/account.service.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { AccountService } from './account.service';
|
||||
|
||||
describe('AccountService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [AccountService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([AccountService], (service: AccountService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
|
@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
|||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AccountsService {
|
||||
export class AccountService {
|
||||
|
||||
constructor() { }
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { AccountsService } from './accounts.service';
|
||||
|
||||
describe('AccountsService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [AccountsService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([AccountsService], (service: AccountsService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
<input matInput [(ngModel)]="currentCategory.name" placeholder="Name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput type="number" [(ngModel)]="currentCategory.amount" placeholder="Amount" required>
|
||||
<input matInput type="text" [(ngModel)]="currentCategory.amount" placeholder="Amount" required currencyMask>
|
||||
</mat-form-field>
|
||||
<!--
|
||||
<mat-form-field>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
|
||||
import { CategoryService } from '../category.service'
|
||||
import { Category } from '../category'
|
||||
import { Location } from '@angular/common';
|
||||
import { CategoryService } from '../category.service';
|
||||
import { Category } from '../category';
|
||||
import { Actionable } from '../actionable';
|
||||
import { AppComponent } from '../app.component';
|
||||
|
||||
|
@ -31,6 +30,7 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
|
|||
}
|
||||
|
||||
doAction(): void {
|
||||
this.currentCategory.amount *= 100;
|
||||
if (this.currentCategory.id) {
|
||||
// This is an existing category, update it
|
||||
this.categoryService.updateCategory(this.currentCategory);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<textarea matInput [(ngModel)]="currentTransaction.description" placeholder="Description"></textarea>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput type="number" [(ngModel)]="currentTransaction.amount" placeholder="Amount" required>
|
||||
<input matInput type="text" [(ngModel)]="currentTransaction.amount" placeholder="Amount" required currencyMask>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput type="date" [(ngModel)]="currentTransaction.date" placeholder="Date" required>
|
||||
|
|
|
@ -18,6 +18,7 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl
|
|||
public transactionType = TransactionType;
|
||||
public selectedCategory: Category;
|
||||
public categories: Category[];
|
||||
public rawAmount: string;
|
||||
|
||||
constructor(
|
||||
private app: AppComponent,
|
||||
|
@ -37,6 +38,9 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl
|
|||
}
|
||||
|
||||
doAction(): void {
|
||||
// The amount will be input as a decimal value so we need to convert it
|
||||
// to an integer
|
||||
this.currentTransaction.amount *= 100;
|
||||
if (this.currentTransaction.id) {
|
||||
// This is an existing transaction, update it
|
||||
this.transactionService.updateTransaction(this.currentTransaction);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { Transaction } from './transaction';
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
|
@ -49,4 +50,42 @@ export class ApiService {
|
|||
httpOptions
|
||||
);
|
||||
}
|
||||
|
||||
getTransactions(): Observable<any> {
|
||||
return this.http.get(
|
||||
host + '/transactions',
|
||||
httpOptions
|
||||
);
|
||||
}
|
||||
|
||||
saveTransaction(transaction: Transaction): Observable<Transaction> {
|
||||
return Observable.create(subscriber => {
|
||||
const params = {
|
||||
name: transaction.title,
|
||||
amount: transaction.amount,
|
||||
accountId: transaction.accountId,
|
||||
categoryId: transaction.categoryId,
|
||||
description: transaction.description,
|
||||
date: transaction.date,
|
||||
type: transaction.type,
|
||||
};
|
||||
|
||||
if (transaction.remoteId > 0) {
|
||||
params['id'] = transaction.remoteId;
|
||||
}
|
||||
|
||||
this.http.post(
|
||||
host + '/transactions',
|
||||
params,
|
||||
httpOptions,
|
||||
).subscribe(
|
||||
value => {
|
||||
console.log(value);
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<mat-sidenav #sidenav mode="over" closed>
|
||||
<mat-nav-list (click)="sidenav.close()">
|
||||
<a mat-list-item routerLink="/accounts">Manage Accounts</a>
|
||||
<a mat-list-item (click)="exportData()">Export Data</a>
|
||||
<a mat-list-item *ngIf="!authService.currentUser" routerLink="/login">Login</a>
|
||||
<a mat-list-item *ngIf="!authService.currentUser" routerLink="/register">Register</a>
|
||||
<a mat-list-item *ngIf="authService.currentUser" (click)="logout()">Logout</a>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component } from '@angular/core';
|
|||
import { Location } from '@angular/common';
|
||||
import { Actionable } from './actionable';
|
||||
import { AuthService } from './auth.service';
|
||||
import { BudgetDatabase } from './budget-database';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -15,7 +16,8 @@ export class AppComponent {
|
|||
|
||||
constructor(
|
||||
public authService: AuthService,
|
||||
private location: Location
|
||||
private location: Location,
|
||||
private db: BudgetDatabase,
|
||||
) { }
|
||||
|
||||
goBack(): void {
|
||||
|
@ -25,4 +27,20 @@ export class AppComponent {
|
|||
logout(): void {
|
||||
this.authService.logout();
|
||||
}
|
||||
|
||||
exportData(): void {
|
||||
this.db.export().subscribe(data => {
|
||||
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.target = '_blank';
|
||||
a.download = 'budget.json';
|
||||
a.style.display = 'none';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,18 @@ import { AddEditAccountComponent } from './add-edit-account/add-edit-account.com
|
|||
import { EditProfileComponent } from './edit-profile/edit-profile.component';
|
||||
import { UserComponent } from './user/user.component';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { CurrencyMaskModule } from "ng2-currency-mask";
|
||||
import { CurrencyMaskConfig, CURRENCY_MASK_CONFIG } from "ng2-currency-mask/src/currency-mask.config";
|
||||
|
||||
export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
|
||||
align: 'left',
|
||||
precision: 2,
|
||||
prefix: '',
|
||||
thousands: ',',
|
||||
decimal: '.',
|
||||
suffix: '',
|
||||
allowNegative: false,
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -74,8 +86,11 @@ import { HttpClientModule } from '@angular/common/http';
|
|||
FormsModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||
HttpClientModule,
|
||||
CurrencyMaskModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: CURRENCY_MASK_CONFIG, useValue: CustomCurrencyMaskConfig },
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
|
|
@ -7,14 +7,12 @@ import { Router } from '@angular/router';
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
private currentUser: User;
|
||||
public currentUser: User;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private router: Router,
|
||||
) {
|
||||
console.log('AuthService constructed');
|
||||
}
|
||||
) { }
|
||||
|
||||
login(user: User) {
|
||||
this.apiService.login(user.name, user.password).subscribe(
|
||||
|
|
|
@ -4,64 +4,92 @@ import { Account } from './account'
|
|||
import { Category } from './category'
|
||||
import { Transaction } from './transaction'
|
||||
import { TransactionType } from './transaction.type';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BudgetDatabase extends Dexie {
|
||||
transactions: Dexie.Table<ITransaction, number>;
|
||||
categories: Dexie.Table<ICategory, number>;
|
||||
accounts: Dexie.Table<IAccount, number>;
|
||||
transactions: Dexie.Table<ITransaction, number>;
|
||||
categories: Dexie.Table<ICategory, number>;
|
||||
accounts: Dexie.Table<IAccount, number>;
|
||||
|
||||
constructor() {
|
||||
super('BudgetDatabase')
|
||||
this.version(1).stores({
|
||||
transactions: `++id, title, description, amount, date, category, type`,
|
||||
categories: `++id, name, amount, repeat, color`
|
||||
})
|
||||
this.version(2).stores({
|
||||
transactions: `++id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, name, amount, repeat, color, type`
|
||||
})
|
||||
this.version(3).stores({
|
||||
transactions: `++id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, name, amount, repeat, color`
|
||||
})
|
||||
this.version(4).stores({
|
||||
transactions: `++id, remote_id, account_id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, remote_id, account_id, name, amount, repeat, color`,
|
||||
accounts: `++id, remote_id, name`
|
||||
})
|
||||
this.transactions.mapToClass(Transaction)
|
||||
this.categories.mapToClass(Category)
|
||||
this.accounts.mapToClass(Account)
|
||||
}
|
||||
constructor() {
|
||||
super('BudgetDatabase')
|
||||
this.version(1).stores({
|
||||
transactions: `++id, title, description, amount, date, category, type`,
|
||||
categories: `++id, name, amount, repeat, color`
|
||||
});
|
||||
this.version(2).stores({
|
||||
transactions: `++id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, name, amount, repeat, color, type`
|
||||
});
|
||||
this.version(3).stores({
|
||||
transactions: `++id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, name, amount, repeat, color`
|
||||
});
|
||||
this.version(4).stores({
|
||||
transactions: `++id, remote_id, account_id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, remote_id, account_id, name, amount, repeat, color`,
|
||||
accounts: `++id, remote_id, name`
|
||||
}).upgrade(dbTransaction => {
|
||||
// Since the server stores amounts as integers, we need to modify the locally stored
|
||||
// values to also use integers
|
||||
return dbTransaction.tables.transactions.toCollection().modify(transaction => {
|
||||
transaction.amount *= 100;
|
||||
}).then(count => {
|
||||
dbTransaction.tables.categories.toCollection().modify(category => {
|
||||
category.amount *= 100;
|
||||
});
|
||||
});
|
||||
});
|
||||
this.version(5).stores({
|
||||
transactions: `++id, &remote_id, account_id, title, description, amount, date, category_id, type`,
|
||||
categories: `++id, &remote_id, account_id, name, amount, repeat, color`,
|
||||
accounts: `++id, &remote_id, name`
|
||||
});
|
||||
this.transactions.mapToClass(Transaction);
|
||||
this.categories.mapToClass(Category);
|
||||
this.accounts.mapToClass(Account);
|
||||
}
|
||||
|
||||
export(): Observable<Array<any>> {
|
||||
const db = this;
|
||||
return Observable.create(observer => {
|
||||
const dump = {};
|
||||
db.tables.forEach(table => {
|
||||
dump[table.name] = table.toArray();
|
||||
});
|
||||
observer.next(dump);
|
||||
observer.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITransaction {
|
||||
id: number;
|
||||
accountId: number;
|
||||
remoteId: number;
|
||||
title: string;
|
||||
description: string;
|
||||
amount: number;
|
||||
date: Date;
|
||||
categoryId: number;
|
||||
type: TransactionType;
|
||||
id: number;
|
||||
accountId: number;
|
||||
remoteId: number;
|
||||
title: string;
|
||||
description: string;
|
||||
amount: number;
|
||||
date: Date;
|
||||
categoryId: number;
|
||||
type: TransactionType;
|
||||
}
|
||||
|
||||
export interface ICategory {
|
||||
id: number;
|
||||
accountId: number;
|
||||
remoteId: number;
|
||||
name: string;
|
||||
amount: number;
|
||||
repeat: string;
|
||||
color: string;
|
||||
id: number;
|
||||
accountId: number;
|
||||
remoteId: number;
|
||||
name: string;
|
||||
amount: number;
|
||||
repeat: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface IAccount {
|
||||
id: number;
|
||||
remoteId: number;
|
||||
name: string;
|
||||
id: number;
|
||||
remoteId: number;
|
||||
name: string;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export class CategoriesComponent implements OnInit {
|
|||
|
||||
ngOnInit() {
|
||||
this.app.title = 'Categories';
|
||||
this.app.backEnabled = true;
|
||||
this.getCategories();
|
||||
this.categoryBalances = new Map();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ export class CategoryDetailsComponent implements OnInit {
|
|||
getCategory(): void {
|
||||
const id = +this.route.snapshot.paramMap.get('id')
|
||||
this.categoryService.getCategory(id)
|
||||
.subscribe(category => this.category = category)
|
||||
.subscribe(category => {
|
||||
category.amount /= 100;
|
||||
this.category = category;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,30 +16,13 @@ export class CategoryListComponent implements OnInit {
|
|||
ngOnInit() {
|
||||
}
|
||||
|
||||
/*
|
||||
ngAfterViewInit() {
|
||||
this.categoryProgressBars.changes.subscribe( list =>
|
||||
list.forEach(progressBar =>
|
||||
progressBar._elementRef.nativeElement.innerHTML += `
|
||||
<style>
|
||||
.mat-progress-bar-fill::after {
|
||||
background-color: ${this.categories[0].color};
|
||||
color: purple;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
)
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
getCategoryRemainingBalance(category: Category): number {
|
||||
let categoryBalance = this.categoryBalances.get(category.id)
|
||||
let categoryBalance = this.categoryBalances.get(category.id);
|
||||
if (!categoryBalance) {
|
||||
categoryBalance = 0
|
||||
categoryBalance = 0;
|
||||
}
|
||||
|
||||
return category.amount + categoryBalance;
|
||||
return (category.amount / 100) + (categoryBalance / 100);
|
||||
}
|
||||
|
||||
getCategoryCompletion(category: Category): number {
|
||||
|
@ -49,12 +32,12 @@ export class CategoryListComponent implements OnInit {
|
|||
|
||||
let categoryBalance = this.categoryBalances.get(category.id)
|
||||
if (!categoryBalance) {
|
||||
categoryBalance = 0
|
||||
categoryBalance = 0;
|
||||
}
|
||||
|
||||
// Invert the negative/positive values for calculating progress
|
||||
// since the limit for a category is saved as a positive but the
|
||||
// balance is used in the calculation.
|
||||
// balance is used in the calculation.
|
||||
if (categoryBalance < 0) {
|
||||
categoryBalance = Math.abs(categoryBalance)
|
||||
} else {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="dashboard-primary">
|
||||
<h2 class="balance">
|
||||
Current Balance: <br />
|
||||
<span [ngClass]="{'income': balance > 0, 'expense': balance < 0}" >{{ balance | currency }}</span>
|
||||
<span [ngClass]="{'income': balance > 0, 'expense': balance < 0}" >{{ balance / 100 | currency }}</span>
|
||||
</h2>
|
||||
<div class="transaction-navigation">
|
||||
<a mat-button routerLink="/transactions">View Transactions</a>
|
||||
|
|
|
@ -9,6 +9,6 @@
|
|||
<input matInput type="password" placeholder="Password" [(ngModel)]="user.password" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput type="password" placeholder="Confirm Password" />
|
||||
<input matInput type="password" placeholder="Confirm Password" [(ngModel)]="confirmedPassword" />
|
||||
</mat-form-field>
|
||||
</div>
|
|
@ -12,6 +12,7 @@ import { Actionable } from '../actionable';
|
|||
export class RegisterComponent implements OnInit, OnDestroy, Actionable {
|
||||
|
||||
public user: User = new User();
|
||||
public confirmedPassword: string;
|
||||
|
||||
constructor(
|
||||
private app: AppComponent,
|
||||
|
@ -29,6 +30,10 @@ export class RegisterComponent implements OnInit, OnDestroy, Actionable {
|
|||
}
|
||||
|
||||
doAction(): void {
|
||||
if (this.user.password !== this.confirmedPassword) {
|
||||
alert('Passwords don\'t match');
|
||||
return;
|
||||
}
|
||||
this.authService.register(this.user);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { TransactionService } from '../transaction.service'
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Transaction } from '../transaction'
|
||||
import { Transaction } from '../transaction'
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction-details',
|
||||
|
@ -24,6 +24,9 @@ export class TransactionDetailsComponent implements OnInit {
|
|||
getTransaction(): void {
|
||||
const id = +this.route.snapshot.paramMap.get('id')
|
||||
this.transactionService.getTransaction(id)
|
||||
.subscribe(transaction => this.transaction = transaction)
|
||||
.subscribe(transaction => {
|
||||
transaction.amount /= 100;
|
||||
this.transaction = transaction;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,54 +2,85 @@ import { Injectable } from '@angular/core';
|
|||
import { of, Observable, from } from 'rxjs';
|
||||
import { Transaction } from './transaction';
|
||||
import { TransactionType } from './transaction.type';
|
||||
import { BudgetDatabase } from './budget-database';
|
||||
import { BudgetDatabase, ITransaction } from './budget-database';
|
||||
import { AuthService } from './auth.service';
|
||||
import { ApiService } from './api.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TransactionService {
|
||||
|
||||
constructor(private db: BudgetDatabase) { }
|
||||
constructor(
|
||||
private db: BudgetDatabase,
|
||||
private authService: AuthService,
|
||||
private apiService: ApiService,
|
||||
) { }
|
||||
|
||||
getTransactions(count?: number): Observable<Transaction[]> {
|
||||
getTransactions(count?: number): Observable<ITransaction[]> {
|
||||
// Check if we have a currently logged in user
|
||||
if (this.authService.currentUser) {
|
||||
this.apiService.getTransactions().subscribe(
|
||||
value => {
|
||||
console.log(value);
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (count) {
|
||||
return from(this.db.transactions.orderBy('date').reverse().limit(count).toArray())
|
||||
return from(this.db.transactions.orderBy('date').reverse().limit(count).toArray());
|
||||
} else {
|
||||
return from(this.db.transactions.orderBy('date').reverse().toArray())
|
||||
return from(this.db.transactions.orderBy('date').reverse().toArray());
|
||||
}
|
||||
}
|
||||
|
||||
getTransaction(id: number): Observable<Transaction> {
|
||||
return from(this.db.transactions.where('id').equals(id).first())
|
||||
return Observable.create(subscriber => {
|
||||
this.db.transactions.where('id').equals(id).first().then(transaction => {
|
||||
if (!transaction) {
|
||||
subscriber.error();
|
||||
subscriber.complete();
|
||||
return;
|
||||
}
|
||||
(transaction as Transaction).loadCategory(this.db);
|
||||
(transaction as Transaction).loadAccount(this.db);
|
||||
subscriber.next(transaction);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
saveTransaction(transaction: Transaction): Observable<Transaction> {
|
||||
this.db.transactions.put(transaction)
|
||||
return of(transaction)
|
||||
this.db.transactions.put(transaction);
|
||||
if (this.authService.currentUser) {
|
||||
return this.apiService.saveTransaction(transaction);
|
||||
} else {
|
||||
return of(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
updateTransaction(transaction: Transaction): Observable<any> {
|
||||
this.db.transactions.update(transaction.id, transaction)
|
||||
return of([])
|
||||
this.db.transactions.update(transaction.id, transaction);
|
||||
return of([]);
|
||||
}
|
||||
|
||||
deleteTransaction(transaction: Transaction): Observable<any> {
|
||||
return from(this.db.transactions.delete(transaction.id))
|
||||
return from(this.db.transactions.delete(transaction.id));
|
||||
}
|
||||
|
||||
getBalance(): Observable<number> {
|
||||
let sum = 0;
|
||||
return from(
|
||||
this.db.transactions.each(function(transaction) {
|
||||
this.db.transactions.each(function (transaction) {
|
||||
if (transaction.type === TransactionType.INCOME) {
|
||||
sum += transaction.amount
|
||||
sum += transaction.amount;
|
||||
} else {
|
||||
sum -= transaction.amount
|
||||
sum -= transaction.amount;
|
||||
}
|
||||
}).then(function() {
|
||||
}).then(function () {
|
||||
return sum;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ITransaction } from './budget-database'
|
||||
import { Category } from './category'
|
||||
import { ITransaction, ICategory, BudgetDatabase, IAccount } from './budget-database';
|
||||
import { Category } from './category';
|
||||
import { TransactionType } from './transaction.type';
|
||||
|
||||
export class Transaction implements ITransaction {
|
||||
|
@ -12,4 +12,18 @@ export class Transaction implements ITransaction {
|
|||
date: Date = new Date();
|
||||
categoryId: number;
|
||||
type: TransactionType = TransactionType.EXPENSE;
|
||||
category: ICategory;
|
||||
account: IAccount;
|
||||
|
||||
loadCategory(db: BudgetDatabase) {
|
||||
db.categories.where('id').equals(this.categoryId).first().then(category => {
|
||||
this.category = category;
|
||||
});
|
||||
}
|
||||
|
||||
loadAccount(db: BudgetDatabase) {
|
||||
db.accounts.where('id').equals(this.accountId).first().then(account => {
|
||||
this.account = account;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
<a mat-list-item *ngFor="let transaction of transactions" routerLink="/transactions/{{ transaction.id }}">
|
||||
<div matLine class="list-row-one">
|
||||
<p>{{transaction.title}}</p>
|
||||
<p class="amount" [class.expense]="transaction.type === transactionType.EXPENSE" [class.income]="transaction.type === transactionType.INCOME">{{
|
||||
transaction.amount | currency }}</p>
|
||||
<p class="amount" [class.expense]="transaction.type === transactionType.EXPENSE" [class.income]="transaction.type === transactionType.INCOME">
|
||||
{{ transaction.amount / 100 | currency }}
|
||||
</p>
|
||||
</div>
|
||||
<p matLine class="text-small">{{ transaction.date | date }}</p>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
<a mat-fab routerLink="/transactions/new">
|
||||
<mat-icon aria-label="Add">add</mat-icon>
|
||||
</a>
|
||||
</a>
|
|
@ -90,6 +90,10 @@ mat-sidenav-container .mat-drawer-backdrop.mat-drawer-shown {
|
|||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
mat-sidenav {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.income {
|
||||
color: #81C784;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue