Skip to content

Commit b3c1cef

Browse files
committed
Merge branch 'ui-fixes2'
2 parents ceca051 + 31a28bc commit b3c1cef

File tree

9 files changed

+137
-56
lines changed

9 files changed

+137
-56
lines changed

frontend/src/app/layout/component/app.menu.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -66,31 +66,6 @@ export class AppMenu implements OnInit {
6666
}
6767
]
6868
},
69-
{
70-
label: 'Accounts',
71-
items: [
72-
{
73-
label: 'Assets',
74-
icon: 'pi pi-fw pi-wallet',
75-
routerLink: ['/accounts']
76-
},
77-
{
78-
label: 'Income',
79-
icon: 'pi pi-fw pi-money-bill',
80-
routerLink: ['/accounts/income']
81-
},
82-
{
83-
label: 'Expense',
84-
icon: 'pi pi-fw pi-cart-minus',
85-
routerLink: ['/accounts/expense']
86-
},
87-
{
88-
label: 'Liabilities',
89-
icon: 'pi pi-fw pi-credit-card',
90-
routerLink: ['/accounts/liabilities']
91-
}
92-
]
93-
},
9469
{
9570
label: 'Transactions',
9671
items: [
@@ -116,6 +91,31 @@ export class AppMenu implements OnInit {
11691
}
11792
]
11893
},
94+
{
95+
label: 'Accounts',
96+
items: [
97+
{
98+
label: 'Assets',
99+
icon: 'pi pi-fw pi-wallet',
100+
routerLink: ['/accounts']
101+
},
102+
{
103+
label: 'Income',
104+
icon: 'pi pi-fw pi-money-bill',
105+
routerLink: ['/accounts/income']
106+
},
107+
{
108+
label: 'Expense',
109+
icon: 'pi pi-fw pi-cart-minus',
110+
routerLink: ['/accounts/expense']
111+
},
112+
{
113+
label: 'Liabilities',
114+
icon: 'pi pi-fw pi-credit-card',
115+
routerLink: ['/accounts/liabilities']
116+
}
117+
]
118+
},
119119
{
120120
label: 'Bulk',
121121
items: [

frontend/src/app/pages/accounts/accounts-detail.component.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class AccountsDetailComponent extends BaseAutoUnsubscribeClass implements
3838
@Inject(TRANSPORT_TOKEN) private transport: Transport,
3939
activeRoute: ActivatedRoute,
4040
private messageService: MessageService,
41-
busService: BusService,
41+
private busService: BusService,
4242
private selectedDateService: SelectedDateService
4343
) {
4444
super();
@@ -47,15 +47,13 @@ export class AccountsDetailComponent extends BaseAutoUnsubscribeClass implements
4747
this.analyticsService = createClient(AnalyticsService, this.transport);
4848
this.configService = createClient(ConfigurationService, this.transport);
4949

50-
busService.currentAccountId.subscribe(async (accountId) => {
51-
await this.setAccount(accountId);
52-
});
53-
54-
activeRoute.params.subscribe((params) => {
55-
let parsed = parseInt(params['id']) ?? undefined;
56-
57-
busService.currentAccountId.next(parsed);
58-
});
50+
activeRoute.params
51+
.pipe(takeUntil(this.cancellableSubject$))
52+
.subscribe(async (params) => {
53+
let parsed = parseInt(params['id']) ?? undefined;
54+
busService.currentAccountId.next(parsed);
55+
await this.setAccount(parsed);
56+
});
5957

6058
combineLatest([this.selectedDateService.fromDate, this.selectedDateService.toDate])
6159
.pipe(takeUntil(this.cancellableSubject$))

frontend/src/app/pages/rules/rule-list.component.html

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
[filters]="filters"
1818
[paginator]="false"
1919
[globalFilterFields]="['id', 'title']"
20-
responsiveLayout="scroll">
20+
responsiveLayout="scroll"
21+
[multiSortMeta]="multiSortMeta">
2122
<ng-template #caption>
2223
<div class="flex flex-column sm:flex-row gap-2 justify-between">
2324
<div class="flex flex-col justify-items-start">
@@ -48,10 +49,16 @@
4849
<p-sortIcon field="title" />
4950
</div>
5051
</th>
51-
<th style="min-width: 15rem" pSortableColumn="title">
52+
<th pSortableColumn="enabled">
53+
<div class="flex justify-between items-center">
54+
Enabled
55+
<p-sortIcon field="enabled" />
56+
</div>
57+
</th>
58+
<th pSortableColumn="isFinalRule">
5259
<div class="flex justify-between items-center">
5360
Is Final Rule
54-
<p-sortIcon field="title" />
61+
<p-sortIcon field="isFinalRule" />
5562
</div>
5663
</th>
5764
<th>
@@ -79,6 +86,7 @@
7986
</th>
8087
<th></th>
8188
<th></th>
89+
<th></th>
8290
</tr>
8391
</ng-template>
8492
<ng-template #body let-accountItem>
@@ -93,15 +101,25 @@
93101
{{ accountItem.title }}
94102
</a>
95103
</td>
104+
<td>
105+
<a [style]="{color : accountItem.enabled ? 'green' : ''}" [href]="getRuleUrl(accountItem)" [routerLink]="getRuleUrl(accountItem)">
106+
{{ accountItem.enabled }}
107+
</a>
108+
</td>
96109
<td>
97110
<a [href]="getRuleUrl(accountItem)" [routerLink]="getRuleUrl(accountItem)">
98111
{{ accountItem.isFinalRule }}
99112
</a>
100113
</td>
101-
<td style="max-width: 1rem">
102-
<p-button icon="pi pi-pencil"
103-
(onClick)="this.router.navigate(['/', 'rules', 'edit', accountItem.id])"
104-
severity="secondary" rounded />
114+
<td>
115+
<div class="flex gap-2">
116+
<p-button icon="pi pi-pencil" pTooltip="Edit rule" tooltipPosition="top"
117+
(onClick)="this.router.navigate(['/', 'rules', 'edit', accountItem.id])"
118+
severity="secondary" rounded />
119+
<p-button icon="pi pi-clone" pTooltip="Clone rule" tooltipPosition="top"
120+
(onClick)="this.router.navigate(['/rules', 'new'], { queryParams: { clone_from: accountItem.id } })"
121+
severity="secondary" rounded />
122+
</div>
105123
</td>
106124
</tr>
107125
</ng-template>

frontend/src/app/pages/rules/rule-list.component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { OverlayModule } from 'primeng/overlay';
2121
import { ListTagsResponse_TagItem, TagsService } from '@buf/xskydev_go-money-pb.bufbuild_es/gomoneypb/tags/v1/tags_pb';
2222
import { RulesService } from '@buf/xskydev_go-money-pb.bufbuild_es/gomoneypb/rules/v1/rules_pb';
2323
import { Rule } from '@buf/xskydev_go-money-pb.bufbuild_es/gomoneypb/v1/rule_pb';
24+
import { Tooltip } from 'primeng/tooltip';
2425

2526
class RuleGroup {
2627
title: string = 'Default';
@@ -30,7 +31,7 @@ class RuleGroup {
3031
@Component({
3132
selector: 'app-rule-list',
3233
templateUrl: 'rule-list.component.html',
33-
imports: [OverlayModule, FormsModule, InputText, ToastModule, TableModule, InputIcon, IconField, Button, MultiSelectModule, SelectModule, CommonModule, RouterLink],
34+
imports: [OverlayModule, FormsModule, InputText, ToastModule, TableModule, InputIcon, IconField, Button, MultiSelectModule, SelectModule, CommonModule, RouterLink, Tooltip],
3435
styles: `
3536
:host ::ng-deep .tagListingTable .p-datatable-header {
3637
border-width: 0 !important;
@@ -48,6 +49,12 @@ export class RuleListComponent implements OnInit {
4849
private ruleService;
4950

5051
public filters: { [s: string]: FilterMetadata } = {};
52+
public multiSortMeta: any[] = [
53+
{
54+
field: 'id',
55+
order: 1
56+
}
57+
];
5158

5259
@ViewChild('filter') filter!: ElementRef;
5360

frontend/src/app/pages/rules/rules-upsert.component.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class RulesUpsertComponent implements OnInit {
3434
constructor(
3535
@Inject(TRANSPORT_TOKEN) private transport: Transport,
3636
private messageService: MessageService,
37-
routeSnapshot: ActivatedRoute,
37+
private routeSnapshot: ActivatedRoute,
3838
private router: Router
3939
) {
4040
this.rulesService = createClient(RulesService, this.transport);
@@ -47,11 +47,26 @@ export class RulesUpsertComponent implements OnInit {
4747
}
4848

4949
async ngOnInit() {
50-
if (this.rule.id) {
50+
const cloneFrom = this.routeSnapshot.snapshot.queryParams['clone_from'];
51+
52+
if (cloneFrom) {
53+
try {
54+
let response = await this.rulesService.listRules({ ids: [+cloneFrom] });
55+
if (response.rules && response.rules.length == 0) {
56+
this.messageService.add({ severity: 'error', detail: 'rule not found' });
57+
return;
58+
}
59+
60+
this.rule = response.rules[0] ?? create(RuleSchema, {});
61+
this.rule.id = 0;
62+
} catch (e) {
63+
this.messageService.add({ severity: 'error', detail: ErrorHelper.getMessage(e) });
64+
}
65+
} else if (this.rule.id) {
5166
try {
5267
let response = await this.rulesService.listRules({ ids: [+this.rule.id] });
5368
if (response.rules && response.rules.length == 0) {
54-
this.messageService.add({ severity: 'error', detail: 'tag not found' });
69+
this.messageService.add({ severity: 'error', detail: 'rule not found' });
5570
return;
5671
}
5772

frontend/src/app/services/enum.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class EnumService {
8181
icon: 'pi pi-arrows-h text-blue-500'
8282
},
8383
{
84-
name: 'Withdrawal',
84+
name: 'Expense',
8585
value: TransactionType.EXPENSE,
8686
icon: 'pi pi-arrow-up text-red-500'
8787
}

frontend/src/app/shared/components/transaction-editor/transaction-editor.component.html

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,22 @@
3535
optionLabel="name"
3636
optionValue="id"
3737
placeholder="Select source account"
38-
/>
38+
>
39+
<ng-template #item let-account>
40+
<div class="flex flex-col text-sm leading-tight py-1 w-full">
41+
<div class="font-semibold mb-1 text-center">{{ account.name }}</div>
42+
<div class="flex gap-3 text-xs text-gray-600 dark:text-gray-400 justify-center">
43+
<span class="flex-shrink-0">Type: <span class="text-gray-900 dark:text-gray-100">{{ getAccountTypeName(account.type) }}</span></span>
44+
<span class="flex-shrink-0">Currency: <span class="text-gray-900 dark:text-gray-100">{{ account.currency }}</span></span>
45+
<span class="flex-shrink-0">Balance: <span class="font-mono"
46+
[class.text-green-600]="parseFloat(account.currentBalance) > 0"
47+
[class.dark:text-green-400]="parseFloat(account.currentBalance) > 0"
48+
[class.text-red-600]="parseFloat(account.currentBalance) < 0"
49+
[class.dark:text-red-400]="parseFloat(account.currentBalance) < 0">{{ account.currentBalance }}</span></span>
50+
</div>
51+
</div>
52+
</ng-template>
53+
</p-select>
3954
@if (sourceAccountId.invalid && (sourceAccountId.dirty || sourceAccountId.touched)) {
4055
<p-message severity="error" variant="simple" size="small">Source Account is required</p-message>
4156
}
@@ -105,7 +120,22 @@
105120
optionLabel="name"
106121
optionValue="id"
107122
placeholder="Select destination account"
108-
/>
123+
>
124+
<ng-template #item let-account>
125+
<div class="flex flex-col text-sm leading-tight py-1 w-full">
126+
<div class="font-semibold mb-1 text-center">{{ account.name }}</div>
127+
<div class="flex gap-3 text-xs text-gray-600 dark:text-gray-400 justify-center">
128+
<span class="flex-shrink-0">Type: <span class="text-gray-900 dark:text-gray-100">{{ getAccountTypeName(account.type) }}</span></span>
129+
<span class="flex-shrink-0">Currency: <span class="text-gray-900 dark:text-gray-100">{{ account.currency }}</span></span>
130+
<span class="flex-shrink-0">Balance: <span class="font-mono"
131+
[class.text-green-600]="parseFloat(account.currentBalance) > 0"
132+
[class.dark:text-green-400]="parseFloat(account.currentBalance) > 0"
133+
[class.text-red-600]="parseFloat(account.currentBalance) < 0"
134+
[class.dark:text-red-400]="parseFloat(account.currentBalance) < 0">{{ account.currentBalance }}</span></span>
135+
</div>
136+
</div>
137+
</ng-template>
138+
</p-select>
109139
@if (destinationAccountId.invalid && (destinationAccountId.dirty || destinationAccountId.touched)) {
110140
<p-message severity="error" variant="simple" size="small">Destination Account is required</p-message>
111141
}

frontend/src/app/shared/components/transaction-editor/transaction-editor.component.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FilterMetadata, MessageService, ConfirmationService } from 'primeng/api
99
import { ToastModule } from 'primeng/toast';
1010
import { DatePickerModule } from 'primeng/datepicker';
1111
import { Account } from '@buf/xskydev_go-money-pb.bufbuild_es/gomoneypb/v1/account_pb';
12-
import { NgClass, NgIf } from '@angular/common';
12+
import { NgIf, NgFor } from '@angular/common';
1313
import { TextareaModule } from 'primeng/textarea';
1414
import { ButtonModule } from 'primeng/button';
1515
import { MultiSelectModule } from 'primeng/multiselect';
@@ -362,11 +362,13 @@ export class TransactionEditorComponent implements OnInit, OnChanges {
362362
return [];
363363
}
364364

365-
if (isSource) {
366-
return applicable.sourceAccounts || [];
367-
} else {
368-
return applicable.destinationAccounts || [];
369-
}
365+
const accounts = isSource ? applicable.sourceAccounts || [] : applicable.destinationAccounts || [];
366+
367+
return accounts.sort((a, b) => {
368+
const orderA = a.displayOrder ?? 999999;
369+
const orderB = b.displayOrder ?? 999999;
370+
return orderA - orderB;
371+
});
370372
}
371373

372374
getAccountById(id: number | undefined): Account | null {
@@ -603,4 +605,14 @@ export class TransactionEditorComponent implements OnInit, OnChanges {
603605
this.messageService.add({ severity: 'error', detail: ErrorHelper.getMessage(e) });
604606
}
605607
}
608+
609+
getAccountTypeName(type: number): string {
610+
const accountTypes = EnumService.getAccountTypes();
611+
const accountType = accountTypes.find(t => t.value === type);
612+
return accountType?.name || 'Unknown';
613+
}
614+
615+
parseFloat(value: string): number {
616+
return parseFloat(value);
617+
}
606618
}

pkg/database/stat.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package database
22

33
import (
4-
"github.com/shopspring/decimal"
54
"time"
5+
6+
"github.com/shopspring/decimal"
67
)
78

89
type DailyStat struct {

0 commit comments

Comments
 (0)