WIP: Integrate app with API

This commit is contained in:
William Brawner 2018-09-15 15:57:11 -05:00
parent c5a4f19fc2
commit 09e32d1cd1
50 changed files with 743 additions and 168 deletions

37
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,37 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch index.html",
"type": "firefox",
"request": "launch",
"reAttach": true,
"file": "${workspaceFolder}/index.html"
},
{
"name": "Launch localhost",
"type": "firefox",
"request": "launch",
"reAttach": true,
"url": "http://localhost/index.html",
"webRoot": "${workspaceFolder}"
},
{
"name": "Attach",
"type": "firefox",
"request": "attach"
},
{
"name": "Launch addon",
"type": "firefox",
"request": "launch",
"reAttach": true,
"addonType": "webExtension",
"addonPath": "${workspaceFolder}"
}
]
}

7
src/app/account.ts Normal file
View file

@ -0,0 +1,7 @@
import { IAccount } from './budget-database'
export class Account implements IAccount {
id: number;
remoteId: number;
name: string;
}

View file

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AccountsService } from './accounts.service';
describe('AccountsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AccountsService]
});
});
it('should be created', inject([AccountsService], (service: AccountsService) => {
expect(service).toBeTruthy();
}));
});

View file

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AccountsService {
constructor() { }
}

4
src/app/actionable.ts Normal file
View file

@ -0,0 +1,4 @@
export interface Actionable {
doAction(): void;
getActionLabel(): string;
}

View file

@ -0,0 +1,3 @@
<p>
add-edit-account works!
</p>

View file

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

View file

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-add-edit-account',
templateUrl: './add-edit-account.component.html',
styleUrls: ['./add-edit-account.component.css']
})
export class AddEditAccountComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View file

@ -1,16 +1,3 @@
.category-form {
padding: 1em;
color: #F1F1F1;
}
.category-form * {
display: block;
}
mat-radio-button {
padding-bottom: 15px;
}
.button-delete { .button-delete {
float: right; float: right;
} }

View file

@ -1,20 +1,7 @@
<mat-toolbar>
<span>
<a mat-button (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</a>
</span>
<span>
{{ title }}
</span>
<span class="action-item">
<a mat-button (click)="save()">Save</a>
</span>
</mat-toolbar>
<div *ngIf="!currentCategory"> <div *ngIf="!currentCategory">
<p>Select a category from the list to view details about it or edit it.</p> <p>Select a category from the list to view details about it or edit it.</p>
</div> </div>
<div *ngIf="currentCategory" class="category-form"> <div *ngIf="currentCategory" class="form category-form">
<mat-form-field> <mat-form-field>
<input matInput [(ngModel)]="currentCategory.name" placeholder="Name" required> <input matInput [(ngModel)]="currentCategory.name" placeholder="Name" required>
</mat-form-field> </mat-form-field>

View file

@ -1,31 +1,36 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { CategoryService } from '../category.service' import { CategoryService } from '../category.service'
import { Category } from '../category' import { Category } from '../category'
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Actionable } from '../actionable';
import { AppComponent } from '../app.component';
@Component({ @Component({
selector: 'app-add-edit-category', selector: 'app-add-edit-category',
templateUrl: './add-edit-category.component.html', templateUrl: './add-edit-category.component.html',
styleUrls: ['./add-edit-category.component.css'] styleUrls: ['./add-edit-category.component.css']
}) })
export class AddEditCategoryComponent implements OnInit { export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
@Input() title: string; @Input() title: string;
@Input() currentCategory: Category; @Input() currentCategory: Category;
constructor( constructor(
private app: AppComponent,
private categoryService: CategoryService, private categoryService: CategoryService,
private location: Location
) { } ) { }
ngOnInit() { ngOnInit() {
this.app.actionable = this;
this.app.backEnabled = true;
this.app.title = this.title;
} }
goBack(): void { ngOnDestroy() {
this.location.back() this.app.actionable = null;
} }
save(): void { doAction(): void {
if (this.currentCategory.id) { if (this.currentCategory.id) {
// This is an existing category, update it // This is an existing category, update it
this.categoryService.updateCategory(this.currentCategory); this.categoryService.updateCategory(this.currentCategory);
@ -33,11 +38,15 @@ export class AddEditCategoryComponent implements OnInit {
// This is a new category, save it // This is a new category, save it
this.categoryService.saveCategory(this.currentCategory); this.categoryService.saveCategory(this.currentCategory);
} }
this.goBack() this.app.goBack();
}
getActionLabel(): string {
return 'Save';
} }
delete(): void { delete(): void {
this.categoryService.deleteCategory(this.currentCategory); this.categoryService.deleteCategory(this.currentCategory);
this.goBack() this.app.goBack();
} }
} }

View file

@ -1,20 +1,7 @@
<mat-toolbar> <div [hidden]="currentTransaction">
<span>
<a mat-button (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</a>
</span>
<span>
{{ title }}
</span>
<span class="action-item">
<a mat-button (click)="save()">Save</a>
</span>
</mat-toolbar>
<div *ngIf="!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 *ngIf="currentTransaction" class="transaction-form"> <div [hidden]="!currentTransaction" class="form transaction-form">
<mat-form-field> <mat-form-field>
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required> <input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
</mat-form-field> </mat-form-field>

View file

@ -1,39 +1,42 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input, OnChanges, OnDestroy } from '@angular/core';
import { Transaction } from '../transaction' import { Transaction } from '../transaction'
import { TransactionType } from '../transaction.type' import { TransactionType } from '../transaction.type'
import { TransactionService } from '../transaction.service' import { TransactionService } from '../transaction.service'
import { Location } from '@angular/common'; import { Category } from '../category'
import { Category } from '../category'
import { CategoryService } from '../category.service' import { CategoryService } from '../category.service'
import { AppComponent } from '../app.component';
import { Actionable } from '../actionable';
@Component({ @Component({
selector: 'app-add-edit-transaction', selector: 'app-add-edit-transaction',
templateUrl: './add-edit-transaction.component.html', templateUrl: './add-edit-transaction.component.html',
styleUrls: ['./add-edit-transaction.component.css'] styleUrls: ['./add-edit-transaction.component.css']
}) })
export class AddEditTransactionComponent implements OnInit { export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionable {
@Input() title: string; @Input() title: string;
@Input() currentTransaction: Transaction; @Input() currentTransaction: Transaction;
public transactionType = TransactionType; public transactionType = TransactionType;
public selectedCategory: Category; public selectedCategory: Category;
public categories: Category[] public categories: Category[];
constructor( constructor(
private app: AppComponent,
private categoryService: CategoryService, private categoryService: CategoryService,
private transactionService: TransactionService, private transactionService: TransactionService,
private location: Location
) { } ) { }
ngOnInit() { ngOnInit() {
this.getCategories() this.app.title = this.title;
this.app.backEnabled = true;
this.app.actionable = this;
this.getCategories();
} }
goBack(): void { ngOnDestroy() {
this.location.back() this.app.actionable = null;
} }
save(): void { doAction(): void {
if (this.currentTransaction.id) { if (this.currentTransaction.id) {
// This is an existing transaction, update it // This is an existing transaction, update it
this.transactionService.updateTransaction(this.currentTransaction); this.transactionService.updateTransaction(this.currentTransaction);
@ -41,15 +44,19 @@ export class AddEditTransactionComponent implements OnInit {
// This is a new transaction, save it // This is a new transaction, save it
this.transactionService.saveTransaction(this.currentTransaction); this.transactionService.saveTransaction(this.currentTransaction);
} }
this.goBack() this.app.goBack();
}
getActionLabel(): string {
return 'Save';
} }
delete(): void { delete(): void {
this.transactionService.deleteTransaction(this.currentTransaction); this.transactionService.deleteTransaction(this.currentTransaction);
this.goBack() this.app.goBack();
} }
getCategories() { getCategories() {
this.categoryService.getCategories().subscribe(categories => this.categories = categories) this.categoryService.getCategories().subscribe(categories => this.categories = categories);
} }
} }

View file

@ -0,0 +1,19 @@
import { TestBed, inject } from '@angular/core/testing';
import { ApiService } from './api.service';
import { HttpClientModule } from '@angular/common/http';
describe('ApiService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
],
providers: [ApiService]
});
});
it('should be created', inject([ApiService], (service: ApiService) => {
expect(service).toBeTruthy();
}));
});

52
src/app/api.service.ts Normal file
View file

@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: true,
};
const host = 'http://localhost:9090';
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(
private http: HttpClient,
) { }
login(username: string, password: string): Observable<any> {
return this.http.post(
host + '/login',
{
'username': username,
'password': password
},
httpOptions
);
}
register(username: string, email: string, password: string): Observable<any> {
return this.http.post(
host + '/register',
{
'username': username,
'email': email,
'password': password
},
httpOptions
);
}
logout(): Observable<any> {
return this.http.get(
host + '/logout',
httpOptions
);
}
}

View file

@ -7,16 +7,20 @@ import { NewTransactionComponent } from './new-transaction/new-transaction.compo
import { CategoriesComponent } from './categories/categories.component'; import { CategoriesComponent } from './categories/categories.component';
import { CategoryDetailsComponent } from './category-details/category-details.component'; import { CategoryDetailsComponent } from './category-details/category-details.component';
import { NewCategoryComponent } from './new-category/new-category.component'; import { NewCategoryComponent } from './new-category/new-category.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: DashboardComponent }, { path: '', component: DashboardComponent },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ 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', component: CategoriesComponent },
{ path: 'categories/new', component: NewCategoryComponent }, { path: 'categories/new', component: NewCategoryComponent },
{ path: 'categories/:id', component: CategoryDetailsComponent }, { path: 'categories/:id', component: CategoryDetailsComponent },
] ];
@NgModule({ @NgModule({
imports: [ imports: [

View file

@ -1 +1,29 @@
<router-outlet></router-outlet> <mat-sidenav-container class="sidenav-container">
<mat-sidenav #sidenav mode="over" closed>
<mat-nav-list (click)="sidenav.close()">
<a mat-list-item routerLink="/accounts">Manage Accounts</a>
<a mat-list-item *ngIf="!authService.currentUser" routerLink="/login">Login</a>
<a mat-list-item *ngIf="!authService.currentUser" routerLink="/register">Register</a>
<a mat-list-item *ngIf="authService.currentUser" (click)="logout()">Logout</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar>
<span>
<a mat-button [hidden]="!backEnabled" (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</a>
<a mat-button [hidden]="backEnabled" (click)="sidenav.open()">
<mat-icon>menu</mat-icon>
</a>
</span>
<span>
{{ title }}
</span>
<span class="action-item">
<a mat-button *ngIf="actionable" (click)="actionable.doAction()">{{ actionable.getActionLabel() }}</a>
</span>
</mat-toolbar>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>

View file

@ -1,4 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Location } from '@angular/common';
import { Actionable } from './actionable';
import { AuthService } from './auth.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -6,5 +9,20 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent { export class AppComponent {
title = 'budget'; public title = 'Budget';
public backEnabled = false;
public actionable: Actionable;
constructor(
public authService: AuthService,
private location: Location
) { }
goBack(): void {
this.location.back();
}
logout(): void {
this.authService.logout();
}
} }

View file

@ -3,16 +3,17 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { import {
MatButtonModule, MatButtonModule,
MatDatepickerModule, MatDatepickerModule,
MatFormFieldModule, MatFormFieldModule,
MatIconModule, MatIconModule,
MatInputModule, MatInputModule,
MatListModule, MatListModule,
MatRadioModule, MatRadioModule,
MatProgressBarModule, MatProgressBarModule,
MatSelectModule, MatSelectModule,
MatToolbarModule, MatToolbarModule,
MatSidenavModule,
} from '@angular/material'; } from '@angular/material';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -29,6 +30,12 @@ import { NewCategoryComponent } from './new-category/new-category.component';
import { CategoryListComponent } from './category-list/category-list.component'; import { CategoryListComponent } from './category-list/category-list.component';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { AddEditAccountComponent } from './add-edit-account/add-edit-account.component';
import { EditProfileComponent } from './edit-profile/edit-profile.component';
import { UserComponent } from './user/user.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -42,7 +49,12 @@ import { environment } from '../environments/environment';
CategoryDetailsComponent, CategoryDetailsComponent,
AddEditCategoryComponent, AddEditCategoryComponent,
NewCategoryComponent, NewCategoryComponent,
CategoryListComponent CategoryListComponent,
LoginComponent,
RegisterComponent,
AddEditAccountComponent,
EditProfileComponent,
UserComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -57,9 +69,11 @@ import { environment } from '../environments/environment';
MatProgressBarModule, MatProgressBarModule,
MatSelectModule, MatSelectModule,
MatToolbarModule, MatToolbarModule,
MatSidenavModule,
AppRoutingModule, AppRoutingModule,
FormsModule, FormsModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }) ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
HttpClientModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View file

@ -0,0 +1,21 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { HttpClientModule } from '@angular/common/http';
import { RouterTestingModule } from '@angular/router/testing';
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
RouterTestingModule,
],
providers: [AuthService]
});
});
it('should be created', inject([AuthService], (service: AuthService) => {
expect(service).toBeTruthy();
}));
});

55
src/app/auth.service.ts Normal file
View file

@ -0,0 +1,55 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { User } from './user';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUser: User;
constructor(
private apiService: ApiService,
private router: Router,
) {
console.log('AuthService constructed');
}
login(user: User) {
this.apiService.login(user.name, user.password).subscribe(
value => {
this.currentUser = value;
this.router.navigate(['/']);
},
error => {
console.log('Login failed');
console.log(error);
}
);
}
register(user: User) {
this.apiService.register(user.name, user.email, user.password).subscribe(
value => {
this.login(value);
},
error => {
console.log('Registration failed');
console.log(error);
}
);
}
logout() {
this.apiService.logout().subscribe(
value => {
window.location.reload();
},
error => {
console.log('Logout failed');
console.log(error);
}
);
}
}

View file

@ -1,49 +1,67 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import Dexie from 'dexie'; import Dexie from 'dexie';
import { Account } from './account'
import { Category } from './category' import { Category } from './category'
import { Transaction } from './transaction' import { Transaction } from './transaction'
import { TransactionType } from './transaction.type'; import { TransactionType } from './transaction.type';
@Injectable({ @Injectable({
providedIn: 'root' 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>;
accounts: Dexie.Table<IAccount, number>;
constructor () { constructor() {
super('BudgetDatabase') super('BudgetDatabase')
this.version(1).stores({ this.version(1).stores({
transactions: `++id, title, description, amount, date, category, type`, transactions: `++id, title, description, amount, date, category, type`,
categories: `++id, name, amount, repeat, color` categories: `++id, name, amount, repeat, color`
}) })
this.version(2).stores({ this.version(2).stores({
transactions: `++id, title, description, amount, date, category_id, type`, transactions: `++id, title, description, amount, date, category_id, type`,
categories: `++id, name, amount, repeat, color, type` categories: `++id, name, amount, repeat, color, type`
}) })
this.version(3).stores({ this.version(3).stores({
transactions: `++id, title, description, amount, date, category_id, type`, transactions: `++id, title, description, amount, date, category_id, type`,
categories: `++id, name, amount, repeat, color` categories: `++id, name, amount, repeat, color`
}) })
this.transactions.mapToClass(Transaction) this.version(4).stores({
this.categories.mapToClass(Category) transactions: `++id, remote_id, account_id, title, description, amount, date, category_id, type`,
} categories: `++id, remote_id, account_id, name, amount, repeat, color`,
accounts: `++id, remote_id, name`
})
this.transactions.mapToClass(Transaction)
this.categories.mapToClass(Category)
this.accounts.mapToClass(Account)
}
} }
export interface ITransaction { export interface ITransaction {
id: number; id: number;
title: string; accountId: number;
description: string; remoteId: number;
amount: number; title: string;
date: Date; description: string;
categoryId: number; amount: number;
type: TransactionType; date: Date;
categoryId: number;
type: TransactionType;
} }
export interface ICategory{ export interface ICategory {
id: number; id: number;
name: string; accountId: number;
amount: number; remoteId: number;
repeat: string; name: string;
color: string; amount: number;
repeat: string;
color: string;
}
export interface IAccount {
id: number;
remoteId: number;
name: string;
} }

View file

@ -1,13 +1,3 @@
<mat-toolbar>
<span>
<a mat-button (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> <app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
<a mat-fab routerLink="/categories/new"> <a mat-fab routerLink="/categories/new">
<mat-icon aria-label="Add">add</mat-icon> <mat-icon aria-label="Add">add</mat-icon>

View file

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { CategoryService } from '../category.service' import { CategoryService } from '../category.service';
import { Category } from '../category' import { Category } from '../category';
import { Location } from '@angular/common'; import { AppComponent } from '../app.component';
@Component({ @Component({
selector: 'app-categories', selector: 'app-categories',
@ -14,25 +14,22 @@ export class CategoriesComponent implements OnInit {
public categoryBalances: Map<number, number>; public categoryBalances: Map<number, number>;
constructor( constructor(
private app: AppComponent,
private categoryService: CategoryService, private categoryService: CategoryService,
private location: Location
) { } ) { }
ngOnInit() { ngOnInit() {
this.app.title = 'Categories';
this.getCategories(); this.getCategories();
this.categoryBalances = new Map(); this.categoryBalances = new Map();
} }
getCategories(): void { getCategories(): void {
this.categoryService.getCategories().subscribe(categories => { this.categoryService.getCategories().subscribe(categories => {
this.categories = categories this.categories = categories;
for (let category of this.categories) { for (const category of this.categories) {
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance)) this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
} }
}) });
}
goBack(): void {
this.location.back()
} }
} }

View file

@ -2,6 +2,8 @@ import { ICategory } from './budget-database'
export class Category implements ICategory { export class Category implements ICategory {
id: number; id: number;
accountId: number;
remoteId: number;
name: string; name: string;
amount: number; amount: number;
repeat: string; repeat: string;

View file

@ -1,10 +1,3 @@
<mat-toolbar>
<!-- empty span object for spacing -->
<span></span>
<span>My Finances</span>
<!-- empty span object for spacing -->
<span></span>
</mat-toolbar>
<div class="dashboard"> <div class="dashboard">
<div class="dashboard-primary"> <div class="dashboard-primary">
<h2 class="balance"> <h2 class="balance">

View file

@ -3,6 +3,8 @@ import { Transaction } from '../transaction'
import { TransactionService } from '../transaction.service' import { TransactionService } from '../transaction.service'
import { CategoryService } from '../category.service' import { CategoryService } from '../category.service'
import { Category } from '../category' import { Category } from '../category'
import { AppComponent } from '../app.component';
import { AuthService } from '../auth.service';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@ -17,11 +19,14 @@ export class DashboardComponent implements OnInit {
categoryBalances: Map<number, number>; categoryBalances: Map<number, number>;
constructor( constructor(
private app: AppComponent,
private transactionService: TransactionService, private transactionService: TransactionService,
private categoryService: CategoryService private categoryService: CategoryService
) { } ) { }
ngOnInit() { ngOnInit() {
this.app.backEnabled = false;
this.app.title = 'My Finances';
this.balance = 0; this.balance = 0;
this.getBalance(); this.getBalance();
this.getTransactions(); this.getTransactions();
@ -39,10 +44,10 @@ export class DashboardComponent implements OnInit {
getCategories(): void { getCategories(): void {
this.categoryService.getCategories(5).subscribe(categories => { this.categoryService.getCategories(5).subscribe(categories => {
this.categories = categories this.categories = categories;
for (let category of this.categories) { for (const category of this.categories) {
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance)) this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
} }
}) });
} }
} }

View file

@ -0,0 +1,3 @@
<p>
edit-profile works!
</p>

View file

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

View file

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-edit-profile',
templateUrl: './edit-profile.component.html',
styleUrls: ['./edit-profile.component.css']
})
export class EditProfileComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View file

View file

@ -0,0 +1,8 @@
<div class="form login-form">
<mat-form-field>
<input matInput placeholder="Username" [(ngModel)]="user.name" />
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="Password" [(ngModel)]="user.password" />
</mat-form-field>
</div>

View file

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

View file

@ -0,0 +1,38 @@
import { Component, OnInit, OnDestroy, OnChanges } from '@angular/core';
import { AuthService } from '../auth.service';
import { User } from '../user';
import { Actionable } from '../actionable';
import { AppComponent } from '../app.component';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit, OnDestroy, Actionable {
public user: User = new User();
constructor(
private app: AppComponent,
private authService: AuthService,
) { }
ngOnInit() {
this.app.title = 'Login';
this.app.actionable = this;
this.app.backEnabled = true;
}
ngOnDestroy() {
this.app.actionable = null;
}
doAction(): void {
this.authService.login(this.user);
}
getActionLabel() {
return 'Submit';
}
}

View file

View file

@ -0,0 +1,14 @@
<div class="form register-form">
<mat-form-field>
<input matInput placeholder="Username" [(ngModel)]="user.name" />
</mat-form-field>
<mat-form-field>
<input matInput type="email" placeholder="Email Address" [(ngModel)]="user.email" />
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="Password" [(ngModel)]="user.password" />
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="Confirm Password" />
</mat-form-field>
</div>

View file

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

View file

@ -0,0 +1,38 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { User } from '../user';
import { AppComponent } from '../app.component';
import { AuthService } from '../auth.service';
import { Actionable } from '../actionable';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit, OnDestroy, Actionable {
public user: User = new User();
constructor(
private app: AppComponent,
private authService: AuthService,
) { }
ngOnInit() {
this.app.title = 'Login';
this.app.actionable = this;
this.app.backEnabled = true;
}
ngOnDestroy() {
this.app.actionable = null;
}
doAction(): void {
this.authService.register(this.user);
}
getActionLabel() {
return 'Submit';
}
}

View file

@ -4,6 +4,8 @@ import { TransactionType } from './transaction.type';
export class Transaction implements ITransaction { export class Transaction implements ITransaction {
id: number; id: number;
accountId: number;
remoteId: number;
title: string; title: string;
description: string; description: string;
amount: number; amount: number;

View file

@ -1,13 +1,3 @@
<mat-toolbar>
<span>
<a mat-button (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</a>
</span>
<span>Transactions</span>
<!-- empty span object for spacing -->
<span></span>
</mat-toolbar>
<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="/transactions/{{ transaction.id }}">
<div matLine class="list-row-one"> <div matLine class="list-row-one">

View file

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ExpensesComponent } from './transactions.component'; import { TransactionsComponent } from './transactions.component';
describe('ExpensesComponent', () => { describe('ExpensesComponent', () => {
let component: ExpensesComponent; let component: TransactionsComponent;
let fixture: ComponentFixture<ExpensesComponent>; let fixture: ComponentFixture<TransactionsComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ ExpensesComponent ] declarations: [ TransactionsComponent ]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ExpensesComponent); fixture = TestBed.createComponent(TransactionsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View file

@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Transaction } from '../transaction'; import { Transaction } from '../transaction';
import { TransactionType } from '../transaction.type'; import { TransactionType } from '../transaction.type';
import { TransactionService } from '../transaction.service'; import { TransactionService } from '../transaction.service';
import { Location } from '@angular/common'; import { AppComponent } from '../app.component';
@Component({ @Component({
selector: 'app-transactions', selector: 'app-transactions',
@ -16,21 +16,19 @@ export class TransactionsComponent implements OnInit {
public transactions: Transaction[] public transactions: Transaction[]
constructor( constructor(
private app: AppComponent,
private transactionService: TransactionService, private transactionService: TransactionService,
private location: Location
) { } ) { }
ngOnInit() { ngOnInit() {
this.getTransactions() this.app.backEnabled = true;
} this.app.title = 'Transactions';
this.getTransactions();
goBack(): void {
this.location.back()
} }
getTransactions(): void { getTransactions(): void {
this.transactionService.getTransactions().subscribe(transactions => { this.transactionService.getTransactions().subscribe(transactions => {
this.transactions = transactions; this.transactions = transactions;
}) });
} }
} }

6
src/app/user.ts Normal file
View file

@ -0,0 +1,6 @@
export class User {
id: number;
name: string;
password: string;
email: string;
}

View file

View file

@ -0,0 +1,3 @@
<p>
user works!
</p>

View file

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

View file

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View file

@ -1,5 +1,7 @@
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
html, body {
html,
body {
background: #333333; background: #333333;
font-family: Roboto, "Helvetica Neue", sans-serif; font-family: Roboto, "Helvetica Neue", sans-serif;
margin: 0; margin: 0;
@ -11,6 +13,10 @@ p {
margin: 0; margin: 0;
} }
a.mat-button[hidden] {
display: none;
}
a.mat-fab { a.mat-fab {
position: fixed; position: fixed;
right: 2em; right: 2em;
@ -26,13 +32,15 @@ mat-toolbar {
box-shadow: 0px 3px 3px 1px #212121; box-shadow: 0px 3px 3px 1px #212121;
} }
mat-toolbar.mat-toolbar-row, mat-toolbar.mat-toolbar-single-row { mat-toolbar.mat-toolbar-row,
mat-toolbar.mat-toolbar-single-row {
padding: 0; padding: 0;
} }
mat-toolbar span { mat-toolbar span {
display: flex; display: flex;
width: 33%;; width: 33%;
;
} }
mat-toolbar span:nth-child(1) { mat-toolbar span:nth-child(1) {
@ -70,11 +78,40 @@ mat-toolbar .action-item a {
vertical-align: middle; vertical-align: middle;
} }
mat-sidenav-container.mat-drawer-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
mat-sidenav-container .mat-drawer-backdrop.mat-drawer-shown {
background-color: rgba(0, 0, 0, 0.8);
}
.income { .income {
color: #81C784; color: #81C784;
} }
.expense { .expense {
color: #E57373; color: #E57373;
} }
/**
* Forms
*/
.form {
padding: 1em;
color: #F1F1F1;
}
.form .mat-form-field,
.form .button {
display: block;
}
.form mat-radio-button {
padding-bottom: 15px;
}