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>
|
<div class="dashboard">
|
||||||
account-details works!
|
<div class="dashboard-primary" [hidden]="!account">
|
||||||
</p>
|
<h2 class="balance">
|
||||||
|
Current Balance: <br />
|
||||||
|
<span
|
||||||
|
[ngClass]="{'income': getBalance() > 0, 'expense': getBalance() < 0}">{{ getBalance() / 100 | currency }}</span>
|
||||||
|
</h2>
|
||||||
|
<div class="transaction-navigation">
|
||||||
|
<a mat-button routerLink="/accounts/{{ account.id }}/transactions">View Transactions</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-categories" [hidden]="!account">
|
||||||
|
<h3 class="categories">Categories</h3>
|
||||||
|
<a mat-button routerLink="/accounts/{{ account.id }}/categories" class="view-all" *ngIf="categories">View All</a>
|
||||||
|
<div class="no-categories" *ngIf="!categories || categories.length === 0">
|
||||||
|
<a mat-button routerLink="/accounts/{{ account.id }}/categories/new">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
<p>Add categories to get more insights into your income and expenses.</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<app-category-list [accountId]="account.id" [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a mat-fab routerLink="/accounts/{{ account.id }}/transactions/new" [hidden]="!account">
|
||||||
|
<mat-icon aria-label="Add">add</mat-icon>
|
||||||
|
</a>
|
|
@ -1,4 +1,14 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, Inject } from '@angular/core';
|
||||||
|
import { Account } from '../account';
|
||||||
|
import { ACCOUNT_SERVICE, AccountService } from '../account.service';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { AppComponent } from 'src/app/app.component';
|
||||||
|
import { Transaction } from 'src/app/transactions/transaction';
|
||||||
|
import { Category } from 'src/app/categories/category';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { TransactionType } from 'src/app/transactions/transaction.type';
|
||||||
|
import { TRANSACTION_SERVICE, TransactionService } from 'src/app/transactions/transaction.service';
|
||||||
|
import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account-details',
|
selector: 'app-account-details',
|
||||||
|
@ -7,9 +17,76 @@ import { Component, OnInit } from '@angular/core';
|
||||||
})
|
})
|
||||||
export class AccountDetailsComponent implements OnInit {
|
export class AccountDetailsComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
account: Account;
|
||||||
|
public transactions: Transaction[];
|
||||||
|
public categories: Category[];
|
||||||
|
categoryBalances: Map<string, number>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
@Inject(ACCOUNT_SERVICE) private accountService: AccountService,
|
||||||
|
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
|
||||||
|
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.getAccount();
|
||||||
|
this.app.backEnabled = false;
|
||||||
|
this.categoryBalances = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccount() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.accountService.getAccount(id)
|
||||||
|
.subscribe(account => {
|
||||||
|
this.app.title = account.name;
|
||||||
|
this.account = account;
|
||||||
|
this.getBalance();
|
||||||
|
this.getTransactions();
|
||||||
|
this.getCategories();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getBalance(): number {
|
||||||
|
let totalBalance = 0;
|
||||||
|
if (!this.categoryBalances) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
this.categoryBalances.forEach(balance => {
|
||||||
|
totalBalance += balance;
|
||||||
|
});
|
||||||
|
return totalBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransactions(): void {
|
||||||
|
this.transactionService.getTransactions(this.account.id, null, 5)
|
||||||
|
.subscribe(transactions => this.transactions = <Transaction[]>transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategories(): void {
|
||||||
|
this.categoryService.getCategories(this.account.id, 5).subscribe(categories => {
|
||||||
|
this.categories = categories;
|
||||||
|
for (const category of categories) {
|
||||||
|
this.getCategoryBalance(category.id).subscribe(balance => this.categoryBalances.set(category.id, balance));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategoryBalance(category: string): Observable<number> {
|
||||||
|
return Observable.create(subscriber => {
|
||||||
|
this.transactionService.getTransactions(this.account.id, category).subscribe(transactions => {
|
||||||
|
let balance = 0;
|
||||||
|
for (const transaction of transactions) {
|
||||||
|
if (transaction.type === TransactionType.INCOME) {
|
||||||
|
balance += transaction.amount;
|
||||||
|
} else {
|
||||||
|
balance -= transaction.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriber.next(balance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,21 @@ export class FirestoreAccountService implements AccountService {
|
||||||
getAccounts(): Observable<Account[]> {
|
getAccounts(): Observable<Account[]> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
const accounts = [];
|
const accounts = [];
|
||||||
firebase.firestore().collection('accounts').onSnapshot(data => {
|
firebase.auth().onAuthStateChanged(user => {
|
||||||
if (!data.empty) {
|
if (user == null) { return; }
|
||||||
data.docs.map(account => accounts.push(Account.fromSnapshotRef(account)));
|
firebase.firestore().collection('accounts')
|
||||||
}
|
.orderBy('name')
|
||||||
subscriber.next(accounts);
|
.where('members', 'array-contains', user.uid)
|
||||||
|
.onSnapshot(
|
||||||
|
data => {
|
||||||
|
if (!data.empty) {
|
||||||
|
data.docs.map(account => accounts.push(Account.fromSnapshotRef(account)));
|
||||||
|
}
|
||||||
|
subscriber.next(accounts);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -33,13 +43,14 @@ export class FirestoreAccountService implements AccountService {
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
currency: string,
|
currency: string,
|
||||||
members: User[],
|
members: string[],
|
||||||
): Observable<Account> {
|
): Observable<Account> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('accounts').add({
|
firebase.firestore().collection('accounts').add({
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
members: members.map(member => member.id)
|
currency: currency,
|
||||||
|
members: members
|
||||||
}).then(docRef => {
|
}).then(docRef => {
|
||||||
docRef.get().then(snapshot => {
|
docRef.get().then(snapshot => {
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface AccountService {
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
currency: string,
|
currency: string,
|
||||||
members: User[],
|
members: string[],
|
||||||
): Observable<Account>;
|
): Observable<Account>;
|
||||||
updateAccount(id: string, changes: object): Observable<Account>;
|
updateAccount(id: string, changes: object): Observable<Account>;
|
||||||
deleteAccount(id: string): Observable<boolean>;
|
deleteAccount(id: string): Observable<boolean>;
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<mat-nav-list class="accounts">
|
<div class="dashboard" *ngIf="!isLoggedIn()">
|
||||||
|
<h2 class="log-in">Get started</h2>
|
||||||
|
<p>To begin tracking your finances, <a routerLink="/login">login</a> or <a routerLink="/register">create an account</a>!</p>
|
||||||
|
</div>
|
||||||
|
<mat-nav-list class="accounts" *ngIf="isLoggedIn()">
|
||||||
<a mat-list-item *ngFor="let account of accounts" routerLink="/accounts/{{ account.id }}">
|
<a mat-list-item *ngFor="let account of accounts" routerLink="/accounts/{{ account.id }}">
|
||||||
<p matLine class="account-list-title">
|
<p matLine class="account-list-title">
|
||||||
{{ account.name }}
|
{{ account.name }}
|
||||||
|
@ -14,3 +18,6 @@
|
||||||
<p>Add accounts to begin tracking your budget.</p>
|
<p>Add accounts to begin tracking your budget.</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<a mat-fab routerLink="/accounts/new">
|
||||||
|
<mat-icon aria-label="Add">add</mat-icon>
|
||||||
|
</a>
|
|
@ -20,9 +20,13 @@ export class AccountsComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.app.backEnabled = true;
|
this.app.backEnabled = true;
|
||||||
this.app.title = 'Transactions';
|
this.app.title = 'Accounts';
|
||||||
this.accountService.getAccounts().subscribe(accounts => {
|
this.accountService.getAccounts().subscribe(accounts => {
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return this.app.isLoggedIn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { AppComponent } from 'src/app/app.component';
|
||||||
import { Actionable } from 'src/app/actionable';
|
import { Actionable } from 'src/app/actionable';
|
||||||
import { UserService, USER_SERVICE } from 'src/app/users/user.service';
|
import { UserService, USER_SERVICE } from 'src/app/users/user.service';
|
||||||
import { User } from 'src/app/users/user';
|
import { User } from 'src/app/users/user';
|
||||||
|
import * as firebase from 'firebase';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-edit-account',
|
selector: 'app-add-edit-account',
|
||||||
|
@ -14,8 +15,8 @@ import { User } from 'src/app/users/user';
|
||||||
export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable {
|
export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable {
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() account: Account;
|
@Input() account: Account;
|
||||||
public users: User[];
|
public userIds: string[] = [firebase.auth().currentUser.uid];
|
||||||
public searchedUsers: User[];
|
public searchedUsers: User[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private app: AppComponent,
|
private app: AppComponent,
|
||||||
|
@ -45,7 +46,7 @@ export class AddEditAccountComponent implements OnInit, OnDestroy, Actionable {
|
||||||
this.account.name,
|
this.account.name,
|
||||||
this.account.description,
|
this.account.description,
|
||||||
this.account.currency,
|
this.account.currency,
|
||||||
this.users
|
this.userIds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: Check if it was actually successful or not
|
// TODO: Check if it was actually successful or not
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
|
||||||
import { TransactionsComponent } from './transactions/transactions.component';
|
import { TransactionsComponent } from './transactions/transactions.component';
|
||||||
import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component';
|
import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component';
|
||||||
import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component';
|
import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component';
|
||||||
|
@ -14,18 +13,18 @@ import { NewAccountComponent } from './accounts/new-account/new-account.componen
|
||||||
import { AccountDetailsComponent } from './accounts/account-details/account-details.component';
|
import { AccountDetailsComponent } from './accounts/account-details/account-details.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: DashboardComponent },
|
{ path: '', component: AccountsComponent },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
{ path: 'register', component: RegisterComponent },
|
{ path: 'register', component: RegisterComponent },
|
||||||
{ path: 'accounts', component: AccountsComponent },
|
{ path: 'accounts', component: AccountsComponent },
|
||||||
{ path: 'accounts/new', component: NewAccountComponent },
|
{ path: 'accounts/new', component: NewAccountComponent },
|
||||||
{ path: 'accounts/:id', component: AccountDetailsComponent },
|
{ path: 'accounts/:id', component: AccountDetailsComponent },
|
||||||
{ path: 'transactions', component: TransactionsComponent },
|
{ path: 'accounts/:accountId/transactions', component: TransactionsComponent },
|
||||||
{ path: 'transactions/new', component: NewTransactionComponent },
|
{ path: 'accounts/:accountId/transactions/new', component: NewTransactionComponent },
|
||||||
{ path: 'transactions/:id', component: TransactionDetailsComponent },
|
{ path: 'accounts/:accountId/transactions/:id', component: TransactionDetailsComponent },
|
||||||
{ path: 'categories', component: CategoriesComponent },
|
{ path: 'accounts/:accountId/categories', component: CategoriesComponent },
|
||||||
{ path: 'categories/new', component: NewCategoryComponent },
|
{ path: 'accounts/:accountId/categories/new', component: NewCategoryComponent },
|
||||||
{ path: 'categories/:id', component: CategoryDetailsComponent },
|
{ path: 'accounts/:accountId/categories/:id', component: CategoryDetailsComponent },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
<mat-sidenav #sidenav mode="over" closed>
|
<mat-sidenav #sidenav mode="over" closed>
|
||||||
<mat-nav-list (click)="sidenav.close()">
|
<mat-nav-list (click)="sidenav.close()">
|
||||||
<a mat-list-item *ngIf="isLoggedIn()" routerLink="">{{ getUsername() }}</a>
|
<a mat-list-item *ngIf="isLoggedIn()" routerLink="">{{ getUsername() }}</a>
|
||||||
<a mat-list-item *ngIf="isLoggedIn()" routerLink="/accounts">Manage Accounts</a>
|
<a mat-list-item *ngIf="isLoggedIn()" routerLink="/accounts">Accounts</a>
|
||||||
<a mat-list-item *ngIf="isLoggedIn()" (click)="exportData()">Export Data</a>
|
|
||||||
<a mat-list-item *ngIf="!isLoggedIn()" routerLink="/login">Login</a>
|
<a mat-list-item *ngIf="!isLoggedIn()" routerLink="/login">Login</a>
|
||||||
<a mat-list-item *ngIf="!isLoggedIn()" routerLink="/register">Register</a>
|
<a mat-list-item *ngIf="!isLoggedIn()" routerLink="/register">Register</a>
|
||||||
<a mat-list-item *ngIf="isLoggedIn()" (click)="logout()">Logout</a>
|
<a mat-list-item *ngIf="isLoggedIn()" (click)="logout()">Logout</a>
|
||||||
|
|
|
@ -14,7 +14,6 @@ export class AppComponent {
|
||||||
public title = 'Budget';
|
public title = 'Budget';
|
||||||
public backEnabled = false;
|
public backEnabled = false;
|
||||||
public actionable: Actionable;
|
public actionable: Actionable;
|
||||||
public group = 'MG3KOiuPu0Xy38O2LdhJ';
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public authService: AuthService,
|
public authService: AuthService,
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { AccountsComponent } from './accounts/accounts.component';
|
||||||
import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component';
|
import { TransactionDetailsComponent } from './transactions/transaction-details/transaction-details.component';
|
||||||
import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component';
|
import { NewTransactionComponent } from './transactions/new-transaction/new-transaction.component';
|
||||||
import { AddEditTransactionComponent } from './transactions/add-edit-transaction/add-edit-transaction.component';
|
import { AddEditTransactionComponent } from './transactions/add-edit-transaction/add-edit-transaction.component';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
|
||||||
import { CategoriesComponent } from './categories/categories.component';
|
import { CategoriesComponent } from './categories/categories.component';
|
||||||
import { CategoryDetailsComponent } from './categories/category-details/category-details.component';
|
import { CategoryDetailsComponent } from './categories/category-details/category-details.component';
|
||||||
import { AddEditCategoryComponent } from './categories/add-edit-category/add-edit-category.component';
|
import { AddEditCategoryComponent } from './categories/add-edit-category/add-edit-category.component';
|
||||||
|
@ -47,6 +46,8 @@ import { CurrencyMaskConfig, CURRENCY_MASK_CONFIG } from 'ng2-currency-mask/src/
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||||
import { ACCOUNT_SERVICE } from './accounts/account.service';
|
import { ACCOUNT_SERVICE } from './accounts/account.service';
|
||||||
import { FirestoreAccountService } from './accounts/account.service.firestore';
|
import { FirestoreAccountService } from './accounts/account.service.firestore';
|
||||||
|
import { USER_SERVICE } from './users/user.service';
|
||||||
|
import { FirestoreUserService } from './users/user.service.firestore';
|
||||||
|
|
||||||
export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
|
export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
@ -65,7 +66,6 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
|
||||||
TransactionDetailsComponent,
|
TransactionDetailsComponent,
|
||||||
NewTransactionComponent,
|
NewTransactionComponent,
|
||||||
AddEditTransactionComponent,
|
AddEditTransactionComponent,
|
||||||
DashboardComponent,
|
|
||||||
CategoriesComponent,
|
CategoriesComponent,
|
||||||
CategoryDetailsComponent,
|
CategoryDetailsComponent,
|
||||||
AddEditCategoryComponent,
|
AddEditCategoryComponent,
|
||||||
|
@ -105,6 +105,7 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
|
||||||
{ provide: TRANSACTION_SERVICE, useClass: TransactionServiceFirebaseFirestoreImpl },
|
{ provide: TRANSACTION_SERVICE, useClass: TransactionServiceFirebaseFirestoreImpl },
|
||||||
{ provide: CATEGORY_SERVICE, useClass: CategoryServiceFirebaseFirestoreImpl },
|
{ provide: CATEGORY_SERVICE, useClass: CategoryServiceFirebaseFirestoreImpl },
|
||||||
{ provide: ACCOUNT_SERVICE, useClass: FirestoreAccountService },
|
{ provide: ACCOUNT_SERVICE, useClass: FirestoreAccountService },
|
||||||
|
{ provide: USER_SERVICE, useClass: FirestoreUserService },
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Category } from '../category';
|
||||||
import { CATEGORY_SERVICE, CategoryService } from '../category.service';
|
import { CATEGORY_SERVICE, CategoryService } from '../category.service';
|
||||||
import { Actionable } from 'src/app/actionable';
|
import { Actionable } from 'src/app/actionable';
|
||||||
import { AppComponent } from 'src/app/app.component';
|
import { AppComponent } from 'src/app/app.component';
|
||||||
|
import { Account } from 'src/app/accounts/account';
|
||||||
|
import { ACCOUNT_SERVICE, AccountService } from 'src/app/accounts/account.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-edit-category',
|
selector: 'app-add-edit-category',
|
||||||
|
@ -11,12 +13,13 @@ import { AppComponent } from 'src/app/app.component';
|
||||||
})
|
})
|
||||||
export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
|
export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
|
||||||
|
|
||||||
|
@Input() accountId: string;
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() currentCategory: Category;
|
@Input() currentCategory: Category;
|
||||||
@Input() group: string;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private app: AppComponent,
|
private app: AppComponent,
|
||||||
|
@Inject(ACCOUNT_SERVICE) private accountService: AccountService,
|
||||||
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
|
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
@ -31,14 +34,24 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
doAction(): void {
|
doAction(): void {
|
||||||
this.currentCategory.amount *= 100;
|
|
||||||
let observable;
|
let observable;
|
||||||
if (this.currentCategory.id) {
|
if (this.currentCategory.id) {
|
||||||
// This is an existing category, update it
|
// This is an existing category, update it
|
||||||
observable = this.categoryService.updateCategory(this.currentCategory.id, this.currentCategory);
|
observable = this.categoryService.updateCategory(
|
||||||
|
this.accountId,
|
||||||
|
this.currentCategory.id,
|
||||||
|
{
|
||||||
|
name: this.currentCategory.name,
|
||||||
|
amount: this.currentCategory.amount * 100
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// This is a new category, save it
|
// This is a new category, save it
|
||||||
observable = this.categoryService.createCategory(this.currentCategory.name, this.currentCategory.amount, this.app.group);
|
observable = this.categoryService.createCategory(
|
||||||
|
this.accountId,
|
||||||
|
this.currentCategory.name,
|
||||||
|
this.currentCategory.amount
|
||||||
|
);
|
||||||
}
|
}
|
||||||
observable.subscribe(val => {
|
observable.subscribe(val => {
|
||||||
this.app.goBack();
|
this.app.goBack();
|
||||||
|
@ -50,7 +63,7 @@ export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
this.categoryService.deleteCategory(this.currentCategory.id);
|
this.categoryService.deleteCategory(this.accountId, this.currentCategory.id);
|
||||||
this.app.goBack();
|
this.app.goBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
<app-category-list [accountId]="accountId" [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
||||||
<a mat-fab routerLink="/categories/new">
|
<a mat-fab routerLink="/accounts/{{ accountId }}/categories/new">
|
||||||
<mat-icon aria-label="Add">add</mat-icon>
|
<mat-icon aria-label="Add">add</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { AppComponent } from '../app.component';
|
||||||
import { TransactionService, TRANSACTION_SERVICE } from '../transactions/transaction.service';
|
import { TransactionService, TRANSACTION_SERVICE } from '../transactions/transaction.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { TransactionType } from '../transactions/transaction.type';
|
import { TransactionType } from '../transactions/transaction.type';
|
||||||
|
import { Account } from '../accounts/account';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-categories',
|
selector: 'app-categories',
|
||||||
|
@ -13,17 +15,19 @@ import { TransactionType } from '../transactions/transaction.type';
|
||||||
})
|
})
|
||||||
export class CategoriesComponent implements OnInit {
|
export class CategoriesComponent implements OnInit {
|
||||||
|
|
||||||
@Input() group: string;
|
accountId: string;
|
||||||
public categories: Category[];
|
public categories: Category[];
|
||||||
public categoryBalances: Map<string, number>;
|
public categoryBalances: Map<string, number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
private app: AppComponent,
|
private app: AppComponent,
|
||||||
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
|
@Inject(CATEGORY_SERVICE) private categoryService: CategoryService,
|
||||||
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
|
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
this.app.title = 'Categories';
|
this.app.title = 'Categories';
|
||||||
this.app.backEnabled = true;
|
this.app.backEnabled = true;
|
||||||
this.getCategories();
|
this.getCategories();
|
||||||
|
@ -31,17 +35,17 @@ export class CategoriesComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategories(): void {
|
getCategories(): void {
|
||||||
this.categoryService.getCategories(this.app.group).subscribe(categories => {
|
this.categoryService.getCategories(this.accountId).subscribe(categories => {
|
||||||
this.categories = categories;
|
this.categories = categories;
|
||||||
for (const category of this.categories) {
|
for (const category of this.categories) {
|
||||||
this.getCategoryBalance(category.id).subscribe(balance => this.categoryBalances.set(category.id, balance));
|
this.getCategoryBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategoryBalance(category: string): Observable<number> {
|
getCategoryBalance(category: Category): Observable<number> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
this.transactionService.getTransactionsForCategory(category).subscribe(transactions => {
|
this.transactionService.getTransactions(this.accountId, category.id).subscribe(transactions => {
|
||||||
let balance = 0;
|
let balance = 0;
|
||||||
for (const transaction of transactions) {
|
for (const transaction of transactions) {
|
||||||
if (transaction.type === TransactionType.INCOME) {
|
if (transaction.type === TransactionType.INCOME) {
|
||||||
|
|
|
@ -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 { CategoryServiceFirebaseFirestoreImpl } from '../category.service.firestore';
|
||||||
import { Category } from '../category';
|
import { Category } from '../category';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Account } from 'src/app/accounts/account';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-category-details',
|
selector: 'app-category-details',
|
||||||
|
@ -10,6 +11,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||||
})
|
})
|
||||||
export class CategoryDetailsComponent implements OnInit {
|
export class CategoryDetailsComponent implements OnInit {
|
||||||
|
|
||||||
|
accountId: string;
|
||||||
category: Category;
|
category: Category;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -22,8 +24,9 @@ export class CategoryDetailsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategory(): void {
|
getCategory(): void {
|
||||||
|
this.accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
const id = this.route.snapshot.paramMap.get('id');
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
this.categoryService.getCategory(id)
|
this.categoryService.getCategory(this.accountId, id)
|
||||||
.subscribe(category => {
|
.subscribe(category => {
|
||||||
category.amount /= 100;
|
category.amount /= 100;
|
||||||
this.category = category;
|
this.category = category;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<mat-nav-list class="categories">
|
<mat-nav-list class="categories">
|
||||||
<a mat-list-item *ngFor="let category of categories" routerLink="/categories/{{ category.id }}">
|
<a mat-list-item *ngFor="let category of categories" routerLink="/accounts/{{ accountId }}/categories/{{ category.id }}">
|
||||||
<p matLine class="category-list-title">
|
<p matLine class="category-list-title">
|
||||||
<span>
|
<span>
|
||||||
{{ category.name }}
|
{{ category.name }}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
import { Category } from '../category';
|
import { Category } from '../category';
|
||||||
|
import { Account } from 'src/app/accounts/account';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-category-list',
|
selector: 'app-category-list',
|
||||||
|
@ -8,6 +9,7 @@ import { Category } from '../category';
|
||||||
})
|
})
|
||||||
export class CategoryListComponent implements OnInit {
|
export class CategoryListComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() accountId: string;
|
||||||
@Input() categories: Category[];
|
@Input() categories: Category[];
|
||||||
@Input() categoryBalances: Map<string, number>;
|
@Input() categoryBalances: Map<string, number>;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { of, Observable, from } from 'rxjs';
|
||||||
import { Category } from './category';
|
import { Category } from './category';
|
||||||
import * as firebase from 'firebase/app';
|
import * as firebase from 'firebase/app';
|
||||||
import 'firebase/firestore';
|
import 'firebase/firestore';
|
||||||
|
import { Account } from '../accounts/account';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -11,9 +12,9 @@ export class CategoryServiceFirebaseFirestoreImpl {
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
getCategories(group: string, count?: number): Observable<Category[]> {
|
getCategories(accountId: string, count?: number): Observable<Category[]> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
let query = firebase.firestore().collection('categories').where('group', '==', group);
|
let query: any = firebase.firestore().collection('accounts').doc(accountId).collection('categories');
|
||||||
if (count) {
|
if (count) {
|
||||||
query = query.limit(count);
|
query = query.limit(count);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +27,7 @@ export class CategoryServiceFirebaseFirestoreImpl {
|
||||||
|
|
||||||
const categories = [];
|
const categories = [];
|
||||||
for (const categoryDoc of snapshot.docs) {
|
for (const categoryDoc of snapshot.docs) {
|
||||||
categories.push(Category.fromSnapshotRef(categoryDoc));
|
categories.push(Category.fromSnapshotRef(accountId, categoryDoc));
|
||||||
}
|
}
|
||||||
subscriber.next(categories);
|
subscriber.next(categories);
|
||||||
}, error => {
|
}, error => {
|
||||||
|
@ -36,24 +37,23 @@ export class CategoryServiceFirebaseFirestoreImpl {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategory(id: string): Observable<Category> {
|
getCategory(accountId: string, id: string): Observable<Category> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('categories').doc(id).onSnapshot(snapshot => {
|
firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id).onSnapshot(snapshot => {
|
||||||
if (!snapshot.exists) {
|
if (!snapshot.exists) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriber.next(Category.fromSnapshotRef(snapshot));
|
subscriber.next(Category.fromSnapshotRef(accountId, snapshot));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createCategory(name: string, amount: number, group: string): Observable<Category> {
|
createCategory(accountId: string, name: string, amount: number): Observable<Category> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('categories').add({
|
firebase.firestore().collection('accounts').doc(accountId).collection('categories').add({
|
||||||
name: name,
|
name: name,
|
||||||
amount: amount,
|
amount: amount
|
||||||
group: group,
|
|
||||||
}).then(docRef => {
|
}).then(docRef => {
|
||||||
if (!docRef) {
|
if (!docRef) {
|
||||||
console.error('Failed to create category');
|
console.error('Failed to create category');
|
||||||
|
@ -64,32 +64,34 @@ export class CategoryServiceFirebaseFirestoreImpl {
|
||||||
subscriber.error('Unable to retrieve saved transaction data');
|
subscriber.error('Unable to retrieve saved transaction data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
subscriber.next(Category.fromSnapshotRef(snapshot));
|
subscriber.next(Category.fromSnapshotRef(accountId, snapshot));
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
console.error('Failed to create new category: ');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
subscriber.error(err);
|
subscriber.error(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCategory(id: string, changes: object): Observable<boolean> {
|
updateCategory(accountId: string, id: string, changes: object): Observable<boolean> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('categories').doc(id).onSnapshot(snapshot => {
|
firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id)
|
||||||
if (!snapshot.exists) {
|
.update(changes)
|
||||||
return;
|
.then(function () {
|
||||||
}
|
subscriber.next(true);
|
||||||
|
})
|
||||||
subscriber.next(Category.fromSnapshotRef(snapshot));
|
.catch(function () {
|
||||||
});
|
subscriber.next(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCategory(id: string): Observable<boolean> {
|
deleteCategory(accountId: string, id: string): Observable<boolean> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('categories').doc(id).delete().then(result => {
|
firebase.firestore().collection('accounts').doc(accountId).collection('categories').doc(id).delete().then(result => {
|
||||||
subscriber.next(true);
|
subscriber.next(true);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Category } from './category';
|
import { Category } from './category';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { Account } from '../accounts/account';
|
||||||
|
|
||||||
export interface CategoryService {
|
export interface CategoryService {
|
||||||
|
|
||||||
getCategories(group: string, count?: number): Observable<Category[]>;
|
getCategories(accountId: string, count?: number): Observable<Category[]>;
|
||||||
|
|
||||||
getCategory(id: string): Observable<Category>;
|
getCategory(accountId: string, id: string): Observable<Category>;
|
||||||
|
|
||||||
createCategory(name: string, amount: number, group: string): Observable<Category>;
|
createCategory(accountId: string, name: string, amount: number): Observable<Category>;
|
||||||
|
|
||||||
updateCategory(id: string, changes: object): Observable<boolean>;
|
updateCategory(accountId: string, id: string, changes: object): Observable<boolean>;
|
||||||
|
|
||||||
deleteCategory(id: string): Observable<boolean>;
|
deleteCategory(accountId: string, id: string): Observable<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let CATEGORY_SERVICE = new InjectionToken<CategoryService>('category.service');
|
export let CATEGORY_SERVICE = new InjectionToken<CategoryService>('category.service');
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
export class Category {
|
export class Category {
|
||||||
id: string;
|
id: string;
|
||||||
accountId: string;
|
|
||||||
remoteId: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
repeat: string;
|
repeat: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
accountId: string;
|
||||||
|
|
||||||
static fromSnapshotRef(snapshot: firebase.firestore.DocumentSnapshot): Category {
|
static fromSnapshotRef(accountId: string, snapshot: firebase.firestore.DocumentSnapshot): Category {
|
||||||
const category = new Category();
|
const category = new Category();
|
||||||
category.id = snapshot.id;
|
category.id = snapshot.id;
|
||||||
category.name = snapshot.get('name');
|
category.name = snapshot.get('name');
|
||||||
category.amount = snapshot.get('amount');
|
category.amount = snapshot.get('amount');
|
||||||
category.accountId = snapshot.get('group');
|
category.accountId = accountId;
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { Component, OnInit } from '@angular/core';
|
||||||
import { Category } from '../category'
|
import { Category } from '../category';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-new-category',
|
selector: 'app-new-category',
|
||||||
|
@ -8,14 +9,18 @@ import { Category } from '../category'
|
||||||
})
|
})
|
||||||
export class NewCategoryComponent implements OnInit {
|
export class NewCategoryComponent implements OnInit {
|
||||||
|
|
||||||
|
accountId: string;
|
||||||
category: Category;
|
category: Category;
|
||||||
|
|
||||||
constructor() { }
|
constructor(
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
this.category = new Category();
|
this.category = new Category();
|
||||||
// TODO: Set random color for category, improve color picker
|
// TODO: Set random color for category, improve color picker
|
||||||
//this.category.color =
|
// this.category.color =
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
<div [hidden]="currentTransaction">
|
||||||
<p>Select a transaction from the list to view details about it or edit it.</p>
|
<p>Select a transaction from the list to view details about it or edit it.</p>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!currentTransaction" class="form transaction-form">
|
<div [hidden]="!currentTransaction" *ngIf="currentTransaction" class="form transaction-form">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
|
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Category } from 'src/app/categories/category';
|
||||||
import { Actionable } from 'src/app/actionable';
|
import { Actionable } from 'src/app/actionable';
|
||||||
import { AppComponent } from 'src/app/app.component';
|
import { AppComponent } from 'src/app/app.component';
|
||||||
import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service';
|
import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.service';
|
||||||
|
import { Account } from 'src/app/accounts/account';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-edit-transaction',
|
selector: 'app-add-edit-transaction',
|
||||||
|
@ -15,9 +16,8 @@ import { CATEGORY_SERVICE, CategoryService } from 'src/app/categories/category.s
|
||||||
export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionable {
|
export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionable {
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() currentTransaction: Transaction;
|
@Input() currentTransaction: Transaction;
|
||||||
@Input() group: string;
|
@Input() accountId: string;
|
||||||
public transactionType = TransactionType;
|
public transactionType = TransactionType;
|
||||||
public selectedCategory: Category;
|
|
||||||
public categories: Category[];
|
public categories: Category[];
|
||||||
public rawAmount: string;
|
public rawAmount: string;
|
||||||
|
|
||||||
|
@ -41,17 +41,28 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl
|
||||||
doAction(): void {
|
doAction(): void {
|
||||||
// The amount will be input as a decimal value so we need to convert it
|
// The amount will be input as a decimal value so we need to convert it
|
||||||
// to an integer
|
// to an integer
|
||||||
this.currentTransaction.amount *= 100;
|
|
||||||
let observable;
|
let observable;
|
||||||
if (this.currentTransaction.id) {
|
if (this.currentTransaction.id) {
|
||||||
// This is an existing transaction, update it
|
// This is an existing transaction, update it
|
||||||
observable = this.transactionService.updateTransaction(this.currentTransaction.id, this.currentTransaction);
|
observable = this.transactionService.updateTransaction(
|
||||||
|
this.accountId,
|
||||||
|
this.currentTransaction.id,
|
||||||
|
{
|
||||||
|
name: this.currentTransaction.title,
|
||||||
|
description: this.currentTransaction.description,
|
||||||
|
amount: this.currentTransaction.amount * 100,
|
||||||
|
date: this.currentTransaction.date,
|
||||||
|
category: this.currentTransaction.categoryId,
|
||||||
|
isExpense: this.currentTransaction.type === TransactionType.EXPENSE
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// This is a new transaction, save it
|
// This is a new transaction, save it
|
||||||
observable = this.transactionService.createTransaction(
|
observable = this.transactionService.createTransaction(
|
||||||
|
this.accountId,
|
||||||
this.currentTransaction.title,
|
this.currentTransaction.title,
|
||||||
this.currentTransaction.description,
|
this.currentTransaction.description,
|
||||||
this.currentTransaction.amount,
|
this.currentTransaction.amount * 100,
|
||||||
this.currentTransaction.date,
|
this.currentTransaction.date,
|
||||||
this.currentTransaction.type === TransactionType.EXPENSE,
|
this.currentTransaction.type === TransactionType.EXPENSE,
|
||||||
this.currentTransaction.categoryId,
|
this.currentTransaction.categoryId,
|
||||||
|
@ -68,11 +79,12 @@ export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionabl
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
this.transactionService.deleteTransaction(this.currentTransaction.id);
|
this.transactionService.deleteTransaction(this.accountId, this.currentTransaction.id).subscribe(() => {
|
||||||
this.app.goBack();
|
this.app.goBack();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategories() {
|
getCategories() {
|
||||||
this.categoryService.getCategories(this.app.group).subscribe(categories => this.categories = categories);
|
this.categoryService.getCategories(this.accountId).subscribe(categories => this.categories = categories);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { Component, OnInit } from '@angular/core';
|
||||||
import { Transaction } from '../transaction';
|
import { Transaction } from '../transaction';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-new-transaction',
|
selector: 'app-new-transaction',
|
||||||
|
@ -8,12 +9,16 @@ import { Transaction } from '../transaction';
|
||||||
})
|
})
|
||||||
export class NewTransactionComponent implements OnInit {
|
export class NewTransactionComponent implements OnInit {
|
||||||
|
|
||||||
|
accountId: string;
|
||||||
transaction: Transaction;
|
transaction: Transaction;
|
||||||
|
|
||||||
constructor() { }
|
constructor(
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.transaction = new Transaction()
|
this.accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
|
this.transaction = new Transaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { TransactionService, TRANSACTION_SERVICE } from '../transaction.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Transaction } from '../transaction';
|
import { Transaction } from '../transaction';
|
||||||
|
import { Account } from 'src/app/accounts/account';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction-details',
|
selector: 'app-transaction-details',
|
||||||
|
@ -10,6 +11,7 @@ import { Transaction } from '../transaction';
|
||||||
})
|
})
|
||||||
export class TransactionDetailsComponent implements OnInit {
|
export class TransactionDetailsComponent implements OnInit {
|
||||||
|
|
||||||
|
accountId: string;
|
||||||
transaction: Transaction;
|
transaction: Transaction;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -22,8 +24,9 @@ export class TransactionDetailsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransaction(): void {
|
getTransaction(): void {
|
||||||
|
this.accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
const id = this.route.snapshot.paramMap.get('id');
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
this.transactionService.getTransaction(id)
|
this.transactionService.getTransaction(this.accountId, id)
|
||||||
.subscribe(transaction => {
|
.subscribe(transaction => {
|
||||||
transaction.amount /= 100;
|
transaction.amount /= 100;
|
||||||
this.transaction = transaction;
|
this.transaction = transaction;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Observable } from 'rxjs';
|
||||||
import { Observable, Subscriber } from 'rxjs';
|
|
||||||
import { Transaction } from './transaction';
|
import { Transaction } from './transaction';
|
||||||
import * as firebase from 'firebase/app';
|
import * as firebase from 'firebase/app';
|
||||||
import 'firebase/firestore';
|
import 'firebase/firestore';
|
||||||
|
@ -9,47 +8,33 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
getTransactionsForCategory(category: string): Observable<Transaction[]> {
|
getTransactions(accountId: string, category?: string, count?: number): Observable<Transaction[]> {
|
||||||
const transactionsQuery = firebase.firestore().collection('transactions').where('category', '==', category);
|
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
const transactions = [];
|
let transactionQuery: any = firebase.firestore().collection('accounts').doc(accountId).collection('transactions');
|
||||||
transactionsQuery.onSnapshot(data => {
|
if (category) {
|
||||||
if (!data.empty) {
|
transactionQuery = transactionQuery.where('category', '==', category);
|
||||||
data.docs.map(transaction => transactions.push(Transaction.fromSnapshotRef(transaction)));
|
}
|
||||||
}
|
if (count) {
|
||||||
subscriber.next(transactions);
|
transactionQuery = transactionQuery.limit(count);
|
||||||
});
|
}
|
||||||
});
|
transactionQuery.onSnapshot(snapshot => {
|
||||||
}
|
if (snapshot.empty) {
|
||||||
|
subscriber.error(`Unable to query transactions within account ${accountId}`);
|
||||||
getTransactions(group: string, count?: number): Observable<Transaction[]> {
|
|
||||||
const categoriesQuery = firebase.firestore().collection('categories').where('group', '==', group);
|
|
||||||
return Observable.create(subscriber => {
|
|
||||||
categoriesQuery.onSnapshot(querySnapshot => {
|
|
||||||
if (querySnapshot.empty) {
|
|
||||||
subscriber.error(`Unable to query categories within group ${group}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactions = [];
|
const transactions = [];
|
||||||
querySnapshot.docs.map(categoryDoc => {
|
snapshot.docs.forEach(transaction => {
|
||||||
firebase.firestore().collection('transactions').where('category', '==', categoryDoc.id).get().then(results => {
|
transactions.push(Transaction.fromSnapshotRef(transaction));
|
||||||
if (results.empty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const transactionDoc of results.docs) {
|
|
||||||
transactions.push(Transaction.fromSnapshotRef(transactionDoc));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
subscriber.next(transactions);
|
subscriber.next(transactions);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransaction(id: string): Observable<Transaction> {
|
getTransaction(accountId: string, id: string): Observable<Transaction> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('transactions').doc(id).onSnapshot(snapshot => {
|
firebase.firestore().collection('accounts').doc(accountId).collection('transactions').doc(id).onSnapshot(snapshot => {
|
||||||
if (!snapshot.exists) {
|
if (!snapshot.exists) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +44,7 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
|
||||||
}
|
}
|
||||||
|
|
||||||
createTransaction(
|
createTransaction(
|
||||||
|
accountId: string,
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
|
@ -67,7 +53,7 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
|
||||||
category: string
|
category: string
|
||||||
): Observable<Transaction> {
|
): Observable<Transaction> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('transactions').add({
|
firebase.firestore().collection('accounts').doc(accountId).collection('transactions').add({
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
date: date,
|
date: date,
|
||||||
|
@ -89,9 +75,9 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
updateTransaction(id: string, changes: object): Observable<boolean> {
|
updateTransaction(accountId: string, id: string, changes: object): Observable<boolean> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('transactions').doc(id).update(changes).then(result => {
|
firebase.firestore().collection('accounts').doc(accountId).collection('transactions').doc(id).update(changes).then(() => {
|
||||||
subscriber.next(true);
|
subscriber.next(true);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
subscriber.next(false);
|
subscriber.next(false);
|
||||||
|
@ -99,9 +85,9 @@ export class TransactionServiceFirebaseFirestoreImpl implements TransactionServi
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTransaction(id: string): Observable<boolean> {
|
deleteTransaction(accountId: string, id: string): Observable<boolean> {
|
||||||
return Observable.create(subscriber => {
|
return Observable.create(subscriber => {
|
||||||
firebase.firestore().collection('transactions').doc(id).delete().then(data => {
|
firebase.firestore().collection('accounts').doc(accountId).collection('transactions').doc(id).delete().then(data => {
|
||||||
subscriber.next(true);
|
subscriber.next(true);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Transaction } from './transaction';
|
import { Transaction } from './transaction';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { Account } from '../accounts/account';
|
||||||
|
|
||||||
export interface TransactionService {
|
export interface TransactionService {
|
||||||
|
|
||||||
getTransactions(group: string, count?: number): Observable<Transaction[]>;
|
getTransactions(accountId: string, categoryId?: string, count?: number): Observable<Transaction[]>;
|
||||||
|
|
||||||
getTransactionsForCategory(category: string, count?: number): Observable<Transaction[]>;
|
getTransaction(accountId: string, id: string): Observable<Transaction>;
|
||||||
|
|
||||||
getTransaction(id: string): Observable<Transaction>;
|
|
||||||
|
|
||||||
createTransaction(
|
createTransaction(
|
||||||
|
accountId: string,
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
|
@ -19,9 +19,9 @@ export interface TransactionService {
|
||||||
category: string
|
category: string
|
||||||
): Observable<Transaction>;
|
): Observable<Transaction>;
|
||||||
|
|
||||||
updateTransaction(id: string, changes: object): Observable<boolean>;
|
updateTransaction(accountId: string, id: string, changes: object): Observable<boolean>;
|
||||||
|
|
||||||
deleteTransaction(id: string): Observable<boolean>;
|
deleteTransaction(accountId: string, id: string): Observable<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let TRANSACTION_SERVICE = new InjectionToken<TransactionService>('transaction.service');
|
export let TRANSACTION_SERVICE = new InjectionToken<TransactionService>('transaction.service');
|
||||||
|
|
|
@ -5,9 +5,8 @@ import * as firebase from 'firebase/app';
|
||||||
export class Transaction {
|
export class Transaction {
|
||||||
id: string;
|
id: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
remoteId: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string = null;
|
||||||
amount: number;
|
amount: number;
|
||||||
date: Date = new Date();
|
date: Date = new Date();
|
||||||
categoryId: string;
|
categoryId: string;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<mat-nav-list *ngIf="transactions" class="transactions">
|
<mat-nav-list *ngIf="transactions" class="transactions">
|
||||||
<a mat-list-item *ngFor="let transaction of transactions" routerLink="/transactions/{{ transaction.id }}">
|
<a mat-list-item *ngFor="let transaction of transactions" routerLink="/accounts/{{ accountId }}/transactions/{{ transaction.id }}">
|
||||||
<div matLine class="list-row-one">
|
<div matLine class="list-row-one">
|
||||||
<p>{{transaction.title}}</p>
|
<p>{{transaction.title}}</p>
|
||||||
<p class="amount" [class.expense]="transaction.type === transactionType.EXPENSE" [class.income]="transaction.type === transactionType.INCOME">
|
<p class="amount" [class.expense]="transaction.type === transactionType.EXPENSE" [class.income]="transaction.type === transactionType.INCOME">
|
||||||
|
@ -9,6 +9,6 @@
|
||||||
<p matLine class="text-small">{{ transaction.date | date }}</p>
|
<p matLine class="text-small">{{ transaction.date | date }}</p>
|
||||||
</a>
|
</a>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
<a mat-fab routerLink="/transactions/new">
|
<a mat-fab routerLink="/accounts/{{ accountId }}/transactions/new">
|
||||||
<mat-icon aria-label="Add">add</mat-icon>
|
<mat-icon aria-label="Add">add</mat-icon>
|
||||||
</a>
|
</a>
|
|
@ -3,6 +3,8 @@ import { Transaction } from './transaction';
|
||||||
import { TransactionType } from './transaction.type';
|
import { TransactionType } from './transaction.type';
|
||||||
import { TransactionService, TRANSACTION_SERVICE } from './transaction.service';
|
import { TransactionService, TRANSACTION_SERVICE } from './transaction.service';
|
||||||
import { AppComponent } from '../app.component';
|
import { AppComponent } from '../app.component';
|
||||||
|
import { Account } from '../accounts/account';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transactions',
|
selector: 'app-transactions',
|
||||||
|
@ -11,24 +13,26 @@ import { AppComponent } from '../app.component';
|
||||||
})
|
})
|
||||||
export class TransactionsComponent implements OnInit {
|
export class TransactionsComponent implements OnInit {
|
||||||
|
|
||||||
@Input() group: string;
|
accountId: string;
|
||||||
public transactionType = TransactionType;
|
public transactionType = TransactionType;
|
||||||
|
|
||||||
public transactions: Transaction[];
|
public transactions: Transaction[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
private app: AppComponent,
|
private app: AppComponent,
|
||||||
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
|
@Inject(TRANSACTION_SERVICE) private transactionService: TransactionService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
this.app.backEnabled = true;
|
this.app.backEnabled = true;
|
||||||
this.app.title = 'Transactions';
|
this.app.title = 'Transactions';
|
||||||
this.getTransactions();
|
this.getTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransactions(): void {
|
getTransactions(): void {
|
||||||
this.transactionService.getTransactions(this.app.group).subscribe(transactions => {
|
this.transactionService.getTransactions(this.accountId).subscribe(transactions => {
|
||||||
this.transactions = transactions;
|
this.transactions = transactions;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue