Finish implementing new accounts structure

Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
Billy Brawner 2019-05-04 18:29:21 -07:00
parent 7c2e58cb11
commit 8d843473e2
39 changed files with 370 additions and 343 deletions

View file

@ -0,0 +1,68 @@
.dashboard {
color: #F1F1F1;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 1em;
}
.dashboard > div {
background: #212121;
display: inline-block;
margin: 1em;
padding: 1em;
max-width: 500px;
position: relative;
width: 100%;
}
.dashboard .dashboard-primary {
padding: 5em 1em;
text-align: center;
}
.dashboard-primary > * {
display: block;
}
.dashboard div h2, .dashboard div h3 {
margin: 0;
}
.dashboard p, .dashboard a {
color: #F1F1F1;
text-align: center;
text-decoration: none;
}
.dashboard-primary div {
bottom: 0.5em;
display: flex;
justify-content: flex-end;
left: 0.5em;
right: 0.5em;
position: absolute;
}
.dashboard .no-categories {
padding: 1em;
text-align: center;
}
.dashboard .no-categories a {
display: inline-block;
border: 1px dashed #F1F1F1;
padding: 1em;
}
.dashboard .no-categories p {
line-height: normal;
white-space: normal;
}
a.view-all {
position: absolute;
right: 0.5em;
top: 0.5em;
}

View file

@ -1,3 +1,26 @@
<p> <div class="dashboard">
account-details works! <div class="dashboard-primary" [hidden]="!account">
</p> <h2 class="balance">
Current Balance: <br />
<span
[ngClass]="{'income': getBalance() > 0, 'expense': getBalance() < 0}">{{ getBalance() / 100 | currency }}</span>
</h2>
<div class="transaction-navigation">
<a mat-button routerLink="/accounts/{{ account.id }}/transactions">View Transactions</a>
</div>
</div>
<div class="dashboard-categories" [hidden]="!account">
<h3 class="categories">Categories</h3>
<a mat-button routerLink="/accounts/{{ account.id }}/categories" class="view-all" *ngIf="categories">View All</a>
<div class="no-categories" *ngIf="!categories || categories.length === 0">
<a mat-button routerLink="/accounts/{{ account.id }}/categories/new">
<mat-icon>add</mat-icon>
<p>Add categories to get more insights into your income and expenses.</p>
</a>
</div>
<app-category-list [accountId]="account.id" [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
</div>
</div>
<a mat-fab routerLink="/accounts/{{ account.id }}/transactions/new" [hidden]="!account">
<mat-icon aria-label="Add">add</mat-icon>
</a>

View file

@ -1,4 +1,14 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, Inject } from '@angular/core';
import { Account } from '../account';
import { ACCOUNT_SERVICE, AccountService } from '../account.service';
import { ActivatedRoute } from '@angular/router';
import { AppComponent } from 'src/app/app.component';
import { Transaction } from 'src/app/transactions/transaction';
import { Category } from 'src/app/categories/category';
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';
@Component({ @Component({
selector: 'app-account-details', selector: 'app-account-details',
@ -7,9 +17,76 @@ import { Component, OnInit } from '@angular/core';
}) })
export class AccountDetailsComponent implements OnInit { export class AccountDetailsComponent implements OnInit {
constructor() { } account: Account;
public transactions: Transaction[];
public categories: Category[];
categoryBalances: Map<string, number>;
constructor(
private app: AppComponent,
private route: ActivatedRoute,
@Inject(ACCOUNT_SERVICE) private accountService: AccountService,
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
) { }
ngOnInit() { ngOnInit() {
this.getAccount();
this.app.backEnabled = false;
this.categoryBalances = new Map();
} }
getAccount() {
const id = this.route.snapshot.paramMap.get('id');
this.accountService.getAccount(id)
.subscribe(account => {
this.app.title = account.name;
this.account = account;
this.getBalance();
this.getTransactions();
this.getCategories();
});
}
getBalance(): number {
let totalBalance = 0;
if (!this.categoryBalances) {
return 0;
}
this.categoryBalances.forEach(balance => {
totalBalance += balance;
});
return totalBalance;
}
getTransactions(): void {
this.transactionService.getTransactions(this.account.id, null, 5)
.subscribe(transactions => this.transactions = <Transaction[]>transactions);
}
getCategories(): void {
this.categoryService.getCategories(this.account.id, 5).subscribe(categories => {
this.categories = categories;
for (const category of categories) {
this.getCategoryBalance(category.id).subscribe(balance => this.categoryBalances.set(category.id, balance));
}
});
}
getCategoryBalance(category: string): Observable<number> {
return Observable.create(subscriber => {
this.transactionService.getTransactions(this.account.id, category).subscribe(transactions => {
let balance = 0;
for (const transaction of transactions) {
if (transaction.type === TransactionType.INCOME) {
balance += transaction.amount;
} else {
balance -= transaction.amount;
}
}
subscriber.next(balance);
});
});
}
} }

View file

@ -9,11 +9,21 @@ export class FirestoreAccountService implements AccountService {
getAccounts(): Observable<Account[]> { getAccounts(): Observable<Account[]> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
const accounts = []; const accounts = [];
firebase.firestore().collection('accounts').onSnapshot(data => { firebase.auth().onAuthStateChanged(user => {
if (!data.empty) { if (user == null) { return; }
data.docs.map(account => accounts.push(Account.fromSnapshotRef(account))); firebase.firestore().collection('accounts')
} .orderBy('name')
subscriber.next(accounts); .where('members', 'array-contains', user.uid)
.onSnapshot(
data => {
if (!data.empty) {
data.docs.map(account => accounts.push(Account.fromSnapshotRef(account)));
}
subscriber.next(accounts);
},
error => {
console.error(error);
});
}); });
}); });
} }
@ -33,13 +43,14 @@ export class FirestoreAccountService implements AccountService {
name: string, name: string,
description: string, description: string,
currency: string, currency: string,
members: User[], members: string[],
): Observable<Account> { ): Observable<Account> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('accounts').add({ firebase.firestore().collection('accounts').add({
name: name, name: name,
description: description, description: description,
members: members.map(member => member.id) currency: currency,
members: members
}).then(docRef => { }).then(docRef => {
docRef.get().then(snapshot => { docRef.get().then(snapshot => {
if (!snapshot) { if (!snapshot) {

View file

@ -10,7 +10,7 @@ export interface AccountService {
name: string, name: string,
description: string, description: string,
currency: string, currency: string,
members: User[], members: string[],
): Observable<Account>; ): Observable<Account>;
updateAccount(id: string, changes: object): Observable<Account>; updateAccount(id: string, changes: object): Observable<Account>;
deleteAccount(id: string): Observable<boolean>; deleteAccount(id: string): Observable<boolean>;

View file

@ -1,4 +1,8 @@
<mat-nav-list class="accounts"> <div class="dashboard" *ngIf="!isLoggedIn()">
<h2 class="log-in">Get started</h2>
<p>To begin tracking your finances, <a routerLink="/login">login</a> or <a routerLink="/register">create an account</a>!</p>
</div>
<mat-nav-list class="accounts" *ngIf="isLoggedIn()">
<a mat-list-item *ngFor="let account of accounts" routerLink="/accounts/{{ account.id }}"> <a mat-list-item *ngFor="let account of accounts" routerLink="/accounts/{{ account.id }}">
<p matLine class="account-list-title"> <p matLine class="account-list-title">
{{ account.name }} {{ account.name }}
@ -14,3 +18,6 @@
<p>Add accounts to begin tracking your budget.</p> <p>Add accounts to begin tracking your budget.</p>
</a> </a>
</div> </div>
<a mat-fab routerLink="/accounts/new">
<mat-icon aria-label="Add">add</mat-icon>
</a>

View file

@ -20,9 +20,13 @@ export class AccountsComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.app.backEnabled = true; this.app.backEnabled = true;
this.app.title = 'Transactions'; this.app.title = 'Accounts';
this.accountService.getAccounts().subscribe(accounts => { this.accountService.getAccounts().subscribe(accounts => {
this.accounts = accounts; this.accounts = accounts;
}); });
} }
isLoggedIn(): boolean {
return this.app.isLoggedIn();
}
} }

View file

@ -5,6 +5,7 @@ import { AppComponent } from 'src/app/app.component';
import { Actionable } from 'src/app/actionable'; import { Actionable } from 'src/app/actionable';
import { UserService, USER_SERVICE } from 'src/app/users/user.service'; import { UserService, USER_SERVICE } from 'src/app/users/user.service';
import { User } from 'src/app/users/user'; import { User } from 'src/app/users/user';
import * as firebase from 'firebase';
@Component({ @Component({
selector: 'app-add-edit-account', selector: 'app-add-edit-account',
@ -14,8 +15,8 @@ import { User } from 'src/app/users/user';
export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable { export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable {
@Input() title: string; @Input() title: string;
@Input() account: Account; @Input() account: Account;
public users: User[]; public userIds: string[] = [firebase.auth().currentUser.uid];
public searchedUsers: User[]; public searchedUsers: User[] = [];
constructor( constructor(
private app: AppComponent, private app: AppComponent,
@ -45,7 +46,7 @@ export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable {
this.account.name, this.account.name,
this.account.description, this.account.description,
this.account.currency, this.account.currency,
this.users this.userIds
); );
} }
// TODO: Check if it was actually successful or not // TODO: Check if it was actually successful or not

View file

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { TransactionsComponent } from './transactions/transactions.component'; import { TransactionsComponent } from './transactions/transactions.component';
import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component'; import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component';
import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component'; import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component';
@ -14,18 +13,18 @@ import { NewAccountComponent } from './accounts/new-account/new-account.componen
import { AccountDetailsComponent } from './accounts/account-details/account-details.component'; import { AccountDetailsComponent } from './accounts/account-details/account-details.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: DashboardComponent }, { path: '', component: AccountsComponent },
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent }, { path: 'register', component: RegisterComponent },
{ path: 'accounts', component: AccountsComponent }, { path: 'accounts', component: AccountsComponent },
{ path: 'accounts/new', component: NewAccountComponent }, { path: 'accounts/new', component: NewAccountComponent },
{ path: 'accounts/:id', component: AccountDetailsComponent }, { path: 'accounts/:id', component: AccountDetailsComponent },
{ path: 'transactions', component: TransactionsComponent }, { path: 'accounts/:accountId/transactions', component: TransactionsComponent },
{ path: 'transactions/new', component: NewTransactionComponent }, { path: 'accounts/:accountId/transactions/new', component: NewTransactionComponent },
{ path: 'transactions/:id', component: TransactionDetailsComponent }, { path: 'accounts/:accountId/transactions/:id', component: TransactionDetailsComponent },
{ path: 'categories', component: CategoriesComponent }, { path: 'accounts/:accountId/categories', component: CategoriesComponent },
{ path: 'categories/new', component: NewCategoryComponent }, { path: 'accounts/:accountId/categories/new', component: NewCategoryComponent },
{ path: 'categories/:id', component: CategoryDetailsComponent }, { path: 'accounts/:accountId/categories/:id', component: CategoryDetailsComponent },
]; ];
@NgModule({ @NgModule({

View file

@ -2,8 +2,7 @@
<mat-sidenav #sidenav mode="over" closed> <mat-sidenav #sidenav mode="over" closed>
<mat-nav-list (click)="sidenav.close()"> <mat-nav-list (click)="sidenav.close()">
<a mat-list-item *ngIf="isLoggedIn()" routerLink="">{{ getUsername() }}</a> <a mat-list-item *ngIf="isLoggedIn()" routerLink="">{{ getUsername() }}</a>
<a mat-list-item *ngIf="isLoggedIn()" routerLink="/accounts">Manage Accounts</a> <a mat-list-item *ngIf="isLoggedIn()" routerLink="/accounts">Accounts</a>
<a mat-list-item *ngIf="isLoggedIn()" (click)="exportData()">Export Data</a>
<a mat-list-item *ngIf="!isLoggedIn()" routerLink="/login">Login</a> <a mat-list-item *ngIf="!isLoggedIn()" routerLink="/login">Login</a>
<a mat-list-item *ngIf="!isLoggedIn()" routerLink="/register">Register</a> <a mat-list-item *ngIf="!isLoggedIn()" routerLink="/register">Register</a>
<a mat-list-item *ngIf="isLoggedIn()" (click)="logout()">Logout</a> <a mat-list-item *ngIf="isLoggedIn()" (click)="logout()">Logout</a>

View file

@ -14,7 +14,6 @@ export class AppComponent {
public title = 'Budget'; public title = 'Budget';
public backEnabled = false; public backEnabled = false;
public actionable: Actionable; public actionable: Actionable;
public group = 'MG3KOiuPu0Xy38O2LdhJ';
constructor( constructor(
public authService: AuthService, public authService: AuthService,

View file

@ -23,7 +23,6 @@ import { AccountsComponent } from './accounts/accounts.component';
import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component'; import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component';
import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component'; import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component';
import { AddEditTransactionComponent } from './transactions/add-edit-transaction/add-edit-transaction.component'; import { AddEditTransactionComponent } from './transactions/add-edit-transaction/add-edit-transaction.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { CategoriesComponent } from './categories/categories.component'; import { CategoriesComponent } from './categories/categories.component';
import { CategoryDetailsComponent } from './categories/category-details/category-details.component'; import { CategoryDetailsComponent } from './categories/category-details/category-details.component';
import { AddEditCategoryComponent } from './categories/add-edit-category/add-edit-category.component'; import { AddEditCategoryComponent } from './categories/add-edit-category/add-edit-category.component';
@ -47,6 +46,8 @@ import { CurrencyMaskConfig, CURRENCY_MASK_CONFIG } from 'ng2-currency-mask/src/
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { ACCOUNT_SERVICE } from './accounts/account.service'; import { ACCOUNT_SERVICE } from './accounts/account.service';
import { FirestoreAccountService } from './accounts/account.service.firestore'; import { FirestoreAccountService } from './accounts/account.service.firestore';
import { USER_SERVICE } from './users/user.service';
import { FirestoreUserService } from './users/user.service.firestore';
export const CustomCurrencyMaskConfig: CurrencyMaskConfig = { export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
align: 'left', align: 'left',
@ -65,7 +66,6 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
TransactionDetailsComponent, TransactionDetailsComponent,
NewTransactionComponent, NewTransactionComponent,
AddEditTransactionComponent, AddEditTransactionComponent,
DashboardComponent,
CategoriesComponent, CategoriesComponent,
CategoryDetailsComponent, CategoryDetailsComponent,
AddEditCategoryComponent, AddEditCategoryComponent,
@ -105,6 +105,7 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
{ provide: TRANSACTION_SERVICE, useClass: TransactionServiceFirebaseFirestoreImpl }, { provide: TRANSACTION_SERVICE, useClass: TransactionServiceFirebaseFirestoreImpl },
{ provide: CATEGORY_SERVICE, useClass: CategoryServiceFirebaseFirestoreImpl }, { provide: CATEGORY_SERVICE, useClass: CategoryServiceFirebaseFirestoreImpl },
{ provide: ACCOUNT_SERVICE, useClass: FirestoreAccountService }, { provide: ACCOUNT_SERVICE, useClass: FirestoreAccountService },
{ provide: USER_SERVICE, useClass: FirestoreUserService },
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View file

@ -3,6 +3,8 @@ import { Category } from '../category';
import { CATEGORY_SERVICE, CategoryService } from '../category.service'; import { CATEGORY_SERVICE, CategoryService } from '../category.service';
import { Actionable } from 'src/app/actionable'; import { Actionable } from 'src/app/actionable';
import { AppComponent } from 'src/app/app.component'; import { AppComponent } from 'src/app/app.component';
import { Account } from 'src/app/accounts/account';
import { ACCOUNT_SERVICE, AccountService } from 'src/app/accounts/account.service';
@Component({ @Component({
selector: 'app-add-edit-category', selector: 'app-add-edit-category',
@ -11,12 +13,13 @@ import { AppComponent } from 'src/app/app.component';
}) })
export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy { export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
@Input() accountId: string;
@Input() title: string; @Input() title: string;
@Input() currentCategory: Category; @Input() currentCategory: Category;
@Input() group: string;
constructor( constructor(
private app: AppComponent, private app: AppComponent,
@Inject(ACCOUNT_SERVICE) private accountService: AccountService,
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService, @Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
) { } ) { }
@ -31,14 +34,24 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
} }
doAction(): void { doAction(): void {
this.currentCategory.amount *= 100;
let observable; let observable;
if (this.currentCategory.id) { if (this.currentCategory.id) {
// This is an existing category, update it // This is an existing category, update it
observable = this.categoryService.updateCategory(this.currentCategory.id, this.currentCategory); observable = this.categoryService.updateCategory(
this.accountId,
this.currentCategory.id,
{
name: this.currentCategory.name,
amount: this.currentCategory.amount * 100
}
);
} else { } else {
// This is a new category, save it // This is a new category, save it
observable = this.categoryService.createCategory(this.currentCategory.name, this.currentCategory.amount, this.app.group); observable = this.categoryService.createCategory(
this.accountId,
this.currentCategory.name,
this.currentCategory.amount
);
} }
observable.subscribe(val => { observable.subscribe(val => {
this.app.goBack(); this.app.goBack();
@ -50,7 +63,7 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
} }
delete(): void { delete(): void {
this.categoryService.deleteCategory(this.currentCategory.id); this.categoryService.deleteCategory(this.accountId, this.currentCategory.id);
this.app.goBack(); this.app.goBack();
} }
} }

View file

@ -1,4 +1,4 @@
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list> <app-category-list [accountId]="accountId" [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
<a mat-fab routerLink="/categories/new"> <a mat-fab routerLink="/accounts/{{ accountId }}/categories/new">
<mat-icon aria-label="Add">add</mat-icon> <mat-icon aria-label="Add">add</mat-icon>
</a> </a>

View file

@ -5,6 +5,8 @@ import { AppComponent } from '../app.component';
import { TransactionService, TRANSACTION_SERVICE } from '../transactions/transaction.service'; import { TransactionService, TRANSACTION_SERVICE } from '../transactions/transaction.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { TransactionType } from '../transactions/transaction.type'; import { TransactionType } from '../transactions/transaction.type';
import { Account } from '../accounts/account';
import { ActivatedRoute } from '@angular/router';
@Component({ @Component({
selector: 'app-categories', selector: 'app-categories',
@ -13,17 +15,19 @@ import { TransactionType } from '../transactions/transaction.type';
}) })
export class CategoriesComponent implements OnInit { export class CategoriesComponent implements OnInit {
@Input() group: string; accountId: string;
public categories: Category[]; public categories: Category[];
public categoryBalances: Map<string, number>; public categoryBalances: Map<string, number>;
constructor( constructor(
private route: ActivatedRoute,
private app: AppComponent, private app: AppComponent,
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService, @Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService, @Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
) { } ) { }
ngOnInit() { ngOnInit() {
this.accountId = this.route.snapshot.paramMap.get('accountId');
this.app.title = 'Categories'; this.app.title = 'Categories';
this.app.backEnabled = true; this.app.backEnabled = true;
this.getCategories(); this.getCategories();
@ -31,17 +35,17 @@ export class CategoriesComponent implements OnInit {
} }
getCategories(): void { getCategories(): void {
this.categoryService.getCategories(this.app.group).subscribe(categories => { this.categoryService.getCategories(this.accountId).subscribe(categories => {
this.categories = categories; this.categories = categories;
for (const category of this.categories) { for (const category of this.categories) {
this.getCategoryBalance(category.id).subscribe(balance => this.categoryBalances.set(category.id, balance)); this.getCategoryBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance));
} }
}); });
} }
getCategoryBalance(category: string): Observable<number> { getCategoryBalance(category: Category): Observable<number> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
this.transactionService.getTransactionsForCategory(category).subscribe(transactions => { this.transactionService.getTransactions(this.accountId, category.id).subscribe(transactions => {
let balance = 0; let balance = 0;
for (const transaction of transactions) { for (const transaction of transactions) {
if (transaction.type === TransactionType.INCOME) { if (transaction.type === TransactionType.INCOME) {

View file

@ -1 +1 @@
<app-add-edit-category [title]="'Edit Category'" [currentCategory]="category"></app-add-edit-category> <app-add-edit-category [title]="'Edit Category'" [accountId]="accountId" [currentCategory]="category"></app-add-edit-category>

View file

@ -1,7 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { CategoryServiceFirebaseFirestoreImpl } from '../category.service.firestore'; import { CategoryServiceFirebaseFirestoreImpl } from '../category.service.firestore';
import { Category } from '../category'; import { Category } from '../category';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Account } from 'src/app/accounts/account';
@Component({ @Component({
selector: 'app-category-details', selector: 'app-category-details',
@ -10,6 +11,7 @@ import { ActivatedRoute } from '@angular/router';
}) })
export class CategoryDetailsComponent implements OnInit { export class CategoryDetailsComponent implements OnInit {
accountId: string;
category: Category; category: Category;
constructor( constructor(
@ -22,8 +24,9 @@ export class CategoryDetailsComponent implements OnInit {
} }
getCategory(): void { getCategory(): void {
this.accountId = this.route.snapshot.paramMap.get('accountId');
const id = this.route.snapshot.paramMap.get('id'); const id = this.route.snapshot.paramMap.get('id');
this.categoryService.getCategory(id) this.categoryService.getCategory(this.accountId, id)
.subscribe(category => { .subscribe(category => {
category.amount /= 100; category.amount /= 100;
this.category = category; this.category = category;

View file

@ -4,7 +4,7 @@
} }
</style> </style>
<mat-nav-list class="categories"> <mat-nav-list class="categories">
<a mat-list-item *ngFor="let category of categories" routerLink="/categories/{{ category.id }}"> <a mat-list-item *ngFor="let category of categories" routerLink="/accounts/{{ accountId }}/categories/{{ category.id }}">
<p matLine class="category-list-title"> <p matLine class="category-list-title">
<span> <span>
{{ category.name }} {{ category.name }}

View file

@ -1,5 +1,6 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { Category } from '../category'; import { Category } from '../category';
import { Account } from 'src/app/accounts/account';
@Component({ @Component({
selector: 'app-category-list', selector: 'app-category-list',
@ -8,6 +9,7 @@ import { Category } from '../category';
}) })
export class CategoryListComponent implements OnInit { export class CategoryListComponent implements OnInit {
@Input() accountId: string;
@Input() categories: Category[]; @Input() categories: Category[];
@Input() categoryBalances: Map<string, number>; @Input() categoryBalances: Map<string, number>;

View file

@ -3,6 +3,7 @@ import { of, Observable, from } from 'rxjs';
import { Category } from './category'; import { Category } from './category';
import * as firebase from 'firebase/app'; import * as firebase from 'firebase/app';
import 'firebase/firestore'; import 'firebase/firestore';
import { Account } from '../accounts/account';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -11,9 +12,9 @@ export class CategoryServiceFirebaseFirestoreImpl {
constructor() { } constructor() { }
getCategories(group: string, count?: number): Observable<Category[]> { getCategories(accountId: string, count?: number): Observable<Category[]> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
let query = firebase.firestore().collection('categories').where('group', '==', group); let query: any = firebase.firestore().collection('accounts').doc(accountId).collection('categories');
if (count) { if (count) {
query = query.limit(count); query = query.limit(count);
} }
@ -26,7 +27,7 @@ export class CategoryServiceFirebaseFirestoreImpl {
const categories = []; const categories = [];
for (const categoryDoc of snapshot.docs) { for (const categoryDoc of snapshot.docs) {
categories.push(Category.fromSnapshotRef(categoryDoc)); categories.push(Category.fromSnapshotRef(accountId, categoryDoc));
} }
subscriber.next(categories); subscriber.next(categories);
}, error => { }, error => {
@ -36,24 +37,23 @@ export class CategoryServiceFirebaseFirestoreImpl {
}); });
} }
getCategory(id: string): Observable<Category> { getCategory(accountId: string, id: string): Observable<Category> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('categories').doc(id).onSnapshot(snapshot => { firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id).onSnapshot(snapshot => {
if (!snapshot.exists) { if (!snapshot.exists) {
return; return;
} }
subscriber.next(Category.fromSnapshotRef(snapshot)); subscriber.next(Category.fromSnapshotRef(accountId, snapshot));
}); });
}); });
} }
createCategory(name: string, amount: number, group: string): Observable<Category> { createCategory(accountId: string, name: string, amount: number): Observable<Category> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('categories').add({ firebase.firestore().collection('accounts').doc(accountId).collection('categories').add({
name: name, name: name,
amount: amount, amount: amount
group: group,
}).then(docRef => { }).then(docRef => {
if (!docRef) { if (!docRef) {
console.error('Failed to create category'); console.error('Failed to create category');
@ -64,32 +64,34 @@ export class CategoryServiceFirebaseFirestoreImpl {
subscriber.error('Unable to retrieve saved transaction data'); subscriber.error('Unable to retrieve saved transaction data');
return; return;
} }
subscriber.next(Category.fromSnapshotRef(snapshot)); subscriber.next(Category.fromSnapshotRef(accountId, snapshot));
}).catch(err => { }).catch(err => {
console.error(err); console.error(err);
}); });
}).catch(err => { }).catch(err => {
console.error('Failed to create new category: ');
console.error(err); console.error(err);
subscriber.error(err); subscriber.error(err);
}); });
}); });
} }
updateCategory(id: string, changes: object): Observable<boolean> { updateCategory(accountId: string, id: string, changes: object): Observable<boolean> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('categories').doc(id).onSnapshot(snapshot => { firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id)
if (!snapshot.exists) { .update(changes)
return; .then(function () {
} subscriber.next(true);
})
subscriber.next(Category.fromSnapshotRef(snapshot)); .catch(function () {
}); subscriber.next(false);
});
}); });
} }
deleteCategory(id: string): Observable<boolean> { deleteCategory(accountId: string, id: string): Observable<boolean> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('categories').doc(id).delete().then(result => { firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id).delete().then(result => {
subscriber.next(true); subscriber.next(true);
}).catch(err => { }).catch(err => {
console.error(err); console.error(err);

View file

@ -1,18 +1,19 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Category } from './category'; import { Category } from './category';
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { Account } from '../accounts/account';
export interface CategoryService { export interface CategoryService {
getCategories(group: string, count?: number): Observable<Category[]>; getCategories(accountId: string, count?: number): Observable<Category[]>;
getCategory(id: string): Observable<Category>; getCategory(accountId: string, id: string): Observable<Category>;
createCategory(name: string, amount: number, group: string): Observable<Category>; createCategory(accountId: string, name: string, amount: number): Observable<Category>;
updateCategory(id: string, changes: object): Observable<boolean>; updateCategory(accountId: string, id: string, changes: object): Observable<boolean>;
deleteCategory(id: string): Observable<boolean>; deleteCategory(accountId: string, id: string): Observable<boolean>;
} }
export let CATEGORY_SERVICE = new InjectionToken<CategoryService>('category.service'); export let CATEGORY_SERVICE = new InjectionToken<CategoryService>('category.service');

View file

@ -1,18 +1,17 @@
export class Category { export class Category {
id: string; id: string;
accountId: string;
remoteId: string;
name: string; name: string;
amount: number; amount: number;
repeat: string; repeat: string;
color: string; color: string;
accountId: string;
static fromSnapshotRef(snapshot: firebase.firestore.DocumentSnapshot): Category { static fromSnapshotRef(accountId: string, snapshot: firebase.firestore.DocumentSnapshot): Category {
const category = new Category(); const category = new Category();
category.id = snapshot.id; category.id = snapshot.id;
category.name = snapshot.get('name'); category.name = snapshot.get('name');
category.amount = snapshot.get('amount'); category.amount = snapshot.get('amount');
category.accountId = snapshot.get('group'); category.accountId = accountId;
return category; return category;
} }
} }

View file

@ -1 +1 @@
<app-add-edit-category [title]="'Add Category'" [currentCategory]="category"></app-add-edit-category> <app-add-edit-category [title]="'Add Category'" [accountId]="accountId" [currentCategory]="category"></app-add-edit-category>

View file

@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Category } from '../category' import { Category } from '../category';
import { ActivatedRoute } from '@angular/router';
@Component({ @Component({
selector: 'app-new-category', selector: 'app-new-category',
@ -8,14 +9,18 @@ import { Category } from '../category'
}) })
export class NewCategoryComponent implements OnInit { export class NewCategoryComponent implements OnInit {
accountId: string;
category: Category; category: Category;
constructor() { } constructor(
private route: ActivatedRoute
) { }
ngOnInit() { ngOnInit() {
this.accountId = this.route.snapshot.paramMap.get('accountId');
this.category = new Category(); this.category = new Category();
// TODO: Set random color for category, improve color picker // TODO: Set random color for category, improve color picker
//this.category.color = // this.category.color =
} }
} }

View file

@ -1,67 +0,0 @@
.dashboard {
color: #F1F1F1;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 1em;
}
.dashboard > div {
background: #212121;
display: inline-block;
margin: 1em;
padding: 1em;
max-width: 500px;
position: relative;
width: 100%;
}
.dashboard .dashboard-primary {
padding: 5em 1em;
text-align: center;
}
.dashboard-primary > * {
display: block;
}
.dashboard div h2, .dashboard div h3 {
margin: 0;
}
.dashboard p, .dashboard a {
color: #F1F1F1;
text-align: center;
text-decoration: none;
}
.dashboard-primary div {
bottom: 0.5em;
display: flex;
justify-content: flex-end;
left: 0.5em;
right: 0.5em;
position: absolute;
}
.dashboard .no-categories {
padding: 1em;
text-align: center;
}
.dashboard .no-categories a {
display: inline-block;
border: 1px dashed #F1F1F1;
padding: 1em;
}
.dashboard .no-categories p {
line-height: normal;
white-space: normal;
}
a.view-all {
position: absolute;
right: 0.5em;
top: 0.5em;
}

View file

@ -1,29 +0,0 @@
<div class="dashboard">
<div class="dashboard" *ngIf="!isLoggedIn()">
<h2 class="log-in">Get started</h2>
<p>To begin tracking your finances, <a routerLink="/login">login</a> or <a routerLink="/register">create an account</a>!</p>
</div>
<div class="dashboard-primary" *ngIf="isLoggedIn()">
<h2 class="balance">
Current Balance: <br />
<span [ngClass]="{'income': getBalance() > 0, 'expense': getBalance() < 0}">{{ getBalance() / 100 | currency }}</span>
</h2>
<div class="transaction-navigation">
<a mat-button routerLink="/transactions">View Transactions</a>
</div>
</div>
<div class="dashboard-categories" *ngIf="isLoggedIn()">
<h3 class="categories">Categories</h3>
<a mat-button routerLink="/categories" class="view-all" *ngIf="categories">View All</a>
<div class="no-categories" *ngIf="!categories || categories.length === 0">
<a mat-button routerLink="/categories/new">
<mat-icon>add</mat-icon>
<p>Add categories to get more insights into your income and expenses.</p>
</a>
</div>
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
</div>
</div>
<a mat-fab routerLink="/transactions/new" *ngIf="isLoggedIn()">
<mat-icon aria-label="Add">add</mat-icon>
</a>

View file

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DashboardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,79 +0,0 @@
import { Component, OnInit, Inject } from '@angular/core';
import { Transaction } from '../transactions/transaction';
import { TransactionService, TRANSACTION_SERVICE } from '../transactions/transaction.service';
import { Category } from '../categories/category';
import { AppComponent } from '../app.component';
import { TransactionType } from '../transactions/transaction.type';
import { Observable } from 'rxjs';
import { CategoryService, CATEGORY_SERVICE } from '../categories/category.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
public transactions: Transaction[];
public categories: Category[];
categoryBalances: Map<string, number>;
constructor(
private app: AppComponent,
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
) { }
ngOnInit() {
this.app.backEnabled = false;
this.app.title = 'My Finances';
this.getBalance();
this.getTransactions();
this.getCategories();
this.categoryBalances = new Map();
}
getBalance(): number {
let totalBalance = 0;
if (!this.categoryBalances) {
return 0;
}
this.categoryBalances.forEach(balance => {
totalBalance += balance;
});
return totalBalance;
}
getTransactions(): void {
this.transactionService.getTransactions(this.app.group, 5).subscribe(transactions => this.transactions = <Transaction[]>transactions);
}
getCategories(): void {
this.categoryService.getCategories(this.app.group, 5).subscribe(categories => {
this.categories = categories;
for (const category of categories) {
this.getCategoryBalance(category.id).subscribe(balance => this.categoryBalances.set(category.id, balance));
}
});
}
getCategoryBalance(category: string): Observable<number> {
return Observable.create(subscriber => {
this.transactionService.getTransactionsForCategory(category).subscribe(transactions => {
let balance = 0;
for (const transaction of transactions) {
if (transaction.type === TransactionType.INCOME) {
balance += transaction.amount;
} else {
balance -= transaction.amount;
}
}
subscriber.next(balance);
});
});
}
isLoggedIn(): boolean {
return this.app.isLoggedIn();
}
}

View file

@ -1,7 +1,7 @@
<div [hidden]="currentTransaction"> <div [hidden]="currentTransaction">
<p>Select a transaction from the list to view details about it or edit it.</p> <p>Select a transaction from the list to view details about it or edit it.</p>
</div> </div>
<div [hidden]="!currentTransaction" class="form transaction-form"> <div [hidden]="!currentTransaction" *ngIf="currentTransaction" class="form transaction-form">
<mat-form-field> <mat-form-field>
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required> <input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
</mat-form-field> </mat-form-field>

View file

@ -6,6 +6,7 @@ import { Category } from 'src/app/categories/category';
import { Actionable } from 'src/app/actionable'; import { Actionable } from 'src/app/actionable';
import { AppComponent } from 'src/app/app.component'; import { AppComponent } from 'src/app/app.component';
import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service'; import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service';
import { Account } from 'src/app/accounts/account';
@Component({ @Component({
selector: 'app-add-edit-transaction', selector: 'app-add-edit-transaction',
@ -15,9 +16,8 @@ import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.s
export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionable { export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionable {
@Input() title: string; @Input() title: string;
@Input() currentTransaction: Transaction; @Input() currentTransaction: Transaction;
@Input() group: string; @Input() accountId: string;
public transactionType = TransactionType; public transactionType = TransactionType;
public selectedCategory: Category;
public categories: Category[]; public categories: Category[];
public rawAmount: string; public rawAmount: string;
@ -41,17 +41,28 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl
doAction(): void { doAction(): void {
// The amount will be input as a decimal value so we need to convert it // The amount will be input as a decimal value so we need to convert it
// to an integer // to an integer
this.currentTransaction.amount *= 100;
let observable; let observable;
if (this.currentTransaction.id) { if (this.currentTransaction.id) {
// This is an existing transaction, update it // This is an existing transaction, update it
observable = this.transactionService.updateTransaction(this.currentTransaction.id, this.currentTransaction); observable = this.transactionService.updateTransaction(
this.accountId,
this.currentTransaction.id,
{
name: this.currentTransaction.title,
description: this.currentTransaction.description,
amount: this.currentTransaction.amount * 100,
date: this.currentTransaction.date,
category: this.currentTransaction.categoryId,
isExpense: this.currentTransaction.type === TransactionType.EXPENSE
}
);
} else { } else {
// This is a new transaction, save it // This is a new transaction, save it
observable = this.transactionService.createTransaction( observable = this.transactionService.createTransaction(
this.accountId,
this.currentTransaction.title, this.currentTransaction.title,
this.currentTransaction.description, this.currentTransaction.description,
this.currentTransaction.amount, this.currentTransaction.amount * 100,
this.currentTransaction.date, this.currentTransaction.date,
this.currentTransaction.type === TransactionType.EXPENSE, this.currentTransaction.type === TransactionType.EXPENSE,
this.currentTransaction.categoryId, this.currentTransaction.categoryId,
@ -68,11 +79,12 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl
} }
delete(): void { delete(): void {
this.transactionService.deleteTransaction(this.currentTransaction.id); this.transactionService.deleteTransaction(this.accountId, this.currentTransaction.id).subscribe(() => {
this.app.goBack(); this.app.goBack();
});
} }
getCategories() { getCategories() {
this.categoryService.getCategories(this.app.group).subscribe(categories => this.categories = categories); this.categoryService.getCategories(this.accountId).subscribe(categories => this.categories = categories);
} }
} }

View file

@ -1 +1 @@
<app-add-edit-transaction [title]="'Add Transaction'" [currentTransaction]="transaction"></app-add-edit-transaction> <app-add-edit-transaction [accountId]="accountId" [title]="'Add Transaction'" [currentTransaction]="transaction"></app-add-edit-transaction>

View file

@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Transaction } from '../transaction'; import { Transaction } from '../transaction';
import { ActivatedRoute } from '@angular/router';
@Component({ @Component({
selector: 'app-new-transaction', selector: 'app-new-transaction',
@ -8,12 +9,16 @@ import { Transaction } from '../transaction';
}) })
export class NewTransactionComponent implements OnInit { export class NewTransactionComponent implements OnInit {
accountId: string;
transaction: Transaction; transaction: Transaction;
constructor() { } constructor(
private route: ActivatedRoute
) { }
ngOnInit() { ngOnInit() {
this.transaction = new Transaction() this.accountId = this.route.snapshot.paramMap.get('accountId');
this.transaction = new Transaction();
} }
} }

View file

@ -1 +1 @@
<app-add-edit-transaction [title]="'Edit Transaction'" [currentTransaction]="transaction"></app-add-edit-transaction> <app-add-edit-transaction [accountId]="accountId" [title]="'Edit Transaction'" [currentTransaction]="transaction"></app-add-edit-transaction>

View file

@ -2,6 +2,7 @@ import { Component, OnInit, Input, Inject } from '@angular/core';
import { TransactionService, TRANSACTION_SERVICE } from '../transaction.service'; import { TransactionService, TRANSACTION_SERVICE } from '../transaction.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Transaction } from '../transaction'; import { Transaction } from '../transaction';
import { Account } from 'src/app/accounts/account';
@Component({ @Component({
selector: 'app-transaction-details', selector: 'app-transaction-details',
@ -10,6 +11,7 @@ import { Transaction } from '../transaction';
}) })
export class TransactionDetailsComponent implements OnInit { export class TransactionDetailsComponent implements OnInit {
accountId: string;
transaction: Transaction; transaction: Transaction;
constructor( constructor(
@ -22,8 +24,9 @@ export class TransactionDetailsComponent implements OnInit {
} }
getTransaction(): void { getTransaction(): void {
this.accountId = this.route.snapshot.paramMap.get('accountId');
const id = this.route.snapshot.paramMap.get('id'); const id = this.route.snapshot.paramMap.get('id');
this.transactionService.getTransaction(id) this.transactionService.getTransaction(this.accountId, id)
.subscribe(transaction => { .subscribe(transaction => {
transaction.amount /= 100; transaction.amount /= 100;
this.transaction = transaction; this.transaction = transaction;

View file

@ -1,5 +1,4 @@
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs';
import { Observable, Subscriber } from 'rxjs';
import { Transaction } from './transaction'; import { Transaction } from './transaction';
import * as firebase from 'firebase/app'; import * as firebase from 'firebase/app';
import 'firebase/firestore'; import 'firebase/firestore';
@ -9,47 +8,33 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
constructor() { } constructor() { }
getTransactionsForCategory(category: string): Observable<Transaction[]> { getTransactions(accountId: string, category?: string, count?: number): Observable<Transaction[]> {
const transactionsQuery = firebase.firestore().collection('transactions').where('category', '==', category);
return Observable.create(subscriber => { return Observable.create(subscriber => {
const transactions = []; let transactionQuery: any = firebase.firestore().collection('accounts').doc(accountId).collection('transactions');
transactionsQuery.onSnapshot(data => { if (category) {
if (!data.empty) { transactionQuery = transactionQuery.where('category', '==', category);
data.docs.map(transaction => transactions.push(Transaction.fromSnapshotRef(transaction))); }
} if (count) {
subscriber.next(transactions); transactionQuery = transactionQuery.limit(count);
}); }
}); transactionQuery.onSnapshot(snapshot => {
} if (snapshot.empty) {
subscriber.error(`Unable to query transactions within account ${accountId}`);
getTransactions(group: string, count?: number): Observable<Transaction[]> {
const categoriesQuery = firebase.firestore().collection('categories').where('group', '==', group);
return Observable.create(subscriber => {
categoriesQuery.onSnapshot(querySnapshot => {
if (querySnapshot.empty) {
subscriber.error(`Unable to query categories within group ${group}`);
return; return;
} }
const transactions = []; const transactions = [];
querySnapshot.docs.map(categoryDoc => { snapshot.docs.forEach(transaction => {
firebase.firestore().collection('transactions').where('category', '==', categoryDoc.id).get().then(results => { transactions.push(Transaction.fromSnapshotRef(transaction));
if (results.empty) {
return;
}
for (const transactionDoc of results.docs) {
transactions.push(Transaction.fromSnapshotRef(transactionDoc));
}
});
}); });
subscriber.next(transactions); subscriber.next(transactions);
}); });
}); });
} }
getTransaction(id: string): Observable<Transaction> { getTransaction(accountId: string, id: string): Observable<Transaction> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('transactions').doc(id).onSnapshot(snapshot => { firebase.firestore().collection('accounts').doc(accountId).collection('transactions').doc(id).onSnapshot(snapshot => {
if (!snapshot.exists) { if (!snapshot.exists) {
return; return;
} }
@ -59,6 +44,7 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
} }
createTransaction( createTransaction(
accountId: string,
name: string, name: string,
description: string, description: string,
amount: number, amount: number,
@ -67,7 +53,7 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
category: string category: string
): Observable<Transaction> { ): Observable<Transaction> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('transactions').add({ firebase.firestore().collection('accounts').doc(accountId).collection('transactions').add({
name: name, name: name,
description: description, description: description,
date: date, date: date,
@ -89,9 +75,9 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
} }
updateTransaction(id: string, changes: object): Observable<boolean> { updateTransaction(accountId: string, id: string, changes: object): Observable<boolean> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('transactions').doc(id).update(changes).then(result => { firebase.firestore().collection('accounts').doc(accountId).collection('transactions').doc(id).update(changes).then(() => {
subscriber.next(true); subscriber.next(true);
}).catch(err => { }).catch(err => {
subscriber.next(false); subscriber.next(false);
@ -99,9 +85,9 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
}); });
} }
deleteTransaction(id: string): Observable<boolean> { deleteTransaction(accountId: string, id: string): Observable<boolean> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
firebase.firestore().collection('transactions').doc(id).delete().then(data => { firebase.firestore().collection('accounts').doc(accountId).collection('transactions').doc(id).delete().then(data => {
subscriber.next(true); subscriber.next(true);
}).catch(err => { }).catch(err => {
console.log(err); console.log(err);

View file

@ -1,16 +1,16 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Transaction } from './transaction'; import { Transaction } from './transaction';
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { Account } from '../accounts/account';
export interface TransactionService { export interface TransactionService {
getTransactions(group: string, count?: number): Observable<Transaction[]>; getTransactions(accountId: string, categoryId?: string, count?: number): Observable<Transaction[]>;
getTransactionsForCategory(category: string, count?: number): Observable<Transaction[]>; getTransaction(accountId: string, id: string): Observable<Transaction>;
getTransaction(id: string): Observable<Transaction>;
createTransaction( createTransaction(
accountId: string,
name: string, name: string,
description: string, description: string,
amount: number, amount: number,
@ -19,9 +19,9 @@ export interface TransactionService {
category: string category: string
): Observable<Transaction>; ): Observable<Transaction>;
updateTransaction(id: string, changes: object): Observable<boolean>; updateTransaction(accountId: string, id: string, changes: object): Observable<boolean>;
deleteTransaction(id: string): Observable<boolean>; deleteTransaction(accountId: string, id: string): Observable<boolean>;
} }
export let TRANSACTION_SERVICE = new InjectionToken<TransactionService>('transaction.service'); export let TRANSACTION_SERVICE = new InjectionToken<TransactionService>('transaction.service');

View file

@ -5,9 +5,8 @@ import * as firebase from 'firebase/app';
export class Transaction { export class Transaction {
id: string; id: string;
accountId: string; accountId: string;
remoteId: string;
title: string; title: string;
description: string; description: string = null;
amount: number; amount: number;
date: Date = new Date(); date: Date = new Date();
categoryId: string; categoryId: string;

View file

@ -1,5 +1,5 @@
<mat-nav-list *ngIf="transactions" class="transactions"> <mat-nav-list *ngIf="transactions" class="transactions">
<a mat-list-item *ngFor="let transaction of transactions" routerLink="/transactions/{{ transaction.id }}"> <a mat-list-item *ngFor="let transaction of transactions" routerLink="/accounts/{{ accountId }}/transactions/{{ transaction.id }}">
<div matLine class="list-row-one"> <div matLine class="list-row-one">
<p>{{transaction.title}}</p> <p>{{transaction.title}}</p>
<p class="amount" [class.expense]="transaction.type === transactionType.EXPENSE" [class.income]="transaction.type === transactionType.INCOME"> <p class="amount" [class.expense]="transaction.type === transactionType.EXPENSE" [class.income]="transaction.type === transactionType.INCOME">
@ -9,6 +9,6 @@
<p matLine class="text-small">{{ transaction.date | date }}</p> <p matLine class="text-small">{{ transaction.date | date }}</p>
</a> </a>
</mat-nav-list> </mat-nav-list>
<a mat-fab routerLink="/transactions/new"> <a mat-fab routerLink="/accounts/{{ accountId }}/transactions/new">
<mat-icon aria-label="Add">add</mat-icon> <mat-icon aria-label="Add">add</mat-icon>
</a> </a>

View file

@ -3,6 +3,8 @@ import { Transaction } from './transaction';
import { TransactionType } from './transaction.type'; import { TransactionType } from './transaction.type';
import { TransactionService, TRANSACTION_SERVICE } from './transaction.service'; import { TransactionService, TRANSACTION_SERVICE } from './transaction.service';
import { AppComponent } from '../app.component'; import { AppComponent } from '../app.component';
import { Account } from '../accounts/account';
import { ActivatedRoute } from '@angular/router';
@Component({ @Component({
selector: 'app-transactions', selector: 'app-transactions',
@ -11,24 +13,26 @@ import { AppComponent } from '../app.component';
}) })
export class TransactionsComponent implements OnInit { export class TransactionsComponent implements OnInit {
@Input() group: string; accountId: string;
public transactionType = TransactionType; public transactionType = TransactionType;
public transactions: Transaction[]; public transactions: Transaction[];
constructor( constructor(
private route: ActivatedRoute,
private app: AppComponent, private app: AppComponent,
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService, @Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
) { } ) { }
ngOnInit() { ngOnInit() {
this.accountId = this.route.snapshot.paramMap.get('accountId');
this.app.backEnabled = true; this.app.backEnabled = true;
this.app.title = 'Transactions'; this.app.title = 'Transactions';
this.getTransactions(); this.getTransactions();
} }
getTransactions(): void { getTransactions(): void {
this.transactionService.getTransactions(this.app.group).subscribe(transactions => { this.transactionService.getTransactions(this.accountId).subscribe(transactions => {
this.transactions = transactions; this.transactions = transactions;
}); });
} }