Skip to content

Commit 3978bf5

Browse files
Merge pull request #444 from TransactionProcessing/copilot/add-operator-screen
Add operator CRUD screens
2 parents c3176b3 + 2499f9c commit 3978bf5

4 files changed

Lines changed: 500 additions & 6 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
@page "/operators/{OperatorId:guid}/edit"
2+
@rendermode InteractiveServer
3+
@using System.ComponentModel.DataAnnotations
4+
@inject IMediator Mediator
5+
@inject NavigationManager NavigationManager
6+
7+
<PageTitle>Edit Operator</PageTitle>
8+
9+
<div class="space-y-6">
10+
@if (isLoading)
11+
{
12+
<div class="flex items-center justify-center py-12">
13+
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
14+
</div>
15+
}
16+
else if (operatorModel != null)
17+
{
18+
<!-- Page Header -->
19+
<div class="flex items-center justify-between">
20+
<div>
21+
<h1 class="text-2xl font-bold text-gray-900">Edit Operator: @operatorModel.Name</h1>
22+
</div>
23+
<div class="flex space-x-2">
24+
<button class="btn btn-secondary" @onclick="BackToList">
25+
Back to List
26+
</button>
27+
</div>
28+
</div>
29+
30+
@if (!string.IsNullOrWhiteSpace(successMessage))
31+
{
32+
<div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg">
33+
<p class="font-medium">Success</p>
34+
<p class="text-sm">@successMessage</p>
35+
</div>
36+
}
37+
38+
@if (!string.IsNullOrWhiteSpace(errorMessage))
39+
{
40+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
41+
<p class="font-medium">Error</p>
42+
<p class="text-sm">@errorMessage</p>
43+
</div>
44+
}
45+
46+
<!-- Form -->
47+
<div class="bg-white rounded-lg shadow-md p-6">
48+
<EditForm Model="@model" OnValidSubmit="@HandleSubmit">
49+
<DataAnnotationsValidator />
50+
51+
<!-- Operator Details Section -->
52+
<div class="mb-6">
53+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Operator Details</h3>
54+
<div class="space-y-4">
55+
<div>
56+
<label class="block text-sm font-medium text-gray-700 mb-1">
57+
Operator Name <span class="text-red-500">*</span>
58+
</label>
59+
<InputText @bind-Value="model.OperatorName" class="input w-full" placeholder="Enter operator name" />
60+
<ValidationMessage For="@(() => model.OperatorName)" class="text-red-600 text-sm mt-1" />
61+
</div>
62+
63+
<div>
64+
<label class="flex items-center space-x-2 cursor-pointer">
65+
<InputCheckbox @bind-Value="model.RequireCustomMerchantNumber" class="w-5 h-5 text-blue-600 rounded focus:ring-blue-500" />
66+
<span class="text-sm font-medium text-gray-700">Require Custom Merchant Number</span>
67+
</label>
68+
<p class="text-sm text-gray-500 mt-1 ml-7">Enable this if the operator requires a custom merchant number for transactions</p>
69+
</div>
70+
71+
<div>
72+
<label class="flex items-center space-x-2 cursor-pointer">
73+
<InputCheckbox @bind-Value="model.RequireCustomTerminalNumber" class="w-5 h-5 text-blue-600 rounded focus:ring-blue-500" />
74+
<span class="text-sm font-medium text-gray-700">Require Custom Terminal Number</span>
75+
</label>
76+
<p class="text-sm text-gray-500 mt-1 ml-7">Enable this if the operator requires a custom terminal number for transactions</p>
77+
</div>
78+
</div>
79+
</div>
80+
81+
<!-- Submit Button -->
82+
<div class="flex justify-end space-x-4 pt-4 border-t">
83+
<button type="button" class="btn btn-secondary" @onclick="BackToList" disabled="@isSaving">
84+
Cancel
85+
</button>
86+
<button type="submit" class="btn btn-primary" disabled="@isSaving">
87+
@if (isSaving)
88+
{
89+
<span class="inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></span>
90+
<span>Saving...</span>
91+
}
92+
else
93+
{
94+
<span>Update Operator</span>
95+
}
96+
</button>
97+
</div>
98+
</EditForm>
99+
</div>
100+
}
101+
else
102+
{
103+
<div class="bg-white rounded-lg shadow-md p-6 text-center">
104+
<p class="text-gray-600">Operator not found</p>
105+
<button class="btn btn-primary mt-4" @onclick="BackToList">Back to List</button>
106+
</div>
107+
}
108+
</div>
109+
110+
@code {
111+
[Parameter]
112+
public Guid OperatorId { get; set; }
113+
114+
private OperatorModel? operatorModel;
115+
private EditOperatorModel model = new();
116+
private bool isLoading = true;
117+
private bool isSaving = false;
118+
private string? errorMessage;
119+
private string? successMessage;
120+
121+
protected override async Task OnInitializedAsync()
122+
{
123+
await LoadOperator();
124+
}
125+
126+
private async Task LoadOperator()
127+
{
128+
try
129+
{
130+
isLoading = true;
131+
var correlationId = new CorrelationId(Guid.NewGuid());
132+
var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111");
133+
var accessToken = "stubbed-token";
134+
135+
var result = await Mediator.Send(new Queries.GetOperatorQuery(correlationId, accessToken, estateId, OperatorId));
136+
137+
if (result.IsSuccess && result.Data != null)
138+
{
139+
operatorModel = result.Data;
140+
141+
// Initialize model with current values
142+
model = new EditOperatorModel
143+
{
144+
OperatorName = operatorModel.Name,
145+
RequireCustomMerchantNumber = operatorModel.RequireCustomMerchantNumber,
146+
RequireCustomTerminalNumber = operatorModel.RequireCustomTerminalNumber
147+
};
148+
}
149+
}
150+
finally
151+
{
152+
isLoading = false;
153+
}
154+
}
155+
156+
private async Task HandleSubmit()
157+
{
158+
isSaving = true;
159+
ClearMessages();
160+
161+
try
162+
{
163+
var correlationId = new CorrelationId(Guid.NewGuid());
164+
var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111");
165+
var accessToken = "stubbed-token";
166+
167+
var command = new Commands.UpdateOperatorCommand(
168+
correlationId,
169+
accessToken,
170+
estateId,
171+
OperatorId,
172+
model.OperatorName!,
173+
model.RequireCustomMerchantNumber,
174+
model.RequireCustomTerminalNumber
175+
);
176+
177+
var result = await Mediator.Send(command);
178+
179+
if (result.IsSuccess)
180+
{
181+
successMessage = "Operator updated successfully";
182+
await LoadOperator();
183+
}
184+
else
185+
{
186+
errorMessage = result.Message ?? "Failed to update operator";
187+
}
188+
}
189+
catch (Exception ex)
190+
{
191+
errorMessage = $"An error occurred: {ex.Message}";
192+
}
193+
finally
194+
{
195+
isSaving = false;
196+
}
197+
}
198+
199+
private void ClearMessages()
200+
{
201+
errorMessage = null;
202+
successMessage = null;
203+
}
204+
205+
private void BackToList()
206+
{
207+
NavigationManager.NavigateTo("/operators");
208+
}
209+
210+
public class EditOperatorModel
211+
{
212+
[Required(ErrorMessage = "Operator name is required")]
213+
public string? OperatorName { get; set; }
214+
215+
public bool RequireCustomMerchantNumber { get; set; }
216+
217+
public bool RequireCustomTerminalNumber { get; set; }
218+
}
219+
}

EstateManagementUI.BlazorServer/Components/Pages/Operators/Index.razor

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/operators"
2+
@rendermode InteractiveServer
23
@inject IMediator Mediator
34
@inject NavigationManager NavigationManager
45

@@ -68,12 +69,19 @@
6869
}
6970
</td>
7071
<td class="text-right">
71-
<button class="text-blue-600 hover:text-blue-800 p-2" @onclick:stopPropagation="true" @onclick='() => NavigationManager.NavigateTo($"/operators/{op.OperatorId}")' title="View">
72-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
73-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
74-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
75-
</svg>
76-
</button>
72+
<div class="flex justify-end space-x-2">
73+
<button class="text-blue-600 hover:text-blue-800 p-2" @onclick:stopPropagation="true" @onclick='() => NavigationManager.NavigateTo($"/operators/{op.OperatorId}")' title="View">
74+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
75+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
76+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
77+
</svg>
78+
</button>
79+
<button class="text-green-600 hover:text-green-800 p-2" @onclick:stopPropagation="true" @onclick='() => NavigationManager.NavigateTo($"/operators/{op.OperatorId}/edit")' title="Edit">
80+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
82+
</svg>
83+
</button>
84+
</div>
7785
</td>
7886
</tr>
7987
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
@page "/operators/new"
2+
@rendermode InteractiveServer
3+
@using System.ComponentModel.DataAnnotations
4+
@inject IMediator Mediator
5+
@inject NavigationManager NavigationManager
6+
7+
<PageTitle>Create New Operator</PageTitle>
8+
9+
<div class="space-y-6">
10+
<!-- Page Header -->
11+
<div class="flex items-center justify-between">
12+
<div>
13+
<h1 class="text-2xl font-bold text-gray-900">Create New Operator</h1>
14+
<p class="text-gray-600 mt-1">Add a new operator to the estate</p>
15+
</div>
16+
<button class="btn btn-secondary" @onclick="Cancel">
17+
Cancel
18+
</button>
19+
</div>
20+
21+
@if (!string.IsNullOrWhiteSpace(errorMessage))
22+
{
23+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
24+
<p class="font-medium">Error</p>
25+
<p class="text-sm">@errorMessage</p>
26+
</div>
27+
}
28+
29+
<!-- Form -->
30+
<div class="bg-white rounded-lg shadow-md p-6">
31+
<EditForm Model="@model" OnValidSubmit="@HandleSubmit">
32+
<DataAnnotationsValidator />
33+
34+
<!-- Operator Details Section -->
35+
<div class="mb-6">
36+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Operator Details</h3>
37+
<div class="space-y-4">
38+
<div>
39+
<label class="block text-sm font-medium text-gray-700 mb-1">
40+
Operator Name <span class="text-red-500">*</span>
41+
</label>
42+
<InputText @bind-Value="model.OperatorName" class="input w-full" placeholder="Enter operator name" />
43+
<ValidationMessage For="@(() => model.OperatorName)" class="text-red-600 text-sm mt-1" />
44+
</div>
45+
46+
<div>
47+
<label class="flex items-center space-x-2 cursor-pointer">
48+
<InputCheckbox @bind-Value="model.RequireCustomMerchantNumber" class="w-5 h-5 text-blue-600 rounded focus:ring-blue-500" />
49+
<span class="text-sm font-medium text-gray-700">Require Custom Merchant Number</span>
50+
</label>
51+
<p class="text-sm text-gray-500 mt-1 ml-7">Enable this if the operator requires a custom merchant number for transactions</p>
52+
</div>
53+
54+
<div>
55+
<label class="flex items-center space-x-2 cursor-pointer">
56+
<InputCheckbox @bind-Value="model.RequireCustomTerminalNumber" class="w-5 h-5 text-blue-600 rounded focus:ring-blue-500" />
57+
<span class="text-sm font-medium text-gray-700">Require Custom Terminal Number</span>
58+
</label>
59+
<p class="text-sm text-gray-500 mt-1 ml-7">Enable this if the operator requires a custom terminal number for transactions</p>
60+
</div>
61+
</div>
62+
</div>
63+
64+
<!-- Submit Button -->
65+
<div class="flex justify-end space-x-4 pt-4 border-t">
66+
<button type="button" class="btn btn-secondary" @onclick="Cancel" disabled="@isSaving">
67+
Cancel
68+
</button>
69+
<button type="submit" class="btn btn-primary" disabled="@isSaving">
70+
@if (isSaving)
71+
{
72+
<span class="inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></span>
73+
<span>Saving...</span>
74+
}
75+
else
76+
{
77+
<span>Create Operator</span>
78+
}
79+
</button>
80+
</div>
81+
</EditForm>
82+
</div>
83+
</div>
84+
85+
@code {
86+
private CreateOperatorModel model = new();
87+
private bool isSaving = false;
88+
private string? errorMessage;
89+
90+
private async Task HandleSubmit()
91+
{
92+
isSaving = true;
93+
errorMessage = null;
94+
95+
try
96+
{
97+
var correlationId = new CorrelationId(Guid.NewGuid());
98+
var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111");
99+
var accessToken = "stubbed-token";
100+
101+
// Create operator
102+
var createCommand = new Commands.CreateOperatorCommand(
103+
correlationId,
104+
accessToken,
105+
estateId,
106+
model.OperatorName!,
107+
model.RequireCustomMerchantNumber,
108+
model.RequireCustomTerminalNumber
109+
);
110+
111+
var createResult = await Mediator.Send(createCommand);
112+
113+
if (!createResult.IsSuccess)
114+
{
115+
errorMessage = createResult.Message ?? "Failed to create operator";
116+
return;
117+
}
118+
119+
// Navigate to operators list with success
120+
NavigationManager.NavigateTo("/operators");
121+
}
122+
catch (Exception ex)
123+
{
124+
errorMessage = $"An error occurred: {ex.Message}";
125+
}
126+
finally
127+
{
128+
isSaving = false;
129+
}
130+
}
131+
132+
private void Cancel()
133+
{
134+
NavigationManager.NavigateTo("/operators");
135+
}
136+
137+
public class CreateOperatorModel
138+
{
139+
[Required(ErrorMessage = "Operator name is required")]
140+
public string? OperatorName { get; set; }
141+
142+
public bool RequireCustomMerchantNumber { get; set; }
143+
144+
public bool RequireCustomTerminalNumber { get; set; }
145+
}
146+
}

0 commit comments

Comments
 (0)