@*
SearchEdit.razor - Main search creation and editing page.
Handles creating new searches, editing existing drafts, and viewing completed searches.
Integrates with SignalR for real-time status updates during search execution.
*@
@page "/search"
@page "/search/{Id:int}"
@attribute [Authorize]
@using JdeScoping.Core.ApiContracts
@using JdeScoping.Client.Extensions
@using JdeScoping.Client.Auth
@using JdeScoping.Client.Components.Search
@using Microsoft.JSInterop
@inject ISearchApiClient SearchApi
@inject IHubConnectionService HubConnection
@inject IAuthStateProvider AuthStateProvider
@inject ISearchValidationService ValidationService
@inject ISearchSubmissionService SubmissionService
@inject NavigationManager NavigationManager
@inject DialogService DialogService
@inject NotificationService NotificationService
@inject IJSRuntime JSRuntime
@implements IDisposable
@(_search.Id == 0 ? "New Search" : "Search") - JDE Scoping Tool
Search
@if (!_search.IsReadOnly)
{
}
@if (_isLoading)
{
}
else if (!string.IsNullOrEmpty(_errorMessage))
{
@_errorMessage
}
else
{
@if (_search.IsReadOnly)
{
Note: Search is read-only because it has already been submitted. To change or re-run the search again click the Copy button.
}
@if (_visibilityManager.ShowTimespan)
{
}
@if (_visibilityManager.ShowWorkOrder)
{
}
@if (_visibilityManager.ShowItemNumber)
{
}
@if (_visibilityManager.ShowProfitCenter)
{
}
@if (_visibilityManager.ShowWorkCenter)
{
}
@if (_visibilityManager.ShowComponentLot)
{
}
@if (_visibilityManager.ShowOperator)
{
}
@if (_visibilityManager.ShowItemOperationMis)
{
}
@if (_visibilityManager.ShowExtractMis)
{
Extract MIS data
}
}
@code {
[Parameter]
public int? Id { get; set; }
[SupplyParameterFromQuery(Name = "copySearchId")]
public int? CopySearchId { get; set; }
private ClientSearchViewModel _search = new() { Criteria = new() };
private FilterVisibilityManager _visibilityManager = null!;
private bool _isLoading = true;
private bool _isSubmitting;
private string? _errorMessage;
protected override async Task OnInitializedAsync()
{
await LoadSearchAsync();
}
private async Task LoadSearchAsync()
{
_isLoading = true;
_errorMessage = null;
try
{
if (CopySearchId.HasValue)
{
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 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
{
_search = new ClientSearchViewModel
{
Status = "New",
UserName = await AuthStateProvider.GetUsernameAsync() ?? "",
Criteria = new SearchCriteriaViewModel()
};
}
}
finally
{
_isLoading = false;
}
}
private static string FormatValidationErrors(IReadOnlyDictionary fieldErrors)
{
var messages = fieldErrors.SelectMany(kv => kv.Value);
return string.Join(" ", messages);
}
private void OnSearchTypeChanged(ValidCombination? combo)
{
StateHasChanged();
}
private void HandleSearchUpdate(SearchUpdateViewModel update)
{
_search.Status = update.Status;
_search.SubmitDt = update.SubmitDt;
_search.StartDt = update.StartDt;
_search.EndDt = update.EndDt;
StateHasChanged();
}
private async Task HandleValidSubmit()
{
var validationError = ValidationService.Validate(_search, _visibilityManager.SelectedSearchType, _visibilityManager);
if (!string.IsNullOrEmpty(validationError))
{
NotificationService.Notify(NotificationSeverity.Error, "Validation Error", validationError);
return;
}
await SubmitSearchInternalAsync();
}
private async Task SubmitSearchAsync()
{
var validationError = ValidationService.Validate(_search, _visibilityManager.SelectedSearchType, _visibilityManager);
if (!string.IsNullOrEmpty(validationError))
{
NotificationService.Notify(NotificationSeverity.Error, "Validation Error", validationError);
return;
}
await SubmitSearchInternalAsync();
}
private async Task SubmitSearchInternalAsync()
{
var confirmed = await DialogService.Confirm("Are you sure you want to submit the search?", "Confirm Submit", new ConfirmOptions
{
OkButtonText = "Submit",
CancelButtonText = "Cancel"
});
if (confirmed != true)
{
return;
}
_isSubmitting = true;
try
{
var result = await SubmissionService.SubmitAsync(_search);
if (result.IsSuccess)
{
NavigationManager.NavigateTo($"/search/{result.SearchId}");
}
else
{
NotificationService.Notify(NotificationSeverity.Error, "Error", result.ErrorMessage);
}
}
finally
{
_isSubmitting = false;
}
}
private void CopySearchAsync()
{
NavigationManager.NavigateTo($"/search?copySearchId={_search.Id}");
}
private async Task DownloadResultsAsync()
{
var result = await SearchApi.GetResultsAsync(_search.Id);
result.Switch(
bytes =>
{
if (bytes.Length > 0)
{
_ = Task.Run(async () =>
{
try
{
await JSRuntime.InvokeVoidAsync("downloadFile", $"search_results_{_search.Id}.xlsx", bytes);
}
catch (JSException ex)
{
Console.WriteLine($"JS interop failed during file download: {ex.Message}");
}
});
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()
{
// SignalRStatusHandler handles its own disposal
}
}