Skip to content

Commit 1158e62

Browse files
committed
Add Dialog for creating Transfer transaction #194
1 parent 426c8d1 commit 1158e62

File tree

8 files changed

+239
-50
lines changed

8 files changed

+239
-50
lines changed

CHANGELOG.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22

33
### :gear: Features & Enhancements
44

5-
* Distribute Budget clears also now negative Bucket Balances [#325](https://github.com/TheAxelander/OpenBudgeteer/issues/325)
6-
* New Charts for Buckets and Bucket Groups [#179](https://github.com/TheAxelander/OpenBudgeteer/issues/179) [#274](https://github.com/TheAxelander/OpenBudgeteer/issues/274)
7-
* Rework charts on Report Page into separate Tab Views
8-
* Rework logging and improved User experience for error messages
5+
* Transaction Page:
6+
* Add Dialog for creating Transfer transaction [#194](https://github.com/TheAxelander/OpenBudgeteer/issues/194)
7+
* (Re-)Introduce Button groups to cluster similar actions
8+
* Bucket Page:
9+
* Distribute Budget clears also now negative Bucket Balances [#325](https://github.com/TheAxelander/OpenBudgeteer/issues/325)
10+
* Report Page:
11+
* New Charts for Buckets and Bucket Groups [#179](https://github.com/TheAxelander/OpenBudgeteer/issues/179) [#274](https://github.com/TheAxelander/OpenBudgeteer/issues/274)
12+
* Rework charts on Report Page into separate Tab Views
13+
* Misc:
14+
* Rework logging and improved User experience for error messages
915

1016
### :beetle: Bug Fixes
1117

12-
* Import Page: Re-enable Payee as optional mapping [#331](https://github.com/TheAxelander/OpenBudgeteer/issues/331)
18+
* Import Page:
19+
* Re-enable Payee as optional mapping [#331](https://github.com/TheAxelander/OpenBudgeteer/issues/331)
1320

1421
### :hammer: Maintenance
1522

OpenBudgeteer.Blazor/Pages/Transaction.razor

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,24 @@
1414
}
1515
else
1616
{
17-
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="ShowCreateTransactionDialog">Create Transaction</MudButton>
17+
<MudButtonGroup Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary">
18+
<MudButton Class="restore-size-small" OnClick="ShowCreateTransactionDialog">Create Transaction</MudButton>
19+
<MudMenu Icon="@Icons.Material.Filled.ArrowDropDown" Style="align-self: auto;">
20+
<MudMenuItem OnClick="ShowCreateTransferDialog">Create Transfer</MudMenuItem>
21+
</MudMenu>
22+
</MudButtonGroup>
1823
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="SwitchToEditMode">Edit @(_selectedTransactions.Count == 0 ? "all" : $"{_selectedTransactions.Count} Selected")</MudButton>
1924
@if (_selectedTransactions.Count > 0)
2025
{
2126
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Error" OnClick="DeleteSelectedTransactions">Delete @_selectedTransactions.Count Selected</MudButton>
2227
}
2328
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="ProposeBucketsAsync">Propose Buckets</MudButton>
24-
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="AddRecurringTransactions">Add Recurring Transactions</MudButton>
25-
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="ShowRecurringTransactionDialog">Manage Recurring Transactions</MudButton>
29+
<MudButtonGroup Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary">
30+
<MudButton Class="restore-size-small" OnClick="AddRecurringTransactions">Add Recurring Transactions</MudButton>
31+
<MudMenu Icon="@Icons.Material.Filled.ArrowDropDown" Style="align-self: auto;">
32+
<MudMenuItem OnClick="ShowRecurringTransactionDialog">Manage Recurring Transactions</MudMenuItem>
33+
</MudMenu>
34+
</MudButtonGroup>
2635
<MudSpacer/>
2736
<MudTooltip RootClass="d-flex align-center">
2837
<ChildContent>

OpenBudgeteer.Blazor/Pages/Transaction.razor.cs

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text;
45
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Components;
67
using MudBlazor;
@@ -10,6 +11,7 @@
1011
using OpenBudgeteer.Core.Common;
1112
using OpenBudgeteer.Core.Common.Extensions;
1213
using OpenBudgeteer.Core.Data.Contracts.Services;
14+
using OpenBudgeteer.Core.Data.Entities.Models;
1315
using OpenBudgeteer.Core.ViewModels.EntityViewModels;
1416
using OpenBudgeteer.Core.ViewModels.Helper;
1517
using OpenBudgeteer.Core.ViewModels.PageViewModels;
@@ -23,6 +25,8 @@ public partial class Transaction : ComponentBase
2325
[Inject] private YearMonthSelectorViewModel YearMonthDataContext { get; set; } = null!;
2426

2527
private TransactionPageViewModel _dataContext = null!;
28+
private TransactionViewModel _createTransactionDataContext = null!;
29+
private Tuple<TransactionViewModel, TransactionViewModel> _createTransferDataContext = null!;
2630
private bool _isEditModeEnabled;
2731

2832
private DateOnlyMudFilter<TransactionViewModel> _dateOnlyMudFilter = null!;
@@ -62,6 +66,10 @@ protected override async Task OnInitializedAsync()
6266
private async Task ReloadDataContext()
6367
{
6468
await HandleResult(await _dataContext.LoadDataAsync());
69+
_createTransactionDataContext = TransactionViewModel.CreateEmpty(ServiceManager);
70+
_createTransferDataContext = new(
71+
TransactionViewModel.CreateEmpty(ServiceManager),
72+
TransactionViewModel.CreateEmpty(ServiceManager));
6573
_selectedTransactions.Clear();
6674

6775
_accountMudFilter.AvailableItems = _dataContext.Transactions
@@ -90,24 +98,28 @@ private void TransactionDateChanged(DateTime? dateTime, TransactionViewModel con
9098
private async Task ShowCreateTransactionDialog()
9199
{
92100
var reloadRequired = false;
101+
var lastEnteredDate = YearMonthDataContext.IsTodayInCurrentMonth ?
102+
DateOnly.FromDateTime(DateTime.Today) : YearMonthDataContext.CurrentMonth;
103+
93104
while (true)
94105
{
106+
_createTransactionDataContext.TransactionDate = lastEnteredDate;
95107
var createDialogParameters = new DialogParameters<CreateTransactionDialog>
96108
{
97-
{ x => x.DataContext, _dataContext.NewTransaction }
109+
{ x => x.DataContext, _createTransactionDataContext }
98110
};
99111
var createDialog = await DialogService.ShowAsync<CreateTransactionDialog>(
100112
"Create Transactions", createDialogParameters);
101113
var createDialogResult = await createDialog.Result;
102114
if (createDialogResult is { Canceled: false })
103115
{
104-
var createItemResult = _dataContext.CreateItem();
116+
var createItemResult = _dataContext.CreateItem(_createTransactionDataContext);
105117
if (createItemResult.IsSuccessful)
106118
{
107119
reloadRequired = true;
108120
if (createDialogResult.Data is CreateDialogResponse.CreateAnother)
109121
{
110-
_dataContext.ResetNewTransaction();
122+
lastEnteredDate = _createTransactionDataContext.TransactionDate;
111123
continue;
112124
}
113125
}
@@ -127,6 +139,95 @@ private async Task ShowCreateTransactionDialog()
127139
if (reloadRequired) await ReloadDataContext();
128140
}
129141

142+
private async Task ShowCreateTransferDialog()
143+
{
144+
var reloadRequired = false;
145+
var lastEnteredDateSender = YearMonthDataContext.IsTodayInCurrentMonth ?
146+
DateOnly.FromDateTime(DateTime.Today) : YearMonthDataContext.CurrentMonth;
147+
var lastEnteredDateReceiver = YearMonthDataContext.IsTodayInCurrentMonth ?
148+
DateOnly.FromDateTime(DateTime.Today) : YearMonthDataContext.CurrentMonth;
149+
150+
while (true)
151+
{
152+
var (sender, receiver) = _createTransferDataContext;
153+
sender.TransactionDate = lastEnteredDateSender;
154+
receiver.TransactionDate = lastEnteredDateReceiver;
155+
var createDialogParameters = new DialogParameters<CreateTransferDialog>
156+
{
157+
{ x => x.SenderDataContext, sender },
158+
{ x => x.ReceiverDataContext, receiver }
159+
};
160+
var createDialog = await DialogService.ShowAsync<CreateTransferDialog>(
161+
"Create Transfer", createDialogParameters);
162+
var createDialogResult = await createDialog.Result;
163+
if (createDialogResult is { Canceled: false })
164+
{
165+
sender.Buckets.Clear(); // Just to be sure
166+
receiver.Buckets.Clear(); // Just to be sure
167+
168+
var transferBucket = new Core.Data.Entities.Models.Bucket()
169+
{
170+
Id = Guid.Parse("00000000-0000-0000-0000-000000000002"),
171+
BucketGroupId = Guid.Parse("00000000-0000-0000-0000-000000000001"),
172+
BucketGroup = new BucketGroup
173+
{
174+
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"),
175+
Name = "System",
176+
Position = 0
177+
},
178+
Name = "Transfer"
179+
};
180+
sender.Buckets.Add(PartialBucketViewModel.CreateFromBucket(ServiceManager, transferBucket, sender.Amount));
181+
receiver.Buckets.Add(PartialBucketViewModel.CreateFromBucket(ServiceManager, transferBucket, receiver.Amount));
182+
183+
var createItemResultSender = sender.PerformConsistencyCheck();
184+
var createItemResultReceiver = receiver.PerformConsistencyCheck();
185+
186+
if (createItemResultSender.IsSuccessful && createItemResultReceiver.IsSuccessful)
187+
{
188+
createItemResultSender = _dataContext.CreateItem(sender);
189+
createItemResultReceiver = _dataContext.CreateItem(receiver);
190+
}
191+
192+
if (createItemResultSender.IsSuccessful && createItemResultReceiver.IsSuccessful)
193+
{
194+
reloadRequired = true;
195+
if (createDialogResult.Data is CreateDialogResponse.CreateAnother)
196+
{
197+
lastEnteredDateSender = sender.TransactionDate;
198+
lastEnteredDateReceiver = receiver.TransactionDate;
199+
continue;
200+
}
201+
}
202+
else
203+
{
204+
var messageStringBuilder = new StringBuilder();
205+
if (!createItemResultSender.IsSuccessful)
206+
{
207+
messageStringBuilder.AppendLine("Sending Transaction:");
208+
messageStringBuilder.AppendLine(createItemResultSender.Message);
209+
}
210+
if (!createItemResultReceiver.IsSuccessful)
211+
{
212+
if (messageStringBuilder.Length > 0) messageStringBuilder.AppendLine();
213+
messageStringBuilder.AppendLine("Receiving Transaction:");
214+
messageStringBuilder.AppendLine(createItemResultReceiver.Message);
215+
}
216+
217+
var errorDialogParameters = new DialogParameters<ErrorMessageDialog>
218+
{
219+
{ x => x.Title, "Create Transfer" },
220+
{ x => x.Message, messageStringBuilder.ToString() }
221+
};
222+
await DialogService.ShowAsync<ErrorMessageDialog>("Create Transfer", errorDialogParameters);
223+
}
224+
}
225+
226+
break;
227+
}
228+
if (reloadRequired) await ReloadDataContext();
229+
}
230+
130231
private void SwitchToEditMode()
131232
{
132233
_isEditModeEnabled = true;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
@using OpenBudgeteer.Blazor.Common
2+
@using OpenBudgeteer.Core.ViewModels.EntityViewModels
3+
4+
<MudDialog Class="dialog-background">
5+
<TitleContent>
6+
<MudText Typo="Typo.h6">Create Transfer Transactions</MudText>
7+
</TitleContent>
8+
<DialogContent>
9+
<MudStack Spacing="8">
10+
<MudGrid Spacing="2">
11+
<MudItem xs="12"><MudText Typo="Typo.subtitle1">Sending</MudText></MudItem>
12+
<MudItem xs="4">
13+
<MudDatePicker Date="@(SenderDataContext.TransactionDate.ToDateTime(TimeOnly.MinValue))" DateChanged="@(time => TransactionDateChanged(SenderDataContext, time))" ShowToolbar="false"/>
14+
</MudItem>
15+
<MudItem xs="8">
16+
<MudSelect T="AccountViewModel" @bind-Value="SenderDataContext.SelectedAccount">
17+
@foreach (var account in SenderDataContext.AvailableAccounts)
18+
{
19+
<MudSelectItem Value="@account">@account.Name</MudSelectItem>
20+
}
21+
</MudSelect>
22+
</MudItem>
23+
<MudItem xs="12"><MudTextField Label="Payee" @bind-Value="SenderDataContext.Payee"/></MudItem>
24+
<MudItem xs="12"><MudTextField Label="Memo" @bind-Value="SenderDataContext.Memo"/></MudItem>
25+
</MudGrid>
26+
<MudGrid Spacing="2">
27+
<MudItem xs="12"><MudText Typo="Typo.subtitle1">Receiving</MudText></MudItem>
28+
<MudItem xs="4">
29+
<MudDatePicker Date="@(ReceiverDataContext.TransactionDate.ToDateTime(TimeOnly.MinValue))" DateChanged="@(time => TransactionDateChanged(ReceiverDataContext, time))" ShowToolbar="false"/>
30+
</MudItem>
31+
<MudItem xs="8">
32+
<MudSelect T="AccountViewModel" @bind-Value="ReceiverDataContext.SelectedAccount">
33+
@foreach (var account in ReceiverDataContext.AvailableAccounts)
34+
{
35+
<MudSelectItem Value="@account">@account.Name</MudSelectItem>
36+
}
37+
</MudSelect>
38+
</MudItem>
39+
<MudItem xs="12"><MudTextField Label="Payee" @bind-Value="ReceiverDataContext.Payee"/></MudItem>
40+
<MudItem xs="12"><MudTextField Label="Memo" @bind-Value="ReceiverDataContext.Memo"/></MudItem>
41+
</MudGrid>
42+
<MudGrid Spacing="2">
43+
<MudItem xs="12"><MudText Typo="Typo.subtitle1">Exchanging amount</MudText></MudItem>
44+
<MudItem xs="12"><MudNumericField T="decimal" Format="N2" Label="Amount" @bind-Value="Amount"/></MudItem>
45+
</MudGrid>
46+
</MudStack>
47+
</DialogContent>
48+
<DialogActions>
49+
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="ConfirmCreate">Create</MudButton>
50+
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Primary" OnClick="ConfirmCreateAnother">Create another</MudButton>
51+
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Error" OnClick="CancelDialog">Cancel</MudButton>
52+
</DialogActions>
53+
</MudDialog>
54+
55+
@code {
56+
[CascadingParameter]
57+
private IMudDialogInstance MudDialog { get; set; } = null!;
58+
59+
[Parameter]
60+
public TransactionViewModel SenderDataContext { get; set; } = null!;
61+
62+
[Parameter]
63+
public TransactionViewModel ReceiverDataContext { get; set; } = null!;
64+
65+
private decimal _amount;
66+
public decimal Amount
67+
{
68+
get => _amount;
69+
set
70+
{
71+
if (value == _amount) return;
72+
_amount = value;
73+
SenderDataContext.Amount = _amount * -1;
74+
ReceiverDataContext.Amount = _amount;
75+
}
76+
}
77+
78+
void ConfirmCreate() => MudDialog.Close(DialogResult.Ok(CreateDialogResponse.Create));
79+
void ConfirmCreateAnother() => MudDialog.Close(DialogResult.Ok(CreateDialogResponse.CreateAnother));
80+
void CancelDialog() => MudDialog.Cancel();
81+
82+
void TransactionDateChanged(TransactionViewModel transaction, DateTime? dateTime)
83+
{
84+
transaction.TransactionDate = DateOnly.FromDateTime(dateTime ?? DateTime.Today);
85+
}
86+
}

OpenBudgeteer.Blazor/Shared/Dialog/ErrorMessageDialog.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
<MudDialog Class="dialog-background">
1+
<MudDialog Class="dialog-background">
22
<TitleContent>
33
<MudText Typo="Typo.h6">@Title</MudText>
44
</TitleContent>
55
<DialogContent>
6-
<MudText>@Message</MudText>
6+
<MudText Style="white-space: pre-line">@Message</MudText>
77
</DialogContent>
88
<DialogActions>
99
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Error" OnClick="CloseDialog">Close</MudButton>

OpenBudgeteer.Blazor/wwwroot/css/site.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
text-align: center;
4242
}
4343

44+
/* required for MudButton in a MudButtonGroup */
45+
.restore-size-small {
46+
padding: 4px 10px!important; /* original padding for Size="Size.Small" */
47+
}
48+
4449
#components-reconnect-modal {
4550
display: none;
4651
}

OpenBudgeteer.Core/ViewModels/EntityViewModels/TransactionViewModel.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -545,12 +545,22 @@ public ViewModelOperationResult CreateOrUpdateTransaction()
545545
/// Executes several data consistency checks (e.g. Bucket assignment, pending amount etc.) to see if changes
546546
/// can be stored in the database
547547
/// </summary>
548-
/// <param name="skipBucketAssignment">Exclude checks on Bucket assignment</param>
549548
/// <returns>Object which contains information and results of this method</returns>
550-
private ViewModelOperationResult PerformConsistencyCheck(out bool skipBucketAssignment)
549+
public ViewModelOperationResult PerformConsistencyCheck()
550+
{
551+
return PerformConsistencyCheck(out _);
552+
}
553+
554+
/// <summary>
555+
/// Executes several data consistency checks (e.g. Bucket assignment, pending amount etc.) to see if changes
556+
/// can be stored in the database
557+
/// </summary>
558+
/// <param name="hasNoSelectionBucket">Will be set to true if Transaction has a "No Selection" Bucket assigned</param>
559+
/// <returns>Object which contains information and results of this method</returns>
560+
private ViewModelOperationResult PerformConsistencyCheck(out bool hasNoSelectionBucket)
551561
{
552562
decimal assignedAmount = 0;
553-
skipBucketAssignment = false;
563+
hasNoSelectionBucket = false;
554564

555565
// Consistency and Validity Checks
556566
if (SelectedAccount.AccountId == Guid.Empty) return new ViewModelOperationResult(false, "No Bank account selected.");
@@ -566,7 +576,7 @@ private ViewModelOperationResult PerformConsistencyCheck(out bool skipBucketAssi
566576
{
567577
// Imported Transaction where Bucket assignment is pending
568578
// Allow Transaction Update but Skip DB Updates for Bucket assignment
569-
skipBucketAssignment = true;
579+
hasNoSelectionBucket = true;
570580
}
571581
else
572582
{

0 commit comments

Comments
 (0)