Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using System.Net.Http.Json;
|
||||
using JdeScoping.Client.Auth;
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles authentication via API calls with cookie-based auth.
|
||||
/// </summary>
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AuthStateProvider _authStateProvider;
|
||||
|
||||
public AuthService(
|
||||
HttpClient httpClient,
|
||||
AuthStateProvider authStateProvider)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_authStateProvider = authStateProvider;
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LoginAsync(LoginModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync("api/auth/login", new
|
||||
{
|
||||
model.Username,
|
||||
model.Password
|
||||
});
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
// API returns UserInfo and sets auth cookie
|
||||
var userInfo = await response.Content.ReadFromJsonAsync<UserInfoViewModel>();
|
||||
if (userInfo != null)
|
||||
{
|
||||
// Notify auth state provider of the login
|
||||
await _authStateProvider.MarkUserAsAuthenticated(userInfo);
|
||||
|
||||
return new AuthResult
|
||||
{
|
||||
Success = true,
|
||||
User = userInfo
|
||||
};
|
||||
}
|
||||
|
||||
return new AuthResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "Invalid response from server"
|
||||
};
|
||||
}
|
||||
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
return new AuthResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = string.IsNullOrEmpty(errorContent)
|
||||
? "Login failed. Please check your credentials."
|
||||
: errorContent
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new AuthResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = $"Login failed: {ex.Message}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Call logout endpoint to clear server-side cookie
|
||||
await _httpClient.PostAsync("api/auth/logout", null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Even if logout API fails, clear local state
|
||||
}
|
||||
|
||||
await _authStateProvider.LogoutAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Net.Http.Json;
|
||||
using JdeScoping.Client.Models;
|
||||
using JdeScoping.Core.ViewModels;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles file upload/download operations via the api/fileio endpoints.
|
||||
/// Authentication is handled via cookies (sent automatically by the browser).
|
||||
/// </summary>
|
||||
public class FileService : IFileService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
|
||||
public FileService(HttpClient httpClient, IJSRuntime jsRuntime)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task DownloadTemplateAsync(string templateType, object? existingData = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Map template type to API endpoint
|
||||
var endpoint = templateType switch
|
||||
{
|
||||
"work-orders" or "workorders" => "api/fileio/workorders/download",
|
||||
"items" or "part-numbers" => "api/fileio/items/download",
|
||||
"component-lots" or "componentlots" => "api/fileio/componentlots/download",
|
||||
"part-operations" or "partoperations" => "api/fileio/partoperations/download",
|
||||
_ => throw new ArgumentException($"Unknown template type: {templateType}")
|
||||
};
|
||||
|
||||
var fileName = templateType switch
|
||||
{
|
||||
"work-orders" or "workorders" => "work_order_template.xlsx",
|
||||
"items" or "part-numbers" => "item_number_template.xlsx",
|
||||
"component-lots" or "componentlots" => "component_lot_template.xlsx",
|
||||
"part-operations" or "partoperations" => "item_operations_mis_template.xlsx",
|
||||
_ => $"{templateType}_template.xlsx"
|
||||
};
|
||||
|
||||
// POST with existing data to get the Excel file
|
||||
var response = await _httpClient.PostAsJsonAsync(endpoint, existingData);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
await _jsRuntime.InvokeVoidAsync("downloadFile", fileName, bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Failed to download template: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to download template: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DownloadPartNumberTemplateAsync(List<ItemViewModel>? existingItems = null)
|
||||
{
|
||||
await DownloadTemplateAsync("items", existingItems);
|
||||
}
|
||||
|
||||
public async Task<UploadResult<T>> UploadAsync<T>(string uploadType, Stream fileStream, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Map upload type to API endpoint
|
||||
var endpoint = uploadType switch
|
||||
{
|
||||
"work-orders" or "workorders" => "api/fileio/workorders/upload",
|
||||
"items" or "part-numbers" => "api/fileio/items/upload",
|
||||
"component-lots" or "componentlots" => "api/fileio/componentlots/upload",
|
||||
"part-operations" or "partoperations" => "api/fileio/partoperations/upload",
|
||||
_ => throw new ArgumentException($"Unknown upload type: {uploadType}")
|
||||
};
|
||||
|
||||
using var content = new MultipartFormDataContent();
|
||||
using var streamContent = new StreamContent(fileStream);
|
||||
content.Add(streamContent, "file", fileName);
|
||||
|
||||
var response = await _httpClient.PostAsync(endpoint, content);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var result = await response.Content.ReadFromJsonAsync<UploadResult<T>>();
|
||||
return result ?? new UploadResult<T>
|
||||
{
|
||||
WasSuccessful = false,
|
||||
ErrorMessage = "Invalid response from server"
|
||||
};
|
||||
}
|
||||
|
||||
return new UploadResult<T>
|
||||
{
|
||||
WasSuccessful = false,
|
||||
ErrorMessage = $"Upload failed: {response.StatusCode}"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new UploadResult<T>
|
||||
{
|
||||
WasSuccessful = false,
|
||||
ErrorMessage = $"Upload failed: {ex.Message}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using JdeScoping.Client.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Manages SignalR connection with auto-reconnect.
|
||||
/// Uses cookie-based authentication (browser automatically sends cookies with requests).
|
||||
/// </summary>
|
||||
public class HubConnectionService : IHubConnectionService, IAsyncDisposable
|
||||
{
|
||||
private readonly NavigationManager _navigationManager;
|
||||
private HubConnection? _hubConnection;
|
||||
|
||||
public event Action<SearchUpdate>? OnSearchUpdate;
|
||||
public event Action<StatusUpdate>? OnStatusUpdate;
|
||||
|
||||
public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected;
|
||||
|
||||
public HubConnectionService(NavigationManager navigationManager)
|
||||
{
|
||||
_navigationManager = navigationManager;
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (_hubConnection != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In Blazor WebAssembly, the browser automatically sends cookies with requests
|
||||
// to the same origin, so we don't need to configure any special auth options
|
||||
_hubConnection = new HubConnectionBuilder()
|
||||
.WithUrl(_navigationManager.ToAbsoluteUri("/hubs/status"))
|
||||
.WithAutomaticReconnect([
|
||||
TimeSpan.Zero,
|
||||
TimeSpan.FromSeconds(2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
])
|
||||
.Build();
|
||||
|
||||
_hubConnection.On<SearchUpdate>("searchUpdate", update =>
|
||||
{
|
||||
OnSearchUpdate?.Invoke(update);
|
||||
});
|
||||
|
||||
_hubConnection.On<StatusUpdate>("statusUpdate", update =>
|
||||
{
|
||||
OnStatusUpdate?.Invoke(update);
|
||||
});
|
||||
|
||||
_hubConnection.Reconnecting += error =>
|
||||
{
|
||||
Console.WriteLine($"SignalR reconnecting: {error?.Message}");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_hubConnection.Reconnected += connectionId =>
|
||||
{
|
||||
Console.WriteLine($"SignalR reconnected: {connectionId}");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_hubConnection.Closed += error =>
|
||||
{
|
||||
Console.WriteLine($"SignalR closed: {error?.Message}");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await _hubConnection.StartAsync();
|
||||
Console.WriteLine("SignalR connected");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"SignalR connection failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (_hubConnection != null)
|
||||
{
|
||||
await _hubConnection.StopAsync();
|
||||
await _hubConnection.DisposeAsync();
|
||||
_hubConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StatusUpdate?> GetCachedStatusAsync()
|
||||
{
|
||||
if (_hubConnection == null || _hubConnection.State != HubConnectionState.Connected)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await _hubConnection.InvokeAsync<StatusUpdate>("GetCachedStatus");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to get cached status: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for authentication operations.
|
||||
/// </summary>
|
||||
public interface IAuthService
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to log in with the provided credentials.
|
||||
/// </summary>
|
||||
Task<AuthResult> LoginAsync(LoginModel model);
|
||||
|
||||
/// <summary>
|
||||
/// Logs out the current user.
|
||||
/// </summary>
|
||||
Task LogoutAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of an authentication attempt.
|
||||
/// </summary>
|
||||
public record AuthResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
public UserInfoViewModel? User { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using JdeScoping.Client.Models;
|
||||
using JdeScoping.Core.ViewModels;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for file upload/download operations.
|
||||
/// </summary>
|
||||
public interface IFileService
|
||||
{
|
||||
/// <summary>
|
||||
/// Downloads the work order template file.
|
||||
/// </summary>
|
||||
Task DownloadTemplateAsync(string templateType, object? existingData = null);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the part number template file.
|
||||
/// </summary>
|
||||
Task DownloadPartNumberTemplateAsync(List<ItemViewModel>? existingItems = null);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a file and returns parsed data.
|
||||
/// </summary>
|
||||
Task<UploadResult<T>> UploadAsync<T>(string uploadType, Stream fileStream, string fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a file upload operation.
|
||||
/// </summary>
|
||||
public class UploadResult<T>
|
||||
{
|
||||
public bool WasSuccessful { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public List<T> Data { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing SignalR hub connection.
|
||||
/// </summary>
|
||||
public interface IHubConnectionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired when a search update is received.
|
||||
/// </summary>
|
||||
event Action<SearchUpdate>? OnSearchUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a processor status update is received.
|
||||
/// </summary>
|
||||
event Action<StatusUpdate>? OnStatusUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the SignalR connection.
|
||||
/// </summary>
|
||||
Task StartAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Stops the SignalR connection.
|
||||
/// </summary>
|
||||
Task StopAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cached processor status from the server.
|
||||
/// </summary>
|
||||
Task<StatusUpdate?> GetCachedStatusAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current connection state.
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using JdeScoping.Client.Models;
|
||||
using JdeScoping.Core.ViewModels;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for lookup/autocomplete API operations.
|
||||
/// </summary>
|
||||
public interface ILookupService
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds items matching the search term.
|
||||
/// </summary>
|
||||
Task<List<ItemViewModel>> FindItemsAsync(string searchTerm);
|
||||
|
||||
/// <summary>
|
||||
/// Finds profit centers matching the search term.
|
||||
/// </summary>
|
||||
Task<List<ProfitCenterViewModel>> FindProfitCentersAsync(string searchTerm);
|
||||
|
||||
/// <summary>
|
||||
/// Finds work centers matching the search term.
|
||||
/// </summary>
|
||||
Task<List<WorkCenterViewModel>> FindWorkCentersAsync(string searchTerm);
|
||||
|
||||
/// <summary>
|
||||
/// Finds operators matching the search term.
|
||||
/// </summary>
|
||||
Task<List<OperatorViewModel>> FindOperatorsAsync(string searchTerm);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for data refresh status API operations.
|
||||
/// </summary>
|
||||
public interface IRefreshStatusService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets refresh status records within the specified date range.
|
||||
/// </summary>
|
||||
Task<List<DataUpdateViewModel>> GetRefreshStatusAsync(DateTime minDt, DateTime maxDt);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for search-related API operations.
|
||||
/// </summary>
|
||||
public interface ISearchService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all searches for the current user.
|
||||
/// </summary>
|
||||
Task<List<SearchViewModel>> GetUserSearchesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific search by ID.
|
||||
/// </summary>
|
||||
Task<SearchViewModel?> GetSearchAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Copies an existing search to create a new one.
|
||||
/// </summary>
|
||||
Task<SearchViewModel?> CopySearchAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Saves and submits a search.
|
||||
/// </summary>
|
||||
Task<int?> SaveSearchAsync(SearchViewModel search);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all searches in the queue.
|
||||
/// </summary>
|
||||
Task<List<SearchViewModel>> GetQueueAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the results for a completed search.
|
||||
/// </summary>
|
||||
Task<byte[]?> DownloadResultsAsync(int id);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Net.Http.Json;
|
||||
using JdeScoping.Client.Models;
|
||||
using JdeScoping.Core.ViewModels;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles lookup/autocomplete API calls.
|
||||
/// Authentication is handled via cookies (sent automatically by the browser).
|
||||
/// </summary>
|
||||
public class LookupService : ILookupService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public LookupService(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<List<ItemViewModel>> FindItemsAsync(string searchTerm)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchTerm) || searchTerm.Length < 3)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _httpClient.GetFromJsonAsync<List<ItemViewModel>>(
|
||||
$"api/lookup/items?q={Uri.EscapeDataString(searchTerm)}");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to find items: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<ProfitCenterViewModel>> FindProfitCentersAsync(string searchTerm)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchTerm) || searchTerm.Length < 3)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _httpClient.GetFromJsonAsync<List<ProfitCenterViewModel>>(
|
||||
$"api/lookup/profit-centers?q={Uri.EscapeDataString(searchTerm)}");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to find profit centers: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<WorkCenterViewModel>> FindWorkCentersAsync(string searchTerm)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchTerm) || searchTerm.Length < 3)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _httpClient.GetFromJsonAsync<List<WorkCenterViewModel>>(
|
||||
$"api/lookup/work-centers?q={Uri.EscapeDataString(searchTerm)}");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to find work centers: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<OperatorViewModel>> FindOperatorsAsync(string searchTerm)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchTerm) || searchTerm.Length < 3)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _httpClient.GetFromJsonAsync<List<OperatorViewModel>>(
|
||||
$"api/lookup/operators?q={Uri.EscapeDataString(searchTerm)}");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to find operators: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Net.Http.Json;
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles refresh status API calls.
|
||||
/// Authentication is handled via cookies (sent automatically by the browser).
|
||||
/// </summary>
|
||||
public class RefreshStatusService : IRefreshStatusService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public RefreshStatusService(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<List<DataUpdateViewModel>> GetRefreshStatusAsync(DateTime minDt, DateTime maxDt)
|
||||
{
|
||||
try
|
||||
{
|
||||
var minDtStr = minDt.ToString("yyyy-MM-dd");
|
||||
var maxDtStr = maxDt.ToString("yyyy-MM-dd");
|
||||
var result = await _httpClient.GetFromJsonAsync<List<DataUpdateViewModel>>(
|
||||
$"api/refresh-status?minDT={minDtStr}&maxDT={maxDtStr}");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to get refresh status: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Net.Http.Json;
|
||||
using JdeScoping.Client.Models;
|
||||
|
||||
namespace JdeScoping.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles search-related API calls.
|
||||
/// Authentication is handled via cookies (sent automatically by the browser).
|
||||
/// </summary>
|
||||
public class SearchService : ISearchService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public SearchService(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<List<SearchViewModel>> GetUserSearchesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _httpClient.GetFromJsonAsync<List<SearchViewModel>>("api/search");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to get searches: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SearchViewModel?> GetSearchAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _httpClient.GetFromJsonAsync<SearchViewModel>($"api/search/{id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to get search {id}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SearchViewModel?> CopySearchAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _httpClient.GetFromJsonAsync<SearchViewModel>($"api/search/{id}/copy");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to copy search {id}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int?> SaveSearchAsync(SearchViewModel search)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync("api/search", new
|
||||
{
|
||||
search.Name,
|
||||
search.UserName,
|
||||
Criteria = new
|
||||
{
|
||||
MinimumDT = search.Criteria.MinimumDt,
|
||||
MaximumDT = search.Criteria.MaximumDt,
|
||||
WorkOrders = search.Criteria.WorkOrders.Select(wo => new { wo.WorkOrderNumber }),
|
||||
Items = search.Criteria.Items.Select(i => new { i.ItemNumber }),
|
||||
ProfitCenters = search.Criteria.ProfitCenters.Select(pc => new { pc.Code }),
|
||||
WorkCenters = search.Criteria.WorkCenters.Select(wc => new { wc.Code }),
|
||||
ComponentLots = search.Criteria.ComponentLots.Select(cl => new { cl.LotNumber, cl.ItemNumber }),
|
||||
Operators = search.Criteria.Operators.Select(op => new { op.AddressNumber, UserID = op.UserId }),
|
||||
PartOperations = search.Criteria.PartOperations.Select(po => new
|
||||
{
|
||||
po.ItemNumber,
|
||||
po.OperationNumber,
|
||||
po.MisNumber,
|
||||
po.MisRevision
|
||||
}),
|
||||
search.Criteria.ExtractMisData
|
||||
}
|
||||
});
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.ReadFromJsonAsync<int>();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Failed to save search: {response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to save search: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SearchViewModel>> GetQueueAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _httpClient.GetFromJsonAsync<List<SearchViewModel>>("api/search/queue");
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to get queue: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]?> DownloadResultsAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _httpClient.GetByteArrayAsync($"api/search/{id}/results");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to download results for {id}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user