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
6 changes: 3 additions & 3 deletions backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

###> symfony/framework-bundle ###
APP_ENV=prod
APP_SECRET={REGENERATE_SECRET}
APP_HASH={REGENERATE_SECRET}
APP_SECRET=e44d0c2bce708e5d00f41345fbb5fedc
APP_HASH=f591924c3c8932c236c89837d70f8f5c
APP_VERSION=1.0.5
###< symfony/framework-bundle ###

Expand Down Expand Up @@ -49,7 +49,7 @@ FRONTEND_URL=http://localhost:18002
###> lexik/jwt-authentication-bundle ###
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE={REGENERATE_SECRET}
JWT_PASSPHRASE=5363aaa5301c9cb8cfbf136e8d8c7fd8
###< lexik/jwt-authentication-bundle ###

###> symfony/messenger ###
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
@if (user?.isAdmin) {
<nb-action icon="people-outline" (click)="manageUsers()" nbTooltip="Users" />
}
@if (statementImportService.expenses.length) {
@if (hasPendingImport) {
<nb-action
icon="attach-2-outline"
(click)="statementImportService.processImport()"
(click)="processImport()"
badgeDot
badgePosition="top right"
badgeStatus="danger" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ export class ActionsComponent {
@Input()
public user: User;

protected statementImportService = inject(StatementImportService);
protected readonly environment = environment;

private dialogService = inject(NbDialogService);
private router = inject(Router);
private readonly dialogService = inject(NbDialogService);
private readonly router = inject(Router);
private readonly statementImportService = inject(StatementImportService);

protected get hasPendingImport(): boolean {
return this.statementImportService.draft().length > 0;
}

public processImport(): void {
this.statementImportService.processImport();
}

public editCategories(): void {
this.dialogService.open(CategoriesDialogComponent, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
</button>
</nb-card-header>
<nb-card-header class="d-flex align-items-center justify-content-between with-background">
<small class="text-hint w-100">TRANSACTIONS - {{ expenses.length }}</small>
<small class="text-hint w-100">TRANSACTIONS - {{ expensesCount }}</small>
<span>{{ totalExpensesAmount | shortNumber }}</span>
</nb-card-header>
<nb-card-body class="p-0">
@for (date of groupedDates; track date) {
<nb-list>
<nb-list [@slideAnimation]>
<nb-list-item [@slideAnimation] class="with-background text-hint">
<div class="d-flex w-100"><nb-icon icon="calendar-outline" class="me-1" /> {{ date }}</div>
<div>
Expand Down Expand Up @@ -60,7 +60,7 @@
aria-label="Cancel statement import"
nbTooltip="Cancel statement import"
(click)="cancelImport()">
<nb-icon icon="trash-outline" />
<nb-icon icon="trash-2-outline" />
</button>

<button nbButton type="button" status="primary" (click)="importExpenses()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ small > nb-icon {
height: 16px;
width: 16px;
}

nb-list {
overflow-y: hidden;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DatePipe, NgStyle } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
NbButtonModule,
NbCardModule,
Expand All @@ -12,6 +12,7 @@ import {
import { slideAnimation } from '../../../../animations/slide.animation';
import { Expense } from '../../../../api/objects/expense';
import { APP_CONFIG } from '../../../../app.initializer';
import { StatementImportService } from '../../services/statement-import.service';
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
import { ExpenseDialogComponent } from '../expense-dialog/expense-dialog.component';
import { ShortNumberPipe } from '../../../../pipes/shortnumber.pipe';
Expand All @@ -26,28 +27,54 @@ export const DIALOG_ACTION_CLOSE = 'close';
animations: slideAnimation,
imports: [NbCardModule, NbButtonModule, NbIconModule, NbListModule, NbTooltipModule, NgStyle, ShortNumberPipe],
})
export class StatementReviewDialogComponent implements OnInit {
public expenses: Expense[] = [];
public onImportChange: (expenses: Expense[]) => void;
export class StatementReviewDialogComponent {
// Passed via dialog context
public importService: StatementImportService;

protected readonly dialogRef = inject<NbDialogRef<StatementReviewDialogComponent>>(NbDialogRef);
protected readonly Object = Object;
protected readonly DIALOG_ACTION_CLOSE = DIALOG_ACTION_CLOSE;
protected groupedByDates: { [key: string]: Expense[] } = {};
protected groupedDates: string[] = [];
protected totalExpensesAmountByDates: { [key: string]: number } = {};
protected totalExpensesAmount: number = 0;
protected calendarRefreshNeeded: boolean = false;

private readonly dialogService = inject(NbDialogService);
private datePipe: DatePipe;
private readonly datePipe: DatePipe = new DatePipe(APP_CONFIG.locale);

public constructor() {
this.datePipe = new DatePipe(APP_CONFIG.locale);
protected get expensesCount(): number {
return this.importService.draft().length;
}

public ngOnInit(): void {
this.reloadGroupedExpenses();
protected get groupedByDates(): Record<string, Expense[]> {
const grouped: Record<string, Expense[]> = {};

for (const expense of this.importService.draft()) {
const key = this.expenseGroup(expense);

if (!grouped[key]) {
grouped[key] = [];
}

grouped[key].push(expense);
}

return grouped;
}

protected get groupedDates(): string[] {
return Object.keys(this.groupedByDates);
}

protected get totalExpensesAmountByDates(): Record<string, number> {
const totals: Record<string, number> = {};

for (const [date, items] of Object.entries(this.groupedByDates)) {
totals[date] = items.reduce((sum, item) => sum + (item.amount ?? 0), 0);
}

return totals;
}

protected get totalExpensesAmount(): number {
return this.importService.draft().reduce((sum, e) => sum + (e.amount ?? 0), 0);
}

public openExpense(expense: Expense): void {
Expand All @@ -72,26 +99,22 @@ export class StatementReviewDialogComponent implements OnInit {
this.calendarRefreshNeeded = true;
}

// Defer list mutation to the next tick so it does not run inside
// the same change detection cycle that is closing the dialog.
setTimeout(() => this.removeExpenseFromList(expense));
});
}
this.importService.removeFromDraft(expense);

private closeIfEmpty(): void {
if (!this.expenses.length) {
this.dialogRef.close({
action: DIALOG_ACTION_CLOSE,
calendarRefreshNeeded: this.calendarRefreshNeeded,
});
}
if (!this.importService.draft().length) {
this.dialogRef.close({
action: DIALOG_ACTION_CLOSE,
calendarRefreshNeeded: this.calendarRefreshNeeded,
});
}
});
}

public importExpenses(): void {
this.dialogService
.open(ConfirmDialogComponent, {
context: {
question: `Are you sure you want to import ${this.expenses.length} transactions?`,
question: `Are you sure you want to import ${this.importService.draft().length} transactions?`,
},
})
.onClose.subscribe((result: boolean) => {
Expand Down Expand Up @@ -121,41 +144,7 @@ export class StatementReviewDialogComponent implements OnInit {
});
}

public removeExpenseFromList(expense: Expense): void {
if (this.expenses.indexOf(expense) === -1) {
return;
}

this.expenses = this.expenses.filter(item => item !== expense);

this.reloadGroupedExpenses();
this.onImportChange([...this.expenses]);
this.closeIfEmpty();
}

private reloadGroupedExpenses(): void {
this.groupedByDates = {};
this.groupedDates = [];
this.totalExpensesAmountByDates = {};
this.totalExpensesAmount = 0;

this.expenses.forEach((expense: Expense) => {
this.totalExpensesAmount += expense.amount;
const expenseGroup = this.expenseGroup(expense);

if (!this.groupedByDates[expenseGroup]) {
this.groupedByDates[expenseGroup] = [];
this.totalExpensesAmountByDates[expenseGroup] = 0;
}

this.groupedByDates[expenseGroup].push(expense);
this.totalExpensesAmountByDates[expenseGroup] += expense.amount;
});

this.groupedDates = Object.keys(this.groupedByDates);
}

private expenseGroup(expense: Expense): string {
return this.datePipe.transform(expense.createdAt);
return this.datePipe.transform(expense.createdAt) ?? 'Unknown';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import { StatementImportService } from '../../services/statement-import.service'
nbButton
type="button"
class="sidebar-toggler-button"
[status]="statementImportService.expenses.length ? 'danger' : 'basic'"
[status]="hasPendingImport ? 'danger' : 'basic'"
(click)="toggleSidebar()"
id="sidebar-toggler">
<nb-icon icon="menu-2-outline"></nb-icon>
</button>
@if (statementImportService.expenses.length) {
@if (hasPendingImport) {
<nb-badge [dotMode]="true" status="danger" position="top right"></nb-badge>
}
</div>
Expand All @@ -38,10 +38,13 @@ import { StatementImportService } from '../../services/statement-import.service'
imports: [NbButtonModule, NbIconModule, NbBadgeModule],
})
export class HeaderSidebarToggleComponent {
protected readonly statementImportService = inject(StatementImportService);

private readonly statementImportService = inject(StatementImportService);
private readonly sidebarService = inject(NbSidebarService);

protected get hasPendingImport(): boolean {
return this.statementImportService.draft().length > 0;
}

public toggleSidebar(): void {
this.sidebarService.toggle(false, SIDEBAR_TAG);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/modules/main/main.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class MainComponent implements OnInit {
this.bindResolvedRouteData();
this.bindVisibleDateQueryParam();

if (this.statementImportService.expenses.length) {
if (this.statementImportService.draft().length) {
this.statementImportService.processImport();
}
}
Expand Down
Loading
Loading