Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Transaction>;
incomes: PagedResponse<Transaction>;
expenses: PagedResponse<Transaction>;
recurrings: PagedResponse<Transaction>;
recurringIncomes: PagedResponse<Transaction>;
recurringExpenses: PagedResponse<Transaction>;
};

// Data caches
transactions: PagedResponse<Transaction>;
incomes: PagedResponse<Transaction>;
expenses: PagedResponse<Transaction>;
recurrings: PagedResponse<Transaction>;
recurringIncomes: PagedResponse<Transaction>;
recurringExpenses: PagedResponse<Transaction>;
}

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<Transaction>,
incomes: {} as PagedResponse<Transaction>,
expenses: {} as PagedResponse<Transaction>,
recurrings: {} as PagedResponse<Transaction>,
recurringIncomes: {} as PagedResponse<Transaction>,
recurringExpenses: {} as PagedResponse<Transaction>,
},

transactions: {} as PagedResponse<Transaction>,
incomes: {} as PagedResponse<Transaction>,
expenses: {} as PagedResponse<Transaction>,
recurrings: {} as PagedResponse<Transaction>,
recurringIncomes: {} as PagedResponse<Transaction>,
recurringExpenses: {} as PagedResponse<Transaction>,
};

export const TransactionStore = signalStore(
Expand All @@ -60,48 +58,48 @@ export const TransactionStore = signalStore(
PagedResponse<Transaction>,
{ 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<PagedResponse<Transaction>, { page: number }>({
params: () => ({ page: store.pages().income }),
params: () => ({ page: store.incomePage() }),
loader: async ({ params }) => await transactionService.listIncomes(params.page),
}),
expenseResource: resource<PagedResponse<Transaction>, { page: number }>({
params: () => ({ page: store.pages().expense }),
params: () => ({ page: store.expensePage() }),
loader: async ({ params }) => await transactionService.listExpenses(params.page),
}),
totalResource: resource<TransactionTotalsResponse, undefined>({
loader: async () => await transactionService.totals(),
}),
recurringResource: resource<PagedResponse<Transaction>, { page: number }>({
params: () => ({ page: store.pages().recurring }),
params: () => ({ page: store.recurringPage() }),
loader: async ({ params }) => await transactionService.listRecurrings(params.page),
}),
recurringIncomeResource: resource<PagedResponse<Transaction>, { page: number }>({
params: () => ({ page: store.pages().recurringIncome }),
params: () => ({ page: store.recurringIncomePage() }),
loader: async ({ params }) => await transactionService.listRecurringIncomes(params.page),
}),
recurringExpenseResource: resource<PagedResponse<Transaction>, { 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(() => {
const incomes = store.totalResource.value()?.totalIncome ?? 0;
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(
Expand All @@ -112,49 +110,61 @@ 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<Transaction>;
newPages.incomePage = 0;
newCaches.incomes = {} as PagedResponse<Transaction>;
store.incomeResource.reload();
if (transaction.recurring) {
newPages.recurringIncome = 0;
newData.recurringIncomes = {} as PagedResponse<Transaction>;
newPages.recurringIncomePage = 0;
newCaches.recurringIncomes = {} as PagedResponse<Transaction>;
store.recurringIncomeResource.reload();
}
break;
case TransactionType.EXPENSE:
newPages.expense = 0;
newData.expenses = {} as PagedResponse<Transaction>;
newPages.expensePage = 0;
newCaches.expenses = {} as PagedResponse<Transaction>;
store.expenseResource.reload();
if (transaction.recurring) {
newPages.recurringExpense = 0;
newData.recurringExpenses = {} as PagedResponse<Transaction>;
newPages.recurringExpensePage = 0;
newCaches.recurringExpenses = {} as PagedResponse<Transaction>;
store.recurringExpenseResource.reload();
}
break;
}
newPages.transaction = 0;
newData.transactions = {} as PagedResponse<Transaction>;
newPages.transactionPage = 0;
newCaches.transactions = {} as PagedResponse<Transaction>;
store.transactionResource.reload();

if (transaction.recurring) {
newPages.recurring = 0;
newData.recurrings = {} as PagedResponse<Transaction>;
newPages.recurringPage = 0;
newCaches.recurrings = {} as PagedResponse<Transaction>;
store.recurringResource.reload();
}

store.totalResource.reload();
categoryStore.topCategoriesResource.reload();
patchState(store, state => ({
...state,
pages: newPages,
data: newData,
...newPages,
...newCaches,
}));
}

Expand Down Expand Up @@ -199,32 +209,48 @@ 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<PagedResponse<Transaction> | 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 };
});
}
},
updateFilters(filters: TransactionFilter): void {
patchState(store, state => ({
...state,
filters: { ...filters },
pages: { ...state.pages, transaction: 0 },
transactionPage: 0,
}));
},
resetState(): void {
Expand All @@ -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<PagedResponse<Transaction> | 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()));
},
}),
);
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ <h2 class="m-0 text-center text-md-start {{ display.isSm() ? 'fs-1' : 'fs-2' }}"
@if (display.isLg()) {
<ex-data-table
[transactions]="transactionStore.recurringExpenses()"
[isDataLoading]="transactionStore.recurringResource.isLoading()"
[isDataLoading]="transactionStore.recurringExpenseResource.isLoading()"
matIcon="money_off"
nonExpandable
title="Recurring Expenses"
Expand All @@ -74,7 +74,7 @@ <h2 class="m-0 text-center text-md-start {{ display.isSm() ? 'fs-1' : 'fs-2' }}"
} @else {
<ex-data-table
[transactions]="transactionStore.recurrings()"
[isDataLoading]="transactionStore.recurringExpenseResource.isLoading()"
[isDataLoading]="transactionStore.recurringResource.isLoading()"
matIcon="attach_money"
nonExpandable
title="Recurrings"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class TransactionsAndCategoriesComponent extends BaseComponent implements
readonly transactionStore = inject(TransactionStore);

categories = computed(() => this.categoryStore.categoryResource.value());
transactions = computed(() => this.transactionStore.data.transactions());
transactions = computed(() => this.transactionStore.transactions());

selectedIndex = 0;
loading = false;
Expand Down
Loading
Loading