diff --git a/package-lock.json b/package-lock.json
index 37680ab..907712b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"@angular/material": "~21.1.4",
"@angular/platform-browser": "^21.1.0",
"@angular/router": "^21.1.0",
+ "@js-temporal/polyfill": "^0.5.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
@@ -2073,6 +2074,18 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@js-temporal/polyfill": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.5.1.tgz",
+ "integrity": "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==",
+ "license": "ISC",
+ "dependencies": {
+ "jsbi": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@listr2/prompt-adapter-inquirer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz",
@@ -5891,6 +5904,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/jsbi": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz",
+ "integrity": "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==",
+ "license": "Apache-2.0"
+ },
"node_modules/jsdom": {
"version": "27.4.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz",
diff --git a/package.json b/package.json
index e3c47c6..419698f 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@angular/material": "~21.1.4",
"@angular/platform-browser": "^21.1.0",
"@angular/router": "^21.1.0",
+ "@js-temporal/polyfill": "^0.5.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
@@ -43,4 +44,4 @@
"typescript": "~5.9.2",
"vitest": "^4.0.8"
}
-}
\ No newline at end of file
+}
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index a24f70e..5a89bba 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,14 +1,14 @@
import { Routes } from '@angular/router';
-import { Expenses } from './pages/expenses/expenses';
-import { Home } from './pages/home/home';
+import { ExpensePage } from './pages/expenses/expense-page.component';
+import { HomePage } from './pages/home/home-page.component';
export const routes: Routes = [
{
path: '',
- component: Home
+ component: HomePage
},
{
path: 'expenses',
- component: Expenses
+ component: ExpensePage
}
];
diff --git a/src/app/components/expense-list/expense-list.component.html b/src/app/components/expense-list/expense-list.component.html
index 080cafc..ffd5211 100644
--- a/src/app/components/expense-list/expense-list.component.html
+++ b/src/app/components/expense-list/expense-list.component.html
@@ -2,36 +2,4 @@
@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 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..88195b3 100644
--- a/src/app/components/expense-list/expense-list.component.ts
+++ b/src/app/components/expense-list/expense-list.component.ts
@@ -1,19 +1,17 @@
import { Component, computed } from '@angular/core';
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';
+import { ExpenseComponent } from '../expense/expense.component';
@Component({
selector: 'app-expense-list',
- imports: [MatTableModule, MatCardModule, DatePipe, CurrencyPipe, ExpenseComponent],
+ imports: [MatTableModule, MatCardModule, ExpenseComponent],
templateUrl: './expense-list.component.html',
styleUrl: './expense-list.component.scss',
})
export class ExpenseListComponent {
protected expenses = computed(() => this.expensesService.expenses())
- protected columns = ['date', 'amount', 'category', 'merchant'];
public constructor(private readonly expensesService: ExpenseService) { }
}
diff --git a/src/app/components/expense/expense-add/expense-add.component.html b/src/app/components/expense/expense-add/expense-add.component.html
new file mode 100644
index 0000000..a881b02
--- /dev/null
+++ b/src/app/components/expense/expense-add/expense-add.component.html
@@ -0,0 +1 @@
+
expense-add works!
diff --git a/src/app/pages/home/home.scss b/src/app/components/expense/expense-add/expense-add.component.scss
similarity index 100%
rename from src/app/pages/home/home.scss
rename to src/app/components/expense/expense-add/expense-add.component.scss
diff --git a/src/app/components/expense/expense-add/expense-add.component.ts b/src/app/components/expense/expense-add/expense-add.component.ts
new file mode 100644
index 0000000..5726791
--- /dev/null
+++ b/src/app/components/expense/expense-add/expense-add.component.ts
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..9e5b53c
--- /dev/null
+++ b/src/app/components/expense/expense-form/expense-form.component.html
@@ -0,0 +1,52 @@
+
+
+ Date
+
+
+
+
+
+
+
+
+
+
+
+ Cents
+
+
+
+
+ Category
+
+
+ @for (category of categories(); track category.id) {
+ {{ category.name }}
+ }
+
+
+
+
+ Merchant
+
+
+ @for (merchant of merchants(); track merchant.id) {
+ {{ merchant.name }}
+ }
+
+
+
+
+ Note
+
+
+
+
+ Tags
+
+
+
+
+
+
+
diff --git a/src/app/components/expense/expense-form/expense-form.component.scss b/src/app/components/expense/expense-form/expense-form.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/expense/expense-form/expense-form.component.ts b/src/app/components/expense/expense-form/expense-form.component.ts
new file mode 100644
index 0000000..884758d
--- /dev/null
+++ b/src/app/components/expense/expense-form/expense-form.component.ts
@@ -0,0 +1,87 @@
+import {Component, computed, input, output, signal} from '@angular/core';
+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 { 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';
+
+interface ExpenseForm {
+ date: Date | string;
+ cents: number;
+ category: Category | string;
+ merchant: Merchant | string;
+ note: string;
+ tags: Tag[];
+}
+
+@Component({
+ selector: 'app-expense-form',
+ imports: [
+ MatAutocompleteModule,
+ MatDatepickerModule,
+ MatInputModule,
+ MatSelectModule
+ ],
+ 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([]);
+
+ 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();
+ // });
+
+ private lastSelectedDate = signal(undefined);
+
+ private expenseDate = computed(() => {
+ const lastDate = this.lastSelectedDate();
+ const expense = this.expense();
+
+ 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);
+ });
+
+
+ public autocompleteDisplay(value: Merchant | Category) {
+ return value.name ?? null;
+ }
+}
diff --git a/src/app/components/expense/expense-view/expense-view.component.html b/src/app/components/expense/expense-view/expense-view.component.html
new file mode 100644
index 0000000..fb4707d
--- /dev/null
+++ b/src/app/components/expense/expense-view/expense-view.component.html
@@ -0,0 +1 @@
+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
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/expense/expense-view/expense-view.component.ts b/src/app/components/expense/expense-view/expense-view.component.ts
new file mode 100644
index 0000000..3b626fd
--- /dev/null
+++ b/src/app/components/expense/expense-view/expense-view.component.ts
@@ -0,0 +1,11 @@
+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 66f6e56..c16825a 100644
--- a/src/app/components/expense/expense.component.html
+++ b/src/app/components/expense/expense.component.html
@@ -1,17 +1,25 @@
- @if (state() === 'add') {
+ @if (state() === 'add' || state() === 'edit') {
}
@if (state() === 'view') {
@@ -19,13 +27,18 @@
- @if (state() === 'add') {
+
Date
-
+ @if (state() === 'view') {
+
+
+ } @else {
+
+ }
-
+
@@ -67,30 +80,56 @@
- }
- @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
index 08a52af..96c5a02 100644
--- a/src/app/components/expense/expense.component.scss
+++ b/src/app/components/expense/expense.component.scss
@@ -29,15 +29,37 @@ 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;
+ }
}
diff --git a/src/app/components/expense/expense.component.ts b/src/app/components/expense/expense.component.ts
index 11dd70a..0dc286b 100644
--- a/src/app/components/expense/expense.component.ts
+++ b/src/app/components/expense/expense.component.ts
@@ -12,8 +12,10 @@ 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 { CurrencyPipe, DatePipe } from '@angular/common';
+import { MatIcon } from '@angular/material/icon';
+import { Temporal } from '@js-temporal/polyfill';
+// import { MatChip } from '@angular/material/chips';
interface ExpenseForm {
date: Date | string;
@@ -26,7 +28,20 @@ interface ExpenseForm {
@Component({
selector: 'app-expense',
- imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, MatCardModule, MatAutocompleteModule, MatSelectModule, MatButtonModule, FormField, DatePipe, MatIcon, CurrencyPipe],
+ imports: [
+ MatDatepickerModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatCardModule,
+ MatAutocompleteModule,
+ MatSelectModule,
+ MatButtonModule,
+ FormField,
+ DatePipe,
+ MatIcon,
+ CurrencyPipe,
+ // MatChip
+ ],
providers: [provideNativeDateAdapter(), DatePipe],
templateUrl: './expense.component.html',
styleUrl: './expense.component.scss',
@@ -49,14 +64,14 @@ export class ExpenseComponent {
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 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(),
+ date: '',
cents: this.expense()?.cents ?? NaN,
category: this.expense()?.category ?? '',
merchant: this.expense()?.merchant ?? '',
@@ -78,32 +93,36 @@ export class ExpenseComponent {
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
- };
+ // 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);
+ // }
+ }
- 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 editClick(): void {
+ this.expenseMode.set('edit');
}
public autocompleteDisplay(value: Merchant | Category) {
diff --git a/src/app/pages/expenses/expenses.html b/src/app/pages/expenses/expense-page.component.html
similarity index 65%
rename from src/app/pages/expenses/expenses.html
rename to src/app/pages/expenses/expense-page.component.html
index 2add09d..4491c8b 100644
--- a/src/app/pages/expenses/expenses.html
+++ b/src/app/pages/expenses/expense-page.component.html
@@ -1,4 +1,4 @@
diff --git a/src/app/pages/expenses/expenses.scss b/src/app/pages/expenses/expense-page.component.scss
similarity index 100%
rename from src/app/pages/expenses/expenses.scss
rename to src/app/pages/expenses/expense-page.component.scss
diff --git a/src/app/pages/expenses/expenses.ts b/src/app/pages/expenses/expense-page.component.ts
similarity index 72%
rename from src/app/pages/expenses/expenses.ts
rename to src/app/pages/expenses/expense-page.component.ts
index 3132b93..18f24e0 100644
--- a/src/app/pages/expenses/expenses.ts
+++ b/src/app/pages/expenses/expense-page.component.ts
@@ -8,7 +8,7 @@ import { ExpenseComponent } from '../../components/expense/expense.component';
ExpenseListComponent,
ExpenseComponent
],
- templateUrl: './expenses.html',
- styleUrl: './expenses.scss'
+ templateUrl: './expense-page.component.html',
+ styleUrl: './expense-page.component.scss'
})
-export class Expenses { }
+export class ExpensePage { }
diff --git a/src/app/pages/home/home.html b/src/app/pages/home/home-page.component.html
similarity index 100%
rename from src/app/pages/home/home.html
rename to src/app/pages/home/home-page.component.html
diff --git a/src/app/pages/home/home-page.component.scss b/src/app/pages/home/home-page.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/home/home.ts b/src/app/pages/home/home-page.component.ts
similarity index 59%
rename from src/app/pages/home/home.ts
rename to src/app/pages/home/home-page.component.ts
index 7c1a5e2..724172f 100644
--- a/src/app/pages/home/home.ts
+++ b/src/app/pages/home/home-page.component.ts
@@ -6,7 +6,7 @@ import { RouterLink } from '@angular/router';
imports: [
RouterLink
],
- templateUrl: './home.html',
- styleUrl: './home.scss',
+ templateUrl: './home-page.component.html',
+ styleUrl: './home-page.component.scss',
})
-export class Home { }
+export class HomePage { }
diff --git a/src/app/services/expense.service.ts b/src/app/services/expense.service.ts
index 2814357..ac11b1e 100644
--- a/src/app/services/expense.service.ts
+++ b/src/app/services/expense.service.ts
@@ -3,6 +3,7 @@ import { Category } from './category.service';
import { Merchant } from './merchant.service';
import { Tag } from './tag.service';
import { HttpService } from './http.service';
+import { Temporal } from '@js-temporal/polyfill';
@Injectable({
providedIn: 'root',
@@ -21,18 +22,18 @@ export class ExpenseService {
}
public async postExpense(expense: CreateExpense): Promise {
- const createdExpense = await this.http.post(this.expensePath, expense);
+ // const createdExpense = await this.http.post(this.expensePath, expense);
+ console.log(expense);
await this.fetchExpenses();
- return createdExpense;
+ // return createdExpense;
+ return { ...expense, id: '', category: { id: '', name: ''}, tags: [] };
}
}
export interface Expense {
id: string;
- year: string;
- month: string;
- day: string;
+ date: Temporal.PlainDate;
cents: number;
category: Category;
note?: string;
@@ -41,12 +42,10 @@ export interface Expense {
}
export interface CreateExpense {
- year: string;
- month: string;
- day: string;
+ date: Temporal.PlainDate;
cents: number;
- category: Category;
+ categoryId: string;
note?: string;
- merchant?: Merchant;
- tags: Tag[];
+ merchantId?: string;
+ tagIds?: string[];
}