diff --git a/frontend/Exence/src/app/private/dashboard/categories/categories.component.ts b/frontend/Exence/src/app/private/dashboard/categories/categories.component.ts index a41a8f17..fe875e78 100644 --- a/frontend/Exence/src/app/private/dashboard/categories/categories.component.ts +++ b/frontend/Exence/src/app/private/dashboard/categories/categories.component.ts @@ -8,6 +8,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { CategoryGet } from '../../../data-model/modules/category/CategoryGet'; import { CategorySummaryResponse } from '../../../data-model/modules/category/CategorySummaryResponse'; import { CategoryType } from '../../../data-model/modules/category/CategoryType'; +import { RecurringTransactionCreate } from '../../../data-model/modules/transaction/RecurringTransactionCreate'; import { TransactionCreate } from '../../../data-model/modules/transaction/TransactionCreate'; import { AnimatedSkeletonLoaderComponent } from '../../../shared/animated-skeleton-loader/animated-skeleton-loader.component'; import { BaseComponent } from '../../../shared/base-component/base.component'; @@ -21,7 +22,9 @@ import { CreateCategoryDialogComponent } from '../../transactions-and-categories import { CreateTransactionDialogComponent, CreateTransactionDialogData, + CreateTransactionDialogResult, } from '../../transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component'; +import { RecurringStore } from '../../transactions-and-categories/recurring.store'; import { TransactionStore } from '../../transactions-and-categories/transaction.store'; @Component({ @@ -44,6 +47,7 @@ export class CategoriesComponent extends BaseComponent { private readonly dialog = inject(DialogService); private readonly categoryStore = inject(CategoryStore); private readonly transactionStore = inject(TransactionStore); + private readonly recurringStore = inject(RecurringStore); readonly display = inject(DisplaySizeService); isLoading = input.required(); @@ -69,10 +73,14 @@ export class CategoriesComponent extends BaseComponent { async openCreateTransactionDialog(): Promise { const result = await this.dialog.openNonModal< CreateTransactionDialogData | undefined, - TransactionCreate | null + CreateTransactionDialogResult | null >(CreateTransactionDialogComponent, undefined); if (!result) return; - this.transactionStore.createTransaction(result); + if (result.isRecurring) { + this.recurringStore.createRecurringTransaction(result.result as RecurringTransactionCreate); + } else { + this.transactionStore.createTransaction(result.result as TransactionCreate); + } } calcPercentage(amount: number): number { diff --git a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts index 0269a952..a43daaec 100644 --- a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts +++ b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts @@ -22,6 +22,7 @@ import { CategoryStore } from '../transactions-and-categories/category.store'; import { CreateTransactionDialogComponent, CreateTransactionDialogData, + CreateTransactionDialogResult, } from '../transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component'; import { RecurringStore } from '../transactions-and-categories/recurring.store'; import { TransactionListComponent } from '../transactions-and-categories/transaction-list/transaction-list.component'; @@ -87,10 +88,13 @@ export class DashboardComponent extends BaseComponent { async openCreateTransactionDialog(transactionType: TransactionType): Promise { const result = await this.dialog.openNonModal< CreateTransactionDialogData | undefined, - TransactionCreate | RecurringTransactionCreate | null + CreateTransactionDialogResult | null >(CreateTransactionDialogComponent, { type: transactionType }); if (!result) return; - if ('date' in result) this.transactionStore.createTransaction(result as TransactionCreate); - else this.recurringStore.createRecurringTransaction(result as RecurringTransactionCreate); + if (result.isRecurring) { + this.recurringStore.createRecurringTransaction(result.result as RecurringTransactionCreate); + } else { + this.transactionStore.createTransaction(result.result as TransactionCreate); + } } } diff --git a/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.ts b/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.ts index d47f780b..15831d3a 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.ts @@ -56,6 +56,11 @@ export interface CreateTransactionDialogData { isRecurring?: boolean; } +export interface CreateTransactionDialogResult { + result: TransactionCreate | RecurringTransactionCreate; + isRecurring: boolean; +} + @Component({ selector: 'ex-create-transaction-dialog', templateUrl: './create-transaction-dialog.component.html', @@ -87,7 +92,7 @@ export interface CreateTransactionDialogData { }) export class CreateTransactionDialogComponent extends DialogWithBaseComponent< CreateTransactionDialogData | undefined, - TransactionCreate | RecurringTransactionCreate | null + CreateTransactionDialogResult | null > { private readonly fb = inject(NonNullableFormBuilder); private readonly categoryService = inject(CategoryService); @@ -302,7 +307,7 @@ export class CreateTransactionDialogComponent extends DialogWithBaseComponent< exchangeRate: formValue.exchangeRate!, } satisfies TransactionCreate; } - this.dialogRef.submit(request); + this.dialogRef.submit({ result: request, isRecurring: this.isRecurring() }); } localizeCurrency(currency: SupportedCurrency): string { diff --git a/frontend/Exence/src/app/private/transactions-and-categories/recurring-list/recurring-list.component.ts b/frontend/Exence/src/app/private/transactions-and-categories/recurring-list/recurring-list.component.ts index 44e70444..76a89bef 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/recurring-list/recurring-list.component.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/recurring-list/recurring-list.component.ts @@ -8,6 +8,7 @@ import { PagedResponse } from '../../../data-model/modules/common/PagedResponse' import { ColumnDef, DataTableComponent, TableAction } from '../../../shared/data-table/data-table.component'; import { ExCellDirective } from '../../../shared/data-table/ex-cell.directive'; import { RecurringStore } from '../recurring.store'; +import { TransactionStore } from '../transaction.store'; import { CategoryStore } from '../category.store'; import { DialogService } from '../../../shared/dialog/dialog.service'; import { SvgIcons } from '../../../shared/svg-icons/svg-icons'; @@ -17,6 +18,7 @@ import { CurrencyPipe } from '../../../shared/pipes/currency.pipe'; import { CreateTransactionDialogComponent, CreateTransactionDialogData, + CreateTransactionDialogResult, } from '../create-transaction-dialog/create-transaction-dialog.component'; import { DisplaySizeService } from '../../../shared/display-size.service'; import { MatLabel } from '@angular/material/form-field'; @@ -48,6 +50,7 @@ import { DayOfWeek } from '../../../data-model/modules/transaction/DayOfWeek'; }) export class RecurringListComponent { private readonly recurringStore = inject(RecurringStore); + private readonly transactionStore = inject(TransactionStore); private readonly categoryStore = inject(CategoryStore); private readonly dialog = inject(DialogService); private readonly display = inject(DisplaySizeService); @@ -134,12 +137,16 @@ export class RecurringListComponent { async openCreate(): Promise { const result = await this.dialog.openNonModal< CreateTransactionDialogData, - TransactionCreate | RecurringTransactionCreate | null + CreateTransactionDialogResult | null >( CreateTransactionDialogComponent, this.type() ? { type: this.type()!, isRecurring: true } : { isRecurring: true }, ); if (!result) return; - this.recurringStore.createRecurringTransaction(result as RecurringTransactionCreate); + if (result.isRecurring) { + this.recurringStore.createRecurringTransaction(result.result as RecurringTransactionCreate); + } else { + this.transactionStore.createTransaction(result.result as TransactionCreate); + } } } diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transaction-list/transaction-list.component.ts b/frontend/Exence/src/app/private/transactions-and-categories/transaction-list/transaction-list.component.ts index b907a30e..c31179f5 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transaction-list/transaction-list.component.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transaction-list/transaction-list.component.ts @@ -3,6 +3,7 @@ import { booleanAttribute, Component, computed, inject, input } from '@angular/c import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; import { PagedResponse } from '../../../data-model/modules/common/PagedResponse'; +import { RecurringTransactionCreate } from '../../../data-model/modules/transaction/RecurringTransactionCreate'; import { TransactionCreate } from '../../../data-model/modules/transaction/TransactionCreate'; import { TransactionGet } from '../../../data-model/modules/transaction/TransactionGet'; import { TransactionModel } from '../../../data-model/modules/transaction/TransactionModel'; @@ -19,11 +20,13 @@ import { CategoryStore } from '../category.store'; import { CreateTransactionDialogComponent, CreateTransactionDialogData, + CreateTransactionDialogResult, } from '../create-transaction-dialog/create-transaction-dialog.component'; import { EditTransactionDialogComponent, EditTransactionDialogData, } from '../edit-transaction-dialog/edit-transaction-dialog.component'; +import { RecurringStore } from '../recurring.store'; import { TransactionStore } from '../transaction.store'; import { TransactionListDetailsComponent } from './transaction-list-details/transaction-list-details.component'; import { TranslocoService } from '@jsverse/transloco'; @@ -50,6 +53,7 @@ import { format } from 'date-fns'; }) export class TransactionListComponent { private readonly transactionStore = inject(TransactionStore); + private readonly recurringStore = inject(RecurringStore); private readonly categoryStore = inject(CategoryStore); private readonly dialog = inject(DialogService); private readonly currencyService = inject(CurrencyService); @@ -135,10 +139,14 @@ export class TransactionListComponent { async openCreate(): Promise { const result = await this.dialog.openNonModal< CreateTransactionDialogData | undefined, - TransactionCreate | null + CreateTransactionDialogResult | null >(CreateTransactionDialogComponent, this.type() ? { type: this.type()! } : undefined); if (!result) return; - this.transactionStore.createTransaction(result as TransactionCreate); + if (result.isRecurring) { + this.recurringStore.createRecurringTransaction(result.result as RecurringTransactionCreate); + } else { + this.transactionStore.createTransaction(result.result as TransactionCreate); + } } private async dupliateTransaction(row: TransactionModel): Promise { diff --git a/frontend/Exence/src/integration/create-transaction-dialog/tests/create-transaction-dialog.component.spec.ts b/frontend/Exence/src/integration/create-transaction-dialog/tests/create-transaction-dialog.component.spec.ts index d48e1582..baaa12a8 100644 --- a/frontend/Exence/src/integration/create-transaction-dialog/tests/create-transaction-dialog.component.spec.ts +++ b/frontend/Exence/src/integration/create-transaction-dialog/tests/create-transaction-dialog.component.spec.ts @@ -376,7 +376,9 @@ describe('CreateTransactionDialogComponent', () => { fillRequiredFields(); component.form.controls.recurring.controls.isRecurring.setValue(false); component.create(); - expect(dialogSubmitSpy).toHaveBeenCalledWith( + const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; + expect(submitted.isRecurring).toBe(false); + expect(submitted.result).toEqual( jasmine.objectContaining({ title: 'Test Transaction', amount: 100, @@ -385,9 +387,8 @@ describe('CreateTransactionDialogComponent', () => { date: jasmine.any(String), }), ); - const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.frequency).toBeUndefined(); - expect(submitted.startDate).toBeUndefined(); + expect(submitted.result.frequency).toBeUndefined(); + expect(submitted.result.startDate).toBeUndefined(); }); it('should call dialogRef.submit with RecurringTransactionCreate when recurring', () => { @@ -396,7 +397,9 @@ describe('CreateTransactionDialogComponent', () => { component.form.controls.recurring.controls.configs.controls.frequency.setValue(RecurrenceFrequency.WEEKLY); component.form.controls.recurring.controls.configs.controls.endCondition.setValue(EndCondition.NEVER); component.create(); - expect(dialogSubmitSpy).toHaveBeenCalledWith( + const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; + expect(submitted.isRecurring).toBe(true); + expect(submitted.result).toEqual( jasmine.objectContaining({ frequency: RecurrenceFrequency.WEEKLY, interval: jasmine.any(Number), @@ -404,9 +407,8 @@ describe('CreateTransactionDialogComponent', () => { endCondition: EndCondition.NEVER, }), ); - const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.date).toBeUndefined(); - expect(submitted.exchangeRate).toBeUndefined(); + expect(submitted.result.date).toBeUndefined(); + expect(submitted.result.exchangeRate).toBeUndefined(); }); it('should include dayOfWeek when frequency is WEEKLY', () => { @@ -415,7 +417,7 @@ describe('CreateTransactionDialogComponent', () => { component.form.controls.recurring.controls.configs.controls.frequency.setValue(RecurrenceFrequency.WEEKLY); component.create(); const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.dayOfWeek).toBeDefined(); + expect(submitted.result.dayOfWeek).toBeDefined(); }); it('should NOT include dayOfWeek when frequency is MONTHLY', () => { @@ -424,7 +426,7 @@ describe('CreateTransactionDialogComponent', () => { component.form.controls.recurring.controls.configs.controls.frequency.setValue(RecurrenceFrequency.MONTHLY); component.create(); const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.dayOfWeek).toBeUndefined(); + expect(submitted.result.dayOfWeek).toBeUndefined(); }); it('should include dayOfMonth when frequency is MONTHLY', () => { @@ -433,7 +435,7 @@ describe('CreateTransactionDialogComponent', () => { component.form.controls.recurring.controls.configs.controls.frequency.setValue(RecurrenceFrequency.MONTHLY); component.create(); const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.dayOfMonth).toBeDefined(); + expect(submitted.result.dayOfMonth).toBeDefined(); }); it('should include endDate when endCondition is UNTIL_DATE', () => { @@ -445,7 +447,7 @@ describe('CreateTransactionDialogComponent', () => { component.form.controls.recurring.controls.configs.controls.endDate.setValue(futureDate); component.create(); const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.endDate).toEqual(jasmine.any(String)); + expect(submitted.result.endDate).toEqual(jasmine.any(String)); }); it('should include maxOccurrences when endCondition is AFTER_OCCURRENCES', () => { @@ -457,7 +459,7 @@ describe('CreateTransactionDialogComponent', () => { component.form.controls.recurring.controls.configs.controls.maxOccurrences.setValue(5); component.create(); const submitted = dialogSubmitSpy.calls.mostRecent().args[0]; - expect(submitted.maxOccurrences).toBe(5); + expect(submitted.result.maxOccurrences).toBe(5); }); });