From c3093ace9d65a43cdb6a1ac541ccd1ca3c988a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Thu, 8 Jan 2026 20:09:02 +0100 Subject: [PATCH 01/11] Fix linting lint --- frontend/Exence/eslint.config.mjs | 3 +++ .../src/app/shared/auth/guard/logged-in.guard.ts | 1 - .../auth/interceptors/refresh-token.interceptor.ts | 14 +++++++------- frontend/Exence/src/app/shared/error.service.ts | 13 +++++++------ .../src/app/shared/user/current-user.service.ts | 4 ++-- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/frontend/Exence/eslint.config.mjs b/frontend/Exence/eslint.config.mjs index b0e3949..224ed1f 100644 --- a/frontend/Exence/eslint.config.mjs +++ b/frontend/Exence/eslint.config.mjs @@ -11,6 +11,7 @@ export default defineConfig([ languageOptions: { parserOptions: { projectService: true, + allowDefaultProject: ['eslint.config.mjs'], }, }, }, @@ -56,6 +57,7 @@ export default defineConfig([ } ], "@typescript-eslint/method-signature-style": ["error", "property"], + "@typescript-eslint/require-await": "off", "@typescript-eslint/no-deprecated": "warn", "@typescript-eslint/no-inferrable-types": "warn", "@typescript-eslint/no-misused-promises": [ @@ -78,6 +80,7 @@ export default defineConfig([ "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/unbound-method": "off", "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-arguments": "off", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": [ "warn", diff --git a/frontend/Exence/src/app/shared/auth/guard/logged-in.guard.ts b/frontend/Exence/src/app/shared/auth/guard/logged-in.guard.ts index 67b4d30..2ae794e 100644 --- a/frontend/Exence/src/app/shared/auth/guard/logged-in.guard.ts +++ b/frontend/Exence/src/app/shared/auth/guard/logged-in.guard.ts @@ -17,7 +17,6 @@ export const loggedInGuard: CanActivateFn = ( filter(user => user !== null), take(1), map((user) => { - console.log(user) if (user && currentUserService.isAuthenticated()) { return true; } else { diff --git a/frontend/Exence/src/app/shared/auth/interceptors/refresh-token.interceptor.ts b/frontend/Exence/src/app/shared/auth/interceptors/refresh-token.interceptor.ts index 2025c04..b887927 100644 --- a/frontend/Exence/src/app/shared/auth/interceptors/refresh-token.interceptor.ts +++ b/frontend/Exence/src/app/shared/auth/interceptors/refresh-token.interceptor.ts @@ -8,6 +8,13 @@ import { CurrentUserService } from '../../user/current-user.service'; export const SUPPRESS_ERROR_SNACKBAR = new HttpContextToken(() => false); +function setErrorContext(errorWithContext: HttpErrorResponse, req: HttpRequest): void { + Object.defineProperty(errorWithContext, 'context', { + value: req.context.set(SUPPRESS_ERROR_SNACKBAR, true), + enumerable: false + }); +} + export function refreshTokenInterceptor(req: HttpRequest, next: HttpHandlerFn): Observable> { const router = inject(Router); const navigationService = inject(NavigationService); @@ -48,11 +55,4 @@ export function refreshTokenInterceptor(req: HttpRequest, next: HttpHan ); }) ); -} - -function setErrorContext(errorWithContext: HttpErrorResponse, req: HttpRequest) { - Object.defineProperty(errorWithContext, 'context', { - value: req.context.set(SUPPRESS_ERROR_SNACKBAR, true), - enumerable: false - }); } \ No newline at end of file diff --git a/frontend/Exence/src/app/shared/error.service.ts b/frontend/Exence/src/app/shared/error.service.ts index 56561e5..e6590f6 100644 --- a/frontend/Exence/src/app/shared/error.service.ts +++ b/frontend/Exence/src/app/shared/error.service.ts @@ -1,9 +1,9 @@ -import { inject, Injectable } from "@angular/core"; -import { SnackbarService } from "./snackbar/snackbar.service"; -import { HttpErrorResponse } from "@angular/common/http"; -import { ErrorResponse } from "../data-model/modules/ErrorResponse"; -import { SUPPRESS_ERROR_SNACKBAR } from "./auth/interceptors/refresh-token.interceptor"; -import { HttpSettings } from "./http/http.service"; +import { inject, Injectable } from '@angular/core'; +import { SnackbarService } from './snackbar/snackbar.service'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorResponse } from '../data-model/modules/ErrorResponse'; +import { SUPPRESS_ERROR_SNACKBAR } from './auth/interceptors/refresh-token.interceptor'; +import { HttpSettings } from './http/http.service'; @Injectable({ providedIn: 'root' @@ -14,6 +14,7 @@ export class ErrorService { handleError(errorResponse: HttpErrorResponse, settings?: HttpSettings): void { settings = settings ?? {}; + // eslint-disable-next-line const suppressFromInterceptor = (errorResponse as any).context?.get?.(SUPPRESS_ERROR_SNACKBAR) ?? false; if (!settings.suppressErrorMessage && !suppressFromInterceptor) { diff --git a/frontend/Exence/src/app/shared/user/current-user.service.ts b/frontend/Exence/src/app/shared/user/current-user.service.ts index c410849..fb01f1a 100644 --- a/frontend/Exence/src/app/shared/user/current-user.service.ts +++ b/frontend/Exence/src/app/shared/user/current-user.service.ts @@ -7,10 +7,10 @@ import { User } from '../../data-model/modules/auth/User'; export class CurrentUserService { private _user: WritableSignal = signal(null); + isAuthenticated = computed(() => !!this._user()); + get user(): Signal { return this._user.asReadonly() as Signal; } set user(user: User | null | undefined) { this._user.set(user); } - - isAuthenticated = computed(() => !!this._user()); clearUser(): void { this.user = undefined; From dca448e7aa3ea47df7ad585d0323f17f1e06afb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Fri, 9 Jan 2026 20:46:39 +0100 Subject: [PATCH 02/11] #EX-139: Add filter-menu.component, matBadge stlyles --- .../filter-menu/filter-menu.component.html | 35 +++++++++++++++++++ .../filter-menu/filter-menu.component.scss | 0 .../filter-menu/filter-menu.component.ts | 24 +++++++++++++ .../Exence/src/styles/components/badge.scss | 7 ++++ frontend/Exence/src/styles/styles.scss | 1 + 5 files changed, 67 insertions(+) create mode 100644 frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html create mode 100644 frontend/Exence/src/app/shared/filter-menu/filter-menu.component.scss create mode 100644 frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts create mode 100644 frontend/Exence/src/styles/components/badge.scss diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html new file mode 100644 index 0000000..840d186 --- /dev/null +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html @@ -0,0 +1,35 @@ + + Add filter + + + +
+
+

Filters

+ +
+ + +
+
diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.scss b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts new file mode 100644 index 0000000..abac9dc --- /dev/null +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts @@ -0,0 +1,24 @@ +import { Component, input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatBadgeModule } from '@angular/material/badge'; +import { ButtonComponent } from '../button/button.component'; + +@Component({ + selector: 'ex-filter-menu', + templateUrl: './filter-menu.component.html', + styleUrl: './filter-menu.component.scss', + imports: [ + MatMenuModule, + MatBadgeModule, + ButtonComponent, + ], +}) +export class FilterMenuComponent { + form = input.required(); + appliedFiltersCount = input.required(); + + clearFilters(): void { + this.form().reset(); + } +} \ No newline at end of file diff --git a/frontend/Exence/src/styles/components/badge.scss b/frontend/Exence/src/styles/components/badge.scss new file mode 100644 index 0000000..c0e343c --- /dev/null +++ b/frontend/Exence/src/styles/components/badge.scss @@ -0,0 +1,7 @@ +.mat-badge-medium.mat-badge-overlap span.mat-badge-content { + --mat-badge-container-overlap-offset: -10px; + --mat-badge-text-size: 10px; + --mat-badge-container-padding: 1px 7px; + --mat-badge-background-color: var(--default-text-color); + --mat-badge-text-color: var(--secondary-text-color); +} \ No newline at end of file diff --git a/frontend/Exence/src/styles/styles.scss b/frontend/Exence/src/styles/styles.scss index 5aedb74..5846dd5 100644 --- a/frontend/Exence/src/styles/styles.scss +++ b/frontend/Exence/src/styles/styles.scss @@ -15,6 +15,7 @@ @import './components/dialog.scss'; @import './components/divider.scss'; @import './components/snackbar.scss'; +@import './components/badge.scss'; @import './fonts/material-icons.scss'; * { From b0a19fca4f79d21dfe24cc2bdfab7bca6da521fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Fri, 9 Jan 2026 21:10:08 +0100 Subject: [PATCH 03/11] Remove unnecessary comments, code, fix max value validator message --- .../Exence/src/app/private/dashboard/dashboard.component.ts | 1 - .../create-transaction-dialog.component.html | 1 - .../transactions-and-categories.component.scss | 1 - .../transactions-and-categories.component.ts | 4 ++-- .../src/app/shared/data-table/data-table.component.scss | 1 - .../Exence/src/app/shared/data-table/data-table.component.ts | 1 - 6 files changed, 2 insertions(+), 7 deletions(-) diff --git a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts index e9a0159..4ede6ba 100644 --- a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts +++ b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts @@ -50,7 +50,6 @@ export class DashboardComponent implements OnInit { readonly dialog = inject(MatDialog); readonly router = inject(Router); readonly navigation = inject(NavigationService); - transactionTypes = TransactionType; dateIntervals = DateInterval; diff --git a/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.html b/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.html index c32befe..6ff291e 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.html +++ b/frontend/Exence/src/app/private/transactions-and-categories/create-transaction-dialog/create-transaction-dialog.component.html @@ -85,7 +85,6 @@

New transaction

- @let recurringControl = form.controls.recurring; Automatically recurring? diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss index b9cae63..1134382 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss +++ b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss @@ -3,7 +3,6 @@ @include displayMd { .smaller-row { flex-basis: 30%; - height: 30%; } .bigger-row { diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts index 8b650e5..3847765 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts @@ -67,7 +67,7 @@ export class TransactionsAndCategoriesComponent implements OnInit { }); } - public openCreateTransactionDialog(): void { + openCreateTransactionDialog(): void { this.dialog.open( CreateTransactionDialogComponent, undefined ).afterClosed().subscribe( @@ -80,7 +80,7 @@ export class TransactionsAndCategoriesComponent implements OnInit { ); } - public openCreateCategoryDialog(): void { + openCreateCategoryDialog(): void { this.dialog.open( CreateCategoryDialogComponent, undefined ).afterClosed().subscribe( diff --git a/frontend/Exence/src/app/shared/data-table/data-table.component.scss b/frontend/Exence/src/app/shared/data-table/data-table.component.scss index 8cf83b1..912ef4b 100644 --- a/frontend/Exence/src/app/shared/data-table/data-table.component.scss +++ b/frontend/Exence/src/app/shared/data-table/data-table.component.scss @@ -174,7 +174,6 @@ td.mat-column-actions mat-icon { background-color: var(--primary-color); } -// TODO details .detail-container { display: flex; 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..45ee761 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 @@ -233,7 +233,6 @@ export class DataTableComponent extends BaseComponent { // } } - // TODO refactor openCreateDialog(): void { // All transactions if (!this.type()) { From 4f5db1d498f62158136dc4195bd67f08afd4d17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sat, 10 Jan 2026 20:14:37 +0100 Subject: [PATCH 04/11] Fix control type, max value message in validator --- .../Exence/src/app/shared/validator/validator.component.html | 2 +- .../Exence/src/app/shared/validator/validator.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/Exence/src/app/shared/validator/validator.component.html b/frontend/Exence/src/app/shared/validator/validator.component.html index aff4a5b..1125091 100644 --- a/frontend/Exence/src/app/shared/validator/validator.component.html +++ b/frontend/Exence/src/app/shared/validator/validator.component.html @@ -19,7 +19,7 @@ Minimum value is {{ errorValue?.min }}! } @case ('max') { - MaximumValue value is {{ errorValue?.max }}! + Maximum value is {{ errorValue?.max }}! } } } diff --git a/frontend/Exence/src/app/shared/validator/validator.component.ts b/frontend/Exence/src/app/shared/validator/validator.component.ts index ce18598..794d705 100644 --- a/frontend/Exence/src/app/shared/validator/validator.component.ts +++ b/frontend/Exence/src/app/shared/validator/validator.component.ts @@ -1,5 +1,5 @@ import { Component, input, OnInit } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { AbstractControl } from '@angular/forms'; import { merge, of } from 'rxjs'; import { BaseComponent } from '../base-component/base.component'; @@ -15,7 +15,7 @@ interface ErrorInfo { imports: [], }) export class ValidatorComponent extends BaseComponent implements OnInit { - control = input.required(); + control = input.required(); errorKey?: string; errorValue?: ErrorInfo; From 10b3c47587a53cc6c148455d4434059f35c5f1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sat, 10 Jan 2026 20:15:08 +0100 Subject: [PATCH 05/11] #EX-139: Add filters to transactions list in transactions-and-categories --- .../modules/transaction/TransactionFilter.ts | 14 +- .../private/dashboard/dashboard.component.ts | 3 +- .../transaction.service.ts | 7 +- ...transactions-and-categories.component.html | 122 ++++++++++++++++-- ...transactions-and-categories.component.scss | 4 + .../transactions-and-categories.component.ts | 77 ++++++++++- .../data-table/data-table.component.scss | 2 +- .../filter-menu/filter-menu.component.html | 2 +- .../filter-menu/filter-menu.component.scss | 3 + .../Exence/src/styles/components/error.scss | 4 + .../src/styles/components/form-field.scss | 19 +++ frontend/Exence/src/styles/styles.scss | 1 + 12 files changed, 235 insertions(+), 23 deletions(-) create mode 100644 frontend/Exence/src/styles/components/error.scss diff --git a/frontend/Exence/src/app/data-model/modules/transaction/TransactionFilter.ts b/frontend/Exence/src/app/data-model/modules/transaction/TransactionFilter.ts index 3dab7a1..bfa0935 100644 --- a/frontend/Exence/src/app/data-model/modules/transaction/TransactionFilter.ts +++ b/frontend/Exence/src/app/data-model/modules/transaction/TransactionFilter.ts @@ -2,11 +2,11 @@ import { TransactionType } from './TransactionType'; export interface TransactionFilter { keyword?: string; - dateFrom: string; - dateTo: string; - categoryId: number; - type: TransactionType; - amountFrom: number; - amountTo: number; - recurring: boolean; + dateFrom?: string; + dateTo?: string; + categoryId?: number; + type?: TransactionType; + amountFrom?: number; + amountTo?: number; + recurring?: boolean; } \ No newline at end of file diff --git a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts index 4ede6ba..bcd4af1 100644 --- a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts +++ b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts @@ -12,6 +12,7 @@ import { CategoriesComponent, DateInterval } from '../../private/dashboard/categ import { SummaryContainerComponent } from '../../private/dashboard/summary-container/summary-container.component'; +import { BaseComponent } from '../../shared/base-component/base.component'; import { ButtonComponent } from '../../shared/button/button.component'; import { CardSliderDirective } from '../../shared/card-slider.directive'; import { ChartComponent } from '../../shared/chart/chart.component'; @@ -41,7 +42,7 @@ import { TransactionService } from '../transactions-and-categories/transaction.s templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss', }) -export class DashboardComponent implements OnInit { +export class DashboardComponent extends BaseComponent implements OnInit { private readonly currentUserService = inject(CurrentUserService); private readonly transactionService = inject(TransactionService); private readonly categoryService = inject(CategoryService); diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts b/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts index 6a60eeb..f8be017 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts @@ -3,6 +3,7 @@ import { lastValueFrom } from 'rxjs'; import { PagedResponse } from '../../data-model/modules/common/PagedResponse'; import { RecurringTransactionsResponse } from '../../data-model/modules/transaction/RecurringTransactionsResponse'; import { Transaction } from '../../data-model/modules/transaction/Transaction'; +import { TransactionFilter } from '../../data-model/modules/transaction/TransactionFilter'; import { TransactionTotalsResponse } from '../../data-model/modules/transaction/TransactionTotalsResponse'; import { HttpService } from '../../shared/http/http.service'; @@ -18,7 +19,11 @@ export class TransactionService { return lastValueFrom(this.http.get(`${this.baseUrl}/${id}`)); } - public list(): Promise> { + public list(filters?: TransactionFilter): Promise> { + if (filters) { + const request: Record = filters as unknown as Record; + return lastValueFrom(this.http.get>(this.baseUrl, request)); + } return lastValueFrom(this.http.get>(this.baseUrl)); } 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..d99645b 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,9 +63,11 @@

@if (display.isLg()) { } @else {
-
+
Add transaction + +
+ + + + + + + @let dateRangeControl = transactionFilterForm.controls.dateRange; + + Enter a date range + + + + + + + + +
+
Amount
+ @let amountRangeControl = transactionFilterForm.controls.amountRange; + + Min + + + + Max + + +
+ + @let categoryControl = transactionFilterForm.controls.category; + + Category + + All + @for (category of categories; track category.id) { + {{ `${category.name} - ${category.emoji}` }} + } + + + + + + + @let typeControl = transactionFilterForm.controls.type; + + Type + + All + @for (type of transactionTypesArr; track type) { + {{ type }} + } + + + + + + + @let recurringControl = transactionFilterForm.controls.recurring; + Recurring +
+
('', [Validators.maxLength(255)]), + dateRange: this.fb.group({ + dateFrom: this.fb.control(null), + dateTo: this.fb.control(null), + }), + amountRange: this.fb.group({ + min: this.fb.control(null), + max: this.fb.control(null), + }), + category: this.fb.control(null), + type: this.fb.control(null), + recurring: this.fb.control(false), + }); + transactions: PagedResponse = {} as PagedResponse; recurringTransactions: RecurringTransactionsResponse = {} as RecurringTransactionsResponse; categories: Category[] = []; @@ -46,13 +86,29 @@ export class TransactionsAndCategoriesComponent implements OnInit { selectedIndex = 0; transactionTypes = TransactionType; + transactionTypesArr = Object.values(this.transactionTypes); + + get canCreateTransaction(): boolean { return !!this.categories.length; } - get canCreateTransaction(): boolean { - return !!this.categories.length; + get appliedFiltersCount(): number { + return Object.entries(this.transactionFilterForm.controls).reduce((sum, [key, control]) => { + if (key === 'dateRange' || key === 'amountRange') { + const groupValue = control.value as Record; + const hasValue = Object.values(groupValue).some(v => !!v); + return sum + (hasValue ? 1 : 0); + } + if (control.value) return sum + 1; + return sum; + }, 0); } async ngOnInit(): Promise { await this.initialize(); + + this.addSubscription(this.transactionFilterForm.valueChanges.subscribe(async () => { + if (this.transactionFilterForm.invalid) return; + this.transactions = await this.getTransactions(); + })); } async initialize(): Promise { @@ -96,4 +152,19 @@ export class TransactionsAndCategoriesComponent implements OnInit { async onDataChanged(): Promise { await this.initialize(); } + + getTransactions(): Promise> { + const formValue = this.transactionFilterForm.getRawValue(); + const filters = { + keyword: formValue.searchText, + dateFrom: formValue.dateRange.dateFrom?.toISOString(), + dateTo: formValue.dateRange.dateTo?.toISOString(), + categoryId: formValue.category?.id, + type: formValue.type, + amountFrom: formValue.amountRange.min, + amountTo: formValue.amountRange.max, + recurring: formValue.recurring, + } as TransactionFilter; + return this.transactionService.list(filters); + } } diff --git a/frontend/Exence/src/app/shared/data-table/data-table.component.scss b/frontend/Exence/src/app/shared/data-table/data-table.component.scss index 912ef4b..51b3a48 100644 --- a/frontend/Exence/src/app/shared/data-table/data-table.component.scss +++ b/frontend/Exence/src/app/shared/data-table/data-table.component.scss @@ -1,7 +1,7 @@ @import '../../../styles/imports.scss'; :host { - height: 100%; + flex-grow: 1; } .table-container { diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html index 840d186..d62144a 100644 --- a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html @@ -14,7 +14,7 @@
Date: Sat, 10 Jan 2026 20:45:48 +0100 Subject: [PATCH 06/11] Fix validator update on content change, alignemtns fix in transactions-and-categories --- .../transactions-and-categories.component.html | 9 ++++++--- .../transactions-and-categories.component.ts | 4 ++-- .../shared/validator/validator.component.html | 10 +++++----- .../app/shared/validator/validator.component.ts | 16 ++++++++++------ 4 files changed, 23 insertions(+), 16 deletions(-) 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 d99645b..68927ca 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 @@ -111,7 +111,7 @@ >
-
+
+ + +
diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts index 7595f11..d0d3dfc 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.ts @@ -22,13 +22,13 @@ import { ButtonComponent } from '../../shared/button/button.component'; import { DataTableComponent } from '../../shared/data-table/data-table.component'; import { DisplaySizeService } from '../../shared/display-size.service'; import { FilterMenuComponent } from '../../shared/filter-menu/filter-menu.component'; +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 { CreateCategoryDialogComponent } from './create-category-dialog/create-category-dialog.component'; import { CreateTransactionDialogComponent, CreateTransactionDialogData } from './create-transaction-dialog/create-transaction-dialog.component'; import { TransactionService } from './transaction.service'; -import { InputClearButtonComponent } from '../../shared/input-clear-button/input-clear-button.component'; @Component({ selector: 'ex-transactions-and-categories', @@ -65,7 +65,7 @@ export class TransactionsAndCategoriesComponent extends BaseComponent implements readonly display = inject(DisplaySizeService); transactionFilterForm = this.fb.group({ - searchText: this.fb.control('', [Validators.maxLength(255)]), + searchText: this.fb.control('', [Validators.maxLength(100)]), dateRange: this.fb.group({ dateFrom: this.fb.control(null), dateTo: this.fb.control(null), diff --git a/frontend/Exence/src/app/shared/validator/validator.component.html b/frontend/Exence/src/app/shared/validator/validator.component.html index 1125091..57b0883 100644 --- a/frontend/Exence/src/app/shared/validator/validator.component.html +++ b/frontend/Exence/src/app/shared/validator/validator.component.html @@ -1,5 +1,5 @@ -@if (errorKey) { - @switch (errorKey) { +@if (errorKey()) { + @switch (errorKey()) { @case ('required') { Field required } @@ -13,13 +13,13 @@ Invalid email format! } @case ('maxlength') { - Max length is {{ errorValue?.requiredLength }}! + Max length is {{ errorValue()?.requiredLength }}! } @case ('min') { - Minimum value is {{ errorValue?.min }}! + Minimum value is {{ errorValue()?.min }}! } @case ('max') { - Maximum value is {{ errorValue?.max }}! + Maximum value is {{ errorValue()?.max }}! } } } diff --git a/frontend/Exence/src/app/shared/validator/validator.component.ts b/frontend/Exence/src/app/shared/validator/validator.component.ts index 794d705..3d1d6bb 100644 --- a/frontend/Exence/src/app/shared/validator/validator.component.ts +++ b/frontend/Exence/src/app/shared/validator/validator.component.ts @@ -1,4 +1,4 @@ -import { Component, input, OnInit } from '@angular/core'; +import { Component, input, OnInit, signal } from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { merge, of } from 'rxjs'; import { BaseComponent } from '../base-component/base.component'; @@ -17,15 +17,19 @@ interface ErrorInfo { export class ValidatorComponent extends BaseComponent implements OnInit { control = input.required(); - errorKey?: string; - errorValue?: ErrorInfo; + errorKey = signal(''); + errorValue = signal(null); ngOnInit(): void { this.addSubscription(merge(of(this.control().dirty), this.control().statusChanges).subscribe( () => { - if (!this.control().errors) return; - this.errorKey = Object.keys(this.control().errors!)[0]; - this.errorValue = this.control().errors![this.errorKey]; + if (!this.control().errors) { + this.errorKey.set(''); + this.errorValue.set(null); + return; + } + this.errorKey.set(Object.keys(this.control().errors!)[0]); + this.errorValue.set(this.control().errors![this.errorKey()] as ErrorInfo); } )); } From 5a9a92a37b6226b36f3a14bd0cc03784628780c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sat, 10 Jan 2026 22:10:37 +0100 Subject: [PATCH 07/11] #EX-139: Add mat-bottom-sheet filter menu on mobile --- ...transactions-and-categories.component.scss | 6 +- .../filter-menu/filter-menu.component.html | 74 +++++++++++++++---- .../filter-menu/filter-menu.component.ts | 24 +++++- .../src/styles/components/bottom-sheet.scss | 7 ++ frontend/Exence/src/styles/styles.scss | 1 + 5 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 frontend/Exence/src/styles/components/bottom-sheet.scss diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss index 0c2ead1..1a8ed25 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss +++ b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss @@ -10,6 +10,8 @@ } } -mat-form-field { - width: 225px; +@include displayMd { + mat-form-field { + width: 225px; + } } \ No newline at end of file diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html index d62144a..07e5780 100644 --- a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html @@ -1,16 +1,28 @@ - - Add filter - +@if (display.isMd()) { + + Add filter + +} @else { + +}
Filters

/>
+ +
+ + + +
+

Filters

+ +
+ +
+ +
+ + Show ({{ appliedFiltersCount() }}) + + + Clear all + +
+
+
diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts index abac9dc..8100c6b 100644 --- a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts @@ -1,23 +1,41 @@ -import { Component, input } from '@angular/core'; +import { Component, inject, input, TemplateRef, viewChild } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { MatMenuModule } from '@angular/material/menu'; import { MatBadgeModule } from '@angular/material/badge'; +import { MatBottomSheet, MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatMenuModule } from '@angular/material/menu'; import { ButtonComponent } from '../button/button.component'; +import { DisplaySizeService } from '../display-size.service'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'ex-filter-menu', templateUrl: './filter-menu.component.html', styleUrl: './filter-menu.component.scss', imports: [ + CommonModule, MatMenuModule, MatBadgeModule, + MatBottomSheetModule, ButtonComponent, ], }) export class FilterMenuComponent { + private readonly bottomSheet = inject(MatBottomSheet); + public display = inject(DisplaySizeService); + form = input.required(); appliedFiltersCount = input.required(); - + + filterSheet = viewChild>('filterSheet'); + + openBottomSheet(): void { + this.bottomSheet.open(this.filterSheet()!); + } + + closeSheet(): void { + this.bottomSheet.dismiss(); + } + clearFilters(): void { this.form().reset(); } diff --git a/frontend/Exence/src/styles/components/bottom-sheet.scss b/frontend/Exence/src/styles/components/bottom-sheet.scss new file mode 100644 index 0000000..a933df6 --- /dev/null +++ b/frontend/Exence/src/styles/components/bottom-sheet.scss @@ -0,0 +1,7 @@ +mat-bottom-sheet-container.mat-bottom-sheet-container { + --mat-bottom-sheet-container-background-color: var(--app-card-color); + --mat-bottom-sheet-container-text-color: var(--default-text-color); + + position: relative; + overflow: hidden; +} \ No newline at end of file diff --git a/frontend/Exence/src/styles/styles.scss b/frontend/Exence/src/styles/styles.scss index 2bc08c0..9259d5a 100644 --- a/frontend/Exence/src/styles/styles.scss +++ b/frontend/Exence/src/styles/styles.scss @@ -17,6 +17,7 @@ @import './components/snackbar.scss'; @import './components/badge.scss'; @import './components/error.scss'; +@import './components/bottom-sheet.scss'; @import './fonts/material-icons.scss'; * { From 11871236ab6c8e28abda5f25792f6ae68e51f7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sat, 10 Jan 2026 22:11:53 +0100 Subject: [PATCH 08/11] Add global vscode style So typescript knows how to import by default --- frontend/Exence/.vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 frontend/Exence/.vscode/settings.json diff --git a/frontend/Exence/.vscode/settings.json b/frontend/Exence/.vscode/settings.json new file mode 100644 index 0000000..8134a6c --- /dev/null +++ b/frontend/Exence/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.preferences.quoteStyle": "single" +} \ No newline at end of file From b770a86310acfe1c0c16e5f94f681c202170d68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sun, 11 Jan 2026 16:20:20 +0100 Subject: [PATCH 09/11] Fix filter menu, transaction list endpoint --- .../transactions-and-categories/transaction.service.ts | 2 +- .../app/shared/filter-menu/filter-menu.component.html | 10 ++++++---- .../app/shared/filter-menu/filter-menu.component.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts b/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts index f8be017..b9695a3 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts @@ -21,7 +21,7 @@ export class TransactionService { public list(filters?: TransactionFilter): Promise> { if (filters) { - const request: Record = filters as unknown as Record; + const request: Record = JSON.parse(JSON.stringify(filters)); return lastValueFrom(this.http.get>(this.baseUrl, request)); } return lastValueFrom(this.http.get>(this.baseUrl)); diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html index 07e5780..87141c5 100644 --- a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.html @@ -42,9 +42,7 @@

Filters

/>

- - - +
@@ -60,7 +58,7 @@

Filters

Filters

- +
@@ -81,3 +79,7 @@

Filters

+ + + + diff --git a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts index 8100c6b..f444624 100644 --- a/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts +++ b/frontend/Exence/src/app/shared/filter-menu/filter-menu.component.ts @@ -21,7 +21,7 @@ import { CommonModule } from '@angular/common'; }) export class FilterMenuComponent { private readonly bottomSheet = inject(MatBottomSheet); - public display = inject(DisplaySizeService); + readonly display = inject(DisplaySizeService); form = input.required(); appliedFiltersCount = input.required(); From 63cb93b4c8a2236966d7350d2395c7a3a0867ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sun, 11 Jan 2026 16:24:03 +0100 Subject: [PATCH 10/11] Refactor list method in transaction.service --- .../transactions-and-categories/transaction.service.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts b/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts index b9695a3..fda4de2 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transaction.service.ts @@ -20,11 +20,8 @@ export class TransactionService { } public list(filters?: TransactionFilter): Promise> { - if (filters) { - const request: Record = JSON.parse(JSON.stringify(filters)); - return lastValueFrom(this.http.get>(this.baseUrl, request)); - } - return lastValueFrom(this.http.get>(this.baseUrl)); + const request: Record = filters !== undefined ? JSON.parse(JSON.stringify(filters)) : {}; + return lastValueFrom(this.http.get>(this.baseUrl, request)); } public listRecurrings(): Promise { From 21619e71ba1e55c8699123d4a2c199c62444cd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sun, 11 Jan 2026 17:26:14 +0100 Subject: [PATCH 11/11] Code review fixes --- .../transactions-and-categories.component.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss index 1a8ed25..1beab8b 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss +++ b/frontend/Exence/src/app/private/transactions-and-categories/transactions-and-categories.component.scss @@ -8,9 +8,7 @@ .bigger-row { flex-basis: 70%; } -} - -@include displayMd { + mat-form-field { width: 225px; }