diff --git a/src/app/app.scss b/src/app/app.scss index 29334f4..79354d0 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -1,5 +1,5 @@ .app-content { - padding: 2rem; + padding: 0.5rem; } h1 { diff --git a/src/app/components/add-expense/add-expense.component.html b/src/app/components/add-expense/add-expense.component.html deleted file mode 100644 index afc5f76..0000000 --- a/src/app/components/add-expense/add-expense.component.html +++ /dev/null @@ -1,71 +0,0 @@ -
- - - 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 deleted file mode 100644 index 74c3967..0000000 --- a/src/app/components/add-expense/add-expense.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -.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-list/expense-list.component.html b/src/app/components/expense-list/expense-list.component.html index bed23d2..080cafc 100644 --- a/src/app/components/expense-list/expense-list.component.html +++ b/src/app/components/expense-list/expense-list.component.html @@ -1,34 +1,37 @@
- - - Tracked Expenses - + @for (expense of expenses(); track expense.id) { + + } + + + + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - -
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 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-list/expense-list.component.ts b/src/app/components/expense-list/expense-list.component.ts index 08864c8..6ebaf88 100644 --- a/src/app/components/expense-list/expense-list.component.ts +++ b/src/app/components/expense-list/expense-list.component.ts @@ -3,10 +3,11 @@ 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], + imports: [MatTableModule, MatCardModule, DatePipe, CurrencyPipe, ExpenseComponent], 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 new file mode 100644 index 0000000..66f6e56 --- /dev/null +++ b/src/app/components/expense/expense.component.html @@ -0,0 +1,96 @@ +
+ + + @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 new file mode 100644 index 0000000..08a52af --- /dev/null +++ b/src/app/components/expense/expense.component.scss @@ -0,0 +1,43 @@ +.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/components/add-expense/add-expense.component.ts b/src/app/components/expense/expense.component.ts similarity index 66% rename from src/app/components/add-expense/add-expense.component.ts rename to src/app/components/expense/expense.component.ts index e3f0d4e..11dd70a 100644 --- a/src/app/components/add-expense/add-expense.component.ts +++ b/src/app/components/expense/expense.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, signal } from '@angular/core'; +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'; @@ -11,11 +11,12 @@ 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, ExpenseService } from '../../services/expense.service'; -import { DatePipe } from '@angular/common'; +import { CreateExpense, Expense, ExpenseService } from '../../services/expense.service'; +import {CurrencyPipe, DatePipe} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; interface ExpenseForm { - date: Date; + date: Date | string; cents: number; category: Category | string; merchant: Merchant | string; @@ -24,13 +25,17 @@ interface ExpenseForm { } @Component({ - selector: 'app-add-expense', - imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, MatCardModule, MatAutocompleteModule, MatSelectModule, MatButtonModule, FormField], + selector: 'app-expense', + imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, MatCardModule, MatAutocompleteModule, MatSelectModule, MatButtonModule, FormField, DatePipe, MatIcon, CurrencyPipe], providers: [provideNativeDateAdapter(), DatePipe], - templateUrl: './add-expense.component.html', - styleUrl: './add-expense.component.scss', + templateUrl: './expense.component.html', + styleUrl: './expense.component.scss', }) -export class AddExpenseComponent { +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()); @@ -41,20 +46,24 @@ export class AddExpenseComponent { const merchantValid = this.expenseForm.merchant().valid(); const noteValid = this.expenseForm.note().valid(); - return dateValid && centsValid && categoryValid && merchantValid && noteValid; + return dateValid && centsValid && categoryValid && merchantValid && noteValid && !this.saving(); }); - protected saving = signal(false); - protected errorSaving = signal(''); - private defaultFormState: ExpenseForm = { - date: new Date(), - cents: NaN, - category: '', - merchant: '', - note: '', - tags: [] + 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(this.defaultFormState); + private expenseModel = signal(this.defaultForm); public expenseForm = form(this.expenseModel, (schema) => { required(schema.date); required(schema.cents); @@ -69,7 +78,6 @@ export class AddExpenseComponent { 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 = { @@ -86,18 +94,19 @@ export class AddExpenseComponent { this.saving.set(true); try { await this.expenseService.postExpense(expense); - this.expenseModel.set({ ...this.defaultFormState, date: saveExpenseModel.date }); + this.lastSelectedDate = saveExpenseModel.date ? saveExpenseModel.date as Date : undefined; + this.expenseModel.set({ ...this.defaultForm, date: saveExpenseModel.date }); this.expenseForm().reset(this.expenseModel()); } catch (error) { - this.errorSaving.set(`Error saving expense: ${error}`) + console.error(error); } finally { this.saving.set(false); } } - public display(value: Merchant | Category | Tag) { + public autocompleteDisplay(value: Merchant | Category) { return value.name ?? null; } } diff --git a/src/app/pages/expenses/expenses.html b/src/app/pages/expenses/expenses.html index 7d8dce9..2add09d 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 90bd738..3132b93 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 { AddExpenseComponent } from '../../components/add-expense/add-expense.component'; +import { ExpenseComponent } from '../../components/expense/expense.component'; @Component({ selector: 'app-expenses', imports: [ ExpenseListComponent, - AddExpenseComponent + ExpenseComponent ], templateUrl: './expenses.html', styleUrl: './expenses.scss'