From d80c9921d2762080633133f66a6ad2be9acc747f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sun, 11 Jan 2026 20:05:44 +0100 Subject: [PATCH 1/2] #EX-209: Add dialog.service to handle dialogs with custom preferences --- .../src/app/shared/dialog/dialog.service.ts | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 frontend/Exence/src/app/shared/dialog/dialog.service.ts diff --git a/frontend/Exence/src/app/shared/dialog/dialog.service.ts b/frontend/Exence/src/app/shared/dialog/dialog.service.ts new file mode 100644 index 0000000..8a6cb21 --- /dev/null +++ b/frontend/Exence/src/app/shared/dialog/dialog.service.ts @@ -0,0 +1,299 @@ +import { Directive, inject, Injectable, Injector, TemplateRef } from '@angular/core'; +import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog'; +import { firstValueFrom } from 'rxjs'; +import { BaseComponent } from '../base-component/base.component'; + + +/** + * Represents a reference to an open dialog instance. + * + * @template I - The type of input data passed to the dialog. Default: `undefined`. + * @template O - The type of output data returned when the dialog closes. Default: `void`. + * + * @remarks + * This class provides methods to control and interact with a dialog instance, + * including the ability to close the dialog and lock/unlock its state. + * + * @method onClose - Callback function invoked when the dialog closes, receiving the output value. + * @method setLocked - Function to to set the locked state (availability to close the dialog) of dialog. + * @field value - The input data passed to the dialog. + * + * @example + * ```typescript + * const dialogRef = new DialogRef( + * (result) => console.log('Dialog closed with:', result), + * (locked) => console.log('Dialog locked state:', locked), + * { inputData: 'example' } + * ); + * + * // Close the dialog with a result + * dialogRef.close({ success: true }); + * + * // Lock the dialog to prevent closing + * dialogRef.setLocked(true); + * ``` + */ +class DialogRef { + constructor( + private onClose: (value: O) => void, + readonly setLocked: (isLocked: boolean) => void, + readonly value: I + ) { } + + public close(value: O): void { + this.onClose(value); + } +} + +/** + * Abstract base class for dialog components that provides a structured way to handle dialog interactions. + * + * @template I - The input type that will be passed to the dialog when it is opened. Default: `undefined`. + * @template O - The output type that will be returned when the dialog is closed. Default: `void`. + * + * @example + * ```typescript + * export class ConfirmDialogComponent extends DialogComponent { + * inputData: ConfirmDialogData = this.dialogRef.value; + * } + * ``` + */ +@Directive() +export abstract class DialogComponent { + constructor(public dialogRef: DialogRef) {} +} + +/** + * Abstract base class for dialog components that extend BaseComponent. + * + * This class combines dialog functionality with the BaseComponent lifecycle management, + * providing a foundation for creating dialog components with proper cleanup and state management. + * + * @template I - The type of data passed to the dialog when opened (contravariant) + * @template O - The type of data returned when the dialog is closed (covariant) + * + * @example + * ```typescript + * export class MyDialogComponent extends DialogWithBaseComponent { + * constructor(dialogRef: DialogRef) { + * super(dialogRef); + * } + * } + * ``` + */ +@Directive() +export abstract class DialogWithBaseComponent extends BaseComponent { + constructor(public dialogRef: DialogRef) { super(); } +} + +/** + * Type definition for a component that accepts a `DialogRef` in it's constructor. + * + * @template I - The type of input data passed to the dialog + * @template O - The type of output data returned from the dialog + * @template T - The type of the dialog component instance, Default: `any`. + * + * @param dialogRef - Reference to the dialog instance with input and output types + * @param rest - Additional parameters that can be injected into the dialog constructor + * + * @returns A new instance of the dialog component + */ +type DialogConstructor = + new (dialogRef: DialogRef, ...rest: any[]) => T; + + +/** + * Represents a dialog that can be opened in the application. + * + * @typeParam I - The input data type passed to the dialog when it's opened + * @typeParam O - The output data type returned when the dialog is closed + * + * @remarks + * This type supports two kinds of dialogs: + * - `DialogConstructor`: A custom Exence dialog type that accepts a `DialogRef in it's constructor`. + * - `TemplateRef`: An Angular template reference that can be used to render any component as a dialog + * + * @example + * ```typescript + * // Exence's custom dialog component + * export class ExenceDialogComponent extends DialogComponent { + * inputData: ExenceDialogData = this.dialogRef.value; + * } + * const isExenceDialog: boolean = this.dialog.open(ExenceDialogComponent, { + * value: 'inputString', + * disableClose: { defaultValue: false } + * ); + * + * // Using a template reference + * ``` + * ```html + * + *

Confirm Action

+ *

Are you sure?

+ *
+ * ``` + * ```typescript + * class MyComponent { + * myTemplateRef = viewChild>('myTemplate'); + * + * openTemplateDialog() { + * const result = await this.dialogService.open(this.myTemplateRef()!, { + * value: undefined, + * disableClose: { defaultValue: false } + * }); + * } + * } + * ``` + */ +type Dialog = DialogConstructor | TemplateRef; + + +/** + * Configuration settings for Exence's custom dialogs, excluding injector and disableClose properties. + * + * This type is derived from MatDialogConfig but omits the 'injector' and 'disableClose' + * properties, providing a simplified interface for configuring dialog behavior and appearance. + * + * @remarks + * The omitted properties: + * - `injector` - managed internally by the dialog service to ensure consistent behavior across the application. + * - `disableClose` - removed so later a `defaultValue` can be assigned that's type is equivalent to the dialog's return type + * + * @see {@link https://material.angular.io/components/dialog/api#MatDialogConfig | MatDialogConfig} + */ +type DialogSettings = Omit; + +@Injectable({ + providedIn: 'root' +}) +export class DialogService extends BaseComponent { + private readonly injector = inject(Injector); + private readonly matDialog = inject(MatDialog); + + /** + * Opens a non-modal dialog that can be closed by clicking outside or pressing ESC. *(`dialog.close()` still closes the dialog)* + * + * @template I - The type of input value passed to the dialog component + * @template O - The type of output value returned by the dialog component + * + * @param component - The dialog component to be displayed + * @param value - The input value to be passed to the dialog component + * @param settings - Optional dialog settings to customize the dialog behavior + * + * @returns A **Promise** that resolves to the output value when the dialog is closed, or undefined if dismissed + * + * @remarks + * This method creates a dialog that can be dismissed by user actions outside the dialog itself. + * It internally calls the `open` method with `disableClose` set to allow closing with `undefined` as default value. + */ + public openNonModal( + component: Dialog, + value: I, + settings?: DialogSettings + ): Promise { + return this.open( + component, + { + ...settings, + value, + disableClose: { defaultValue: undefined }, + } + ); + } + + /** + * Opens a modal dialog with the specified component and configuration. + * + * @template I - The type of input data passed to the dialog component + * @template O - The type of output data returned from the dialog component + * + * @param component - The dialog component to be displayed + * @param value - The input value to be passed to the dialog component + * @param settings - Configuration settings for the dialog + * + * @returns A **Promise** that resolves with the output data when the dialog is closed. + * + * @remarks + * This method creates a modal dialog that cannot be closed by clicking outside or pressing ESC. + * The dialog's `disableClose` property is automatically set to `true`. + */ + public openModal( + component: Dialog, + value: I, + settings?: DialogSettings + ): Promise { + return this.open( + component, + { + ...settings, + value, + disableClose: true, + } + ); + } + + /** + * Opens a dialog with the specified component and settings. + * + * @template I - The type of the input value passed to the dialog + * @template O - The type of the output value returned by the dialog + * + * @param component - The dialog component to be displayed + * @param settings - Configuration options for the dialog, including: + * - `value`: The input value to pass to the dialog component + * - `disableClose`: Either `true` to prevent closing, or an object with `defaultValue` + * that will be returned when the dialog is closed via backdrop click + * - Additional MatDialog settings ({@link https://material.angular.io/components/dialog/api#MatDialogConfig | MatDialogConfig}) + * + * @returns A Promise that resolves with the dialog's output value when the dialog is closed + * + * @remarks + * - When `disableClose` is `true`, the dialog cannot be closed by clicking the backdrop + * - When `disableClose` is an object with `defaultValue`, backdrop clicks will close the dialog + * with the specified default value (unless the dialog is dynamically locked) + * - The dialog can be dynamically locked/unlocked during its lifetime using the DialogRef + * - Sets the `dialogOpened` signal to `true` when a dialog is opened + */ + public open( + component: Dialog, + settings: DialogSettings & { value: I; disableClose: true | { defaultValue: O } } + ): Promise { + return new Promise((resolve) => { + let matDialogRef: MatDialogRef | undefined; + let locked = settings.disableClose === true; + + const dialogRef = new DialogRef( + (value) => matDialogRef?.close(value), + (value) => { + locked = value; + matDialogRef!.disableClose = value; + }, + settings.value, + ); + + const injector = Injector.create({ + providers: [{ provide: DialogRef, useValue: dialogRef }], + parent: this.injector, + }); + matDialogRef = this.matDialog.open( + component, { + ...settings, + maxHeight: settings.maxHeight ?? 'auto', + maxWidth: settings.maxWidth ?? 'auto', + height: settings.height ?? 'auto', + disableClose: locked, + injector, + } + ); + + if (settings.disableClose !== true) { + const value = settings.disableClose.defaultValue; + this.addSubscription(matDialogRef.backdropClick().subscribe(() => { + if (!locked) dialogRef.close(value); + })); + } + + firstValueFrom(matDialogRef.afterClosed()).then(resolve); // convert close to promise + }) + } +} \ No newline at end of file From 59f9ece61717cd3053236329b33d5d1a951fbdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sun, 11 Jan 2026 20:34:49 +0100 Subject: [PATCH 2/2] Migrate to using dialog.service --- .../categories/categories.component.ts | 23 ++++----- .../private/dashboard/dashboard.component.ts | 31 ++++------- .../create-category-dialog.component.ts | 16 +++--- .../create-transaction-dialog.component.ts | 21 ++++---- ...transactions-and-categories.component.html | 35 ++++++++++--- .../transactions-and-categories.component.ts | 41 +++++---------- .../shared/data-table/data-table.component.ts | 51 +++++++------------ 7 files changed, 101 insertions(+), 117 deletions(-) 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 de6b1dd..4e067c7 100644 --- a/frontend/Exence/src/app/private/dashboard/categories/categories.component.ts +++ b/frontend/Exence/src/app/private/dashboard/categories/categories.component.ts @@ -1,13 +1,11 @@ import { Component, inject, input } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; -import { MatDialog } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { Category } from '../../../data-model/modules/category/Category'; import { CategorySummaryResponse } from '../../../data-model/modules/category/CategorySummaryResponse'; import { BaseComponent } from '../../../shared/base-component/base.component'; import { ButtonComponent } from '../../../shared/button/button.component'; +import { DialogService } from '../../../shared/dialog/dialog.service'; import { DisplaySizeService } from '../../../shared/display-size.service'; -import { SnackbarService } from '../../../shared/snackbar/snackbar.service'; import { CreateCategoryDialogComponent } from '../../transactions-and-categories/create-category-dialog/create-category-dialog.component'; // TODO move to interval filter component when created @@ -24,26 +22,25 @@ export interface IntervalInfo { @Component({ selector: 'ex-categories', - imports: [MatProgressBarModule, MatCardModule, ButtonComponent], + imports: [ + MatProgressBarModule, + MatCardModule, + ButtonComponent + ], templateUrl: './categories.component.html', styleUrl: './categories.component.scss', }) export class CategoriesComponent extends BaseComponent { public display = inject(DisplaySizeService); - private readonly snackbarService = inject(SnackbarService); - private readonly dialog = inject(MatDialog); + private readonly dialog = inject(DialogService); totalExpense = input.required(); categories = input.required(); - openCreateCategoryDialog(): void { - this.dialog.open( + async openCreateCategoryDialog(): Promise { + await this.dialog.openNonModal( CreateCategoryDialogComponent, undefined - ).afterClosed().subscribe((newCategory) => { - if (newCategory) { - this.snackbarService.showSuccess(`Category '${newCategory.emoji}' created successfully!`); - } - }); + ); } 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 e9a0159..43cde93 100644 --- a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts +++ b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts @@ -1,7 +1,6 @@ import { CommonModule } from '@angular/common'; import { Component, computed, inject, OnInit } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { Router, RouterModule } from '@angular/router'; +import { RouterModule } from '@angular/router'; import { Category } from '../../data-model/modules/category/Category'; import { CategorySummaryResponse } from '../../data-model/modules/category/CategorySummaryResponse'; import { PagedResponse } from '../../data-model/modules/common/PagedResponse'; @@ -16,13 +15,13 @@ import { ButtonComponent } from '../../shared/button/button.component'; import { CardSliderDirective } from '../../shared/card-slider.directive'; import { ChartComponent } from '../../shared/chart/chart.component'; import { DataTableComponent } from '../../shared/data-table/data-table.component'; +import { DialogService } from '../../shared/dialog/dialog.service'; import { DisplaySizeService } from '../../shared/display-size.service'; import { NavigationService } from '../../shared/navigation/navigation.service'; -import { SnackbarService } from '../../shared/snackbar/snackbar.service'; import { CurrentUserService } from '../../shared/user/current-user.service'; import { ViewToggleComponent } from '../../shared/view-toggle/view-toggle.component'; import { CategoryService } from '../category.service'; -import { CreateTransactionDialogComponent, CreateTransactionDialogData } from '../transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component'; +import { CreateTransactionDialogComponent } from '../transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component'; import { TransactionService } from '../transactions-and-categories/transaction.service'; @Component({ @@ -45,10 +44,8 @@ export class DashboardComponent implements OnInit { private readonly currentUserService = inject(CurrentUserService); private readonly transactionService = inject(TransactionService); private readonly categoryService = inject(CategoryService); - private readonly snackbarService = inject(SnackbarService); + private readonly dialog = inject(DialogService); readonly display = inject(DisplaySizeService); - readonly dialog = inject(MatDialog); - readonly router = inject(Router); readonly navigation = inject(NavigationService); @@ -93,21 +90,15 @@ export class DashboardComponent implements OnInit { }); } - public openCreateTransactionDialog(transactionType: TransactionType): void { - const data: CreateTransactionDialogData = { - type: transactionType, - }; - this.dialog.open( - CreateTransactionDialogComponent, { data } - ).afterClosed().subscribe( - async (newTransaction?: Transaction) => { - if (newTransaction) { - this.transactions = (await this.transactionService.list()); - this.snackbarService.showSuccess(`Transaction '${newTransaction.title.slice(0, 10)}${newTransaction.title.length > 10 ? '...' : ''}' created successfully!`); - } - }); + public async openCreateTransactionDialog(transactionType: TransactionType): Promise { + const result = await this.dialog.openNonModal( + CreateTransactionDialogComponent, { type: transactionType } + ); + if (!result) return; + this.transactions = await this.transactionService.list(); } + async onDataChanged(): Promise { await this.initialize(); } diff --git a/frontend/Exence/src/app/private/transactions-and-categories/create-category-dialog/create-category-dialog.component.ts b/frontend/Exence/src/app/private/transactions-and-categories/create-category-dialog/create-category-dialog.component.ts index 73ed26d..21d3319 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/create-category-dialog/create-category-dialog.component.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/create-category-dialog/create-category-dialog.component.ts @@ -1,7 +1,6 @@ import { Component, inject } from '@angular/core'; import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -10,7 +9,9 @@ import { PickerComponent } from '@ctrl/ngx-emoji-mart'; import { EmojiEvent } from '@ctrl/ngx-emoji-mart/ngx-emoji'; import { Category } from '../../../data-model/modules/category/Category'; import { ButtonComponent } from '../../../shared/button/button.component'; +import { DialogComponent } from '../../../shared/dialog/dialog.service'; import { InputClearButtonComponent } from '../../../shared/input-clear-button/input-clear-button.component'; +import { SnackbarService } from '../../../shared/snackbar/snackbar.service'; import { ValidatorComponent } from '../../../shared/validator/validator.component'; import { CategoryService } from '../../category.service'; @@ -31,12 +32,12 @@ import { CategoryService } from '../../category.service'; ValidatorComponent, ], }) -export class CreateCategoryDialogComponent { - private readonly dialogRef = inject(MatDialogRef); +export class CreateCategoryDialogComponent extends DialogComponent { private readonly categoryService = inject(CategoryService); private readonly fb = inject(NonNullableFormBuilder); + private readonly snackbarService = inject(SnackbarService); - data = inject(MAT_DIALOG_DATA); + data = this.dialogRef.value; form = this.fb.group({ name: this.fb.control('', [Validators.required, Validators.maxLength(255)]), @@ -58,7 +59,7 @@ export class CreateCategoryDialogComponent { } close(): void { - this.dialogRef.close(); + this.dialogRef.close(false); } async create(): Promise { @@ -69,9 +70,10 @@ export class CreateCategoryDialogComponent { }; try { const newCategory = await this.categoryService.create(request); - this.dialogRef.close(newCategory); + this.snackbarService.showSuccess(`Category '${newCategory.emoji}' created successfully!`); + this.dialogRef.close(true); } catch (_err) { - this.dialogRef.close(); + this.dialogRef.close(false); } } } \ No newline at end of file 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 83fdfa8..08018b4 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 @@ -3,16 +3,16 @@ import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angula import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { Category } from '../../../data-model/modules/category/Category'; import { Transaction } from '../../../data-model/modules/transaction/Transaction'; import { TransactionType } from '../../../data-model/modules/transaction/TransactionType'; -import { BaseComponent } from '../../../shared/base-component/base.component'; import { ButtonComponent } from '../../../shared/button/button.component'; +import { DialogWithBaseComponent } from '../../../shared/dialog/dialog.service'; import { InputClearButtonComponent } from '../../../shared/input-clear-button/input-clear-button.component'; +import { SnackbarService } from '../../../shared/snackbar/snackbar.service'; import { ValidatorComponent } from '../../../shared/validator/validator.component'; import { CategoryService } from '../../category.service'; import { TransactionService } from '../transaction.service'; @@ -38,13 +38,13 @@ export interface CreateTransactionDialogData { ValidatorComponent, ], }) -export class CreateTransactionDialogComponent extends BaseComponent implements OnInit { - private readonly dialogRef = inject(MatDialogRef); +export class CreateTransactionDialogComponent extends DialogWithBaseComponent implements OnInit { private readonly transactionService = inject(TransactionService); private readonly fb = inject(NonNullableFormBuilder); private readonly categoryService = inject(CategoryService); + private readonly snackbarService = inject(SnackbarService); - data: CreateTransactionDialogData = inject(MAT_DIALOG_DATA); + data = this.dialogRef.value; transactionTypes: TransactionType[] = Object.values(TransactionType); @@ -62,10 +62,10 @@ export class CreateTransactionDialogComponent extends BaseComponent implements O async ngOnInit(): Promise { this.categories = await this.categoryService.list(); - if (this.data.type) { + if (this.data?.type) { this.form.controls.type.setValue(this.data.type); } - if (this.data.isRecurring) { + if (this.data?.isRecurring) { this.form.controls.recurring.setValue(this.data.isRecurring); } this.addSubscription(this.form.controls.amount.valueChanges.subscribe(value => { @@ -76,7 +76,7 @@ export class CreateTransactionDialogComponent extends BaseComponent implements O } close(): void { - this.dialogRef.close(); + this.dialogRef.close(false); } async create(): Promise { @@ -92,9 +92,10 @@ export class CreateTransactionDialogComponent extends BaseComponent implements O } as Transaction; try { const newTransaction = await this.transactionService.create(request); - this.dialogRef.close(newTransaction); + this.snackbarService.showSuccess(`Transaction '${newTransaction.title.slice(0, 10)}${newTransaction.title.length > 10 ? '...' : ''}' created successfully!`); + this.dialogRef.close(true); } catch (_err) { - this.dialogRef.close(); + this.dialogRef.close(false); } } } \ No newline at end of file diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.html b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.html index 376b352..ef72480 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.html +++ b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.html @@ -5,7 +5,11 @@
-

+

@if (selectedIndex === 0) { Transactions } @else { @@ -20,7 +24,9 @@

-
+
payments @if (display.isMd()) { Transactions @@ -30,7 +36,9 @@

-
+
cached @if (display.isMd()) { Categories @@ -55,7 +63,10 @@

@if (display.isLg()) { } @else {
-
+
= {} as PagedResponse; @@ -67,30 +66,16 @@ export class TransactionsAndCategoriesComponent implements OnInit { }); } - public openCreateTransactionDialog(): void { - this.dialog.open( - CreateTransactionDialogComponent, undefined - ).afterClosed().subscribe( - async (newTransaction?: Transaction) => { - if (newTransaction) { - await this.initialize(); // to trigger data refresh in all tables (e.g. if a recurring transaction was created) - this.snackbarService.showSuccess(`Transaction '${newTransaction.title.slice(0, 10)}${newTransaction.title.length > 10 ? '...' : ''}' created successfully!`); - } - } - ); + public async openCreateTransactionDialog(): Promise { + const result = await this.dialog.openNonModal(CreateTransactionDialogComponent, undefined); + if (!result) return; + await this.initialize(); // to trigger data refresh in all tables (e.g. if a recurring transaction was created) } - public openCreateCategoryDialog(): void { - this.dialog.open( - CreateCategoryDialogComponent, undefined - ).afterClosed().subscribe( - async (newCategory?: Category) => { - if (newCategory) { - this.categories = (await this.categoryService.list()); - this.snackbarService.showSuccess(`Category '${newCategory.emoji}' created successfully!`); - } - } - ); + public async openCreateCategoryDialog(): Promise { + const result = await this.dialog.openNonModal(CreateCategoryDialogComponent, undefined); + if (!result) return; + this.categories = (await this.categoryService.list()); } async onDataChanged(): Promise { diff --git a/frontend/Exence/src/app/shared/data-table/data-table.component.ts b/frontend/Exence/src/app/shared/data-table/data-table.component.ts index 0994676..bc809b0 100644 --- a/frontend/Exence/src/app/shared/data-table/data-table.component.ts +++ b/frontend/Exence/src/app/shared/data-table/data-table.component.ts @@ -5,7 +5,6 @@ import { FormGroup, FormsModule, NonNullableFormBuilder, ReactiveFormsModule, Va import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatDialog } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -21,10 +20,11 @@ import { TransactionModel } from '../../data-model/modules/transaction/Transacti import { TransactionType } from '../../data-model/modules/transaction/TransactionType'; import { CategoryService } from '../../private/category.service'; import { CreateCategoryDialogComponent } from '../../private/transactions-and-categories/create-category-dialog/create-category-dialog.component'; -import { CreateTransactionDialogComponent, CreateTransactionDialogData } from '../../private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component'; +import { CreateTransactionDialogComponent } from '../../private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component'; import { TransactionService } from '../../private/transactions-and-categories/transaction.service'; import { BaseComponent } from '../base-component/base.component'; import { ButtonComponent } from '../button/button.component'; +import { DialogService } from '../dialog/dialog.service'; import { DisplaySizeService } from '../display-size.service'; import { SnackbarService } from '../snackbar/snackbar.service'; import { SvgIcons } from '../svg-icons/svg-icons'; @@ -74,7 +74,7 @@ export class DataTableComponent extends BaseComponent { private readonly transactionService = inject(TransactionService); private readonly categoryService = inject(CategoryService); private readonly fb = inject(NonNullableFormBuilder); - private readonly dialog = inject(MatDialog); + private readonly dialog = inject(DialogService); readonly display = inject(DisplaySizeService); noteForm!: FormGroup; @@ -234,46 +234,33 @@ export class DataTableComponent extends BaseComponent { } // TODO refactor - openCreateDialog(): void { + async openCreateDialog(): Promise { // All transactions if (!this.type()) { - const data: CreateTransactionDialogData = { isRecurring: this.isRecurring() ?? false }; - this.dialog.open( - CreateTransactionDialogComponent, { data } - ).afterClosed().subscribe( - (newTransaction) => { - if (newTransaction) { - this.dataChangedEvent.emit(); - this.snackbarService.showSuccess(`Transaction '${newTransaction.title.slice(0, 10)}${newTransaction.title.length > 10 ? '...' : ''}' created successfully!`); - } - } + const result = await this.dialog.openNonModal( + CreateTransactionDialogComponent, + { isRecurring: this.isRecurring() ?? false } ); + if (!result) return; + this.dataChangedEvent.emit(); // Income or expense } else if (this.type() === TransactionType.EXPENSE || this.type() === TransactionType.INCOME) { - const data = { isRecurring: this.isRecurring() ?? false, type: this.type()! as TransactionType }; - - this.dialog.open( - CreateTransactionDialogComponent, { data } - ).afterClosed().subscribe( - (newTransaction) => { - if (newTransaction) { - this.dataChangedEvent.emit(); - this.snackbarService.showSuccess(`Transaction '${newTransaction.title.slice(0, 10)}${newTransaction.title.length > 10 ? '...' : ''}' created successfully!`); - } + const result = await this.dialog.openNonModal( + CreateTransactionDialogComponent, + { + isRecurring: this.isRecurring() ?? false, + type: this.type()! as TransactionType } ); + if (!result) return; + this.dataChangedEvent.emit(); // Categories } else if (this.type() === 'category') { - this.dialog.open( + const result = await this.dialog.openNonModal( CreateCategoryDialogComponent, undefined - ).afterClosed().subscribe( - (newCategory) => { - if (newCategory) { - this.dataChangedEvent.emit(); - this.snackbarService.showSuccess(`Category '${newCategory.emoji}' created successfully!`); - } - } ); + if (!result) return; + this.dataChangedEvent.emit(); } }