Finish implementing new accounts structure
Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
parent
7c2e58cb11
commit
8d843473e2
39 changed files with 370 additions and 343 deletions
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,3 +1,26 @@
|
|||
<p>
|
||||
account-details works!
|
||||
</p>
|
||||
<div class="dashboard">
|
||||
<div class="dashboard-primary" [hidden]="!account">
|
||||
<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>
|
|
@ -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<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() {
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,21 @@ export class FirestoreAccountService implements AccountService {
|
|||
getAccounts(): Observable<Account[]> {
|
||||
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<Account> {
|
||||
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) {
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface AccountService {
|
|||
name: string,
|
||||
description: string,
|
||||
currency: string,
|
||||
members: User[],
|
||||
members: string[],
|
||||
): Observable<Account>;
|
||||
updateAccount(id: string, changes: object): Observable<Account>;
|
||||
deleteAccount(id: string): Observable<boolean>;
|
||||
|
|
|
@ -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 }}">
|
||||
<p matLine class="account-list-title">
|
||||
{{ account.name }}
|
||||
|
@ -14,3 +18,6 @@
|
|||
<p>Add accounts to begin tracking your budget.</p>
|
||||
</a>
|
||||
</div>
|
||||
<a mat-fab routerLink="/accounts/new">
|
||||
<mat-icon aria-label="Add">add</mat-icon>
|
||||
</a>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
<mat-sidenav #sidenav mode="over" closed>
|
||||
<mat-nav-list (click)="sidenav.close()">
|
||||
<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()" (click)="exportData()">Export Data</a>
|
||||
<a mat-list-item *ngIf="isLoggedIn()" routerLink="/accounts">Accounts</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()" (click)="logout()">Logout</a>
|
||||
|
|
|
@ -14,7 +14,6 @@ export class AppComponent {
|
|||
public title = 'Budget';
|
||||
public backEnabled = false;
|
||||
public actionable: Actionable;
|
||||
public group = 'MG3KOiuPu0Xy38O2LdhJ';
|
||||
|
||||
constructor(
|
||||
public authService: AuthService,
|
||||
|
|
|
@ -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]
|
||||
})
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
||||
<a mat-fab routerLink="/categories/new">
|
||||
<app-category-list [accountId]="accountId" [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
||||
<a mat-fab routerLink="/accounts/{{ accountId }}/categories/new">
|
||||
<mat-icon aria-label="Add">add</mat-icon>
|
||||
</a>
|
||||
|
|
|
@ -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<string, number>;
|
||||
|
||||
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<number> {
|
||||
getCategoryBalance(category: Category): Observable<number> {
|
||||
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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
}
|
||||
</style>
|
||||
<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">
|
||||
<span>
|
||||
{{ category.name }}
|
||||
|
|
|
@ -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<string, number>;
|
||||
|
||||
|
|
|
@ -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<Category[]> {
|
||||
getCategories(accountId: string, count?: number): Observable<Category[]> {
|
||||
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<Category> {
|
||||
getCategory(accountId: string, id: string): Observable<Category> {
|
||||
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<Category> {
|
||||
createCategory(accountId: string, name: string, amount: number): Observable<Category> {
|
||||
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<boolean> {
|
||||
updateCategory(accountId: string, id: string, changes: object): Observable<boolean> {
|
||||
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<boolean> {
|
||||
deleteCategory(accountId: string, id: string): Observable<boolean> {
|
||||
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);
|
||||
|
|
|
@ -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<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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 =
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<div [hidden]="currentTransaction">
|
||||
<p>Select a transaction from the list to view details about it or edit it.</p>
|
||||
</div>
|
||||
<div [hidden]="!currentTransaction" class="form transaction-form">
|
||||
<div [hidden]="!currentTransaction" *ngIf="currentTransaction" class="form transaction-form">
|
||||
<mat-form-field>
|
||||
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
|
||||
</mat-form-field>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Transaction[]> {
|
||||
const transactionsQuery = firebase.firestore().collection('transactions').where('category', '==', category);
|
||||
getTransactions(accountId: string, category?: string, count?: number): Observable<Transaction[]> {
|
||||
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<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}`);
|
||||
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<Transaction> {
|
||||
getTransaction(accountId: string, id: string): Observable<Transaction> {
|
||||
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<Transaction> {
|
||||
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<boolean> {
|
||||
updateTransaction(accountId: string, id: string, changes: object): Observable<boolean> {
|
||||
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<boolean> {
|
||||
deleteTransaction(accountId: string, id: string): Observable<boolean> {
|
||||
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);
|
||||
|
|
|
@ -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<Transaction[]>;
|
||||
getTransactions(accountId: string, categoryId?: string, count?: number): Observable<Transaction[]>;
|
||||
|
||||
getTransactionsForCategory(category: string, count?: number): Observable<Transaction[]>;
|
||||
|
||||
getTransaction(id: string): Observable<Transaction>;
|
||||
getTransaction(accountId: string, id: string): Observable<Transaction>;
|
||||
|
||||
createTransaction(
|
||||
accountId: string,
|
||||
name: string,
|
||||
description: string,
|
||||
amount: number,
|
||||
|
@ -19,9 +19,9 @@ export interface TransactionService {
|
|||
category: string
|
||||
): 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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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">
|
||||
<p>{{transaction.title}}</p>
|
||||
<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>
|
||||
</a>
|
||||
</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>
|
||||
</a>
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue