added expense state (view, edit, add) logic

This commit is contained in:
Joe Arndt 2026-02-14 23:39:51 -06:00
parent 23132741a9
commit fed0f7908a
9 changed files with 180 additions and 117 deletions

View file

@ -0,0 +1,112 @@
import { Component, computed, input, signal } from '@angular/core';
import { Category, CategoryService } from '../../services/category.service';
import { Merchant, MerchantService } from '../../services/merchant.service';
import { Tag, TagService } from '../../services/tag.service';
import { form, FormField, min, required } from '@angular/forms/signals';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatSelectModule } from '@angular/material/select';
import { MatButtonModule } from '@angular/material/button';
import { provideNativeDateAdapter } from '@angular/material/core';
import { CreateExpense, Expense, ExpenseService } from '../../services/expense.service';
import {CurrencyPipe, DatePipe} from '@angular/common';
import {MatIcon} from '@angular/material/icon';
interface ExpenseForm {
date: Date | string;
cents: number;
category: Category | string;
merchant: Merchant | string;
note: string;
tags: Tag[];
}
@Component({
selector: 'app-expense',
imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, MatCardModule, MatAutocompleteModule, MatSelectModule, MatButtonModule, FormField, DatePipe, MatIcon, CurrencyPipe],
providers: [provideNativeDateAdapter(), DatePipe],
templateUrl: './expense.component.html',
styleUrl: './expense.component.scss',
})
export class ExpenseComponent {
public expense = input<Expense>();
protected expenseMode = signal<'view' | 'edit'>('view');
protected state = computed<'add' | 'view' | 'edit'>(() => this.expense() ? this.expenseMode() : 'add');
protected saving = signal(false);
protected categories = computed(() => this.categoryService.categories());
protected merchants = computed(() => this.merchantService.merchants());
protected tags = computed(() => this.tagService.tags());
protected enableSaveButton = computed(() => {
const dateValid = this.expenseForm.date().valid();
const centsValid = this.expenseForm.cents().valid();
const categoryValid = this.expenseForm.category().valid();
const merchantValid = this.expenseForm.merchant().valid();
const noteValid = this.expenseForm.note().valid();
return dateValid && centsValid && categoryValid && merchantValid && noteValid && !this.saving();
});
private lastSelectedDate: Date | undefined;
private expenseDate = computed(() => {
return this.expense()
? new Date(`${this.expense()?.year}-${this.expense()?.month}-${this.expense()?.day}`)
: this.lastSelectedDate ?? '';
});
private defaultForm: ExpenseForm = {
date: this.expenseDate(),
cents: this.expense()?.cents ?? NaN,
category: this.expense()?.category ?? '',
merchant: this.expense()?.merchant ?? '',
note: this.expense()?.note ?? '',
tags: this.expense()?.tags ?? []
};
private expenseModel = signal<ExpenseForm>(this.defaultForm);
public expenseForm = form(this.expenseModel, (schema) => {
required(schema.date);
required(schema.cents);
min(schema.cents, 1);
required(schema.category);
});
public constructor(private readonly categoryService: CategoryService,
private readonly merchantService: MerchantService,
private readonly tagService: TagService,
private readonly expenseService: ExpenseService,
private readonly datePipe: DatePipe) { }
public async saveClick(): Promise<void> {
const saveExpenseModel = this.expenseModel();
const date = this.datePipe.transform(saveExpenseModel.date, 'yyyy-MM-dd')?.split('-') ?? [];
const expense: CreateExpense = {
year: date[0],
month: date[1],
day: date[2],
cents: saveExpenseModel.cents,
category: saveExpenseModel.category as Category,
merchant: saveExpenseModel.merchant ? saveExpenseModel.merchant as Merchant : undefined,
note: saveExpenseModel.note ? saveExpenseModel.note : undefined,
tags: saveExpenseModel.tags
};
this.saving.set(true);
try {
await this.expenseService.postExpense(expense);
this.lastSelectedDate = saveExpenseModel.date ? saveExpenseModel.date as Date : undefined;
this.expenseModel.set({ ...this.defaultForm, date: saveExpenseModel.date });
this.expenseForm().reset(this.expenseModel());
}
catch (error) {
console.error(error);
}
finally {
this.saving.set(false);
}
}
public autocompleteDisplay(value: Merchant | Category) {
return value.name ?? null;
}
}