From 8d843473e2501d960048946cc240a0eab63d7192 Mon Sep 17 00:00:00 2001 From: Billy Brawner Date: Sat, 4 May 2019 18:29:21 -0700 Subject: [PATCH] Finish implementing new accounts structure Signed-off-by: Billy Brawner --- .../account-details.component.css | 68 ++++++++++++++++ .../account-details.component.html | 29 ++++++- .../account-details.component.ts | 81 ++++++++++++++++++- src/app/accounts/account.service.firestore.ts | 25 ++++-- src/app/accounts/account.service.ts | 2 +- src/app/accounts/accounts.component.html | 9 ++- src/app/accounts/accounts.component.ts | 6 +- .../add-edit-account.component.ts | 7 +- src/app/app-routing.module.ts | 15 ++-- src/app/app.component.html | 3 +- src/app/app.component.ts | 1 - src/app/app.module.ts | 5 +- .../add-edit-category.component.ts | 23 ++++-- src/app/categories/categories.component.html | 4 +- src/app/categories/categories.component.ts | 14 ++-- .../category-details.component.html | 2 +- .../category-details.component.ts | 7 +- .../category-list.component.html | 2 +- .../category-list/category-list.component.ts | 2 + .../categories/category.service.firestore.ts | 44 +++++----- src/app/categories/category.service.ts | 11 +-- src/app/categories/category.ts | 7 +- .../new-category/new-category.component.html | 2 +- .../new-category/new-category.component.ts | 11 ++- src/app/dashboard/dashboard.component.css | 67 --------------- src/app/dashboard/dashboard.component.html | 29 ------- src/app/dashboard/dashboard.component.spec.ts | 25 ------ src/app/dashboard/dashboard.component.ts | 79 ------------------ .../add-edit-transaction.component.html | 2 +- .../add-edit-transaction.component.ts | 28 +++++-- .../new-transaction.component.html | 2 +- .../new-transaction.component.ts | 9 ++- .../transaction-details.component.html | 2 +- .../transaction-details.component.ts | 5 +- .../transaction.service.firestore.ts | 58 +++++-------- src/app/transactions/transaction.service.ts | 12 +-- src/app/transactions/transaction.ts | 3 +- .../transactions/transactions.component.html | 4 +- .../transactions/transactions.component.ts | 8 +- 39 files changed, 370 insertions(+), 343 deletions(-) delete mode 100644 src/app/dashboard/dashboard.component.css delete mode 100644 src/app/dashboard/dashboard.component.html delete mode 100644 src/app/dashboard/dashboard.component.spec.ts delete mode 100644 src/app/dashboard/dashboard.component.ts diff --git a/src/app/accounts/account-details/account-details.component.css b/src/app/accounts/account-details/account-details.component.css index e69de29..dafccd5 100644 --- a/src/app/accounts/account-details/account-details.component.css +++ b/src/app/accounts/account-details/account-details.component.css @@ -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; + } + \ No newline at end of file diff --git a/src/app/accounts/account-details/account-details.component.html b/src/app/accounts/account-details/account-details.component.html index d44e3c1..eb8020c 100644 --- a/src/app/accounts/account-details/account-details.component.html +++ b/src/app/accounts/account-details/account-details.component.html @@ -1,3 +1,26 @@ -

- account-details works! -

+
+
+

+ Current Balance:
+ {{ 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 00d1c4c..1d30991 100644 --- a/src/app/accounts/account-details/account-details.component.ts +++ b/src/app/accounts/account-details/account-details.component.ts @@ -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({ selector: 'app-account-details', @@ -7,9 +17,76 @@ import { Component, OnInit } from '@angular/core'; }) export class AccountDetailsComponent implements OnInit { - constructor() { } + account: Account; + public transactions: Transaction[]; + public categories: Category[]; + categoryBalances: Map; + + 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() { + 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 = 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 { + 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); + }); + }); + } } diff --git a/src/app/accounts/account.service.firestore.ts b/src/app/accounts/account.service.firestore.ts index 527ed26..62df6cd 100644 --- a/src/app/accounts/account.service.firestore.ts +++ b/src/app/accounts/account.service.firestore.ts @@ -9,11 +9,21 @@ export class FirestoreAccountService implements AccountService { getAccounts(): Observable { return Observable.create(subscriber => { const accounts = []; - firebase.firestore().collection('accounts').onSnapshot(data => { - if (!data.empty) { - data.docs.map(account => accounts.push(Account.fromSnapshotRef(account))); - } - subscriber.next(accounts); + firebase.auth().onAuthStateChanged(user => { + if (user == null) { return; } + firebase.firestore().collection('accounts') + .orderBy('name') + .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, description: string, currency: string, - members: User[], + members: string[], ): Observable { return Observable.create(subscriber => { firebase.firestore().collection('accounts').add({ name: name, description: description, - members: members.map(member => member.id) + currency: currency, + members: members }).then(docRef => { docRef.get().then(snapshot => { if (!snapshot) { diff --git a/src/app/accounts/account.service.ts b/src/app/accounts/account.service.ts index a534e89..378fa2b 100644 --- a/src/app/accounts/account.service.ts +++ b/src/app/accounts/account.service.ts @@ -10,7 +10,7 @@ export interface AccountService { name: string, description: string, currency: string, - members: User[], + members: string[], ): Observable; updateAccount(id: string, changes: object): Observable; deleteAccount(id: string): Observable; diff --git a/src/app/accounts/accounts.component.html b/src/app/accounts/accounts.component.html index 9665d6d..9315775 100644 --- a/src/app/accounts/accounts.component.html +++ b/src/app/accounts/accounts.component.html @@ -1,4 +1,8 @@ - +
+ +

To begin tracking your finances, login or create an account!

+
+

Add accounts to begin tracking your budget.

+ + add + \ No newline at end of file diff --git a/src/app/accounts/accounts.component.ts b/src/app/accounts/accounts.component.ts index 4b48f1d..a5ec1b9 100644 --- a/src/app/accounts/accounts.component.ts +++ b/src/app/accounts/accounts.component.ts @@ -20,9 +20,13 @@ export class AccountsComponent implements OnInit { ngOnInit() { this.app.backEnabled = true; - this.app.title = 'Transactions'; + this.app.title = 'Accounts'; this.accountService.getAccounts().subscribe(accounts => { this.accounts = accounts; }); } + + isLoggedIn(): boolean { + return this.app.isLoggedIn(); + } } diff --git a/src/app/accounts/add-edit-account/add-edit-account.component.ts b/src/app/accounts/add-edit-account/add-edit-account.component.ts index 247cb07..69d162b 100644 --- a/src/app/accounts/add-edit-account/add-edit-account.component.ts +++ b/src/app/accounts/add-edit-account/add-edit-account.component.ts @@ -5,6 +5,7 @@ import { AppComponent } from 'src/app/app.component'; import { Actionable } from 'src/app/actionable'; import { UserService, USER_SERVICE } from 'src/app/users/user.service'; import { User } from 'src/app/users/user'; +import * as firebase from 'firebase'; @Component({ selector: 'app-add-edit-account', @@ -14,8 +15,8 @@ import { User } from 'src/app/users/user'; export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable { @Input() title: string; @Input() account: Account; - public users: User[]; - public searchedUsers: User[]; + public userIds: string[] = [firebase.auth().currentUser.uid]; + public searchedUsers: User[] = []; constructor( private app: AppComponent, @@ -45,7 +46,7 @@ export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable { this.account.name, this.account.description, this.account.currency, - this.users + this.userIds ); } // TODO: Check if it was actually successful or not diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index dbabb9b..037cdd5 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { DashboardComponent } from './dashboard/dashboard.component'; import { TransactionsComponent } from './transactions/transactions.component'; import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.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'; const routes: Routes = [ - { path: '', component: DashboardComponent }, + { path: '', component: AccountsComponent }, { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent }, { path: 'accounts', component: AccountsComponent }, { path: 'accounts/new', component: NewAccountComponent }, { path: 'accounts/:id', component: AccountDetailsComponent }, - { path: 'transactions', component: TransactionsComponent }, - { path: 'transactions/new', component: NewTransactionComponent }, - { path: 'transactions/:id', component: TransactionDetailsComponent }, - { path: 'categories', component: CategoriesComponent }, - { path: 'categories/new', component: NewCategoryComponent }, - { path: 'categories/:id', component: CategoryDetailsComponent }, + { path: 'accounts/:accountId/transactions', component: TransactionsComponent }, + { path: 'accounts/:accountId/transactions/new', component: NewTransactionComponent }, + { path: 'accounts/:accountId/transactions/:id', component: TransactionDetailsComponent }, + { path: 'accounts/:accountId/categories', component: CategoriesComponent }, + { path: 'accounts/:accountId/categories/new', component: NewCategoryComponent }, + { path: 'accounts/:accountId/categories/:id', component: CategoryDetailsComponent }, ]; @NgModule({ diff --git a/src/app/app.component.html b/src/app/app.component.html index 69e5248..f80c92e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,8 +2,7 @@ {{ getUsername() }} - Manage Accounts - Export Data + Accounts Login Register Logout diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4032b00..ac434f6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -14,7 +14,6 @@ export class AppComponent { public title = 'Budget'; public backEnabled = false; public actionable: Actionable; - public group = 'MG3KOiuPu0Xy38O2LdhJ'; constructor( public authService: AuthService, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3ca8a83..934dd8c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,7 +23,6 @@ import { AccountsComponent } from './accounts/accounts.component'; import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component'; import { NewTransactionComponent } from './transactions/new-transaction/new-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 { CategoryDetailsComponent } from './categories/category-details/category-details.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 { 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'; export const CustomCurrencyMaskConfig: CurrencyMaskConfig = { align: 'left', @@ -65,7 +66,6 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = { TransactionDetailsComponent, NewTransactionComponent, AddEditTransactionComponent, - DashboardComponent, CategoriesComponent, CategoryDetailsComponent, AddEditCategoryComponent, @@ -105,6 +105,7 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = { { provide: TRANSACTION_SERVICE, useClass: TransactionServiceFirebaseFirestoreImpl }, { provide: CATEGORY_SERVICE, useClass: CategoryServiceFirebaseFirestoreImpl }, { provide: ACCOUNT_SERVICE, useClass: FirestoreAccountService }, + { provide: USER_SERVICE, useClass: FirestoreUserService }, ], bootstrap: [AppComponent] }) diff --git a/src/app/categories/add-edit-category/add-edit-category.component.ts b/src/app/categories/add-edit-category/add-edit-category.component.ts index 286e2cd..6e96a43 100644 --- a/src/app/categories/add-edit-category/add-edit-category.component.ts +++ b/src/app/categories/add-edit-category/add-edit-category.component.ts @@ -3,6 +3,8 @@ import { Category } from '../category'; import { CATEGORY_SERVICE, CategoryService } from '../category.service'; import { Actionable } from 'src/app/actionable'; 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({ selector: 'app-add-edit-category', @@ -11,12 +13,13 @@ import { AppComponent } from 'src/app/app.component'; }) export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy { + @Input() accountId: string; @Input() title: string; @Input() currentCategory: Category; - @Input() group: string; constructor( private app: AppComponent, + @Inject(ACCOUNT_SERVICE) private accountService: AccountService, @Inject(CATEGORY_SERVICE) private categoryService: CategoryService, ) { } @@ -31,14 +34,24 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy { } doAction(): void { - this.currentCategory.amount *= 100; let observable; if (this.currentCategory.id) { // 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 { // 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 => { this.app.goBack(); @@ -50,7 +63,7 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy { } delete(): void { - this.categoryService.deleteCategory(this.currentCategory.id); + this.categoryService.deleteCategory(this.accountId, this.currentCategory.id); this.app.goBack(); } } diff --git a/src/app/categories/categories.component.html b/src/app/categories/categories.component.html index f699e64..1bb85d1 100644 --- a/src/app/categories/categories.component.html +++ b/src/app/categories/categories.component.html @@ -1,4 +1,4 @@ - - + + add diff --git a/src/app/categories/categories.component.ts b/src/app/categories/categories.component.ts index 9af7c4d..5d2c677 100644 --- a/src/app/categories/categories.component.ts +++ b/src/app/categories/categories.component.ts @@ -5,6 +5,8 @@ import { AppComponent } from '../app.component'; import { TransactionService, TRANSACTION_SERVICE } from '../transactions/transaction.service'; import { Observable } from 'rxjs'; import { TransactionType } from '../transactions/transaction.type'; +import { Account } from '../accounts/account'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-categories', @@ -13,17 +15,19 @@ import { TransactionType } from '../transactions/transaction.type'; }) export class CategoriesComponent implements OnInit { - @Input() group: string; + accountId: string; public categories: Category[]; public categoryBalances: Map; constructor( + private route: ActivatedRoute, private app: AppComponent, @Inject(CATEGORY_SERVICE) private categoryService: CategoryService, @Inject(TRANSACTION_SERVICE) private transactionService: TransactionService, ) { } ngOnInit() { + this.accountId = this.route.snapshot.paramMap.get('accountId'); this.app.title = 'Categories'; this.app.backEnabled = true; this.getCategories(); @@ -31,17 +35,17 @@ export class CategoriesComponent implements OnInit { } getCategories(): void { - this.categoryService.getCategories(this.app.group).subscribe(categories => { + this.categoryService.getCategories(this.accountId).subscribe(categories => { this.categories = 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 { + getCategoryBalance(category: Category): Observable { return Observable.create(subscriber => { - this.transactionService.getTransactionsForCategory(category).subscribe(transactions => { + this.transactionService.getTransactions(this.accountId, category.id).subscribe(transactions => { let balance = 0; for (const transaction of transactions) { if (transaction.type === TransactionType.INCOME) { diff --git a/src/app/categories/category-details/category-details.component.html b/src/app/categories/category-details/category-details.component.html index aece769..151eaca 100644 --- a/src/app/categories/category-details/category-details.component.html +++ b/src/app/categories/category-details/category-details.component.html @@ -1 +1 @@ - + diff --git a/src/app/categories/category-details/category-details.component.ts b/src/app/categories/category-details/category-details.component.ts index b0cbd87..25f381a 100644 --- a/src/app/categories/category-details/category-details.component.ts +++ b/src/app/categories/category-details/category-details.component.ts @@ -1,7 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input } from '@angular/core'; import { CategoryServiceFirebaseFirestoreImpl } from '../category.service.firestore'; import { Category } from '../category'; import { ActivatedRoute } from '@angular/router'; +import { Account } from 'src/app/accounts/account'; @Component({ selector: 'app-category-details', @@ -10,6 +11,7 @@ import { ActivatedRoute } from '@angular/router'; }) export class CategoryDetailsComponent implements OnInit { + accountId: string; category: Category; constructor( @@ -22,8 +24,9 @@ export class CategoryDetailsComponent implements OnInit { } getCategory(): void { + this.accountId = this.route.snapshot.paramMap.get('accountId'); const id = this.route.snapshot.paramMap.get('id'); - this.categoryService.getCategory(id) + this.categoryService.getCategory(this.accountId, id) .subscribe(category => { category.amount /= 100; this.category = category; diff --git a/src/app/categories/category-list/category-list.component.html b/src/app/categories/category-list/category-list.component.html index 01e91d8..66755c9 100644 --- a/src/app/categories/category-list/category-list.component.html +++ b/src/app/categories/category-list/category-list.component.html @@ -4,7 +4,7 @@ } - +

{{ category.name }} diff --git a/src/app/categories/category-list/category-list.component.ts b/src/app/categories/category-list/category-list.component.ts index c541afe..7110fe2 100644 --- a/src/app/categories/category-list/category-list.component.ts +++ b/src/app/categories/category-list/category-list.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, Input } from '@angular/core'; import { Category } from '../category'; +import { Account } from 'src/app/accounts/account'; @Component({ selector: 'app-category-list', @@ -8,6 +9,7 @@ import { Category } from '../category'; }) export class CategoryListComponent implements OnInit { + @Input() accountId: string; @Input() categories: Category[]; @Input() categoryBalances: Map; diff --git a/src/app/categories/category.service.firestore.ts b/src/app/categories/category.service.firestore.ts index 1da7893..5820d3c 100644 --- a/src/app/categories/category.service.firestore.ts +++ b/src/app/categories/category.service.firestore.ts @@ -3,6 +3,7 @@ import { of, Observable, from } from 'rxjs'; import { Category } from './category'; import * as firebase from 'firebase/app'; import 'firebase/firestore'; +import { Account } from '../accounts/account'; @Injectable({ providedIn: 'root' @@ -11,9 +12,9 @@ export class CategoryServiceFirebaseFirestoreImpl { constructor() { } - getCategories(group: string, count?: number): Observable { + getCategories(accountId: string, count?: number): Observable { 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) { query = query.limit(count); } @@ -26,7 +27,7 @@ export class CategoryServiceFirebaseFirestoreImpl { const categories = []; for (const categoryDoc of snapshot.docs) { - categories.push(Category.fromSnapshotRef(categoryDoc)); + categories.push(Category.fromSnapshotRef(accountId, categoryDoc)); } subscriber.next(categories); }, error => { @@ -36,24 +37,23 @@ export class CategoryServiceFirebaseFirestoreImpl { }); } - getCategory(id: string): Observable { + getCategory(accountId: string, id: string): Observable { 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) { return; } - subscriber.next(Category.fromSnapshotRef(snapshot)); + subscriber.next(Category.fromSnapshotRef(accountId, snapshot)); }); }); } - createCategory(name: string, amount: number, group: string): Observable { + createCategory(accountId: string, name: string, amount: number): Observable { return Observable.create(subscriber => { - firebase.firestore().collection('categories').add({ + firebase.firestore().collection('accounts').doc(accountId).collection('categories').add({ name: name, - amount: amount, - group: group, + amount: amount }).then(docRef => { if (!docRef) { console.error('Failed to create category'); @@ -64,32 +64,34 @@ export class CategoryServiceFirebaseFirestoreImpl { subscriber.error('Unable to retrieve saved transaction data'); return; } - subscriber.next(Category.fromSnapshotRef(snapshot)); + subscriber.next(Category.fromSnapshotRef(accountId, snapshot)); }).catch(err => { console.error(err); }); }).catch(err => { + console.error('Failed to create new category: '); console.error(err); subscriber.error(err); }); }); } - updateCategory(id: string, changes: object): Observable { + updateCategory(accountId: string, id: string, changes: object): Observable { return Observable.create(subscriber => { - firebase.firestore().collection('categories').doc(id).onSnapshot(snapshot => { - if (!snapshot.exists) { - return; - } - - subscriber.next(Category.fromSnapshotRef(snapshot)); - }); + firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id) + .update(changes) + .then(function () { + subscriber.next(true); + }) + .catch(function () { + subscriber.next(false); + }); }); } - deleteCategory(id: string): Observable { + deleteCategory(accountId: string, id: string): Observable { 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); }).catch(err => { console.error(err); diff --git a/src/app/categories/category.service.ts b/src/app/categories/category.service.ts index dc88712..98fcbdc 100644 --- a/src/app/categories/category.service.ts +++ b/src/app/categories/category.service.ts @@ -1,18 +1,19 @@ import { Observable } from 'rxjs'; import { Category } from './category'; import { InjectionToken } from '@angular/core'; +import { Account } from '../accounts/account'; export interface CategoryService { - getCategories(group: string, count?: number): Observable; + getCategories(accountId: string, count?: number): Observable; - getCategory(id: string): Observable; + getCategory(accountId: string, id: string): Observable; - createCategory(name: string, amount: number, group: string): Observable; + createCategory(accountId: string, name: string, amount: number): Observable; - updateCategory(id: string, changes: object): Observable; + updateCategory(accountId: string, id: string, changes: object): Observable; - deleteCategory(id: string): Observable; + deleteCategory(accountId: string, id: string): Observable; } export let CATEGORY_SERVICE = new InjectionToken('category.service'); diff --git a/src/app/categories/category.ts b/src/app/categories/category.ts index 143831a..2ff3a1c 100644 --- a/src/app/categories/category.ts +++ b/src/app/categories/category.ts @@ -1,18 +1,17 @@ export class Category { id: string; - accountId: string; - remoteId: string; name: string; amount: number; repeat: 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(); category.id = snapshot.id; category.name = snapshot.get('name'); category.amount = snapshot.get('amount'); - category.accountId = snapshot.get('group'); + category.accountId = accountId; return category; } } diff --git a/src/app/categories/new-category/new-category.component.html b/src/app/categories/new-category/new-category.component.html index 9076dfe..86d9118 100644 --- a/src/app/categories/new-category/new-category.component.html +++ b/src/app/categories/new-category/new-category.component.html @@ -1 +1 @@ - + diff --git a/src/app/categories/new-category/new-category.component.ts b/src/app/categories/new-category/new-category.component.ts index 2bd64ad..07b742e 100644 --- a/src/app/categories/new-category/new-category.component.ts +++ b/src/app/categories/new-category/new-category.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { Category } from '../category' +import { Category } from '../category'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-new-category', @@ -8,14 +9,18 @@ import { Category } from '../category' }) export class NewCategoryComponent implements OnInit { + accountId: string; category: Category; - constructor() { } + constructor( + private route: ActivatedRoute + ) { } ngOnInit() { + this.accountId = this.route.snapshot.paramMap.get('accountId'); this.category = new Category(); // TODO: Set random color for category, improve color picker - //this.category.color = + // this.category.color = } } diff --git a/src/app/dashboard/dashboard.component.css b/src/app/dashboard/dashboard.component.css deleted file mode 100644 index 3253b7b..0000000 --- a/src/app/dashboard/dashboard.component.css +++ /dev/null @@ -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; -} diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html deleted file mode 100644 index d4528f6..0000000 --- a/src/app/dashboard/dashboard.component.html +++ /dev/null @@ -1,29 +0,0 @@ -

- - add - \ No newline at end of file diff --git a/src/app/dashboard/dashboard.component.spec.ts b/src/app/dashboard/dashboard.component.spec.ts deleted file mode 100644 index 9c996c3..0000000 --- a/src/app/dashboard/dashboard.component.spec.ts +++ /dev/null @@ -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; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ DashboardComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DashboardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts deleted file mode 100644 index 50ddb5a..0000000 --- a/src/app/dashboard/dashboard.component.ts +++ /dev/null @@ -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; - - 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 = 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 { - 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(); - } -} 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 002d369..ebe3553 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 @@ -1,7 +1,7 @@

Select a transaction from the list to view details about it or edit it.

-
+
diff --git a/src/app/transactions/add-edit-transaction/add-edit-transaction.component.ts b/src/app/transactions/add-edit-transaction/add-edit-transaction.component.ts index f396ff9..1fbdd5e 100644 --- a/src/app/transactions/add-edit-transaction/add-edit-transaction.component.ts +++ b/src/app/transactions/add-edit-transaction/add-edit-transaction.component.ts @@ -6,6 +6,7 @@ import { Category } from 'src/app/categories/category'; import { Actionable } from 'src/app/actionable'; import { AppComponent } from 'src/app/app.component'; import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service'; +import { Account } from 'src/app/accounts/account'; @Component({ 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 { @Input() title: string; @Input() currentTransaction: Transaction; - @Input() group: string; + @Input() accountId: string; public transactionType = TransactionType; - public selectedCategory: Category; public categories: Category[]; public rawAmount: string; @@ -41,17 +41,28 @@ 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; let observable; if (this.currentTransaction.id) { // 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 { // This is a new transaction, save it observable = this.transactionService.createTransaction( + this.accountId, this.currentTransaction.title, this.currentTransaction.description, - this.currentTransaction.amount, + this.currentTransaction.amount * 100, this.currentTransaction.date, this.currentTransaction.type === TransactionType.EXPENSE, this.currentTransaction.categoryId, @@ -68,11 +79,12 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl } delete(): void { - this.transactionService.deleteTransaction(this.currentTransaction.id); - this.app.goBack(); + this.transactionService.deleteTransaction(this.accountId, this.currentTransaction.id).subscribe(() => { + this.app.goBack(); + }); } getCategories() { - this.categoryService.getCategories(this.app.group).subscribe(categories => this.categories = categories); + this.categoryService.getCategories(this.accountId).subscribe(categories => this.categories = categories); } } diff --git a/src/app/transactions/new-transaction/new-transaction.component.html b/src/app/transactions/new-transaction/new-transaction.component.html index d3904c5..4f590de 100644 --- a/src/app/transactions/new-transaction/new-transaction.component.html +++ b/src/app/transactions/new-transaction/new-transaction.component.html @@ -1 +1 @@ - + diff --git a/src/app/transactions/new-transaction/new-transaction.component.ts b/src/app/transactions/new-transaction/new-transaction.component.ts index a3d096d..1e99e29 100644 --- a/src/app/transactions/new-transaction/new-transaction.component.ts +++ b/src/app/transactions/new-transaction/new-transaction.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Transaction } from '../transaction'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-new-transaction', @@ -8,12 +9,16 @@ import { Transaction } from '../transaction'; }) export class NewTransactionComponent implements OnInit { + accountId: string; transaction: Transaction; - constructor() { } + constructor( + private route: ActivatedRoute + ) { } ngOnInit() { - this.transaction = new Transaction() + this.accountId = this.route.snapshot.paramMap.get('accountId'); + this.transaction = new Transaction(); } } diff --git a/src/app/transactions/transaction-details/transaction-details.component.html b/src/app/transactions/transaction-details/transaction-details.component.html index dcb7fdf..1a9fd6f 100644 --- a/src/app/transactions/transaction-details/transaction-details.component.html +++ b/src/app/transactions/transaction-details/transaction-details.component.html @@ -1 +1 @@ - + diff --git a/src/app/transactions/transaction-details/transaction-details.component.ts b/src/app/transactions/transaction-details/transaction-details.component.ts index 51f5cd8..dcada27 100644 --- a/src/app/transactions/transaction-details/transaction-details.component.ts +++ b/src/app/transactions/transaction-details/transaction-details.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit, Input, Inject } from '@angular/core'; import { TransactionService, TRANSACTION_SERVICE } from '../transaction.service'; import { ActivatedRoute } from '@angular/router'; import { Transaction } from '../transaction'; +import { Account } from 'src/app/accounts/account'; @Component({ selector: 'app-transaction-details', @@ -10,6 +11,7 @@ import { Transaction } from '../transaction'; }) export class TransactionDetailsComponent implements OnInit { + accountId: string; transaction: Transaction; constructor( @@ -22,8 +24,9 @@ export class TransactionDetailsComponent implements OnInit { } getTransaction(): void { + this.accountId = this.route.snapshot.paramMap.get('accountId'); const id = this.route.snapshot.paramMap.get('id'); - this.transactionService.getTransaction(id) + this.transactionService.getTransaction(this.accountId, id) .subscribe(transaction => { transaction.amount /= 100; this.transaction = transaction; diff --git a/src/app/transactions/transaction.service.firestore.ts b/src/app/transactions/transaction.service.firestore.ts index 2f08c16..bdbfbc4 100644 --- a/src/app/transactions/transaction.service.firestore.ts +++ b/src/app/transactions/transaction.service.firestore.ts @@ -1,5 +1,4 @@ -import { Injectable } from '@angular/core'; -import { Observable, Subscriber } from 'rxjs'; +import { Observable } from 'rxjs'; import { Transaction } from './transaction'; import * as firebase from 'firebase/app'; import 'firebase/firestore'; @@ -9,47 +8,33 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi constructor() { } - getTransactionsForCategory(category: string): Observable { - const transactionsQuery = firebase.firestore().collection('transactions').where('category', '==', category); + getTransactions(accountId: string, category?: string, count?: number): Observable { return Observable.create(subscriber => { - const transactions = []; - transactionsQuery.onSnapshot(data => { - if (!data.empty) { - data.docs.map(transaction => transactions.push(Transaction.fromSnapshotRef(transaction))); - } - subscriber.next(transactions); - }); - }); - } - - getTransactions(group: string, count?: number): Observable { - 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}`); + let transactionQuery: any = firebase.firestore().collection('accounts').doc(accountId).collection('transactions'); + if (category) { + transactionQuery = transactionQuery.where('category', '==', category); + } + if (count) { + transactionQuery = transactionQuery.limit(count); + } + transactionQuery.onSnapshot(snapshot => { + if (snapshot.empty) { + subscriber.error(`Unable to query transactions within account ${accountId}`); return; } const transactions = []; - querySnapshot.docs.map(categoryDoc => { - firebase.firestore().collection('transactions').where('category', '==', categoryDoc.id).get().then(results => { - if (results.empty) { - return; - } - for (const transactionDoc of results.docs) { - transactions.push(Transaction.fromSnapshotRef(transactionDoc)); - } - }); + snapshot.docs.forEach(transaction => { + transactions.push(Transaction.fromSnapshotRef(transaction)); }); subscriber.next(transactions); }); }); } - getTransaction(id: string): Observable { + getTransaction(accountId: string, id: string): Observable { 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) { return; } @@ -59,6 +44,7 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi } createTransaction( + accountId: string, name: string, description: string, amount: number, @@ -67,7 +53,7 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi category: string ): Observable { return Observable.create(subscriber => { - firebase.firestore().collection('transactions').add({ + firebase.firestore().collection('accounts').doc(accountId).collection('transactions').add({ name: name, description: description, date: date, @@ -89,9 +75,9 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi } - updateTransaction(id: string, changes: object): Observable { + updateTransaction(accountId: string, id: string, changes: object): Observable { 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); }).catch(err => { subscriber.next(false); @@ -99,9 +85,9 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi }); } - deleteTransaction(id: string): Observable { + deleteTransaction(accountId: string, id: string): Observable { 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); }).catch(err => { console.log(err); diff --git a/src/app/transactions/transaction.service.ts b/src/app/transactions/transaction.service.ts index 255a98c..beb9374 100644 --- a/src/app/transactions/transaction.service.ts +++ b/src/app/transactions/transaction.service.ts @@ -1,16 +1,16 @@ import { Observable } from 'rxjs'; import { Transaction } from './transaction'; import { InjectionToken } from '@angular/core'; +import { Account } from '../accounts/account'; export interface TransactionService { - getTransactions(group: string, count?: number): Observable; + getTransactions(accountId: string, categoryId?: string, count?: number): Observable; - getTransactionsForCategory(category: string, count?: number): Observable; - - getTransaction(id: string): Observable; + getTransaction(accountId: string, id: string): Observable; createTransaction( + accountId: string, name: string, description: string, amount: number, @@ -19,9 +19,9 @@ export interface TransactionService { category: string ): Observable; - updateTransaction(id: string, changes: object): Observable; + updateTransaction(accountId: string, id: string, changes: object): Observable; - deleteTransaction(id: string): Observable; + deleteTransaction(accountId: string, id: string): Observable; } export let TRANSACTION_SERVICE = new InjectionToken('transaction.service'); diff --git a/src/app/transactions/transaction.ts b/src/app/transactions/transaction.ts index fde54c6..8ee42e1 100644 --- a/src/app/transactions/transaction.ts +++ b/src/app/transactions/transaction.ts @@ -5,9 +5,8 @@ import * as firebase from 'firebase/app'; export class Transaction { id: string; accountId: string; - remoteId: string; title: string; - description: string; + description: string = null; amount: number; date: Date = new Date(); categoryId: string; diff --git a/src/app/transactions/transactions.component.html b/src/app/transactions/transactions.component.html index 9fcd798..04de86a 100644 --- a/src/app/transactions/transactions.component.html +++ b/src/app/transactions/transactions.component.html @@ -1,5 +1,5 @@ - +

{{transaction.title}}

@@ -9,6 +9,6 @@

{{ transaction.date | date }}

- + add \ No newline at end of file diff --git a/src/app/transactions/transactions.component.ts b/src/app/transactions/transactions.component.ts index 267b4a2..c56a985 100644 --- a/src/app/transactions/transactions.component.ts +++ b/src/app/transactions/transactions.component.ts @@ -3,6 +3,8 @@ import { Transaction } from './transaction'; import { TransactionType } from './transaction.type'; import { TransactionService, TRANSACTION_SERVICE } from './transaction.service'; import { AppComponent } from '../app.component'; +import { Account } from '../accounts/account'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-transactions', @@ -11,24 +13,26 @@ import { AppComponent } from '../app.component'; }) export class TransactionsComponent implements OnInit { - @Input() group: string; + accountId: string; public transactionType = TransactionType; public transactions: Transaction[]; constructor( + private route: ActivatedRoute, private app: AppComponent, @Inject(TRANSACTION_SERVICE) private transactionService: TransactionService, ) { } ngOnInit() { + this.accountId = this.route.snapshot.paramMap.get('accountId'); this.app.backEnabled = true; this.app.title = 'Transactions'; this.getTransactions(); } getTransactions(): void { - this.transactionService.getTransactions(this.app.group).subscribe(transactions => { + this.transactionService.getTransactions(this.accountId).subscribe(transactions => { this.transactions = transactions; }); }