From a77b71e53de77ef02ea8b75156075f34eb57b7ed Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 6 Jan 2026 10:23:36 -0500 Subject: [PATCH] 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 - 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 --- .../JdeScoping.Client/Pages/SearchEdit.razor | 109 ++++++++++++------ 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor b/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor index 97b05a8..d1c39f2 100644 --- a/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor +++ b/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor @@ -1,7 +1,9 @@ @page "/search" @page "/search/{Id:int}" @attribute [Authorize] -@inject ISearchService SearchService +@using JdeScoping.Core.ApiContracts +@using JdeScoping.Client.Extensions +@inject ISearchApiClient SearchApi @inject IHubConnectionService HubConnection @inject IFileService FileService @inject AuthStateProvider AuthStateProvider @@ -25,6 +27,12 @@ { } +else if (!string.IsNullOrEmpty(_errorMessage)) +{ + + @_errorMessage + +} else { @@ -169,6 +177,7 @@ else private bool _isLoading = true; private bool _isSubmitting; + private string? _errorMessage; // Filter visibility flags private bool _showTimespan; @@ -190,25 +199,37 @@ else private async Task LoadSearchAsync() { _isLoading = true; + _errorMessage = null; try { if (CopySearchId.HasValue) { - var copied = await SearchService.CopySearchAsync(CopySearchId.Value); - if (copied != null) - { - _search = copied; - _search.Id = 0; - _search.Status = "New"; - } + var result = await SearchApi.CopySearchAsync(CopySearchId.Value); + result.Switch( + copied => + { + _search = copied.ToClient(); + _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) { - var loaded = await SearchService.GetSearchAsync(Id.Value); - if (loaded != null) - { - _search = loaded; - } + var result = await SearchApi.GetSearchAsync(Id.Value); + result.Switch( + loaded => { _search = loaded.ToClient(); }, + notFound => { _errorMessage = "Search not found."; }, + validation => { _errorMessage = FormatValidationErrors(validation.FieldErrors); }, + unauthorized => { _errorMessage = "Session expired."; }, + forbidden => { _errorMessage = "Access denied."; }, + error => { _errorMessage = error.Message; } + ); } else { @@ -221,8 +242,11 @@ else }; } - // Detect search type from criteria - DetectSearchType(); + // Detect search type from criteria (only if no error) + if (string.IsNullOrEmpty(_errorMessage)) + { + DetectSearchType(); + } } finally { @@ -230,6 +254,12 @@ else } } + private static string FormatValidationErrors(IReadOnlyDictionary fieldErrors) + { + var messages = fieldErrors.SelectMany(kv => kv.Value); + return string.Join(" ", messages); + } + private void DetectSearchType() { var criteria = _search.Criteria; @@ -364,15 +394,15 @@ else _isSubmitting = true; try { - var id = await SearchService.SaveSearchAsync(_search); - if (id.HasValue) - { - NavigationManager.NavigateTo($"/search/{id}"); - } - else - { - NotificationService.Notify(NotificationSeverity.Error, "Error", "Failed to submit search."); - } + var result = await SearchApi.CreateSearchAsync(_search.ToCore()); + result.Switch( + id => { NavigationManager.NavigateTo($"/search/{id}"); }, + notFound => { NotificationService.Notify(NotificationSeverity.Error, "Error", "Search not found."); }, + validation => { NotificationService.Notify(NotificationSeverity.Error, "Validation 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); } + ); } finally { @@ -413,17 +443,26 @@ else private async Task DownloadResultsAsync() { - var results = await SearchService.DownloadResultsAsync(_search.Id); - if (results != null && results.Length > 0) - { - // Trigger download via JS interop - await JSRuntime.InvokeVoidAsync("downloadFile", $"search_results_{_search.Id}.xlsx", results); - NotificationService.Notify(NotificationSeverity.Success, "Download", "Results downloaded successfully."); - } - else - { - NotificationService.Notify(NotificationSeverity.Warning, "Download", "No results available to download."); - } + var result = await SearchApi.GetResultsAsync(_search.Id); + result.Switch( + bytes => + { + if (bytes.Length > 0) + { + _ = JSRuntime.InvokeVoidAsync("downloadFile", $"search_results_{_search.Id}.xlsx", bytes); + NotificationService.Notify(NotificationSeverity.Success, "Download", "Results downloaded successfully."); + } + 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()