diff --git a/src/app/components/expense-list/expense-list.component.html b/src/app/components/expense-list/expense-list.component.html
index ffd5211..18a7201 100644
--- a/src/app/components/expense-list/expense-list.component.html
+++ b/src/app/components/expense-list/expense-list.component.html
@@ -1,4 +1,5 @@
+
Expenses
@for (expense of expenses(); track expense.id) {
}
diff --git a/src/app/components/expense-list/expense-list.component.scss b/src/app/components/expense-list/expense-list.component.scss
index e69de29..818821c 100644
--- a/src/app/components/expense-list/expense-list.component.scss
+++ b/src/app/components/expense-list/expense-list.component.scss
@@ -0,0 +1,4 @@
+.expense-list-container {
+ display: grid;
+ gap: 1rem;
+}
diff --git a/src/app/components/expense/expense-add/expense-add.component.html b/src/app/components/expense/expense-add/expense-add.component.html
deleted file mode 100644
index a881b02..0000000
--- a/src/app/components/expense/expense-add/expense-add.component.html
+++ /dev/null
@@ -1 +0,0 @@
-
expense-add works!
diff --git a/src/app/components/expense/expense-add/expense-add.component.scss b/src/app/components/expense/expense-add/expense-add.component.scss
deleted file mode 100644
index e69de29..0000000
diff --git a/src/app/components/expense/expense-add/expense-add.component.ts b/src/app/components/expense/expense-add/expense-add.component.ts
deleted file mode 100644
index 5726791..0000000
--- a/src/app/components/expense/expense-add/expense-add.component.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'app-expense-add',
- imports: [],
- templateUrl: './expense-add.component.html',
- styleUrl: './expense-add.component.scss',
-})
-export class ExpenseAddComponent {
-
-}
diff --git a/src/app/components/expense/expense-form/expense-form.component.html b/src/app/components/expense/expense-form/expense-form.component.html
index 9e5b53c..4822a8b 100644
--- a/src/app/components/expense/expense-form/expense-form.component.html
+++ b/src/app/components/expense/expense-form/expense-form.component.html
@@ -1,24 +1,19 @@
Date
-
-
-
-
-
-
+
Cents
-
+
Category
-
+
@for (category of categories(); track category.id) {
{{ category.name }}
@@ -28,7 +23,7 @@
Merchant
-
+
@for (merchant of merchants(); track merchant.id) {
{{ merchant.name }}
@@ -38,15 +33,15 @@
Note
-
+
Tags
-
-
-
-
-
+
+ @for (tag of tags(); track tag.id) {
+ {{ tag.name }}
+ }
+
diff --git a/src/app/components/expense/expense-form/expense-form.component.scss b/src/app/components/expense/expense-form/expense-form.component.scss
index e69de29..2e8b1c2 100644
--- a/src/app/components/expense/expense-form/expense-form.component.scss
+++ b/src/app/components/expense/expense-form/expense-form.component.scss
@@ -0,0 +1,20 @@
+.expense-form-container {
+ display: grid;
+
+ mat-form-field {
+ width: 100%;
+ }
+}
+
+@media (min-width: 550px) {
+ .expense-form-container {
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ }
+}
+
+@media (min-width: 800px) {
+ .expense-form-container {
+ grid-template-columns: 1fr 1fr 1fr;
+ }
+}
diff --git a/src/app/components/expense/expense-form/expense-form.component.ts b/src/app/components/expense/expense-form/expense-form.component.ts
index 884758d..02b93c5 100644
--- a/src/app/components/expense/expense-form/expense-form.component.ts
+++ b/src/app/components/expense/expense-form/expense-form.component.ts
@@ -1,87 +1,96 @@
-import {Component, computed, input, output, signal} from '@angular/core';
+import { Component, computed, effect, input, OnInit, output, signal } from '@angular/core';
+import { min, required, disabled, form, FormField, Schema, schema } from '@angular/forms/signals';
import { Expense } from '../../../services/expense.service';
-import { Category } from '../../../services/category.service';
-import { Merchant } from '../../../services/merchant.service';
-import { Tag } from '../../../services/tag.service';
+import { Category, CategoryService } from '../../../services/category.service';
+import { Merchant, MerchantService } from '../../../services/merchant.service';
+import { Tag, TagService } from '../../../services/tag.service';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
-import {form, min, required} from '@angular/forms/signals';
+import { provideNativeDateAdapter } from '@angular/material/core';
interface ExpenseForm {
- date: Date | string;
+ date: Date;
cents: number;
- category: Category | string;
- merchant: Merchant | string;
+ category: Category;
+ merchant: Merchant;
note: string;
tags: Tag[];
}
@Component({
selector: 'app-expense-form',
- imports: [
- MatAutocompleteModule,
- MatDatepickerModule,
- MatInputModule,
- MatSelectModule
- ],
+ imports: [MatAutocompleteModule, MatDatepickerModule, MatInputModule, MatSelectModule, FormField],
+ providers: [provideNativeDateAdapter()],
templateUrl: './expense-form.component.html',
styleUrl: './expense-form.component.scss',
})
-export class ExpenseFormComponent {
- public disabled = input
(); // TODO: should this even exist?
- public expense = input(undefined);
- public categories = input([]);
- public merchants = input([]);
- public tags = input([]);
+export class ExpenseFormComponent implements OnInit {
+ public expense = input();
+ public disabled = input(false);
public valid = output();
- public value = output(); // TODO: form value or expense value?
-
-
- // 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();
- // });
+ public dirty = output()
+ public value = output();
+ private formValid = computed(() => this.form().valid() && this.form().dirty());
+ private formDirty = computed(() => this.form().dirty() && this.form().touched());
private lastSelectedDate = signal(undefined);
+ private formData = computed(() => this.buildForm(this.expense()));
+ private formModel = signal(this.formData());
- private expenseDate = computed(() => {
- const lastDate = this.lastSelectedDate();
- const expense = this.expense();
+ protected categories = computed(() => this.categoryService.categories());
+ protected merchants = computed(() => this.merchantService.merchants());
+ protected tags = computed(() => this.tagService.tags());
+ protected form = form(this.formModel, this.buildFormOptions());
- if (expense) {
-
- }
- return this.expense()
- ? new Date()
- : this.lastSelectedDate ?? '';
- });
-
- private defaultForm: ExpenseForm = {
- date: new Date(),
- cents: this.expense()?.cents ?? NaN,
- category: this.expense()?.category ?? '',
- merchant: this.expense()?.merchant ?? '',
- note: this.expense()?.note ?? '',
- tags: this.expense()?.tags ?? []
- };
- private expenseModel = signal(this.defaultForm);
- public expenseForm = form(this.expenseModel, (schema) => {
- required(schema.date);
- required(schema.cents);
- min(schema.cents, 1);
- required(schema.category);
- });
+ constructor(private readonly categoryService: CategoryService,
+ private readonly merchantService: MerchantService,
+ private readonly tagService: TagService) {
+ effect(() => {
+ this.valid.emit(this.formValid());
+ this.dirty.emit(this.formDirty());
+ });
+ }
+ public ngOnInit(): void {
+ console.log({ expense: this.expense() });
+ this.formModel.set(this.formData());
+ this.form().reset();
+ }
public autocompleteDisplay(value: Merchant | Category) {
- return value.name ?? null;
+ return value?.name ?? null;
+ }
+
+ private buildForm(expense?: Expense): ExpenseForm {
+ let formData = { date: this.lastSelectedDate() ?? '', cents: '', category: '', merchant: '', note: '', tags: ''} as unknown as ExpenseForm
+ if (expense) {
+ formData = {
+ date: new Date(expense.date.toString()),
+ cents: expense.cents,
+ category: expense.category,
+ merchant: expense.merchant,
+ note: expense.note,
+ tags: expense.tags ?? []
+ } as ExpenseForm;
+ }
+ return formData;
+ }
+
+ private buildFormOptions(): Schema {
+ return schema((schema) => {
+ required(schema.date);
+ required(schema.cents);
+ required(schema.category);
+ min(schema.cents, 1);
+ disabled(schema.date, () => this.disabled());
+ disabled(schema.cents, () => this.disabled());
+ disabled(schema.category, () => this.disabled());
+ disabled(schema.merchant, () => this.disabled());
+ disabled(schema.note, () => this.disabled());
+ disabled(schema.tags, () => this.disabled());
+ });
}
}
diff --git a/src/app/components/expense/expense-view/expense-view.component.html b/src/app/components/expense/expense-view/expense-view.component.html
deleted file mode 100644
index fb4707d..0000000
--- a/src/app/components/expense/expense-view/expense-view.component.html
+++ /dev/null
@@ -1 +0,0 @@
-expense-view works!
diff --git a/src/app/components/expense/expense-view/expense-view.component.scss b/src/app/components/expense/expense-view/expense-view.component.scss
deleted file mode 100644
index e69de29..0000000
diff --git a/src/app/components/expense/expense-view/expense-view.component.ts b/src/app/components/expense/expense-view/expense-view.component.ts
deleted file mode 100644
index 3b626fd..0000000
--- a/src/app/components/expense/expense-view/expense-view.component.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'app-expense-view',
- imports: [],
- templateUrl: './expense-view.component.html',
- styleUrl: './expense-view.component.scss',
-})
-export class ExpenseViewComponent {
-
-}
diff --git a/src/app/components/expense/expense.component.html b/src/app/components/expense/expense.component.html
index c16825a..b3916d4 100644
--- a/src/app/components/expense/expense.component.html
+++ b/src/app/components/expense/expense.component.html
@@ -1,135 +1,29 @@
diff --git a/src/app/components/expense/expense.component.scss b/src/app/components/expense/expense.component.scss
index 96c5a02..8a9e4cf 100644
--- a/src/app/components/expense/expense.component.scss
+++ b/src/app/components/expense/expense.component.scss
@@ -17,49 +17,6 @@ mat-card-header {
width: 100%;
display: flex;
align-items: center;
- justify-content: space-between;
- }
-}
-
-.expense-content {
- display: grid;
-}
-
-mat-form-field {
- width: 100%;
-}
-
-.view-tags {
- display: flex;
- gap: 0.5rem;
- align-items: center;
-}
-
-@media (min-width: 550px) {
- .expense-content {
- grid-template-columns: 1fr 1fr;
- gap: 1rem;
- }
-
- .view-note {
- grid-column: 1 / span 2;
- }
-
- .view-tags {
- grid-column: 1 / span 2;
- }
-}
-
-@media (min-width: 800px) {
- .expense-content {
- grid-template-columns: 1fr 1fr 1fr;
- }
-
- .view-note {
- grid-column: 2 / span 2;
- }
-
- .view-tags {
- grid-column: 1 / span 3;
+ gap: 0.5rem;
}
}
diff --git a/src/app/components/expense/expense.component.ts b/src/app/components/expense/expense.component.ts
index 0dc286b..52fa70a 100644
--- a/src/app/components/expense/expense.component.ts
+++ b/src/app/components/expense/expense.component.ts
@@ -1,131 +1,73 @@
-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 { Component, input, model, signal } from '@angular/core';
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 { Expense, ExpenseService } from '../../services/expense.service';
import { MatIcon } from '@angular/material/icon';
-import { Temporal } from '@js-temporal/polyfill';
-// import { MatChip } from '@angular/material/chips';
-
-interface ExpenseForm {
- date: Date | string;
- cents: number;
- category: Category | string;
- merchant: Merchant | string;
- note: string;
- tags: Tag[];
-}
+import { ExpenseFormComponent } from './expense-form/expense-form.component';
+import { CurrencyPipe, DatePipe } from '@angular/common';
@Component({
selector: 'app-expense',
- imports: [
- MatDatepickerModule,
- MatFormFieldModule,
- MatInputModule,
- MatCardModule,
- MatAutocompleteModule,
- MatSelectModule,
- MatButtonModule,
- FormField,
- DatePipe,
- MatIcon,
- CurrencyPipe,
- // MatChip
- ],
- providers: [provideNativeDateAdapter(), DatePipe],
+ imports: [MatCardModule, MatButtonModule, MatIcon, ExpenseFormComponent, DatePipe, CurrencyPipe],
+ providers: [],
templateUrl: './expense.component.html',
styleUrl: './expense.component.scss',
})
export class ExpenseComponent {
public expense = input();
- 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();
- });
+ public editingExpense = signal(false);
+ public formValid = model(false);
+ public formDirty = model(false);
- // 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: '',
- cents: this.expense()?.cents ?? NaN,
- category: this.expense()?.category ?? '',
- merchant: this.expense()?.merchant ?? '',
- note: this.expense()?.note ?? '',
- tags: this.expense()?.tags ?? []
- };
- private expenseModel = signal(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 {
- // 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 constructor(private readonly expenseService: ExpenseService) { }
public editClick(): void {
- this.expenseMode.set('edit');
+ this.editingExpense.set(true);
}
- public autocompleteDisplay(value: Merchant | Category) {
- return value.name ?? null;
+ public async addClick(): Promise {
+ console.log('Adding new expense');
}
+
+ public resetAddClick(): void {
+ console.log('Reset Add expense form');
+ }
+
+ public async updateClick(): Promise {
+ console.log('Updating expense')
+ this.editingExpense.set(false);
+ }
+
+ public cancelUpdateClick(): void {
+ console.log('Canceling update');
+ this.editingExpense.set(false);
+ }
+
+ // 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);
+ // }
}
diff --git a/src/app/pages/expenses/expense-page.component.html b/src/app/pages/expenses/expense-page.component.html
index 4491c8b..2add09d 100644
--- a/src/app/pages/expenses/expense-page.component.html
+++ b/src/app/pages/expenses/expense-page.component.html
@@ -1,4 +1,4 @@
diff --git a/src/app/services/category.service.ts b/src/app/services/category.service.ts
index d79dc47..211e6e8 100644
--- a/src/app/services/category.service.ts
+++ b/src/app/services/category.service.ts
@@ -9,8 +9,6 @@ export class CategoryService {
public readonly categories = this.internalCategories.asReadonly();
public readonly categoryPath = 'http://localhost:3000/common-cents/categories';
- // public categories = signal([]);
-
public constructor(private readonly http: HttpService) {
void this.fetchCategories();
}
diff --git a/src/app/services/expense.service.ts b/src/app/services/expense.service.ts
index ac11b1e..e782ae5 100644
--- a/src/app/services/expense.service.ts
+++ b/src/app/services/expense.service.ts
@@ -19,15 +19,23 @@ export class ExpenseService {
public async fetchExpenses(): Promise {
this.internalExpenses.set(await this.http.get(this.expensePath));
+ console.log({ expenses: this.internalExpenses() }); // TODO: Remove
}
- public async postExpense(expense: CreateExpense): Promise {
- // const createdExpense = await this.http.post(this.expensePath, expense);
- console.log(expense);
+ public async postExpense(createExpense: CreateExpense): Promise {
+ const createdExpense = await this.http.post(this.expensePath, createExpense);
await this.fetchExpenses();
+ console.log({ createExpense, createdExpense}); // TODO: Remove
- // return createdExpense;
- return { ...expense, id: '', category: { id: '', name: ''}, tags: [] };
+ return createdExpense;
+ }
+
+ public async updateExpense(updateExpense: UpdateExpense): Promise {
+ const updatedExpense = await this.http.put(this.expensePath, updateExpense);
+ await this.fetchExpenses();
+ console.log({ updateExpense, updatedExpense}); // TODO: Remove
+
+ return updatedExpense;
}
}
@@ -49,3 +57,7 @@ export interface CreateExpense {
merchantId?: string;
tagIds?: string[];
}
+
+export interface UpdateExpense extends CreateExpense {
+ id: string;
+}
diff --git a/src/styles.scss b/src/styles.scss
index cf6f56c..ac47f6e 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -22,7 +22,7 @@ body {
// Default the application to a light color theme. This can be changed to
// `dark` to enable the dark color theme, or to `light dark` to defer to the
// user's system settings.
- color-scheme: light; // TODO: choose 'light dark'
+ color-scheme: light dark;
// Set a default background, font and text colors for the application using
// Angular Material's system-level CSS variables. Learn more about these