diff --git a/angular.json b/angular.json index 0b73397..3ed4374 100644 --- a/angular.json +++ b/angular.json @@ -31,7 +31,12 @@ ], "styles": [ "src/styles.scss" - ] + ], + "stylePreprocessorOptions": { + "includePaths": [ + "src/assets/styles" + ] + } }, "configurations": { "production": { diff --git a/src/app/app.html b/src/app/app.html index c065c67..524c63f 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1,7 +1,11 @@

Common Cents

-

The common sense expense tracker.

+
+ @for (link of navLinks(); track link) { + {{ link.text }} + } +
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 5a89bba..69fb40b 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,6 +1,8 @@ import { Routes } from '@angular/router'; import { ExpensePage } from './pages/expenses/expense-page.component'; import { HomePage } from './pages/home/home-page.component'; +import { MetadataPageComponent } from './pages/metadata/metadata-page.component'; +import { ReportsPageComponent } from './pages/reports/reports-page.component'; export const routes: Routes = [ { @@ -10,5 +12,13 @@ export const routes: Routes = [ { path: 'expenses', component: ExpensePage + }, + { + path: 'metadata', + component: MetadataPageComponent + }, + { + path: 'reports', + component: ReportsPageComponent } ]; diff --git a/src/app/app.scss b/src/app/app.scss index 46d0307..330ac4b 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -14,19 +14,13 @@ } h1 { - font-size: 3.125rem; + font-size: 2.5rem; line-height: 100%; letter-spacing: -0.125rem; - font-weight: 500; + font-weight: 400; margin: 0; - font-family: sans-serif; } -h2 { - font-size: 1.2rem; - line-height: 100%; - font-weight: 500; - margin: 0; - font-family: sans-serif; - font-style: italic; +.app-navigation { + display: flex; } diff --git a/src/app/app.ts b/src/app/app.ts index 68f4f4a..9f8339a 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,11 +1,36 @@ -import { Component } from '@angular/core'; +import { Component, signal } from '@angular/core'; import { RouterOutlet } from '@angular/router'; -import { Divider } from './components/divider/divider'; +import { DividerComponent } from './components/divider/divider.component'; +import { MatButton } from '@angular/material/button'; + +interface NavLink { + text: string; + href: string; +} @Component({ selector: 'app-root', - imports: [RouterOutlet, Divider], + imports: [RouterOutlet, DividerComponent, MatButton], templateUrl: './app.html', styleUrl: './app.scss' }) -export class App { } +export class App { + public navLinks = signal([ + { + text: 'Home', + href: '/' + }, + { + text: 'Expenses', + href: '/expenses' + }, + { + text: 'Metadata', + href: '/metadata' + }, + { + text: 'Reports', + href: '/reports' + } + ]); +} diff --git a/src/app/components/divider/divider.html b/src/app/components/divider/divider.component.html similarity index 100% rename from src/app/components/divider/divider.html rename to src/app/components/divider/divider.component.html diff --git a/src/app/components/divider/divider.scss b/src/app/components/divider/divider.component.scss similarity index 100% rename from src/app/components/divider/divider.scss rename to src/app/components/divider/divider.component.scss diff --git a/src/app/components/divider/divider.component.ts b/src/app/components/divider/divider.component.ts new file mode 100644 index 0000000..1c18680 --- /dev/null +++ b/src/app/components/divider/divider.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-divider', + imports: [], + templateUrl: './divider.component.html', + styleUrl: './divider.component.scss' +}) +export class DividerComponent { } diff --git a/src/app/components/divider/divider.ts b/src/app/components/divider/divider.ts deleted file mode 100644 index fb197c5..0000000 --- a/src/app/components/divider/divider.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-divider', - imports: [], - templateUrl: './divider.html', - styleUrl: './divider.scss' -}) -export class Divider { } diff --git a/src/app/components/expense-list/expense-list.component.scss b/src/app/components/expense-list/expense-list.component.scss index 1b125f4..99f5d4f 100644 --- a/src/app/components/expense-list/expense-list.component.scss +++ b/src/app/components/expense-list/expense-list.component.scss @@ -12,9 +12,8 @@ p { font-size: 1.8rem; line-height: 100%; - font-weight: 200; + font-weight: 400; margin: 0; - font-family: sans-serif; } app-divider { @@ -25,5 +24,5 @@ .expense-list { display: grid; gap: 1rem; - justify-content: center; + justify-content: stretch; } diff --git a/src/app/components/expense-list/expense-list.component.ts b/src/app/components/expense-list/expense-list.component.ts index 532559e..017435a 100644 --- a/src/app/components/expense-list/expense-list.component.ts +++ b/src/app/components/expense-list/expense-list.component.ts @@ -3,11 +3,11 @@ import { ExpenseService } from '../../services/expense.service'; import { MatTableModule } from '@angular/material/table'; import { MatCardModule } from '@angular/material/card'; import { ExpenseComponent } from '../expense/expense.component'; -import {Divider} from '../divider/divider'; +import { DividerComponent } from '../divider/divider.component'; @Component({ selector: 'app-expense-list', - imports: [MatTableModule, MatCardModule, ExpenseComponent, Divider], + imports: [MatTableModule, MatCardModule, ExpenseComponent, DividerComponent], templateUrl: './expense-list.component.html', styleUrl: './expense-list.component.scss' }) 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 2e8b1c2..32d44bc 100644 --- a/src/app/components/expense/expense-form/expense-form.component.scss +++ b/src/app/components/expense/expense-form/expense-form.component.scss @@ -1,3 +1,5 @@ +@use "variables"; + .expense-form-container { display: grid; @@ -6,14 +8,14 @@ } } -@media (min-width: 550px) { +@media (min-width: variables.$mid-screen) { .expense-form-container { grid-template-columns: 1fr 1fr; gap: 1rem; } } -@media (min-width: 800px) { +@media (min-width: variables.$wide-screen) { .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 ceca49c..677275c 100644 --- a/src/app/components/expense/expense-form/expense-form.component.ts +++ b/src/app/components/expense/expense-form/expense-form.component.ts @@ -48,13 +48,18 @@ export class ExpenseFormComponent implements OnInit { private readonly merchantService: MerchantService, private readonly tagService: TagService) { effect(() => { + const form = this.form().value(); const valid = this.formValid(); const dirty = this.formDirty(); - const value = this.form().value(); + const value = { + ...form, + merchant: Boolean(form.merchant) ? form.merchant : null, + note: Boolean(form.note) ? form.note : null + }; this.valid.emit(valid); this.dirty.emit(dirty); - this.value.emit(value); + this.value.emit(value as ExpenseForm); this.lastDate.set(value.date); }); } @@ -98,8 +103,8 @@ export class ExpenseFormComponent implements OnInit { date: new Date(year, month, day), cents: expense.cents, category: expense.category, - merchant: expense.merchant ?? {}, - note: expense.note ?? '', + merchant: expense.merchant ?? null, + note: expense.note, tags: expense.tags ?? [] } as ExpenseForm; } diff --git a/src/app/components/expense/expense.component.scss b/src/app/components/expense/expense.component.scss index 8a9e4cf..be80510 100644 --- a/src/app/components/expense/expense.component.scss +++ b/src/app/components/expense/expense.component.scss @@ -1,3 +1,5 @@ +@use "variables"; + .expense-container { width: 100%; display: flex; @@ -5,7 +7,7 @@ align-items: center; mat-card { - max-width: 800px; + max-width: variables.$wide-screen; width: 100%; } } diff --git a/src/app/components/expense/expense.component.ts b/src/app/components/expense/expense.component.ts index 51cbdda..32491ce 100644 --- a/src/app/components/expense/expense.component.ts +++ b/src/app/components/expense/expense.component.ts @@ -38,15 +38,16 @@ export class ExpenseComponent { const postExpense: CreateExpense = { date: this.dateToPlainDate(form.date), cents: form.cents, - categoryId: form.category.id, - note: !!form.note ? form.note : undefined, - merchantId: !!form.merchant ? form.merchant.id : undefined, - tagIds: form.tags.map(tag => tag.id) + category: form.category, + merchant: form.merchant, + note: form.note, + tags: form.tags }; this.savingExpense.set(true); const snackId = this.snackBar.staticBar('Tracking new expense...'); try { - await this.expenseService.postExpense(postExpense); + await this.expenseService.create(postExpense); + await this.expenseService.fetch(); this.resetForm(); this.snackBar.dismiss(snackId); this.snackBar.autoBar('Expense tracked!'); @@ -70,15 +71,15 @@ export class ExpenseComponent { id: this.expense()!.id, date: this.dateToPlainDate(form.date), cents: form.cents, - categoryId: form.category.id, - note: !!form.note ? form.note : undefined, - merchantId: !!form.merchant ? form.merchant.id : undefined, - tagIds: form.tags.map(tag => tag.id) + category: form.category, + merchant: form.merchant, + note: form.note, + tags: form.tags }; this.savingExpense.set(true); const snackId = this.snackBar.staticBar('Updating expense...'); try { - const expense = await this.expenseService.updateExpense(putExpense); + const expense = await this.expenseService.update(putExpense); this.expense.set(expense); this.form()?.refresh(expense); this.editingExpense.set(false); diff --git a/src/app/components/metadata/categories/categories.component.html b/src/app/components/metadata/categories/categories.component.html new file mode 100644 index 0000000..fd22dac --- /dev/null +++ b/src/app/components/metadata/categories/categories.component.html @@ -0,0 +1,24 @@ +
+ + + Categories + + + +
+ +
+ +
+ @for (category of categories(); track category.id) { + + } + @if (!categories().length) { +
+ No categories to display +
+ } +
+
+
+
diff --git a/src/app/components/metadata/categories/categories.component.scss b/src/app/components/metadata/categories/categories.component.scss new file mode 100644 index 0000000..619f899 --- /dev/null +++ b/src/app/components/metadata/categories/categories.component.scss @@ -0,0 +1,21 @@ +@use "variables"; + +.categories-container { + padding-top: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + + mat-card { + max-width: variables.$wide-screen; + width: 100%; + } +} + +.new-category { + padding-top: 0.5rem; +} + +.category-list { + display: grid; +} diff --git a/src/app/components/metadata/categories/categories.component.ts b/src/app/components/metadata/categories/categories.component.ts new file mode 100644 index 0000000..41fec0b --- /dev/null +++ b/src/app/components/metadata/categories/categories.component.ts @@ -0,0 +1,29 @@ +import { Component, computed } from '@angular/core'; +import { Category, CategoryService } from '../../../services/category.service'; +import { MatCardModule } from '@angular/material/card'; +import { MetadataFormComponent } from '../metadata-form/metadata-form.component'; + +@Component({ + selector: 'app-categories', + imports: [ + MatCardModule, + MetadataFormComponent + ], + templateUrl: './categories.component.html', + styleUrl: './categories.component.scss' +}) +export class CategoriesComponent { + protected categories = computed(() => this.categoryService.categories()); + + public constructor(private readonly categoryService: CategoryService) { } + + public async createCategory(name: string): Promise { + await this.categoryService.create({ name }); + await this.categoryService.fetch(); + } + + public async updateCategory(category: Category): Promise { + await this.categoryService.update(category); + await this.categoryService.fetch(); + } +} diff --git a/src/app/components/metadata/merchants/merchants.component.html b/src/app/components/metadata/merchants/merchants.component.html new file mode 100644 index 0000000..7c0e464 --- /dev/null +++ b/src/app/components/metadata/merchants/merchants.component.html @@ -0,0 +1,24 @@ +
+ + + Merchants + + + +
+ +
+ +
+ @for (merchant of merchants(); track merchant.id) { + + } + @if (!merchants().length) { +
+ No merchants to display +
+ } +
+
+
+
diff --git a/src/app/components/metadata/merchants/merchants.component.scss b/src/app/components/metadata/merchants/merchants.component.scss new file mode 100644 index 0000000..7f8f410 --- /dev/null +++ b/src/app/components/metadata/merchants/merchants.component.scss @@ -0,0 +1,21 @@ +@use "variables"; + +.merchants-container { + padding-top: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + + mat-card { + max-width: variables.$wide-screen; + width: 100%; + } +} + +.new-merchant { + padding-top: 0.5rem; +} + +.merchant-list { + display: grid; +} diff --git a/src/app/components/metadata/merchants/merchants.component.ts b/src/app/components/metadata/merchants/merchants.component.ts new file mode 100644 index 0000000..78d93be --- /dev/null +++ b/src/app/components/metadata/merchants/merchants.component.ts @@ -0,0 +1,29 @@ +import { Component, computed } from '@angular/core'; +import { Merchant, MerchantService } from '../../../services/merchant.service'; +import { MatCardModule } from '@angular/material/card'; +import { MetadataFormComponent } from '../metadata-form/metadata-form.component'; + +@Component({ + selector: 'app-merchants', + imports: [ + MatCardModule, + MetadataFormComponent + ], + templateUrl: './merchants.component.html', + styleUrl: './merchants.component.scss' +}) +export class MerchantsComponent { + protected merchants = computed(() => this.merchantService.merchants()); + + public constructor(private readonly merchantService: MerchantService) { } + + public async createMerchant(name: string): Promise { + await this.merchantService.create({ name }); + await this.merchantService.fetch(); + } + + public async updateMerchant(merchant: Merchant): Promise { + await this.merchantService.update(merchant); + await this.merchantService.fetch(); + } +} diff --git a/src/app/components/metadata/metadata-form/metadata-form.component.html b/src/app/components/metadata/metadata-form/metadata-form.component.html new file mode 100644 index 0000000..928c351 --- /dev/null +++ b/src/app/components/metadata/metadata-form/metadata-form.component.html @@ -0,0 +1,28 @@ + diff --git a/src/app/components/metadata/metadata-form/metadata-form.component.scss b/src/app/components/metadata/metadata-form/metadata-form.component.scss new file mode 100644 index 0000000..102e1f0 --- /dev/null +++ b/src/app/components/metadata/metadata-form/metadata-form.component.scss @@ -0,0 +1,16 @@ +.metadata-form-container { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.metadata-edit { + display: flex; + gap: 0.5rem; + + .metadata-edit-buttons { + padding-top: 0.5rem; + display: flex; + gap: 0.5rem; + } +} diff --git a/src/app/components/metadata/metadata-form/metadata-form.component.ts b/src/app/components/metadata/metadata-form/metadata-form.component.ts new file mode 100644 index 0000000..76ef407 --- /dev/null +++ b/src/app/components/metadata/metadata-form/metadata-form.component.ts @@ -0,0 +1,53 @@ +import { Component, computed, input, model, OnInit, output } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; + +interface MetaData { + id: string; + name: string; +} + +@Component({ + selector: 'app-metadata-form', + imports: [ + MatIconModule, + MatButtonModule, + MatInputModule, + FormsModule, + ], + templateUrl: './metadata-form.component.html', + styleUrl: './metadata-form.component.scss' +}) +export class MetadataFormComponent implements OnInit { + public editing = model(false); + public label = input(); + public metadata = input(); + public newMetaData = output(); + + public name = model(''); + public nameValid = computed(() => { + const existingName = this.metadata()?.name; + const newName = this.name(); + + return !!newName && newName !== existingName; + }); + + public ngOnInit(): void { + this.reset(); + } + + public saveMetaData(): void { + this.newMetaData.emit({ + ...this.metadata()!, + name: this.name() + }); + this.reset(); + } + + public reset(): void { + this.name.set(this.metadata()?.name ?? ''); + this.editing.set(!this.metadata()); + } +} diff --git a/src/app/components/metadata/metadata.component.html b/src/app/components/metadata/metadata.component.html new file mode 100644 index 0000000..60613e9 --- /dev/null +++ b/src/app/components/metadata/metadata.component.html @@ -0,0 +1,36 @@ + diff --git a/src/app/components/metadata/metadata.component.scss b/src/app/components/metadata/metadata.component.scss new file mode 100644 index 0000000..3d2c434 --- /dev/null +++ b/src/app/components/metadata/metadata.component.scss @@ -0,0 +1,17 @@ +@use "variables"; + +.metadata-tab { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.tab-text { + display: none; +} + +@media (min-width: variables.$mid-screen) { + .tab-text { + display: unset; + } +} diff --git a/src/app/components/metadata/metadata.component.ts b/src/app/components/metadata/metadata.component.ts new file mode 100644 index 0000000..59471c5 --- /dev/null +++ b/src/app/components/metadata/metadata.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +import { MatTab, MatTabGroup, MatTabLabel } from '@angular/material/tabs'; +import { MatIcon } from '@angular/material/icon'; +import { TagsComponent } from './tags/tags.component'; +import { MerchantsComponent } from './merchants/merchants.component'; +import { CategoriesComponent } from './categories/categories.component'; + +@Component({ + selector: 'app-metadata', + imports: [ + MatTabGroup, + MatTab, + MatIcon, + MatTabLabel, + TagsComponent, + MerchantsComponent, + CategoriesComponent + ], + templateUrl: './metadata.component.html', + styleUrl: './metadata.component.scss' +}) +export class MetadataComponent { } diff --git a/src/app/components/metadata/tags/tags.component.html b/src/app/components/metadata/tags/tags.component.html new file mode 100644 index 0000000..d0ec707 --- /dev/null +++ b/src/app/components/metadata/tags/tags.component.html @@ -0,0 +1,24 @@ +
+ + + Tags + + + +
+ +
+ +
+ @for (tag of tags(); track tag.id) { + + } + @if (!tags().length) { +
+ No tags to display +
+ } +
+
+
+
diff --git a/src/app/components/metadata/tags/tags.component.scss b/src/app/components/metadata/tags/tags.component.scss new file mode 100644 index 0000000..d841d0f --- /dev/null +++ b/src/app/components/metadata/tags/tags.component.scss @@ -0,0 +1,21 @@ +@use "variables"; + +.tags-container { + padding-top: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + + mat-card { + max-width: variables.$wide-screen; + width: 100%; + } +} + +.new-tag { + padding-top: 0.5rem; +} + +.tag-list { + display: grid; +} diff --git a/src/app/components/metadata/tags/tags.component.ts b/src/app/components/metadata/tags/tags.component.ts new file mode 100644 index 0000000..2d1ae29 --- /dev/null +++ b/src/app/components/metadata/tags/tags.component.ts @@ -0,0 +1,29 @@ +import { Component, computed } from '@angular/core'; +import { Tag, TagService } from '../../../services/tag.service'; +import { MatCardModule } from '@angular/material/card'; +import { MetadataFormComponent } from '../metadata-form/metadata-form.component'; + +@Component({ + selector: 'app-tags', + imports: [ + MatCardModule, + MetadataFormComponent + ], + templateUrl: './tags.component.html', + styleUrl: './tags.component.scss' +}) +export class TagsComponent { + protected tags = computed(() => this.tagService.tags()); + + public constructor(private readonly tagService: TagService) { } + + public async createTag(name: string): Promise { + await this.tagService.create({ name }); + await this.tagService.fetch(); + } + + public async updateTag(tag: Tag): Promise { + await this.tagService.update(tag); + await this.tagService.fetch(); + } +} diff --git a/src/app/pages/expenses/expense-page.component.html b/src/app/pages/expenses/expense-page.component.html index 2add09d..de00fc6 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/pages/expenses/expense-page.component.scss b/src/app/pages/expenses/expense-page.component.scss index b35508b..30bf02c 100644 --- a/src/app/pages/expenses/expense-page.component.scss +++ b/src/app/pages/expenses/expense-page.component.scss @@ -1,4 +1,4 @@ -.expenses-container { +.expenses-page-container { display: flex; flex-direction: column; gap: 1rem; diff --git a/src/app/pages/expenses/expense-page.component.ts b/src/app/pages/expenses/expense-page.component.ts index 18f24e0..542a3fc 100644 --- a/src/app/pages/expenses/expense-page.component.ts +++ b/src/app/pages/expenses/expense-page.component.ts @@ -3,7 +3,7 @@ import { ExpenseListComponent } from '../../components/expense-list/expense-list import { ExpenseComponent } from '../../components/expense/expense.component'; @Component({ - selector: 'app-expenses', + selector: 'app-expenses-page', imports: [ ExpenseListComponent, ExpenseComponent diff --git a/src/app/pages/home/home-page.component.html b/src/app/pages/home/home-page.component.html index 94c5c02..19cf282 100644 --- a/src/app/pages/home/home-page.component.html +++ b/src/app/pages/home/home-page.component.html @@ -1,7 +1,3 @@ -
- +
+

The common sense expense tracker.

diff --git a/src/app/pages/home/home-page.component.scss b/src/app/pages/home/home-page.component.scss index e69de29..0fcfa03 100644 --- a/src/app/pages/home/home-page.component.scss +++ b/src/app/pages/home/home-page.component.scss @@ -0,0 +1,7 @@ +h2 { + font-size: 1.3rem; + line-height: 100%; + font-weight: 500; + margin: 0; + font-family: sans-serif; +} diff --git a/src/app/pages/home/home-page.component.ts b/src/app/pages/home/home-page.component.ts index 724172f..e9a654d 100644 --- a/src/app/pages/home/home-page.component.ts +++ b/src/app/pages/home/home-page.component.ts @@ -1,12 +1,9 @@ import { Component } from '@angular/core'; -import { RouterLink } from '@angular/router'; @Component({ - selector: 'app-home', - imports: [ - RouterLink - ], + selector: 'app-home-page', + imports: [], templateUrl: './home-page.component.html', - styleUrl: './home-page.component.scss', + styleUrl: './home-page.component.scss' }) export class HomePage { } diff --git a/src/app/pages/metadata/metadata-page.component.html b/src/app/pages/metadata/metadata-page.component.html new file mode 100644 index 0000000..61dae30 --- /dev/null +++ b/src/app/pages/metadata/metadata-page.component.html @@ -0,0 +1,3 @@ + diff --git a/src/app/pages/metadata/metadata-page.component.scss b/src/app/pages/metadata/metadata-page.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/metadata/metadata-page.component.ts b/src/app/pages/metadata/metadata-page.component.ts new file mode 100644 index 0000000..418c735 --- /dev/null +++ b/src/app/pages/metadata/metadata-page.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { MetadataComponent } from '../../components/metadata/metadata.component'; + +@Component({ + selector: 'app-metadata-page', + imports: [ + MetadataComponent + ], + templateUrl: './metadata-page.component.html', + styleUrl: './metadata-page.component.scss' +}) +export class MetadataPageComponent { } diff --git a/src/app/pages/reports/reports-page.component.html b/src/app/pages/reports/reports-page.component.html new file mode 100644 index 0000000..cbc6bbb --- /dev/null +++ b/src/app/pages/reports/reports-page.component.html @@ -0,0 +1,3 @@ +
+ Reports page +
diff --git a/src/app/pages/reports/reports-page.component.scss b/src/app/pages/reports/reports-page.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/reports/reports-page.component.ts b/src/app/pages/reports/reports-page.component.ts new file mode 100644 index 0000000..e0fc9e4 --- /dev/null +++ b/src/app/pages/reports/reports-page.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-reports-page', + imports: [], + templateUrl: './reports-page.component.html', + styleUrl: './reports-page.component.scss' +}) +export class ReportsPageComponent { } diff --git a/src/app/services/category.service.ts b/src/app/services/category.service.ts index 9c2da93..b1b4ac3 100644 --- a/src/app/services/category.service.ts +++ b/src/app/services/category.service.ts @@ -10,15 +10,27 @@ export class CategoryService { public readonly categoryPath = 'http://localhost:3000/common-cents/categories'; public constructor(private readonly http: HttpService) { - void this.fetchCategories(); + void this.fetch(); } - public async fetchCategories(): Promise { + public async fetch(): Promise { this.internalCategories.set(await this.http.get(this.categoryPath)); } + + public async create(category: CreateCategory): Promise { + return await this.http.post(this.categoryPath, category); + } + + public async update(category: Category): Promise { + return await this.http.put(this.categoryPath, category); + } } export interface Category { id: string; name: string; } + +export interface CreateCategory { + name: string; +} diff --git a/src/app/services/expense.service.ts b/src/app/services/expense.service.ts index 243c53b..0ca6ff7 100644 --- a/src/app/services/expense.service.ts +++ b/src/app/services/expense.service.ts @@ -14,21 +14,18 @@ export class ExpenseService { public readonly expensePath = 'http://localhost:3000/common-cents/expenses'; // TODO: refactor public constructor(private readonly http: HttpService) { - void this.fetchExpenses(); + void this.fetch(); } - public async fetchExpenses(): Promise { + public async fetch(): Promise { this.internalExpenses.set(await this.http.get(this.expensePath)); } - public async postExpense(createExpense: CreateExpense): Promise { - const createdExpense = await this.http.post(this.expensePath, createExpense); - await this.fetchExpenses(); - - return createdExpense; + public async create(createExpense: CreateExpense): Promise { + return await this.http.post(this.expensePath, createExpense); } - public async updateExpense(updateExpense: UpdateExpense): Promise { + public async update(updateExpense: UpdateExpense): Promise { return await this.http.put(this.expensePath, updateExpense); } } @@ -38,18 +35,18 @@ export interface Expense { date: Date; cents: number; category: Category; - note?: string; merchant?: Merchant; + note?: string; tags: Tag[]; } export interface CreateExpense { date: Temporal.PlainDate; cents: number; - categoryId: string; + category: Category; + merchant?: Merchant; note?: string; - merchantId?: string; - tagIds?: string[]; + tags: Tag[]; } export interface UpdateExpense extends CreateExpense { diff --git a/src/app/services/merchant.service.ts b/src/app/services/merchant.service.ts index 8cca35b..1d0290b 100644 --- a/src/app/services/merchant.service.ts +++ b/src/app/services/merchant.service.ts @@ -10,15 +10,27 @@ export class MerchantService { public readonly merchantPath = 'http://localhost:3000/common-cents/merchants'; public constructor(private readonly http: HttpService) { - void this.fetchMerchants(); + void this.fetch(); } - public async fetchMerchants(): Promise { + public async fetch(): Promise { this.internalMerchants.set(await this.http.get(this.merchantPath)); } + + public async create(merchant: CreateMerchant): Promise { + return await this.http.post(this.merchantPath, merchant); + } + + public async update(merchant: Merchant): Promise { + return await this.http.put(this.merchantPath, merchant); + } } export interface Merchant { id: string; name: string; } + +export interface CreateMerchant { + name: string; +} diff --git a/src/app/services/tag.service.ts b/src/app/services/tag.service.ts index 06a373b..c23ccab 100644 --- a/src/app/services/tag.service.ts +++ b/src/app/services/tag.service.ts @@ -10,15 +10,27 @@ export class TagService { public readonly tagPath = 'http://localhost:3000/common-cents/tags'; public constructor(private readonly http: HttpService) { - void this.fetchTags(); + void this.fetch(); } - public async fetchTags(): Promise { + public async fetch(): Promise { this.internalTags.set(await this.http.get(this.tagPath)); } + + public async create(tag: CreateTag): Promise { + return await this.http.post(this.tagPath, tag); + } + + public async update(tag: Tag): Promise { + return await this.http.put(this.tagPath, tag); + } } export interface Tag { id: string; name: string; } + +export interface CreateTag { + name: string; +} diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss new file mode 100644 index 0000000..7d6130d --- /dev/null +++ b/src/assets/styles/variables.scss @@ -0,0 +1,2 @@ +$mid-screen: 500px; +$wide-screen: 800px; diff --git a/src/index.html b/src/index.html index 66e56cd..9dbd9e8 100644 --- a/src/index.html +++ b/src/index.html @@ -13,7 +13,7 @@ - + diff --git a/src/styles.scss b/src/styles.scss index ac47f6e..fe32d0a 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -10,7 +10,7 @@ html { height: 100%; @include mat.theme(( color: ( - primary: mat.$cyan-palette, + primary: mat.$azure-palette, tertiary: mat.$orange-palette, ), typography: Roboto,