diff --git a/src/app/app.scss b/src/app/app.scss index 79354d0..29334f4 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -1,5 +1,5 @@ .app-content { - padding: 0.5rem; + padding: 2rem; } h1 { diff --git a/src/app/components/add-expense/add-expense.component.html b/src/app/components/add-expense/add-expense.component.html new file mode 100644 index 0000000..afc5f76 --- /dev/null +++ b/src/app/components/add-expense/add-expense.component.html @@ -0,0 +1,71 @@ +
+ + + Track new Expense + + + +
+ + Date + + + + + + + Cents + + + + + Category + + + @for (category of categories(); track category.id) { + {{ category.name }} + } + + + + + Merchant + + + @for (merchant of merchants(); track merchant.id) { + {{ merchant.name }} + } + + +
+ +
+ + Tags + + @for (tag of tags(); track tag.id) { + {{ tag.name }} + } + + + + + Note + + +
+
+ + + + + + + @if (errorSaving()) { + + {{ errorSaving() }} + + } + +
+
diff --git a/src/app/components/add-expense/add-expense.component.scss b/src/app/components/add-expense/add-expense.component.scss new file mode 100644 index 0000000..74c3967 --- /dev/null +++ b/src/app/components/add-expense/add-expense.component.scss @@ -0,0 +1,23 @@ +.add-expense-header { + padding-bottom: 1rem; +} + +.add-expense-footer { + padding: 0 0 1rem 0.5rem; +} + +.add-expense-dropdowns { + display: flex; + gap: 1rem; + justify-content: space-between; +} + +.add-expense-additional { + display: flex; + gap: 1rem; + justify-content: space-between; +} + +.add-expense-note { + width: 100%; +} diff --git a/src/app/components/expense/expense.component.ts b/src/app/components/add-expense/add-expense.component.ts similarity index 66% rename from src/app/components/expense/expense.component.ts rename to src/app/components/add-expense/add-expense.component.ts index 11dd70a..e3f0d4e 100644 --- a/src/app/components/expense/expense.component.ts +++ b/src/app/components/add-expense/add-expense.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, input, signal } from '@angular/core'; +import { Component, computed, 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'; @@ -11,12 +11,11 @@ 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'; +import { CreateExpense, ExpenseService } from '../../services/expense.service'; +import { DatePipe } from '@angular/common'; interface ExpenseForm { - date: Date | string; + date: Date; cents: number; category: Category | string; merchant: Merchant | string; @@ -25,17 +24,13 @@ interface ExpenseForm { } @Component({ - selector: 'app-expense', - imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, MatCardModule, MatAutocompleteModule, MatSelectModule, MatButtonModule, FormField, DatePipe, MatIcon, CurrencyPipe], + selector: 'app-add-expense', + imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, MatCardModule, MatAutocompleteModule, MatSelectModule, MatButtonModule, FormField], providers: [provideNativeDateAdapter(), DatePipe], - templateUrl: './expense.component.html', - styleUrl: './expense.component.scss', + templateUrl: './add-expense.component.html', + styleUrl: './add-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); +export class AddExpenseComponent { protected categories = computed(() => this.categoryService.categories()); protected merchants = computed(() => this.merchantService.merchants()); protected tags = computed(() => this.tagService.tags()); @@ -46,24 +41,20 @@ export class ExpenseComponent { const merchantValid = this.expenseForm.merchant().valid(); const noteValid = this.expenseForm.note().valid(); - return dateValid && centsValid && categoryValid && merchantValid && noteValid && !this.saving(); + return dateValid && centsValid && categoryValid && merchantValid && noteValid; }); + protected saving = signal(false); + protected errorSaving = signal(''); - 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 defaultFormState: ExpenseForm = { + date: new Date(), + cents: NaN, + category: '', + merchant: '', + note: '', + tags: [] }; - private expenseModel = signal(this.defaultForm); + private expenseModel = signal(this.defaultFormState); public expenseForm = form(this.expenseModel, (schema) => { required(schema.date); required(schema.cents); @@ -78,6 +69,7 @@ export class ExpenseComponent { private readonly datePipe: DatePipe) { } public async saveClick(): Promise { + this.errorSaving.set(''); const saveExpenseModel = this.expenseModel(); const date = this.datePipe.transform(saveExpenseModel.date, 'yyyy-MM-dd')?.split('-') ?? []; const expense: CreateExpense = { @@ -94,19 +86,18 @@ export class ExpenseComponent { 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.expenseModel.set({ ...this.defaultFormState, date: saveExpenseModel.date }); this.expenseForm().reset(this.expenseModel()); } catch (error) { - console.error(error); + this.errorSaving.set(`Error saving expense: ${error}`) } finally { this.saving.set(false); } } - public autocompleteDisplay(value: Merchant | Category) { + public display(value: Merchant | Category | Tag) { return value.name ?? null; } } diff --git a/src/app/components/expense-list/expense-list.component.html b/src/app/components/expense-list/expense-list.component.html index 080cafc..bed23d2 100644 --- a/src/app/components/expense-list/expense-list.component.html +++ b/src/app/components/expense-list/expense-list.component.html @@ -1,37 +1,34 @@
- @for (expense of expenses(); track expense.id) { - - } - - - - + + + Tracked Expenses + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + +
Date{{ `${expense.year}/${expense.month}/${expense.day}` | date }}Amount{{ (expense.cents / 100) | currency: 'USD' }}Category{{ expense.category.name }}Merchant{{ expense.merchant?.name ?? '--' }}
+
+
diff --git a/src/app/components/expense-list/expense-list.component.scss b/src/app/components/expense-list/expense-list.component.scss index 818821c..e69de29 100644 --- a/src/app/components/expense-list/expense-list.component.scss +++ b/src/app/components/expense-list/expense-list.component.scss @@ -1,4 +0,0 @@ -.expense-list-container { - display: grid; - gap: 1rem; -} diff --git a/src/app/components/expense-list/expense-list.component.ts b/src/app/components/expense-list/expense-list.component.ts index 6ebaf88..08864c8 100644 --- a/src/app/components/expense-list/expense-list.component.ts +++ b/src/app/components/expense-list/expense-list.component.ts @@ -3,11 +3,10 @@ import { ExpenseService } from '../../services/expense.service'; import { MatTableModule } from '@angular/material/table'; import { CurrencyPipe, DatePipe } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; -import {ExpenseComponent} from '../expense/expense.component'; @Component({ selector: 'app-expense-list', - imports: [MatTableModule, MatCardModule, DatePipe, CurrencyPipe, ExpenseComponent], + imports: [MatTableModule, MatCardModule, DatePipe, CurrencyPipe], templateUrl: './expense-list.component.html', styleUrl: './expense-list.component.scss', }) diff --git a/src/app/components/expense/expense.component.html b/src/app/components/expense/expense.component.html deleted file mode 100644 index 66f6e56..0000000 --- a/src/app/components/expense/expense.component.html +++ /dev/null @@ -1,96 +0,0 @@ -
- - - @if (state() === 'add') { -
- Track new Expense - -
- } - @if (state() === 'view') { -
- {{ `${expense()?.year}-${expense()?.month}-${expense()?.day}` | date }} - - -
- } -
- - - @if (state() === 'add') { -
- - Date - - - - - - - Cents - - - - - Category - - - @for (category of categories(); track category.id) { - {{ category.name }} - } - - - - - Merchant - - - @for (merchant of merchants(); track merchant.id) { - {{ merchant.name }} - } - - - - - Note - - - - - Tags - - @for (tag of tags(); track tag.id) { - {{ tag.name }} - } - - -
- } - @if (state() === 'view') { -
-
- Amount: {{ expense()?.cents! / 100 | currency}} -
- -
- Category: {{ expense()?.category!.name }} -
- -
- Merchant: {{ expense()?.merchant?.name ?? '--' }} -
-
- -
- Note: {{ expense()?.note ?? '--' }} -
- -
- Tags: -
- } -
-
-
diff --git a/src/app/components/expense/expense.component.scss b/src/app/components/expense/expense.component.scss deleted file mode 100644 index 08a52af..0000000 --- a/src/app/components/expense/expense.component.scss +++ /dev/null @@ -1,43 +0,0 @@ -.expense-container { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - - mat-card { - max-width: 800px; - width: 100%; - } -} - -mat-card-header { - padding-bottom: 1rem; - - .expense-header { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - } -} - -.expense-content { - display: grid; -} - -mat-form-field { - width: 100%; -} - -@media (min-width: 550px) { - .expense-content { - grid-template-columns: 1fr 1fr; - gap: 1rem; - } -} - -@media (min-width: 800px) { - .expense-content { - grid-template-columns: 1fr 1fr 1fr; - } -} diff --git a/src/app/pages/expenses/expenses.html b/src/app/pages/expenses/expenses.html index 2add09d..7d8dce9 100644 --- a/src/app/pages/expenses/expenses.html +++ b/src/app/pages/expenses/expenses.html @@ -1,4 +1,4 @@
- +
diff --git a/src/app/pages/expenses/expenses.ts b/src/app/pages/expenses/expenses.ts index 3132b93..90bd738 100644 --- a/src/app/pages/expenses/expenses.ts +++ b/src/app/pages/expenses/expenses.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; import { ExpenseListComponent } from '../../components/expense-list/expense-list.component'; -import { ExpenseComponent } from '../../components/expense/expense.component'; +import { AddExpenseComponent } from '../../components/add-expense/add-expense.component'; @Component({ selector: 'app-expenses', imports: [ ExpenseListComponent, - ExpenseComponent + AddExpenseComponent ], templateUrl: './expenses.html', styleUrl: './expenses.scss'