diff --git a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts index cd11227..4652284 100644 --- a/frontend/Exence/src/app/private/dashboard/dashboard.component.ts +++ b/frontend/Exence/src/app/private/dashboard/dashboard.component.ts @@ -47,12 +47,12 @@ export class DashboardComponent extends BaseComponent implements OnInit { user = computed(() => this.currentUserService.user()); categories = computed(() => this.categoryStore.categoryResource.value()); - transactions = computed(() => this.transactionStore.data.transactions()); - incomes = computed(() => this.transactionStore.data.incomes()); - expenses = computed(() => this.transactionStore.data.expenses()); + transactions = computed(() => this.transactionStore.transactions()); + incomes = computed(() => this.transactionStore.incomes()); + expenses = computed(() => this.transactionStore.expenses()); ngOnInit(): void { - if (this.transactionStore.data.transactions().content?.length) this.transactionStore.resetState(); + if (this.transactionStore.transactions().content?.length) this.transactionStore.resetState(); } async openCreateTransactionDialog(transactionType: TransactionType): Promise { diff --git a/frontend/Exence/src/app/private/transactions-and-categories/transaction.store.ts b/frontend/Exence/src/app/private/transactions-and-categories/transaction.store.ts index ccb48fd..8096ad1 100644 --- a/frontend/Exence/src/app/private/transactions-and-categories/transaction.store.ts +++ b/frontend/Exence/src/app/private/transactions-and-categories/transaction.store.ts @@ -10,43 +10,41 @@ import { CategoryStore } from './category.store'; import { TransactionService } from './transaction.service'; export interface TransactionStoreData { - pages: { - transaction: number; - income: number; - expense: number; - recurring: number; - recurringIncome: number; - recurringExpense: number; - }; + // Page + transactionPage: number; + incomePage: number; + expensePage: number; + recurringPage: number; + recurringIncomePage: number; + recurringExpensePage: number; + filters?: TransactionFilter; - data: { - transactions: PagedResponse; - incomes: PagedResponse; - expenses: PagedResponse; - recurrings: PagedResponse; - recurringIncomes: PagedResponse; - recurringExpenses: PagedResponse; - }; + + // Data caches + transactions: PagedResponse; + incomes: PagedResponse; + expenses: PagedResponse; + recurrings: PagedResponse; + recurringIncomes: PagedResponse; + recurringExpenses: PagedResponse; } const initialState: TransactionStoreData = { - pages: { - transaction: 0, - income: 0, - expense: 0, - recurring: 0, - recurringIncome: 0, - recurringExpense: 0, - }, + transactionPage: 0, + incomePage: 0, + expensePage: 0, + recurringPage: 0, + recurringIncomePage: 0, + recurringExpensePage: 0, + filters: {} as TransactionFilter, - data: { - transactions: {} as PagedResponse, - incomes: {} as PagedResponse, - expenses: {} as PagedResponse, - recurrings: {} as PagedResponse, - recurringIncomes: {} as PagedResponse, - recurringExpenses: {} as PagedResponse, - }, + + transactions: {} as PagedResponse, + incomes: {} as PagedResponse, + expenses: {} as PagedResponse, + recurrings: {} as PagedResponse, + recurringIncomes: {} as PagedResponse, + recurringExpenses: {} as PagedResponse, }; export const TransactionStore = signalStore( @@ -60,38 +58,38 @@ export const TransactionStore = signalStore( PagedResponse, { page: number; filters?: TransactionFilter | undefined } >({ - params: () => ({ page: store.pages().transaction, filters: store.filters ? store.filters() : undefined }), + params: () => ({ page: store.transactionPage(), filters: store.filters ? store.filters() : undefined }), loader: async ({ params }) => await transactionService.list(params.filters, params.page), }), incomeResource: resource, { page: number }>({ - params: () => ({ page: store.pages().income }), + params: () => ({ page: store.incomePage() }), loader: async ({ params }) => await transactionService.listIncomes(params.page), }), expenseResource: resource, { page: number }>({ - params: () => ({ page: store.pages().expense }), + params: () => ({ page: store.expensePage() }), loader: async ({ params }) => await transactionService.listExpenses(params.page), }), totalResource: resource({ loader: async () => await transactionService.totals(), }), recurringResource: resource, { page: number }>({ - params: () => ({ page: store.pages().recurring }), + params: () => ({ page: store.recurringPage() }), loader: async ({ params }) => await transactionService.listRecurrings(params.page), }), recurringIncomeResource: resource, { page: number }>({ - params: () => ({ page: store.pages().recurringIncome }), + params: () => ({ page: store.recurringIncomePage() }), loader: async ({ params }) => await transactionService.listRecurringIncomes(params.page), }), recurringExpenseResource: resource, { page: number }>({ - params: () => ({ page: store.pages().recurringExpense }), + params: () => ({ page: store.recurringExpensePage() }), loader: async ({ params }) => await transactionService.listRecurringExpenses(params.page), }), })), withComputed(store => ({ - transactions: computed(() => store.data.transactions()), - incomes: computed(() => store.data.incomes()), - expenses: computed(() => store.data.expenses()), + transactions: computed(() => store.transactions()), + incomes: computed(() => store.incomes()), + expenses: computed(() => store.expenses()), totalIncome: computed(() => store.totalResource.value()?.totalIncome ?? 0), totalExpense: computed(() => store.totalResource.value()?.totalExpense ?? 0), balance: computed(() => { @@ -99,9 +97,9 @@ export const TransactionStore = signalStore( const expenses = store.totalResource.value()?.totalExpense ?? 0; return (Math.round(incomes - expenses) / 100) * 100; }), - recurrings: computed(() => store.data.recurrings()), - recurringIncomes: computed(() => store.data.recurringIncomes()), - recurringExpenses: computed(() => store.data.recurringExpenses()), + recurrings: computed(() => store.recurrings()), + recurringIncomes: computed(() => store.recurringIncomes()), + recurringExpenses: computed(() => store.recurringExpenses()), })), withMethods( @@ -112,40 +110,52 @@ export const TransactionStore = signalStore( categoryStore = inject(CategoryStore), ) => { function reload(transaction: Transaction): void { - const currentPages = store.pages(); - const currentData = store.data(); - const newPages = { ...currentPages }; - const newData = { ...currentData }; + const newPages = { + transactionPage: store.transactionPage(), + incomePage: store.incomePage(), + expensePage: store.expensePage(), + recurringPage: store.recurringExpensePage(), + recurringIncomePage: store.recurringIncomePage(), + recurringExpensePage: store.recurringExpensePage(), + }; + const newCaches = { + transactions: store.transactions(), + incomes: store.incomes(), + expenses: store.expenses(), + recurrings: store.recurrings(), + recurringIncomes: store.recurringIncomes(), + recurringExpenses: store.recurringExpenses(), + }; switch (transaction.type) { case TransactionType.INCOME: - newPages.income = 0; - newData.incomes = {} as PagedResponse; + newPages.incomePage = 0; + newCaches.incomes = {} as PagedResponse; store.incomeResource.reload(); if (transaction.recurring) { - newPages.recurringIncome = 0; - newData.recurringIncomes = {} as PagedResponse; + newPages.recurringIncomePage = 0; + newCaches.recurringIncomes = {} as PagedResponse; store.recurringIncomeResource.reload(); } break; case TransactionType.EXPENSE: - newPages.expense = 0; - newData.expenses = {} as PagedResponse; + newPages.expensePage = 0; + newCaches.expenses = {} as PagedResponse; store.expenseResource.reload(); if (transaction.recurring) { - newPages.recurringExpense = 0; - newData.recurringExpenses = {} as PagedResponse; + newPages.recurringExpensePage = 0; + newCaches.recurringExpenses = {} as PagedResponse; store.recurringExpenseResource.reload(); } break; } - newPages.transaction = 0; - newData.transactions = {} as PagedResponse; + newPages.transactionPage = 0; + newCaches.transactions = {} as PagedResponse; store.transactionResource.reload(); if (transaction.recurring) { - newPages.recurring = 0; - newData.recurrings = {} as PagedResponse; + newPages.recurringPage = 0; + newCaches.recurrings = {} as PagedResponse; store.recurringResource.reload(); } @@ -153,8 +163,8 @@ export const TransactionStore = signalStore( categoryStore.topCategoriesResource.reload(); patchState(store, state => ({ ...state, - pages: newPages, - data: newData, + ...newPages, + ...newCaches, })); } @@ -199,24 +209,40 @@ export const TransactionStore = signalStore( reload(request); }, loadNextPage(type?: TransactionType, recurring?: boolean): void { + console.log(type, recurring); const resourceMap = { [TransactionType.INCOME]: store.incomeResource, [TransactionType.EXPENSE]: store.expenseResource, }; - const res = type ? resourceMap[type] : store.transactionResource; + const recurringResourceMap = { + [TransactionType.INCOME]: store.recurringIncomeResource, + [TransactionType.EXPENSE]: store.recurringExpenseResource, + }; + let res: ResourceRef | undefined>; + + if (recurring) res = type ? recurringResourceMap[type] : store.recurringResource; + else res = type ? resourceMap[type] : store.transactionResource; + if (!res.isLoading() && !res.value()?.last) { patchState(store, state => { - const pages = { ...state.pages }; + const pages = { + transactionPage: state.transactionPage, + incomePage: state.incomePage, + expensePage: state.expensePage, + recurringPage: state.recurringExpensePage, + recurringIncomePage: state.recurringIncomePage, + recurringExpensePage: state.recurringExpensePage, + }; if (recurring) { - if (type === TransactionType.INCOME) pages.recurringIncome++; - else if (type === TransactionType.EXPENSE) pages.recurringExpense++; - else pages.recurring++; + if (type === TransactionType.INCOME) pages.recurringIncomePage++; + else if (type === TransactionType.EXPENSE) pages.recurringExpensePage++; + else pages.recurringPage++; } else { - if (type === TransactionType.INCOME) pages.income++; - else if (type === TransactionType.EXPENSE) pages.expense++; - else pages.transaction++; + if (type === TransactionType.INCOME) pages.incomePage++; + else if (type === TransactionType.EXPENSE) pages.expensePage++; + else pages.transactionPage++; } - return { ...state, pages }; + return { ...state, ...pages }; }); } }, @@ -224,7 +250,7 @@ export const TransactionStore = signalStore( patchState(store, state => ({ ...state, filters: { ...filters }, - pages: { ...state.pages, transaction: 0 }, + transactionPage: 0, })); }, resetState(): void { @@ -237,36 +263,33 @@ export const TransactionStore = signalStore( withHooks({ onInit(store): void { function merge( - type: keyof TransactionStoreData['data'], + type: 'transactions' | 'incomes' | 'expenses' | 'recurrings' | 'recurringIncomes' | 'recurringExpenses', // keep in sync with TransactionStoreData data caches res: ResourceRef | undefined>, page: number, ): void { const val = res.value(); if (val && !res.isLoading()) { patchState(store, state => { - if (page === 0) return { data: { ...state.data, [type]: val } }; + if (page === 0) return { [type]: val }; return { - data: { - ...state.data, - [type]: - page === 0 - ? val - : { - ...val, - content: [...(state.data[type].content ?? []), ...(val.content ?? [])], - }, - }, + [type]: + page === 0 + ? val + : { + ...val, + content: [...(state[type].content ?? []), ...val.content!], + }, }; }); } } - effect(() => merge('transactions', store.transactionResource, store.pages.transaction())); - effect(() => merge('incomes', store.incomeResource, store.pages.income())); - effect(() => merge('expenses', store.expenseResource, store.pages.expense())); - effect(() => merge('recurrings', store.recurringResource, store.pages.recurring())); - effect(() => merge('recurringIncomes', store.recurringIncomeResource, store.pages.recurringIncome())); - effect(() => merge('recurringExpenses', store.recurringExpenseResource, store.pages.recurringExpense())); + effect(() => merge('transactions', store.transactionResource, store.transactionPage())); + effect(() => merge('incomes', store.incomeResource, store.incomePage())); + effect(() => merge('expenses', store.expenseResource, store.expensePage())); + effect(() => merge('recurrings', store.recurringResource, store.recurringPage())); + effect(() => merge('recurringIncomes', store.recurringIncomeResource, store.recurringIncomePage())); + effect(() => merge('recurringExpenses', store.recurringExpenseResource, store.recurringExpensePage())); }, }), ); 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 2d07a3d..d908ec6 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 @@ -53,7 +53,7 @@

this.categoryStore.categoryResource.value()); - transactions = computed(() => this.transactionStore.data.transactions()); + transactions = computed(() => this.transactionStore.transactions()); selectedIndex = 0; loading = false; diff --git a/frontend/Exence/src/app/shared/data-table/data-table.component.html b/frontend/Exence/src/app/shared/data-table/data-table.component.html index f972356..0016ff4 100644 --- a/frontend/Exence/src/app/shared/data-table/data-table.component.html +++ b/frontend/Exence/src/app/shared/data-table/data-table.component.html @@ -1,5 +1,3 @@ - -

@@ -100,7 +98,7 @@

@if (!nonExpandable()) {
- @if (expandedElement === row) { + @if (expandedRowId === row.id) {
@@ -257,13 +255,19 @@

} + + + + + + @if (!nonExpandable()) { @@ -274,6 +278,10 @@

style="height: 0" > } + + + + } @else if (!emptyCategoryTable && type() === 'category') { @@ -335,12 +343,30 @@

+ +

+ + + +
+ +
}

@@ -362,13 +388,7 @@

No items found

- - @if (!!isDataLoading()) { -
- -
- } + +
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 e086338..f2853d1 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 @@ -4,7 +4,6 @@ flex-grow: 1; flex-basis: 50%; height: 100%; - position: relative; } .table-container { 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 188f27a..afa26c0 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 @@ -21,7 +21,6 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -42,6 +41,7 @@ import { DisplaySizeService } from '../display-size.service'; import { StopPropagationDirective } from '../stop-propagation.directive'; import { SvgIcons } from '../svg-icons/svg-icons'; import { ValidatorComponent } from '../validator/validator.component'; +import { AnimatedSkeletonLoaderComponent } from '../animated-skeleton-loader/animated-skeleton-loader.component'; @Component({ selector: 'ex-data-table', @@ -61,9 +61,9 @@ import { ValidatorComponent } from '../validator/validator.component'; MatMenuModule, MatCheckboxModule, MatSelectModule, - MatProgressSpinnerModule, ButtonComponent, ValidatorComponent, + AnimatedSkeletonLoaderComponent, StopPropagationDirective, InfiniteScrollDirective, ], @@ -106,7 +106,7 @@ export class DataTableComponent extends BaseComponent { displayedColumns = ['title', 'date', 'amount', 'category', 'actions']; displayedCategoryColumns = ['name', 'icon', 'type', 'actions']; - expandedElement: Transaction | null = null; + expandedRowId: number | null = null; transactionTypes = TransactionType; @@ -127,7 +127,7 @@ export class DataTableComponent extends BaseComponent { return ( !transactions?.content?.length || (!this.transactionDataSource()?.data.length && - !this.transactionStore.data.transactions.content?.length && + !this.transactionStore.transactions.content?.length && !this.transactionStore.transactionResource.isLoading()) ); }); @@ -246,7 +246,7 @@ export class DataTableComponent extends BaseComponent { toggleExpand(row: Transaction | null): void { if (this.nonExpandable() || !row) return; - this.expandedElement = this.expandedElement === row ? null : row; + this.expandedRowId = this.expandedRowId === row.id ? null : row.id!; // if (row.id === this.currentlyEditedRow()) { // this.currentlyEditedRow.set(undefined); // }