Skip to content

Commit 495670e

Browse files
PhantomDaveCopilotCopilot
authored
feat: implement financial tracking components with movements and recurring records (#81)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
1 parent cf6e714 commit 495670e

File tree

17 files changed

+258
-51
lines changed

17 files changed

+258
-51
lines changed

frontend/src/app/app.routes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Routes } from '@angular/router';
22

3-
import MonthlyRecapComponent from './components/tracking/monthly-recap-component/monthly-recap-component';
43
import { HomeComponent } from './components/home-component/home-component';
54
import { LoginComponent } from './components/welcome-layout/login-component/login-component';
65
import { RegisterComponent } from './components/welcome-layout/register-component/register-component';
@@ -9,13 +8,14 @@ import { BalanceComponent } from './balance/balance-component/balance-component'
98
import { SettingsComponent } from './components/settings-component/settings-component';
109
import { ImportWizardComponent } from './components/import/import-wizard-component/import-wizard-component';
1110
import { MonthlyComparisonComponent } from './components/analytics/monthly-comparison-component/monthly-comparison-component';
11+
import { TrackingComponent } from './components/tracking/tracking/tracking.component';
1212

1313
export const routes: Routes = [
1414
{ path: '', redirectTo: 'login', pathMatch: 'full' },
1515
{ path: 'login', component: LoginComponent },
1616
{ path: 'register', component: RegisterComponent },
1717
{ path: 'home', component: HomeComponent, canActivate: [authenticateGuard] },
18-
{ path: 'config', component: MonthlyRecapComponent, canActivate: [authenticateGuard] },
18+
{ path: 'movements', component: TrackingComponent, canActivate: [authenticateGuard] },
1919
{ path: 'balance', component: BalanceComponent, canActivate: [authenticateGuard] },
2020
{ path: 'settings', component: SettingsComponent, canActivate: [authenticateGuard] },
2121
{ path: 'import', component: ImportWizardComponent, canActivate: [authenticateGuard] },

frontend/src/app/components/analytics/monthly-comparison-component/monthly-comparison-component.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
import { CurrencyPipe, DatePipe } from '@angular/common';
2-
import { ChangeDetectionStrategy, Component, computed, inject, OnInit } from '@angular/core';
3-
import { MatButtonModule } from '@angular/material/button';
4-
import { MatCardModule } from '@angular/material/card';
5-
import { MatIconModule } from '@angular/material/icon';
6-
import { MatTableModule } from '@angular/material/table';
7-
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
8-
import { MatFormFieldModule } from '@angular/material/form-field';
9-
import { MatDatepickerModule } from '@angular/material/datepicker';
10-
import { MatInputModule } from '@angular/material/input';
11-
import { MatNativeDateModule } from '@angular/material/core';
12-
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
13-
import { FinanceRecordService } from '../../../models/finance-record/finance-record-service';
14-
import { FlexComponent } from '../../ui-library/flex-component/flex-component';
1+
import {CurrencyPipe} from '@angular/common';
2+
import {ChangeDetectionStrategy, Component, computed, inject, OnInit} from '@angular/core';
3+
import {MatButtonModule} from '@angular/material/button';
4+
import {MatCardModule} from '@angular/material/card';
5+
import {MatIconModule} from '@angular/material/icon';
6+
import {MatTableModule} from '@angular/material/table';
7+
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
8+
import {MatFormFieldModule} from '@angular/material/form-field';
9+
import {MatDatepickerModule} from '@angular/material/datepicker';
10+
import {MatInputModule} from '@angular/material/input';
11+
import {MatNativeDateModule} from '@angular/material/core';
12+
import {FormBuilder, ReactiveFormsModule} from '@angular/forms';
13+
import {FinanceRecordService} from '../../../models/finance-record/finance-record-service';
14+
import {FlexComponent} from '../../ui-library/flex-component/flex-component';
1515

1616
@Component({
1717
selector: 'app-monthly-comparison',
1818
standalone: true,
1919
imports: [
2020
CurrencyPipe,
21-
DatePipe,
2221
MatCardModule,
2322
MatButtonModule,
2423
MatIconModule,

frontend/src/app/components/side-nav-component/side-nav-component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@
3434
</a>
3535
<a
3636
mat-list-item
37-
routerLink="/config"
37+
routerLink="/movements"
3838
class="nav-item"
3939
routerLinkActive="active"
40-
#tlaTracking="routerLinkActive"
40+
#movTracking="routerLinkActive"
4141
[routerLinkActiveOptions]="{ exact: true }"
42-
[attr.aria-current]="tlaTracking.isActive ? 'page' : null"
42+
[attr.aria-current]="movTracking.isActive ? 'page' : null"
4343
>
4444
<mat-icon matListItemIcon>insights</mat-icon>
45-
<span matListItemTitle>Tracking</span>
45+
<span matListItemTitle>Movements</span>
4646
<span matListItemLine>Track your expenses</span>
4747
</a>
4848
<a

frontend/src/app/components/tracking/monthly-recap-component/monthly-recap-component.css renamed to frontend/src/app/components/tracking/financial/financial.component.css

File renamed without changes.

frontend/src/app/components/tracking/monthly-recap-component/monthly-recap-component.html renamed to frontend/src/app/components/tracking/financial/financial.component.html

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
<mat-card appearance="outlined">
1+
<mat-card appearance="outlined" style="width: 100%">
22
@if (loading()) {
33
<mat-card-header>
4-
<mat-card-title>Loading Configurations...</mat-card-title>
4+
<mat-card-title>Loading {{ title() }}...</mat-card-title>
55
</mat-card-header>
66
} @else {
77
<mat-card-header>
8-
<mat-card-title>Configurations</mat-card-title>
8+
<mat-card-title>{{ title() }}</mat-card-title>
99
</mat-card-header>
1010

1111
<mat-card-actions align="end">
@@ -20,43 +20,55 @@
2020
<ng-container matColumnDef="name">
2121
<th mat-header-cell *matHeaderCellDef>Name</th>
2222
<td mat-cell *matCellDef="let element">{{ element.name }}</td>
23-
<td mat-footer-cell *matFooterCellDef class="footer-cell-label">Totals</td>
23+
@if (showFooter()) {
24+
<td mat-footer-cell *matFooterCellDef class="footer-cell-label">Totals</td>
25+
}
2426
</ng-container>
2527

2628
<ng-container matColumnDef="description">
2729
<th mat-header-cell *matHeaderCellDef>Description</th>
2830
<td mat-cell *matCellDef="let element" class="description-cell">
2931
{{ element.description }}
3032
</td>
31-
<td mat-footer-cell *matFooterCellDef>{{ recordCount() }} records</td>
33+
@if (showFooter()) {
34+
<td mat-footer-cell *matFooterCellDef>{{ recordCount() }} records</td>
35+
}
3236
</ng-container>
3337

3438
<ng-container matColumnDef="recurring">
3539
<th mat-header-cell *matHeaderCellDef>Recurring</th>
3640
<td mat-cell *matCellDef="let element">{{ element.recurring ? 'Yes' : 'No' }}</td>
37-
<td mat-footer-cell *matFooterCellDef>{{ recurringCount() }} recurring</td>
41+
@if (showFooter()) {
42+
<td mat-footer-cell *matFooterCellDef>{{ recurringCount() }} recurring</td>
43+
}
3844
</ng-container>
3945

4046
<ng-container matColumnDef="recurrenceFrequency">
4147
<th mat-header-cell *matHeaderCellDef>Frequency</th>
4248
<td mat-cell *matCellDef="let element">{{ element.recurrenceFrequency }}</td>
43-
<td mat-footer-cell *matFooterCellDef></td>
49+
@if (showFooter()) {
50+
<td mat-footer-cell *matFooterCellDef></td>
51+
}
4452
</ng-container>
4553

4654
<ng-container matColumnDef="amount">
4755
<th mat-header-cell *matHeaderCellDef>Amount</th>
4856
<td mat-cell *matCellDef="let element">
4957
{{ element.amount | currency: element.currency }}
5058
</td>
51-
<td mat-footer-cell *matFooterCellDef>
52-
{{ signedTotalAmount().total | currency: signedTotalAmount().currency }}
53-
</td>
59+
@if (showFooter()) {
60+
<td mat-footer-cell *matFooterCellDef>
61+
{{ signedTotalAmount().total | currency: signedTotalAmount().currency }}
62+
</td>
63+
}
5464
</ng-container>
5565

5666
<ng-container matColumnDef="date">
5767
<th mat-header-cell *matHeaderCellDef>Date</th>
5868
<td mat-cell *matCellDef="let element">{{ element.date | date: 'short' }}</td>
59-
<td mat-footer-cell *matFooterCellDef>--</td>
69+
@if (showFooter()) {
70+
<td mat-footer-cell *matFooterCellDef>--</td>
71+
}
6072
</ng-container>
6173

6274
<ng-container matColumnDef="actions">
@@ -72,12 +84,16 @@
7284
>delete</mat-icon
7385
>
7486
</td>
75-
<td mat-footer-cell *matFooterCellDef>--</td>
87+
@if (showFooter()) {
88+
<td mat-footer-cell *matFooterCellDef>--</td>
89+
}
7690
</ng-container>
7791

7892
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
7993
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
80-
<tr mat-footer-row *matFooterRowDef="displayedColumns"></tr>
94+
@if (showFooter()) {
95+
<tr mat-footer-row *matFooterRowDef="displayedColumns"></tr>
96+
}
8197
</table>
8298
<mat-paginator
8399
[pageSizeOptions]="[10, 25, 50, 100]"

frontend/src/app/components/tracking/monthly-recap-component/monthly-recap-component.ts renamed to frontend/src/app/components/tracking/financial/financial.component.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import {
33
AfterViewInit,
44
ChangeDetectionStrategy,
55
Component,
6-
LOCALE_ID,
7-
OnInit,
86
ViewChild,
97
computed,
108
effect,
119
inject,
10+
input,
1211
} from '@angular/core';
1312
import { MatButtonModule } from '@angular/material/button';
1413
import { MatCardModule } from '@angular/material/card';
@@ -19,8 +18,9 @@ import { FinanceRecordService } from '../../../models/finance-record/finance-rec
1918
import { AddEntry } from '../add-entry/add-entry';
2019
import { FinanceRecord } from '../../../models/finance-record/finance-record';
2120
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
21+
2222
@Component({
23-
selector: 'app-configurator',
23+
selector: 'app-financial-table',
2424
imports: [
2525
MatTableModule,
2626
MatCardModule,
@@ -31,18 +31,20 @@ import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
3131
CurrencyPipe,
3232
MatPaginatorModule,
3333
],
34-
templateUrl: './monthly-recap-component.html',
35-
styleUrl: './monthly-recap-component.css',
34+
templateUrl: './financial.component.html',
35+
styleUrl: './financial.component.css',
3636
changeDetection: ChangeDetectionStrategy.OnPush,
3737
})
38-
class MonthlyRecapComponent implements OnInit, AfterViewInit {
38+
class FinancialTableComponent implements AfterViewInit {
3939
private readonly dialog = inject(MatDialog);
4040
private readonly financeRecordService = inject(FinanceRecordService);
41-
private readonly locale = inject(LOCALE_ID);
4241

43-
readonly loading = computed(() => this.financeRecordService.loading());
42+
readonly title = input<string>('Financial Records');
43+
readonly showFooter = input<boolean>(true);
44+
45+
readonly loading = input<boolean>(false);
46+
readonly records = input<readonly FinanceRecord[]>([]);
4447
readonly dataSource = new MatTableDataSource<FinanceRecord>([]);
45-
readonly records = computed(() => this.financeRecordService.financeRecords());
4648
readonly recordCount = computed(() => this.records().length);
4749
readonly recurringCount = computed(
4850
() => this.records().filter((record) => record.recurring).length,
@@ -61,17 +63,12 @@ class MonthlyRecapComponent implements OnInit, AfterViewInit {
6163

6264
constructor() {
6365
effect(() => {
64-
this.dataSource.data = [...this.records()];
66+
this.dataSource.data = this.records() as FinanceRecord[];
6567
});
6668
}
6769

6870
ngAfterViewInit() {
6971
this.dataSource.paginator = this.paginator;
70-
this.dataSource.data = [...this.records()];
71-
}
72-
73-
async ngOnInit(): Promise<void> {
74-
await this.financeRecordService.getFinanceRecords();
7572
}
7673

7774
readonly displayedColumns: string[] = [
@@ -117,4 +114,4 @@ class MonthlyRecapComponent implements OnInit, AfterViewInit {
117114
}
118115
}
119116

120-
export default MonthlyRecapComponent;
117+
export default FinancialTableComponent;

frontend/src/app/components/tracking/monthly-recap-component/movements-component.css

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<app-financial-table
2+
[title]="title()"
3+
[loading]="loading()"
4+
[records]="filteredMovements()"
5+
[showFooter]="false"
6+
/>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2+
import { MatButtonModule } from '@angular/material/button';
3+
import { MatCardModule } from '@angular/material/card';
4+
import { MatDialogModule } from '@angular/material/dialog';
5+
import { MatIconModule } from '@angular/material/icon';
6+
import { MatTableModule } from '@angular/material/table';
7+
import { FinanceRecord } from '../../../models/finance-record/finance-record';
8+
import FinancialTableComponent from '../financial/financial.component';
9+
10+
@Component({
11+
selector: 'app-movements-table',
12+
imports: [
13+
MatTableModule,
14+
MatCardModule,
15+
MatButtonModule,
16+
MatIconModule,
17+
MatDialogModule,
18+
FinancialTableComponent,
19+
],
20+
templateUrl: './movements-component.html',
21+
styleUrl: './movements-component.css',
22+
changeDetection: ChangeDetectionStrategy.OnPush,
23+
})
24+
class MovementsTableComponent {
25+
26+
readonly title = input<string>('Movements');
27+
readonly loading = input<boolean>(false);
28+
readonly records = input<readonly FinanceRecord[]>([]);
29+
30+
readonly filteredMovements = computed(() => this.records().filter((record) => !record.recurring));
31+
}
32+
33+
export default MovementsTableComponent;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
:host {
2+
background-color: var(--mat-sys-surface-container);
3+
color: var(--mat-sys-on-surface);
4+
}
5+
6+
table[mat-table] {
7+
background-color: var(--mat-sys-surface);
8+
border-radius: 12px;
9+
box-shadow: 0 2px 8px 0 var(--mat-sys-outline-variant);
10+
overflow: hidden;
11+
}
12+
13+
th.mat-header-cell {
14+
background-color: var(--mat-sys-surface-container-high);
15+
color: var(--mat-sys-on-surface);
16+
font: var(--mat-sys-label-large);
17+
border-bottom: 1px solid var(--mat-sys-outline-variant);
18+
}
19+
20+
td.mat-cell,
21+
td.mat-footer-cell {
22+
background-color: var(--mat-sys-surface);
23+
color: var(--mat-sys-on-surface);
24+
font: var(--mat-sys-body-medium);
25+
border-bottom: 1px solid var(--mat-sys-outline-variant);
26+
}
27+
28+
tr.mat-row:hover td.mat-cell {
29+
background-color: var(--mat-sys-surface-container);
30+
}
31+
32+
.footer-cell-label {
33+
font-weight: 600;
34+
color: var(--mat-sys-primary);
35+
}
36+
37+
.action-icon {
38+
cursor: pointer;
39+
color: var(--mat-sys-on-surface);
40+
}
41+
42+
.action-icon.delete {
43+
color: var(--mat-sys-error);
44+
}
45+
46+
.action-icon.edit {
47+
color: var(--mat-sys-primary);
48+
}
49+
50+
.description-cell {
51+
max-width: 300px;
52+
white-space: nowrap;
53+
overflow: hidden;
54+
text-overflow: ellipsis;
55+
}

0 commit comments

Comments
 (0)