313 lines
12 KiB
Plaintext
313 lines
12 KiB
Plaintext
@*
|
|
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
|
|
|
|
<PageTitle>@(_search.Id == 0 ? "New Search" : "Search") - JDE Scoping Tool</PageTitle>
|
|
|
|
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.SpaceBetween" class="rz-mb-4">
|
|
<RadzenText TextStyle="TextStyle.H4" class="rz-m-0">Search</RadzenText>
|
|
@if (!_search.IsReadOnly)
|
|
{
|
|
<RadzenButton Text="Submit" Icon="send" ButtonStyle="ButtonStyle.Primary" Click="@SubmitSearchAsync" IsBusy="@_isSubmitting" BusyText="Submitting..." class="btn-submit-blue" />
|
|
}
|
|
</RadzenStack>
|
|
|
|
@if (_isLoading)
|
|
{
|
|
<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
|
|
{
|
|
<SignalRStatusHandler SearchId="@_search.Id" OnStatusChanged="HandleSearchUpdate" />
|
|
|
|
<FilterVisibilityManager @ref="_visibilityManager" Criteria="@_search.Criteria" OnSearchTypeChanged="OnSearchTypeChanged">
|
|
<EditForm Model="@_search" OnValidSubmit="@HandleValidSubmit">
|
|
<DataAnnotationsValidator />
|
|
|
|
@if (_search.IsReadOnly)
|
|
{
|
|
<RadzenAlert AlertStyle="AlertStyle.Warning" ShowIcon="true" Variant="Variant.Flat" class="rz-mb-4">
|
|
<strong>Note:</strong> Search is read-only because it has already been submitted. To change or re-run the search again click the Copy button.
|
|
<RadzenButton Text="Copy" ButtonStyle="ButtonStyle.Secondary" Size="ButtonSize.Small" Click="@CopySearchAsync" class="rz-ml-2" />
|
|
</RadzenAlert>
|
|
}
|
|
|
|
<ValidationSummary class="rz-mb-4" />
|
|
|
|
<SearchDetailsSection
|
|
Search="@_search"
|
|
@bind-SelectedSearchType="@_visibilityManager.SelectedSearchType"
|
|
ValidCombinations="@_visibilityManager.ValidCombinations"
|
|
OnDownloadResults="@DownloadResultsAsync" />
|
|
|
|
@if (_visibilityManager.ShowTimespan)
|
|
{
|
|
<TimeSpanFilterPanel @bind-MinimumDT="_search.Criteria.MinimumDt" @bind-MaximumDT="_search.Criteria.MaximumDt" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowWorkOrder)
|
|
{
|
|
<WorkOrderFilterPanel @bind-WorkOrders="_search.Criteria.WorkOrders" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowItemNumber)
|
|
{
|
|
<ItemNumberFilterPanel @bind-Items="_search.Criteria.Items" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowProfitCenter)
|
|
{
|
|
<ProfitCenterFilterPanel @bind-ProfitCenters="_search.Criteria.ProfitCenters" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowWorkCenter)
|
|
{
|
|
<WorkCenterFilterPanel @bind-WorkCenters="_search.Criteria.WorkCenters" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowComponentLot)
|
|
{
|
|
<ComponentLotFilterPanel @bind-ComponentLots="_search.Criteria.ComponentLots" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowOperator)
|
|
{
|
|
<OperatorFilterPanel @bind-Operators="_search.Criteria.Operators" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowItemOperationMis)
|
|
{
|
|
<PartOperationFilterPanel @bind-PartOperations="_search.Criteria.PartOperations" IsReadOnly="@_search.IsReadOnly" />
|
|
}
|
|
|
|
@if (_visibilityManager.ShowExtractMis)
|
|
{
|
|
<RadzenCard class="rz-mb-4">
|
|
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem">
|
|
<RadzenCheckBox @bind-Value="_search.Criteria.ExtractMisData" Disabled="true" />
|
|
<RadzenText TextStyle="TextStyle.Body1">Extract MIS data</RadzenText>
|
|
</RadzenStack>
|
|
</RadzenCard>
|
|
}
|
|
</EditForm>
|
|
</FilterVisibilityManager>
|
|
}
|
|
|
|
@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<string, string[]> 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
|
|
}
|
|
}
|