Use strings instead of numbers for id and generate client-side

This commit is contained in:
William Brawner 2021-01-01 04:14:44 +00:00
parent 06850c8b8e
commit 92f93861e9
29 changed files with 172 additions and 145 deletions

View file

@ -10,5 +10,5 @@
<textarea matInput [(ngModel)]="budget.description" placeholder="Description"></textarea> <textarea matInput [(ngModel)]="budget.description" placeholder="Description"></textarea>
</mat-form-field> </mat-form-field>
<button mat-raised-button color="accent" (click)="save()">Save</button> <button mat-raised-button color="accent" (click)="save()">Save</button>
<button class="button-delete" mat-raised-button color="warn" *ngIf="budget.id" (click)="delete()">Delete</button> <button class="button-delete" mat-raised-button color="warn" *ngIf="!create" (click)="delete()">Delete</button>
</div> </div>

View file

@ -12,6 +12,7 @@ import { TWIGS_SERVICE, TwigsService } from 'src/app/shared/twigs.service';
export class AddEditBudgetComponent { export class AddEditBudgetComponent {
@Input() title: string; @Input() title: string;
@Input() budget: Budget; @Input() budget: Budget;
@Input() create: boolean;
public users: UserPermission[]; public users: UserPermission[];
public searchedUsers: User[] = []; public searchedUsers: User[] = [];
public isLoading = false; public isLoading = false;
@ -28,16 +29,17 @@ export class AddEditBudgetComponent {
save(): void { save(): void {
let observable; let observable;
this.isLoading = true; this.isLoading = true;
if (this.budget.id) { if (this.create) {
// This is an existing budget, update it
observable = this.twigsService.updateBudget(this.budget.id, this.budget);
} else {
// This is a new budget, save it // This is a new budget, save it
observable = this.twigsService.createBudget( observable = this.twigsService.createBudget(
this.budget.id,
this.budget.name, this.budget.name,
this.budget.description, this.budget.description,
this.users this.users
); );
} else {
// This is an existing budget, update it
observable = this.twigsService.updateBudget(this.budget.id, this.budget);
} }
// TODO: Check if it was actually successful or not // TODO: Check if it was actually successful or not
observable.subscribe(val => { observable.subscribe(val => {

View file

@ -21,7 +21,7 @@ export class BudgetDetailsComponent implements OnInit, OnDestroy, Actionable {
public transactions: Transaction[]; public transactions: Transaction[];
public expenses: Category[] = []; public expenses: Category[] = [];
public income: Category[] = []; public income: Category[] = [];
categoryBalances: Map<number, number>; categoryBalances: Map<string, number>;
expectedIncome = 0; expectedIncome = 0;
actualIncome = 0; actualIncome = 0;
expectedExpenses = 0; expectedExpenses = 0;
@ -51,7 +51,7 @@ export class BudgetDetailsComponent implements OnInit, OnDestroy, Actionable {
} }
getBudget() { getBudget() {
const id = Number.parseInt(this.route.snapshot.paramMap.get('id')); const id = this.route.snapshot.paramMap.get('id');
this.twigsService.getBudget(id) this.twigsService.getBudget(id)
.subscribe(budget => { .subscribe(budget => {
this.app.setTitle(budget.name) this.app.setTitle(budget.name)
@ -108,7 +108,7 @@ export class BudgetDetailsComponent implements OnInit, OnDestroy, Actionable {
getCategories(): void { getCategories(): void {
this.twigsService.getCategories(this.budget.id).subscribe(categories => { this.twigsService.getCategories(this.budget.id).subscribe(categories => {
const categoryBalances = new Map<number, number>(); const categoryBalances = new Map<string, number>();
let categoryBalancesCount = 0; let categoryBalancesCount = 0;
console.log(categories); console.log(categories);
for (const category of categories) { for (const category of categories) {

View file

@ -1,7 +1,8 @@
import { UserPermission } from '../users/user'; import { UserPermission } from '../users/user';
import { uuidv4 } from '../shared/utils';
export class Budget { export class Budget {
id: number; id: string = uuidv4();
name: string; name: string;
description: string; description: string;
users: UserPermission[]; users: UserPermission[];

View file

@ -1 +1 @@
<app-add-edit-budget [title]="'Edit Budget'" [budget]="budget"></app-add-edit-budget> <app-add-edit-budget [title]="'Edit Budget'" [budget]="budget" [create]="false"></app-add-edit-budget>

View file

@ -18,7 +18,7 @@ export class EditBudgetComponent implements OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
const id = Number.parseInt(this.route.snapshot.paramMap.get('id')); const id = this.route.snapshot.paramMap.get('id');
this.twigsService.getBudget(id) this.twigsService.getBudget(id)
.subscribe(budget => { .subscribe(budget => {
this.budget = budget; this.budget = budget;

View file

@ -1 +1 @@
<app-add-edit-budget [title]="'Add Budget'" [budget]="budget"></app-add-edit-budget> <app-add-edit-budget [title]="'Add Budget'" [budget]="budget" [create]="true"></app-add-edit-budget>

View file

@ -14,9 +14,9 @@ import { TWIGS_SERVICE, TwigsService } from '../shared/twigs.service';
}) })
export class CategoriesComponent implements OnInit { export class CategoriesComponent implements OnInit {
budgetId: number; budgetId: string;
public categories: Category[]; public categories: Category[];
public categoryBalances: Map<number, number>; public categoryBalances: Map<string, number>;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -25,7 +25,7 @@ export class CategoriesComponent implements OnInit {
) { } ) { }
ngOnInit() { ngOnInit() {
this.budgetId = Number.parseInt(this.route.snapshot.paramMap.get('budgetId')); this.budgetId = this.route.snapshot.paramMap.get('budgetId');
this.app.setTitle('Categories') this.app.setTitle('Categories')
this.app.setBackEnabled(true); this.app.setBackEnabled(true);
this.getCategories(); this.getCategories();

View file

@ -13,7 +13,7 @@ import { Actionable } from '../../shared/actionable';
}) })
export class CategoryDetailsComponent implements OnInit, OnDestroy, Actionable { export class CategoryDetailsComponent implements OnInit, OnDestroy, Actionable {
budgetId: number; budgetId: string;
category: Category; category: Category;
public transactions: Transaction[]; public transactions: Transaction[];
@ -43,7 +43,7 @@ export class CategoryDetailsComponent implements OnInit, OnDestroy, Actionable {
} }
getCategory(): void { getCategory(): void {
const id = Number.parseInt(this.route.snapshot.paramMap.get('id')); const id = this.route.snapshot.paramMap.get('id');
this.twigsService.getCategory(id) this.twigsService.getCategory(id)
.subscribe(category => { .subscribe(category => {
category.amount /= 100; category.amount /= 100;

View file

@ -22,5 +22,5 @@
</mat-form-field> </mat-form-field>
--> -->
<button mat-raised-button color="accent" (click)="save()">Save</button> <button mat-raised-button color="accent" (click)="save()">Save</button>
<button class="button-delete" mat-raised-button color="warn" *ngIf="currentCategory.id" (click)="delete()">Delete</button> <button class="button-delete" mat-raised-button color="warn" *ngIf="!create" (click)="delete()">Delete</button>
</div> </div>

View file

@ -10,9 +10,10 @@ import { TWIGS_SERVICE, TwigsService } from 'src/app/shared/twigs.service';
}) })
export class CategoryFormComponent implements OnInit { export class CategoryFormComponent implements OnInit {
@Input() budgetId: number; @Input() budgetId: string;
@Input() title: string; @Input() title: string;
@Input() currentCategory: Category; @Input() currentCategory: Category;
@Input() create: boolean;
constructor( constructor(
private app: AppComponent, private app: AppComponent,
@ -26,10 +27,19 @@ export class CategoryFormComponent implements OnInit {
save(): void { save(): void {
let observable; let observable;
if (this.currentCategory.id) { if (this.create) {
// This is a new category, save it
observable = this.twigsService.createCategory(
this.currentCategory.id,
this.budgetId,
this.currentCategory.title,
this.currentCategory.description,
this.currentCategory.amount * 100,
this.currentCategory.expense
);
} else {
// This is an existing category, update it // This is an existing category, update it
observable = this.twigsService.updateCategory( observable = this.twigsService.updateCategory(
this.budgetId,
this.currentCategory.id, this.currentCategory.id,
{ {
name: this.currentCategory.title, name: this.currentCategory.title,
@ -39,15 +49,6 @@ export class CategoryFormComponent implements OnInit {
archived: this.currentCategory.archived archived: this.currentCategory.archived
} }
); );
} else {
// This is a new category, save it
observable = this.twigsService.createCategory(
this.budgetId,
this.currentCategory.title,
this.currentCategory.description,
this.currentCategory.amount * 100,
this.currentCategory.expense
);
} }
observable.subscribe(val => { observable.subscribe(val => {
this.app.goBack(); this.app.goBack();
@ -55,7 +56,7 @@ export class CategoryFormComponent implements OnInit {
} }
delete(): void { delete(): void {
this.twigsService.deleteCategory(this.budgetId, this.currentCategory.id).subscribe(() => { this.twigsService.deleteCategory(this.currentCategory.id).subscribe(() => {
this.app.goBack(); this.app.goBack();
}); });
} }

View file

@ -10,7 +10,7 @@ export class CategoryListComponent implements OnInit {
@Input() budgetId: string; @Input() budgetId: string;
@Input() categories: Category[]; @Input() categories: Category[];
@Input() categoryBalances: Map<number, number>; @Input() categoryBalances: Map<string, number>;
constructor() { } constructor() { }

View file

@ -1,9 +1,11 @@
import { uuidv4 } from '../shared/utils';
export class Category { export class Category {
id: number; id: string = uuidv4();
title: string; title: string;
description: string; description: string;
amount: number; amount: number;
expense: boolean; expense: boolean;
archived: boolean; archived: boolean;
budgetId: number; budgetId: string;
} }

View file

@ -1 +1 @@
<app-category-form [title]="'Edit Category'" [budgetId]="budgetId" [currentCategory]="category"></app-category-form> <app-category-form [title]="'Edit Category'" [budgetId]="budgetId" [currentCategory]="category" [create]="false"></app-category-form>

View file

@ -11,7 +11,7 @@ import { TWIGS_SERVICE, TwigsService } from '../../shared/twigs.service';
}) })
export class EditCategoryComponent implements OnInit { export class EditCategoryComponent implements OnInit {
budgetId: number; budgetId: string;
category: Category; category: Category;
constructor( constructor(
@ -26,7 +26,7 @@ export class EditCategoryComponent implements OnInit {
} }
getCategory(): void { getCategory(): void {
const id = Number.parseInt(this.route.snapshot.paramMap.get('id')); const id = this.route.snapshot.paramMap.get('id');
this.twigsService.getCategory(id) this.twigsService.getCategory(id)
.subscribe(category => { .subscribe(category => {
category.amount /= 100; category.amount /= 100;

View file

@ -1 +1 @@
<app-category-form [title]="'Add Category'" [budgetId]="budgetId" [currentCategory]="category"></app-category-form> <app-category-form [title]="'Add Category'" [budgetId]="budgetId" [currentCategory]="category" [create]="true"></app-category-form>

View file

@ -67,7 +67,7 @@ export class TwigsHttpService implements TwigsService {
return this.budgets; return this.budgets;
} }
getBudget(id: number): Observable<Budget> { getBudget(id: string): Observable<Budget> {
return new Observable(emitter => { return new Observable(emitter => {
var cachedBudget: Budget var cachedBudget: Budget
if (this.budgets.value) { if (this.budgets.value) {
@ -96,11 +96,13 @@ export class TwigsHttpService implements TwigsService {
} }
createBudget( createBudget(
id: string,
name: string, name: string,
description: string, description: string,
users: UserPermission[], users: UserPermission[],
): Observable<Budget> { ): Observable<Budget> {
const params = { const params = {
'id': id,
'name': name, 'name': name,
'description': description, 'description': description,
'users': users.map(userPermission => { 'users': users.map(userPermission => {
@ -120,7 +122,7 @@ export class TwigsHttpService implements TwigsService {
})) }))
} }
updateBudget(id: number, changes: object): Observable<Budget> { updateBudget(id: string, changes: object): Observable<Budget> {
let budget = changes as Budget; let budget = changes as Budget;
const params = { const params = {
'name': budget.name, 'name': budget.name,
@ -146,7 +148,7 @@ export class TwigsHttpService implements TwigsService {
})); }));
} }
deleteBudget(id: number): Observable<void> { deleteBudget(id: String): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/budgets/${id}`, this.options) return this.http.delete<void>(`${this.apiUrl}/budgets/${id}`, this.options)
.pipe(map(() => { .pipe(map(() => {
var updatedBudgets: Budget[] = JSON.parse(JSON.stringify(this.budgets.value)); var updatedBudgets: Budget[] = JSON.parse(JSON.stringify(this.budgets.value));
@ -161,7 +163,7 @@ export class TwigsHttpService implements TwigsService {
} }
// Categories // Categories
getCategories(budgetId: number, count?: number): Observable<Category[]> { getCategories(budgetId: string, count?: number): Observable<Category[]> {
const params = { const params = {
params: new HttpParams() params: new HttpParams()
.set('budgetIds', `${budgetId}`) .set('budgetIds', `${budgetId}`)
@ -169,17 +171,18 @@ export class TwigsHttpService implements TwigsService {
return this.http.get<Category[]>(`${this.apiUrl}/categories`, Object.assign(params, this.options)); return this.http.get<Category[]>(`${this.apiUrl}/categories`, Object.assign(params, this.options));
} }
getCategory(id: number): Observable<Category> { getCategory(id: string): Observable<Category> {
return this.http.get<Category>(`${this.apiUrl}/categories/${id}`, this.options); return this.http.get<Category>(`${this.apiUrl}/categories/${id}`, this.options);
} }
getCategoryBalance(id: number): Observable<number> { getCategoryBalance(id: string): Observable<number> {
return this.http.get<any>(`${this.apiUrl}/categories/${id}/balance`, this.options) return this.http.get<any>(`${this.apiUrl}/categories/${id}/balance`, this.options)
.pipe(map(obj => obj.balance)); .pipe(map(obj => obj.balance));
} }
createCategory(budgetId: number, name: string, description: string, amount: number, isExpense: boolean): Observable<Category> { createCategory(id: string, budgetId: string, name: string, description: string, amount: number, isExpense: boolean): Observable<Category> {
const params = { const params = {
'id': id,
'title': name, 'title': name,
'description': description, 'description': description,
'amount': amount, 'amount': amount,
@ -189,18 +192,18 @@ export class TwigsHttpService implements TwigsService {
return this.http.post<Category>(this.apiUrl + '/categories', params, this.options); return this.http.post<Category>(this.apiUrl + '/categories', params, this.options);
} }
updateCategory(budgetId: number, id: number, changes: object): Observable<Category> { updateCategory(id: string, changes: object): Observable<Category> {
return this.http.put<Category>(`${this.apiUrl}/categories/${id}`, changes, this.options); return this.http.put<Category>(`${this.apiUrl}/categories/${id}`, changes, this.options);
} }
deleteCategory(budgetId: number, id: number): Observable<void> { deleteCategory(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/categories/${id}`, this.options); return this.http.delete<void>(`${this.apiUrl}/categories/${id}`, this.options);
} }
// Transactions // Transactions
getTransactions( getTransactions(
budgetId?: number, budgetId?: string,
categoryId?: number, categoryId?: string,
count?: number, count?: number,
from?: Date from?: Date
): Observable<Transaction[]> { ): Observable<Transaction[]> {
@ -224,7 +227,7 @@ export class TwigsHttpService implements TwigsService {
})); }));
} }
getTransaction(id: number): Observable<Transaction> { getTransaction(id: string): Observable<Transaction> {
return this.http.get<Transaction>(`${this.apiUrl}/transactions/${id}`, this.options) return this.http.get<Transaction>(`${this.apiUrl}/transactions/${id}`, this.options)
.pipe(map(transaction => { .pipe(map(transaction => {
transaction.date = new Date(transaction.date); transaction.date = new Date(transaction.date);
@ -233,15 +236,17 @@ export class TwigsHttpService implements TwigsService {
} }
createTransaction( createTransaction(
budgetId: number, id: string,
budgetId: string,
name: string, name: string,
description: string, description: string,
amount: number, amount: number,
date: Date, date: Date,
expense: boolean, expense: boolean,
category: number category: string
): Observable<Transaction> { ): Observable<Transaction> {
const params = { const params = {
'id': id,
'title': name, 'title': name,
'description': description, 'description': description,
'date': date.toISOString(), 'date': date.toISOString(),
@ -253,11 +258,11 @@ export class TwigsHttpService implements TwigsService {
return this.http.post<Transaction>(this.apiUrl + '/transactions', params, this.options); return this.http.post<Transaction>(this.apiUrl + '/transactions', params, this.options);
} }
updateTransaction(budgetId: number, id: number, changes: object): Observable<Transaction> { updateTransaction(id: string, changes: object): Observable<Transaction> {
return this.http.put<Transaction>(`${this.apiUrl}/transactions/${id}`, changes, this.options); return this.http.put<Transaction>(`${this.apiUrl}/transactions/${id}`, changes, this.options);
} }
deleteTransaction(budgetId: number, id: number): Observable<void> { deleteTransaction(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/transactions/${id}`, this.options); return this.http.delete<void>(`${this.apiUrl}/transactions/${id}`, this.options);
} }
@ -267,7 +272,7 @@ export class TwigsHttpService implements TwigsService {
} }
getUsersByUsername(username: string): Observable<User[]> { getUsersByUsername(username: string): Observable<User[]> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.error("Not yet implemented") subscriber.error("Not yet implemented")
}); });
} }

View file

@ -6,6 +6,7 @@ import { TwigsService } from './twigs.service';
import { Budget } from '../budgets/budget'; import { Budget } from '../budgets/budget';
import { Category } from '../categories/category'; import { Category } from '../categories/category';
import { Transaction } from '../transactions/transaction'; import { Transaction } from '../transactions/transaction';
import { uuidv4 } from '../shared/utils';
/** /**
* This is intended to be a very simple implementation of the TwigsService used for testing out the UI and quickly iterating on it. * This is intended to be a very simple implementation of the TwigsService used for testing out the UI and quickly iterating on it.
@ -20,14 +21,14 @@ export class TwigsLocalService implements TwigsService {
private http: HttpClient private http: HttpClient
) { } ) { }
private users: User[] = [new User(1, 'test', 'test@example.com')]; private users: User[] = [new User(uuidv4(), 'test', 'test@example.com')];
private budgets: Budget[] = []; private budgets: Budget[] = [];
private transactions: Transaction[] = []; private transactions: Transaction[] = [];
private categories: Category[] = []; private categories: Category[] = [];
// Auth // Auth
login(email: string, password: string): Observable<User> { login(email: string, password: string): Observable<User> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const filteredUsers = this.users.filter(user => { const filteredUsers = this.users.filter(user => {
return (user.email === email || user.username === email); return (user.email === email || user.username === email);
}); });
@ -40,11 +41,11 @@ export class TwigsLocalService implements TwigsService {
} }
register(username: string, email: string, password: string): Observable<User> { register(username: string, email: string, password: string): Observable<User> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const user = new User(); const user = new User();
user.username = username; user.username = username;
user.email = email; user.email = email;
user.id = this.users.length + 1; user.id = uuidv4();
this.users.push(user); this.users.push(user);
subscriber.next(user); subscriber.next(user);
subscriber.complete(); subscriber.complete();
@ -52,21 +53,21 @@ export class TwigsLocalService implements TwigsService {
} }
logout(): Observable<void> { logout(): Observable<void> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.complete(); subscriber.complete();
}); });
} }
// Budgets // Budgets
getBudgets(): Observable<Budget[]> { getBudgets(): Observable<Budget[]> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.next(this.budgets); subscriber.next(this.budgets);
subscriber.complete(); subscriber.complete();
}); });
} }
getBudget(id: number): Observable<Budget> { getBudget(id: string): Observable<Budget> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const budget = this.budgets.filter(it => { const budget = this.budgets.filter(it => {
return it.id === id; return it.id === id;
})[0]; })[0];
@ -80,24 +81,25 @@ export class TwigsLocalService implements TwigsService {
} }
createBudget( createBudget(
id: string,
name: string, name: string,
description: string, description: string,
users: UserPermission[], users: UserPermission[],
): Observable<Budget> { ): Observable<Budget> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const budget = new Budget(); const budget = new Budget();
budget.name = name; budget.name = name;
budget.description = description; budget.description = description;
budget.users = users; budget.users = users;
budget.id = this.budgets.length + 1; budget.id = id;
this.budgets.push(budget); this.budgets.push(budget);
subscriber.next(budget); subscriber.next(budget);
subscriber.complete(); subscriber.complete();
}); });
} }
updateBudget(id: number, changes: object): Observable<Budget> { updateBudget(id: string, changes: object): Observable<Budget> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const budget = this.budgets.filter(it => { const budget = this.budgets.filter(it => {
return it.id === id; return it.id === id;
})[0]; })[0];
@ -121,8 +123,8 @@ export class TwigsLocalService implements TwigsService {
}); });
} }
deleteBudget(id: number): Observable<void> { deleteBudget(id: string): Observable<void> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const budget = this.budgets.filter(it => { const budget = this.budgets.filter(it => {
return budget.id === id; return budget.id === id;
})[0]; })[0];
@ -137,8 +139,8 @@ export class TwigsLocalService implements TwigsService {
} }
// Categories // Categories
getCategories(budgetId: number, count?: number): Observable<Category[]> { getCategories(budgetId: string, count?: number): Observable<Category[]> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.next(this.categories.filter(category => { subscriber.next(this.categories.filter(category => {
return category.budgetId === budgetId; return category.budgetId === budgetId;
})); }));
@ -146,21 +148,21 @@ export class TwigsLocalService implements TwigsService {
}); });
} }
getCategory(id: number): Observable<Category> { getCategory(id: string): Observable<Category> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.next(this.findById(this.categories, id)); subscriber.next(this.findById(this.categories, id));
subscriber.complete(); subscriber.complete();
}); });
} }
getCategoryBalance(id: number): Observable<number> { getCategoryBalance(id: string): Observable<number> {
return new Observable(emitter => { return new Observable(emitter => {
emitter.next(20); emitter.next(20);
emitter.complete() emitter.complete()
}) })
} }
createCategory(budgetId: number, name: string, description: string, amount: number, isExpense: boolean): Observable<Category> { createCategory(id: string, budgetId: string, name: string, description: string, amount: number, isExpense: boolean): Observable<Category> {
return Observable.create(subscriber => { return Observable.create(subscriber => {
const category = new Category(); const category = new Category();
category.title = name; category.title = name;
@ -168,15 +170,15 @@ export class TwigsLocalService implements TwigsService {
category.amount = amount; category.amount = amount;
category.expense = isExpense; category.expense = isExpense;
category.budgetId = budgetId; category.budgetId = budgetId;
category.id = this.categories.length + 1; category.id = id;
this.categories.push(category); this.categories.push(category);
subscriber.next(category); subscriber.next(category);
subscriber.complete(); subscriber.complete();
}); });
} }
updateCategory(budgetId: number, id: number, changes: object): Observable<Category> { updateCategory(id: string, changes: object): Observable<Category> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const category = this.findById(this.categories, id); const category = this.findById(this.categories, id);
if (category) { if (category) {
const index = this.categories.indexOf(category); const index = this.categories.indexOf(category);
@ -199,8 +201,8 @@ export class TwigsLocalService implements TwigsService {
}); });
} }
deleteCategory(budgetId: number, id: number): Observable<void> { deleteCategory(id: string): Observable<void> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const category = this.findById(this.categories, id); const category = this.findById(this.categories, id);
if (category) { if (category) {
const index = this.categories.indexOf(category); const index = this.categories.indexOf(category);
@ -213,8 +215,8 @@ export class TwigsLocalService implements TwigsService {
} }
// Transactions // Transactions
getTransactions(budgetId?: number, categoryId?: number, count?: number): Observable<Transaction[]> { getTransactions(budgetId?: string, categoryId?: string, count?: number): Observable<Transaction[]> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.next(this.transactions.filter(transaction => { subscriber.next(this.transactions.filter(transaction => {
let include = true; let include = true;
if (budgetId) { if (budgetId) {
@ -229,23 +231,24 @@ export class TwigsLocalService implements TwigsService {
}); });
} }
getTransaction(id: number): Observable<Transaction> { getTransaction(id: string): Observable<Transaction> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.next(this.findById(this.transactions, id)); subscriber.next(this.findById(this.transactions, id));
subscriber.complete(); subscriber.complete();
}); });
} }
createTransaction( createTransaction(
budgetId: number, id: string,
budgetId: string,
name: string, name: string,
description: string, description: string,
amount: number, amount: number,
date: Date, date: Date,
isExpense: boolean, isExpense: boolean,
category: number category: string
): Observable<Transaction> { ): Observable<Transaction> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const transaction = new Transaction(); const transaction = new Transaction();
transaction.title = name; transaction.title = name;
transaction.description = description; transaction.description = description;
@ -254,15 +257,15 @@ export class TwigsLocalService implements TwigsService {
transaction.expense = isExpense; transaction.expense = isExpense;
transaction.categoryId = category; transaction.categoryId = category;
transaction.budgetId = budgetId; transaction.budgetId = budgetId;
transaction.id = this.transactions.length + 1; transaction.id = uuidv4();
this.transactions.push(transaction); this.transactions.push(transaction);
subscriber.next(transaction); subscriber.next(transaction);
subscriber.complete(); subscriber.complete();
}); });
} }
updateTransaction(budgetId: number, id: number, changes: object): Observable<Transaction> { updateTransaction(id: string, changes: object): Observable<Transaction> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const transaction = this.findById(this.transactions, id); const transaction = this.findById(this.transactions, id);
if (transaction) { if (transaction) {
const index = this.transactions.indexOf(transaction); const index = this.transactions.indexOf(transaction);
@ -289,8 +292,8 @@ export class TwigsLocalService implements TwigsService {
}); });
} }
deleteTransaction(budgetId: number, id: number): Observable<void> { deleteTransaction(id: string): Observable<void> {
return Observable.create(subscriber => { return new Observable(subscriber => {
const transaction = this.findById(this.transactions, id); const transaction = this.findById(this.transactions, id);
if (transaction) { if (transaction) {
const index = this.transactions.indexOf(transaction); const index = this.transactions.indexOf(transaction);
@ -304,13 +307,13 @@ export class TwigsLocalService implements TwigsService {
// Users // Users
getProfile(): Observable<User> { getProfile(): Observable<User> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.error("Not yet implemented") subscriber.error("Not yet implemented")
}); });
} }
getUsersByUsername(username: string): Observable<User[]> { getUsersByUsername(username: string): Observable<User[]> {
return Observable.create(subscriber => { return new Observable(subscriber => {
subscriber.next(this.users.filter(user => user.username.indexOf(username) > -1 )); subscriber.next(this.users.filter(user => user.username.indexOf(username) > -1 ));
}); });
} }
@ -323,7 +326,7 @@ export class TwigsLocalService implements TwigsService {
}); });
} }
private findById<T>(items: T[], id: number): T { private findById<T>(items: T[], id: string): T {
return items.filter(item => { return items.filter(item => {
return item['id'] === id; return item['id'] === id;
})[0]; })[0];

View file

@ -13,42 +13,44 @@ export interface TwigsService {
// Budgets // Budgets
getBudgets(): Observable<Budget[]>; getBudgets(): Observable<Budget[]>;
getBudget(id: number): Observable<Budget>; getBudget(id: string): Observable<Budget>;
createBudget( createBudget(
id: string,
name: string, name: string,
description: string, description: string,
users: UserPermission[], users: UserPermission[],
): Observable<Budget>; ): Observable<Budget>;
updateBudget(id: number, changes: object): Observable<Budget>; updateBudget(id: string, changes: object): Observable<Budget>;
deleteBudget(id: number): Observable<void>; deleteBudget(id: string): Observable<void>;
// Categories // Categories
getCategories(budgetId?: number, count?: number): Observable<Category[]>; getCategories(budgetId?: string, count?: number): Observable<Category[]>;
getCategory(id: number): Observable<Category>; getCategory(id: string): Observable<Category>;
getCategoryBalance(id: number): Observable<number>; getCategoryBalance(id: string): Observable<number>;
createCategory(budgetId: number, name: string, description: string, amount: number, isExpense: boolean): Observable<Category>; createCategory(id: string, budgetId: string, name: string, description: string, amount: number, isExpense: boolean): Observable<Category>;
updateCategory(budgetId: number, id: number, changes: object): Observable<Category>; updateCategory(id: string, changes: object): Observable<Category>;
deleteCategory(budgetId: number, id: number): Observable<void>; deleteCategory(id: string): Observable<void>;
// Transactions // Transactions
getTransactions( getTransactions(
budgetId?: number, budgetId?: string,
categoryId?: number, categoryId?: string,
count?: number, count?: number,
from?: Date from?: Date
): Observable<Transaction[]>; ): Observable<Transaction[]>;
getTransaction(id: number): Observable<Transaction>; getTransaction(id: string): Observable<Transaction>;
createTransaction( createTransaction(
budgetId: number, id: string,
budgetId: string,
name: string, name: string,
description: string, description: string,
amount: number, amount: number,
date: Date, date: Date,
isExpense: boolean, isExpense: boolean,
category: number category: string
): Observable<Transaction>; ): Observable<Transaction>;
updateTransaction(budgetId: number, id: number, changes: object): Observable<Transaction>; updateTransaction(id: string, changes: object): Observable<Transaction>;
deleteTransaction(budgetId: number, id: number): Observable<void>; deleteTransaction(id: string): Observable<void>;
getProfile(): Observable<User>; getProfile(): Observable<User>;
getUsersByUsername(username: string): Observable<User[]>; getUsersByUsername(username: string): Observable<User[]>;

6
src/app/shared/utils.ts Normal file
View file

@ -0,0 +1,6 @@
export function uuidv4(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

View file

@ -29,5 +29,5 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<button mat-raised-button color="accent" (click)="save()">Save</button> <button mat-raised-button color="accent" (click)="save()">Save</button>
<button class="button-delete" mat-raised-button color="warn" *ngIf="currentTransaction.id" (click)="delete()">Delete</button> <button class="button-delete" mat-raised-button color="warn" *ngIf="!create" (click)="delete()">Delete</button>
</div> </div>

View file

@ -14,7 +14,8 @@ import { MatRadioChange } from '@angular/material/radio';
export class AddEditTransactionComponent implements OnInit, OnChanges { export class AddEditTransactionComponent implements OnInit, OnChanges {
@Input() title: string; @Input() title: string;
@Input() currentTransaction: Transaction; @Input() currentTransaction: Transaction;
@Input() budgetId: number; @Input() budgetId: string;
@Input() create: boolean
public transactionType = TransactionType; public transactionType = TransactionType;
public categories: Category[]; public categories: Category[];
public rawAmount: string; public rawAmount: string;
@ -48,15 +49,15 @@ export class AddEditTransactionComponent implements OnInit, OnChanges {
} }
const d = new Date(changes.currentTransaction.currentValue.date * 1000); const d = new Date(changes.currentTransaction.currentValue.date * 1000);
this.transactionDate = d.toLocaleDateString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit'}); this.transactionDate = d.toLocaleDateString(undefined, { year: 'numeric', month: '2-digit', day: '2-digit' });
this.currentTime = d.toLocaleTimeString(undefined, {hour: '2-digit', hour12: false, minute: '2-digit'}); this.currentTime = d.toLocaleTimeString(undefined, { hour: '2-digit', hour12: false, minute: '2-digit' });
} }
updateCategories(change: MatRadioChange) { updateCategories(change: MatRadioChange) {
this.twigsService.getCategories(this.budgetId) this.twigsService.getCategories(this.budgetId)
.subscribe(newCategories => { .subscribe(newCategories => {
this.categories = newCategories.filter(category => category.expense === change.value) this.categories = newCategories.filter(category => category.expense === change.value)
}) })
} }
save(): void { save(): void {
@ -71,10 +72,21 @@ export class AddEditTransactionComponent implements OnInit, OnChanges {
const timeParts = this.currentTime.split(':'); const timeParts = this.currentTime.split(':');
this.currentTransaction.date.setHours(parseInt(timeParts[0], 10)); this.currentTransaction.date.setHours(parseInt(timeParts[0], 10));
this.currentTransaction.date.setMinutes(parseInt(timeParts[1], 10)); this.currentTransaction.date.setMinutes(parseInt(timeParts[1], 10));
if (this.currentTransaction.id) { if (this.create) {
// This is a new transaction, save it
observable = this.twigsService.createTransaction(
this.currentTransaction.id,
this.budgetId,
this.currentTransaction.title,
this.currentTransaction.description,
this.currentTransaction.amount * 100,
this.currentTransaction.date,
this.currentTransaction.expense,
this.currentTransaction.categoryId,
);
} else {
// This is an existing transaction, update it // This is an existing transaction, update it
observable = this.twigsService.updateTransaction( observable = this.twigsService.updateTransaction(
this.budgetId,
this.currentTransaction.id, this.currentTransaction.id,
{ {
name: this.currentTransaction.title, name: this.currentTransaction.title,
@ -85,17 +97,6 @@ export class AddEditTransactionComponent implements OnInit, OnChanges {
expense: this.currentTransaction.expense expense: this.currentTransaction.expense
} }
); );
} else {
// This is a new transaction, save it
observable = this.twigsService.createTransaction(
this.budgetId,
this.currentTransaction.title,
this.currentTransaction.description,
this.currentTransaction.amount * 100,
this.currentTransaction.date,
this.currentTransaction.expense,
this.currentTransaction.categoryId,
);
} }
observable.subscribe(val => { observable.subscribe(val => {
@ -104,7 +105,7 @@ export class AddEditTransactionComponent implements OnInit, OnChanges {
} }
delete(): void { delete(): void {
this.twigsService.deleteTransaction(this.budgetId, this.currentTransaction.id).subscribe(() => { this.twigsService.deleteTransaction(this.currentTransaction.id).subscribe(() => {
this.app.goBack(); this.app.goBack();
}); });
} }

View file

@ -1 +1 @@
<app-add-edit-transaction [budgetId]="budgetId" [title]="'Add Transaction'" [currentTransaction]="transaction"></app-add-edit-transaction> <app-add-edit-transaction [budgetId]="budgetId" [title]="'Add Transaction'" [currentTransaction]="transaction" [create]="true"></app-add-edit-transaction>

View file

@ -1 +1 @@
<app-add-edit-transaction [budgetId]="budgetId" [title]="'Edit Transaction'" [currentTransaction]="transaction" *ngIf="transaction"></app-add-edit-transaction> <app-add-edit-transaction [budgetId]="budgetId" [title]="'Edit Transaction'" [currentTransaction]="transaction" *ngIf="transaction" [create]="true"></app-add-edit-transaction>

View file

@ -10,7 +10,7 @@ import { TWIGS_SERVICE, TwigsService } from 'src/app/shared/twigs.service';
}) })
export class TransactionDetailsComponent implements OnInit { export class TransactionDetailsComponent implements OnInit {
budgetId: number; budgetId: string;
transaction: Transaction; transaction: Transaction;
constructor( constructor(
@ -23,7 +23,7 @@ export class TransactionDetailsComponent implements OnInit {
} }
getTransaction(): void { getTransaction(): void {
const id = Number.parseInt(this.route.snapshot.paramMap.get('id')); const id = this.route.snapshot.paramMap.get('id');
this.twigsService.getTransaction(id) this.twigsService.getTransaction(id)
.subscribe(transaction => { .subscribe(transaction => {
transaction.amount /= 100; transaction.amount /= 100;

View file

@ -9,8 +9,8 @@ import { TWIGS_SERVICE, TwigsService } from '../../shared/twigs.service';
}) })
export class TransactionListComponent implements OnInit { export class TransactionListComponent implements OnInit {
@Input() budgetId: number; @Input() budgetId: string;
@Input() categoryId?: number; @Input() categoryId?: string;
public transactions: Transaction[]; public transactions: Transaction[];
constructor( constructor(

View file

@ -1,11 +1,13 @@
import { uuidv4 } from '../shared/utils';
export class Transaction { export class Transaction {
id: number; id: string = uuidv4();
title: string; title: string;
description: string = null; description: string = null;
date: Date = new Date(); date: Date = new Date();
amount: number; amount: number;
expense = true; expense = true;
categoryId: number; categoryId: string;
budgetId: number; budgetId: string;
createdBy: number; createdBy: string;
} }

View file

@ -1,9 +1,11 @@
import { uuidv4 } from "../shared/utils";
export class User { export class User {
id: number; id: string = uuidv4();
username: string; username: string;
email: string; email: string;
constructor(id?: number, username?: string, email?: string) { constructor(id?: string, username?: string, email?: string) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.email = email; this.email = email;

View file

@ -1,4 +1,4 @@
export const environment = { export const environment = {
production: false, production: false,
apiUrl: 'https://3000code.brawner.home' apiUrl: 'https://3000code.wbrawner.com'
}; };