feat(client): migrate SearchEdit.razor to ISearchApiClient
- Replace ISearchService with ISearchApiClient - Add @using for JdeScoping.Core.ApiContracts and JdeScoping.Client.Extensions - Update LoadSearchAsync to use result.Switch() pattern with ApiResult<T> - Handle CopySearchId, Id, and new search cases with proper error handling - Use ToClient() extension method to convert Core to Client SearchViewModel - Add _errorMessage field and display error alert in UI - Update SubmitSearchInternalAsync and DownloadResultsAsync for consistency - Add FormatValidationErrors helper for ValidationError.FieldErrors
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
@page "/search"
|
@page "/search"
|
||||||
@page "/search/{Id:int}"
|
@page "/search/{Id:int}"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject ISearchService SearchService
|
@using JdeScoping.Core.ApiContracts
|
||||||
|
@using JdeScoping.Client.Extensions
|
||||||
|
@inject ISearchApiClient SearchApi
|
||||||
@inject IHubConnectionService HubConnection
|
@inject IHubConnectionService HubConnection
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject AuthStateProvider AuthStateProvider
|
@inject AuthStateProvider AuthStateProvider
|
||||||
@@ -25,6 +27,12 @@
|
|||||||
{
|
{
|
||||||
<LoadingIndicator Message="Loading search..." />
|
<LoadingIndicator Message="Loading search..." />
|
||||||
}
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(_errorMessage))
|
||||||
|
{
|
||||||
|
<RadzenAlert AlertStyle="AlertStyle.Danger" ShowIcon="true" Variant="Variant.Flat" class="rz-mb-4">
|
||||||
|
@_errorMessage
|
||||||
|
</RadzenAlert>
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<EditForm Model="@_search" OnValidSubmit="@HandleValidSubmit">
|
<EditForm Model="@_search" OnValidSubmit="@HandleValidSubmit">
|
||||||
@@ -169,6 +177,7 @@ else
|
|||||||
|
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
|
private string? _errorMessage;
|
||||||
|
|
||||||
// Filter visibility flags
|
// Filter visibility flags
|
||||||
private bool _showTimespan;
|
private bool _showTimespan;
|
||||||
@@ -190,25 +199,37 @@ else
|
|||||||
private async Task LoadSearchAsync()
|
private async Task LoadSearchAsync()
|
||||||
{
|
{
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (CopySearchId.HasValue)
|
if (CopySearchId.HasValue)
|
||||||
{
|
{
|
||||||
var copied = await SearchService.CopySearchAsync(CopySearchId.Value);
|
var result = await SearchApi.CopySearchAsync(CopySearchId.Value);
|
||||||
if (copied != null)
|
result.Switch(
|
||||||
{
|
copied =>
|
||||||
_search = copied;
|
{
|
||||||
_search.Id = 0;
|
_search = copied.ToClient();
|
||||||
_search.Status = "New";
|
_search.Id = 0;
|
||||||
}
|
_search.Status = "New";
|
||||||
|
},
|
||||||
|
notFound => { _errorMessage = "Search to copy not found."; },
|
||||||
|
validation => { _errorMessage = FormatValidationErrors(validation.FieldErrors); },
|
||||||
|
unauthorized => { _errorMessage = "Session expired."; },
|
||||||
|
forbidden => { _errorMessage = "Access denied."; },
|
||||||
|
error => { _errorMessage = error.Message; }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else if (Id.HasValue && Id.Value > 0)
|
else if (Id.HasValue && Id.Value > 0)
|
||||||
{
|
{
|
||||||
var loaded = await SearchService.GetSearchAsync(Id.Value);
|
var result = await SearchApi.GetSearchAsync(Id.Value);
|
||||||
if (loaded != null)
|
result.Switch(
|
||||||
{
|
loaded => { _search = loaded.ToClient(); },
|
||||||
_search = loaded;
|
notFound => { _errorMessage = "Search not found."; },
|
||||||
}
|
validation => { _errorMessage = FormatValidationErrors(validation.FieldErrors); },
|
||||||
|
unauthorized => { _errorMessage = "Session expired."; },
|
||||||
|
forbidden => { _errorMessage = "Access denied."; },
|
||||||
|
error => { _errorMessage = error.Message; }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -221,8 +242,11 @@ else
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect search type from criteria
|
// Detect search type from criteria (only if no error)
|
||||||
DetectSearchType();
|
if (string.IsNullOrEmpty(_errorMessage))
|
||||||
|
{
|
||||||
|
DetectSearchType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -230,6 +254,12 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatValidationErrors(IReadOnlyDictionary<string, string[]> fieldErrors)
|
||||||
|
{
|
||||||
|
var messages = fieldErrors.SelectMany(kv => kv.Value);
|
||||||
|
return string.Join(" ", messages);
|
||||||
|
}
|
||||||
|
|
||||||
private void DetectSearchType()
|
private void DetectSearchType()
|
||||||
{
|
{
|
||||||
var criteria = _search.Criteria;
|
var criteria = _search.Criteria;
|
||||||
@@ -364,15 +394,15 @@ else
|
|||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var id = await SearchService.SaveSearchAsync(_search);
|
var result = await SearchApi.CreateSearchAsync(_search.ToCore());
|
||||||
if (id.HasValue)
|
result.Switch(
|
||||||
{
|
id => { NavigationManager.NavigateTo($"/search/{id}"); },
|
||||||
NavigationManager.NavigateTo($"/search/{id}");
|
notFound => { NotificationService.Notify(NotificationSeverity.Error, "Error", "Search not found."); },
|
||||||
}
|
validation => { NotificationService.Notify(NotificationSeverity.Error, "Validation Error", FormatValidationErrors(validation.FieldErrors)); },
|
||||||
else
|
unauthorized => { NotificationService.Notify(NotificationSeverity.Error, "Error", "Session expired."); },
|
||||||
{
|
forbidden => { NotificationService.Notify(NotificationSeverity.Error, "Error", "Access denied."); },
|
||||||
NotificationService.Notify(NotificationSeverity.Error, "Error", "Failed to submit search.");
|
error => { NotificationService.Notify(NotificationSeverity.Error, "Error", error.Message); }
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -413,17 +443,26 @@ else
|
|||||||
|
|
||||||
private async Task DownloadResultsAsync()
|
private async Task DownloadResultsAsync()
|
||||||
{
|
{
|
||||||
var results = await SearchService.DownloadResultsAsync(_search.Id);
|
var result = await SearchApi.GetResultsAsync(_search.Id);
|
||||||
if (results != null && results.Length > 0)
|
result.Switch(
|
||||||
{
|
bytes =>
|
||||||
// Trigger download via JS interop
|
{
|
||||||
await JSRuntime.InvokeVoidAsync("downloadFile", $"search_results_{_search.Id}.xlsx", results);
|
if (bytes.Length > 0)
|
||||||
NotificationService.Notify(NotificationSeverity.Success, "Download", "Results downloaded successfully.");
|
{
|
||||||
}
|
_ = JSRuntime.InvokeVoidAsync("downloadFile", $"search_results_{_search.Id}.xlsx", bytes);
|
||||||
else
|
NotificationService.Notify(NotificationSeverity.Success, "Download", "Results downloaded successfully.");
|
||||||
{
|
}
|
||||||
NotificationService.Notify(NotificationSeverity.Warning, "Download", "No results available to download.");
|
else
|
||||||
}
|
{
|
||||||
|
NotificationService.Notify(NotificationSeverity.Warning, "Download", "No results available to download.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notFound => { NotificationService.Notify(NotificationSeverity.Warning, "Download", "Results not found."); },
|
||||||
|
validation => { NotificationService.Notify(NotificationSeverity.Error, "Error", FormatValidationErrors(validation.FieldErrors)); },
|
||||||
|
unauthorized => { NotificationService.Notify(NotificationSeverity.Error, "Error", "Session expired."); },
|
||||||
|
forbidden => { NotificationService.Notify(NotificationSeverity.Error, "Error", "Access denied."); },
|
||||||
|
error => { NotificationService.Notify(NotificationSeverity.Error, "Error", error.Message); }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
Reference in New Issue
Block a user