Finish implementing category add/edit/list features
This commit is contained in:
parent
f8b33cb67e
commit
b79f754808
29 changed files with 524 additions and 17 deletions
16
src/app/add-edit-category/add-edit-category.component.css
Normal file
16
src/app/add-edit-category/add-edit-category.component.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.category-form {
|
||||||
|
padding: 1em;
|
||||||
|
color: #F1F1F1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-form * {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-radio-button {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-delete {
|
||||||
|
float: right;
|
||||||
|
}
|
28
src/app/add-edit-category/add-edit-category.component.html
Normal file
28
src/app/add-edit-category/add-edit-category.component.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<mat-toolbar>
|
||||||
|
<span>
|
||||||
|
<a (click)="goBack()">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<span class="action-item">
|
||||||
|
<a (click)="save()">Save</a>
|
||||||
|
</span>
|
||||||
|
</mat-toolbar>
|
||||||
|
<div *ngIf="!currentCategory">
|
||||||
|
<p>Select a category from the list to view details about it or edit it.</p>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="currentCategory" class="category-form">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput [(ngModel)]="currentCategory.name" placeholder="Name" required>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="number" [(ngModel)]="currentCategory.amount" placeholder="Amount" required>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input type="color" matInput [(ngModel)]="currentCategory.color" placeholder="Color">
|
||||||
|
</mat-form-field>
|
||||||
|
<button class="button-delete" mat-button color="warn" *ngIf="currentCategory.id" (click)="delete()">Delete</button>
|
||||||
|
</div>
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddEditCategoryComponent } from './add-edit-category.component';
|
||||||
|
|
||||||
|
describe('AddEditCategoryComponent', () => {
|
||||||
|
let component: AddEditCategoryComponent;
|
||||||
|
let fixture: ComponentFixture<AddEditCategoryComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ AddEditCategoryComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AddEditCategoryComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
43
src/app/add-edit-category/add-edit-category.component.ts
Normal file
43
src/app/add-edit-category/add-edit-category.component.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { CategoryService } from '../category.service'
|
||||||
|
import { Category } from '../category'
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-edit-category',
|
||||||
|
templateUrl: './add-edit-category.component.html',
|
||||||
|
styleUrls: ['./add-edit-category.component.css']
|
||||||
|
})
|
||||||
|
export class AddEditCategoryComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() title: string;
|
||||||
|
@Input() currentCategory: Category;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private categoryService: CategoryService,
|
||||||
|
private location: Location
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
this.location.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
if (this.currentCategory.id) {
|
||||||
|
// This is an existing category, update it
|
||||||
|
this.categoryService.updateCategory(this.currentCategory);
|
||||||
|
} else {
|
||||||
|
// This is a new category, save it
|
||||||
|
this.categoryService.saveCategory(this.currentCategory);
|
||||||
|
}
|
||||||
|
this.goBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(): void {
|
||||||
|
this.categoryService.deleteCategory(this.currentCategory);
|
||||||
|
this.goBack()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,18 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
import { TransactionsComponent } from './transactions/transactions.component';
|
import { TransactionsComponent } from './transactions/transactions.component';
|
||||||
import { TransactionDetailsComponent } from './transaction-details/transaction-details.component';
|
import { TransactionDetailsComponent } from './transaction-details/transaction-details.component';
|
||||||
import { NewTransactionComponent } from './new-transaction/new-transaction.component';
|
import { NewTransactionComponent } from './new-transaction/new-transaction.component';
|
||||||
|
import { CategoriesComponent } from './categories/categories.component';
|
||||||
|
import { CategoryDetailsComponent } from './category-details/category-details.component';
|
||||||
|
import { NewCategoryComponent } from './new-category/new-category.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: DashboardComponent },
|
{ path: '', component: DashboardComponent },
|
||||||
{ path: 'transactions', component: TransactionsComponent },
|
{ path: 'transactions', component: TransactionsComponent },
|
||||||
{ path: 'transactions/new', component: NewTransactionComponent },
|
{ path: 'transactions/new', component: NewTransactionComponent },
|
||||||
{ path: 'transactions/:id', component: TransactionDetailsComponent },
|
{ path: 'transactions/:id', component: TransactionDetailsComponent },
|
||||||
|
{ path: 'categories', component: CategoriesComponent },
|
||||||
|
{ path: 'categories/new', component: NewCategoryComponent },
|
||||||
|
{ path: 'categories/:id', component: CategoryDetailsComponent },
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatListModule,
|
MatListModule,
|
||||||
MatRadioModule,
|
MatRadioModule,
|
||||||
|
MatProgressBarModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
} from '@angular/material';
|
} from '@angular/material';
|
||||||
|
|
||||||
|
@ -20,6 +21,11 @@ import { TransactionDetailsComponent } from './transaction-details/transaction-d
|
||||||
import { NewTransactionComponent } from './new-transaction/new-transaction.component';
|
import { NewTransactionComponent } from './new-transaction/new-transaction.component';
|
||||||
import { AddEditTransactionComponent } from './add-edit-transaction/add-edit-transaction.component';
|
import { AddEditTransactionComponent } from './add-edit-transaction/add-edit-transaction.component';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
|
import { CategoriesComponent } from './categories/categories.component';
|
||||||
|
import { CategoryDetailsComponent } from './category-details/category-details.component';
|
||||||
|
import { AddEditCategoryComponent } from './add-edit-category/add-edit-category.component';
|
||||||
|
import { NewCategoryComponent } from './new-category/new-category.component';
|
||||||
|
import { CategoryListComponent } from './category-list/category-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -28,7 +34,12 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
TransactionDetailsComponent,
|
TransactionDetailsComponent,
|
||||||
NewTransactionComponent,
|
NewTransactionComponent,
|
||||||
AddEditTransactionComponent,
|
AddEditTransactionComponent,
|
||||||
DashboardComponent
|
DashboardComponent,
|
||||||
|
CategoriesComponent,
|
||||||
|
CategoryDetailsComponent,
|
||||||
|
AddEditCategoryComponent,
|
||||||
|
NewCategoryComponent,
|
||||||
|
CategoryListComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -40,6 +51,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatListModule,
|
MatListModule,
|
||||||
MatRadioModule,
|
MatRadioModule,
|
||||||
|
MatProgressBarModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule
|
FormsModule
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
import Dexie from 'dexie';
|
import Dexie from 'dexie';
|
||||||
import { TransactionType } from './transaction.type';
|
import { TransactionType } from './transaction.type';
|
||||||
import { Category } from './category'
|
import { Category } from './category'
|
||||||
import { Transaction } from './transaction'
|
import { Transaction } from './transaction'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class BudgetDatabase extends Dexie {
|
export class BudgetDatabase extends Dexie {
|
||||||
transactions: Dexie.Table<ITransaction, number>;
|
transactions: Dexie.Table<ITransaction, number>;
|
||||||
categories: Dexie.Table<ICategory, number>;
|
categories: Dexie.Table<ICategory, number>;
|
||||||
|
|
0
src/app/categories/categories.component.css
Normal file
0
src/app/categories/categories.component.css
Normal file
14
src/app/categories/categories.component.html
Normal file
14
src/app/categories/categories.component.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<mat-toolbar>
|
||||||
|
<span>
|
||||||
|
<a (click)="goBack()">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span>Categories</span>
|
||||||
|
<!-- empty span object for spacing -->
|
||||||
|
<span></span>
|
||||||
|
</mat-toolbar>
|
||||||
|
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
||||||
|
<a mat-fab routerLink="/categories/new">
|
||||||
|
<mat-icon aria-label="Add">add</mat-icon>
|
||||||
|
</a>
|
25
src/app/categories/categories.component.spec.ts
Normal file
25
src/app/categories/categories.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CategoriesComponent } from './categories.component';
|
||||||
|
|
||||||
|
describe('CategoriesComponent', () => {
|
||||||
|
let component: CategoriesComponent;
|
||||||
|
let fixture: ComponentFixture<CategoriesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CategoriesComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CategoriesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
38
src/app/categories/categories.component.ts
Normal file
38
src/app/categories/categories.component.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CategoryService } from '../category.service'
|
||||||
|
import { Category } from '../category'
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-categories',
|
||||||
|
templateUrl: './categories.component.html',
|
||||||
|
styleUrls: ['./categories.component.css']
|
||||||
|
})
|
||||||
|
export class CategoriesComponent implements OnInit {
|
||||||
|
|
||||||
|
public categories: Category[];
|
||||||
|
private categoryBalances: Map<number, number>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private categoryService: CategoryService,
|
||||||
|
private location: Location
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getCategories();
|
||||||
|
this.categoryBalances = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategories(): void {
|
||||||
|
this.categoryService.getCategories().subscribe(categories => {
|
||||||
|
this.categories = categories
|
||||||
|
for (let category of this.categories) {
|
||||||
|
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
this.location.back()
|
||||||
|
}
|
||||||
|
}
|
0
src/app/category-details/category-details.component.css
Normal file
0
src/app/category-details/category-details.component.css
Normal file
1
src/app/category-details/category-details.component.html
Normal file
1
src/app/category-details/category-details.component.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<app-add-edit-category [title]="'Edit Category'" [currentCategory]="category"></app-add-edit-category>
|
25
src/app/category-details/category-details.component.spec.ts
Normal file
25
src/app/category-details/category-details.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CategoryDetailsComponent } from './category-details.component';
|
||||||
|
|
||||||
|
describe('CategoryDetailsComponent', () => {
|
||||||
|
let component: CategoryDetailsComponent;
|
||||||
|
let fixture: ComponentFixture<CategoryDetailsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CategoryDetailsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CategoryDetailsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
28
src/app/category-details/category-details.component.ts
Normal file
28
src/app/category-details/category-details.component.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CategoryService } from '../category.service'
|
||||||
|
import { Category } from '../category'
|
||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-category-details',
|
||||||
|
templateUrl: './category-details.component.html',
|
||||||
|
styleUrls: ['./category-details.component.css']
|
||||||
|
})
|
||||||
|
export class CategoryDetailsComponent implements OnInit {
|
||||||
|
|
||||||
|
category: Category;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private categoryService: CategoryService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getCategory()
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategory(): void {
|
||||||
|
const id = +this.route.snapshot.paramMap.get('id')
|
||||||
|
this.categoryService.getCategory(id)
|
||||||
|
.subscribe(category => this.category = category)
|
||||||
|
}
|
8
src/app/category-list/category-list.component.css
Normal file
8
src/app/category-list/category-list.component.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.categories mat-progress-bar.mat-progress-bar {
|
||||||
|
background-color: #BDBDBD;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-progress-bar-buffer {
|
||||||
|
background-color: #BDBDBD;
|
||||||
|
}
|
16
src/app/category-list/category-list.component.html
Normal file
16
src/app/category-list/category-list.component.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<style>
|
||||||
|
.categories mat-progress-bar div.mat-progress-bar-element.mat-progress-bar-buffer {
|
||||||
|
background-color: #BDBDBD !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<mat-nav-list class="categories">
|
||||||
|
<a mat-list-item *ngFor="let category of categories" routerLink="/categories/{{ category.id }}">
|
||||||
|
<p matLine>{{category.name}}</p>
|
||||||
|
<style>
|
||||||
|
::ng-deep .mat-progress-bar-fill::after {
|
||||||
|
background-color: "{{ category.color }}";
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<mat-progress-bar matLine mode="determinate" value="{{ getCategoryCompletion(category) }}"></mat-progress-bar>
|
||||||
|
</a>
|
||||||
|
</mat-nav-list>
|
25
src/app/category-list/category-list.component.spec.ts
Normal file
25
src/app/category-list/category-list.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CategoryListComponent } from './category-list.component';
|
||||||
|
|
||||||
|
describe('CategoryListComponent', () => {
|
||||||
|
let component: CategoryListComponent;
|
||||||
|
let fixture: ComponentFixture<CategoryListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CategoryListComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CategoryListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
31
src/app/category-list/category-list.component.ts
Normal file
31
src/app/category-list/category-list.component.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Category } from '../category'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-category-list',
|
||||||
|
templateUrl: './category-list.component.html',
|
||||||
|
styleUrls: ['./category-list.component.css']
|
||||||
|
})
|
||||||
|
export class CategoryListComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() categories: Category[];
|
||||||
|
@Input() categoryBalances: Map<number, number>;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategoryCompletion(category: Category): number {
|
||||||
|
if (category.amount <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let categoryBalance = this.categoryBalances.get(category.id)
|
||||||
|
if (!categoryBalance) {
|
||||||
|
categoryBalance = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryBalance / category.amount;
|
||||||
|
}
|
||||||
|
}
|
15
src/app/category.service.spec.ts
Normal file
15
src/app/category.service.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CategoryService } from './category.service';
|
||||||
|
|
||||||
|
describe('CategoryService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [CategoryService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([CategoryService], (service: CategoryService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
54
src/app/category.service.ts
Normal file
54
src/app/category.service.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { of, Observable, from } from 'rxjs';
|
||||||
|
import { BudgetDatabase } from './budget-database';
|
||||||
|
import { TransactionType } from './transaction.type'
|
||||||
|
import { Category } from './category'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CategoryService {
|
||||||
|
|
||||||
|
constructor(private db: BudgetDatabase) { }
|
||||||
|
|
||||||
|
getCategories(count?: number): Observable<Category[]> {
|
||||||
|
if (count) {
|
||||||
|
return from(this.db.categories.toCollection().limit(count).toArray())
|
||||||
|
} else {
|
||||||
|
return from(this.db.categories.toCollection().toArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategory(id: number): Observable<Category> {
|
||||||
|
return from(this.db.categories.where('id').equals(id).first())
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCategory(category: Category): Observable<Category> {
|
||||||
|
this.db.categories.put(category)
|
||||||
|
return of(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCategory(category: Category): Observable<any> {
|
||||||
|
this.db.categories.update(category.id, category)
|
||||||
|
return of([])
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCategory(category: Category): Observable<any> {
|
||||||
|
return from(this.db.categories.delete(category.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
getBalance(category: Category): Observable<number> {
|
||||||
|
let sum = 0;
|
||||||
|
return from(
|
||||||
|
this.db.transactions.filter(transaction => transaction.category === category).each(function(transaction) {
|
||||||
|
if (transaction.type === TransactionType.INCOME) {
|
||||||
|
sum += transaction.amount
|
||||||
|
} else {
|
||||||
|
sum -= transaction.amount
|
||||||
|
}
|
||||||
|
}).then(function() {
|
||||||
|
return sum;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +1,61 @@
|
||||||
.dashboard {
|
.dashboard {
|
||||||
|
color: #F1F1F1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-primary {
|
.dashboard > div {
|
||||||
background: #212121;
|
background: #212121;
|
||||||
color: #F1F1F1;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 1em;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
padding: 5em 1em;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard .dashboard-primary {
|
||||||
|
padding: 5em 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-primary > * {
|
.dashboard-primary > * {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-primary h2 {
|
.dashboard div h2, .dashboard div h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard p, .dashboard a {
|
||||||
|
color: #F1F1F1;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-primary a {
|
.dashboard-primary a {
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
color: #F1F1F1;
|
|
||||||
right: 1em;
|
right: 1em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
text-decoration: none;
|
}
|
||||||
|
|
||||||
|
.dashboard .no-categories {
|
||||||
|
padding: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard .no-categories a {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px dashed #F1F1F1;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.view-all {
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,17 @@
|
||||||
<h2 class="balance">
|
<h2 class="balance">
|
||||||
Current Balance: <span [ngClass]="{'income': balance > 0, 'expense': balance < 0}" >{{ balance | currency }}</span>
|
Current Balance: <span [ngClass]="{'income': balance > 0, 'expense': balance < 0}" >{{ balance | currency }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<a routerLink="/transactions">View Transactions >></a>
|
<a routerLink="/transactions">View Transactions >></a>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-categories">
|
||||||
|
<h3 class="categories">Categories</h3>
|
||||||
|
<a routerLink="/categories" class="view-all" *ngIf="categories">View All</a>
|
||||||
|
<div class="no-categories" *ngIf="!categories">
|
||||||
|
<a 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Transaction } from '../transaction'
|
import { Transaction } from '../transaction'
|
||||||
import { TransactionService } from '../transaction.service'
|
import { TransactionService } from '../transaction.service'
|
||||||
|
import { CategoryService } from '../category.service'
|
||||||
|
import { Category } from '../category'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
|
@ -11,14 +13,19 @@ export class DashboardComponent implements OnInit {
|
||||||
|
|
||||||
public balance: number;
|
public balance: number;
|
||||||
public transactions: Transaction[];
|
public transactions: Transaction[];
|
||||||
|
public categories: Category[];
|
||||||
|
categoryBalances: Map<number, number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private transactionService: TransactionService
|
private transactionService: TransactionService,
|
||||||
|
private categoryService: CategoryService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getBalance();
|
this.getBalance();
|
||||||
this.getTransactions();
|
this.getTransactions();
|
||||||
|
this.getCategories();
|
||||||
|
this.categoryBalances = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBalance(): void {
|
getBalance(): void {
|
||||||
|
@ -26,6 +33,10 @@ export class DashboardComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransactions(): void {
|
getTransactions(): void {
|
||||||
this.transactionService.getTransactions().subscribe(transactions => this.transactions = transactions)
|
this.transactionService.getTransactions(5).subscribe(transactions => this.transactions = transactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategories(): void {
|
||||||
|
this.categoryService.getCategories(5).subscribe(categories => this.categories = categories)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
0
src/app/new-category/new-category.component.css
Normal file
0
src/app/new-category/new-category.component.css
Normal file
1
src/app/new-category/new-category.component.html
Normal file
1
src/app/new-category/new-category.component.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<app-add-edit-category [title]="'Add Category'" [currentCategory]="category"></app-add-edit-category>
|
25
src/app/new-category/new-category.component.spec.ts
Normal file
25
src/app/new-category/new-category.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NewCategoryComponent } from './new-category.component';
|
||||||
|
|
||||||
|
describe('NewCategoryComponent', () => {
|
||||||
|
let component: NewCategoryComponent;
|
||||||
|
let fixture: ComponentFixture<NewCategoryComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ NewCategoryComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(NewCategoryComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
21
src/app/new-category/new-category.component.ts
Normal file
21
src/app/new-category/new-category.component.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Category } from '../category'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-new-category',
|
||||||
|
templateUrl: './new-category.component.html',
|
||||||
|
styleUrls: ['./new-category.component.css']
|
||||||
|
})
|
||||||
|
export class NewCategoryComponent implements OnInit {
|
||||||
|
|
||||||
|
category: Category;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.category = new Category();
|
||||||
|
// TODO: Set random color for category, improve color picker
|
||||||
|
//this.category.color =
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,11 +9,7 @@ import { BudgetDatabase } from './budget-database';
|
||||||
})
|
})
|
||||||
export class TransactionService {
|
export class TransactionService {
|
||||||
|
|
||||||
db: BudgetDatabase;
|
constructor(private db: BudgetDatabase) { }
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.db = new BudgetDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTransactions(count?: number): Observable<Transaction[]> {
|
getTransactions(count?: number): Observable<Transaction[]> {
|
||||||
if (count) {
|
if (count) {
|
||||||
|
@ -42,7 +38,6 @@ export class TransactionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getBalance(): Observable<number> {
|
getBalance(): Observable<number> {
|
||||||
console.log("Getting balance")
|
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
return from(
|
return from(
|
||||||
this.db.transactions.each(function(transaction) {
|
this.db.transactions.each(function(transaction) {
|
||||||
|
@ -56,4 +51,5 @@ export class TransactionService {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue