UI refactor (structure cleanup)
Some checks failed
Build Docker Images / test (map[name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Failing after 1m18s
Build Docker Images / test (map[name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Failing after 1m18s
Build Docker Images / build-and-push (map[image_suffix:morska name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Failing after 1m38s
Build Docker Images / build-and-push (map[image_suffix:pedrollopl name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Failing after 1m37s
Some checks failed
Build Docker Images / test (map[name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Failing after 1m18s
Build Docker Images / test (map[name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Failing after 1m18s
Build Docker Images / build-and-push (map[image_suffix:morska name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Failing after 1m38s
Build Docker Images / build-and-push (map[image_suffix:pedrollopl name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Failing after 1m37s
This commit is contained in:
360
DiunaBI.UI.Shared/Pages/Layers/Details.razor
Normal file
360
DiunaBI.UI.Shared/Pages/Layers/Details.razor
Normal file
@@ -0,0 +1,360 @@
|
||||
@page "/layers/{id:guid}"
|
||||
@using DiunaBI.UI.Shared.Services
|
||||
@using DiunaBI.Application.DTOModels
|
||||
@using MudBlazor
|
||||
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">Layer Details</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
@if (layer != null && layer.Type == LayerType.Administration && IsWorkerLayer())
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="RunNow"
|
||||
Disabled="isRunningJob"
|
||||
StartIcon="@Icons.Material.Filled.PlayArrow">
|
||||
@if (isRunningJob)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true"/>
|
||||
<span style="margin-left: 8px;">Creating Job...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Run Now</span>
|
||||
}
|
||||
</MudButton>
|
||||
}
|
||||
<MudButton Variant="Variant.Text" OnClick="GoBack" StartIcon="@Icons.Material.Filled.ArrowBack">Back to List</MudButton>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
@if (isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else if (layer == null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error">Layer not found</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField @bind-Value="layer.Name"
|
||||
Label="Name"
|
||||
Variant="Variant.Outlined"
|
||||
ReadOnly="true"
|
||||
FullWidth="true"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
@if (layer.IsCancelled)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||
This layer is cancelled. Will not be used in any further processing.
|
||||
</MudAlert>
|
||||
}
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField Value="@layer.CreatedAt.ToString("g")"
|
||||
Label="Created"
|
||||
Variant="Variant.Outlined"
|
||||
ReadOnly="true"
|
||||
FullWidth="true"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentText="@(layer.CreatedBy?.Username ?? "")"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField Value="@layer.ModifiedAt.ToString("g")"
|
||||
Label="Modified"
|
||||
Variant="Variant.Outlined"
|
||||
ReadOnly="true"
|
||||
FullWidth="true"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentText="@(layer.ModifiedBy?.Username ?? "")"/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDivider Class="my-4"/>
|
||||
|
||||
<MudTabs Elevation="0" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pt-4">
|
||||
<MudTabPanel Text="Details" Icon="@Icons.Material.Filled.TableChart">
|
||||
|
||||
<MudTable Items="@records"
|
||||
Dense="true"
|
||||
Striped="true"
|
||||
FixedHeader="true"
|
||||
FixedFooter="true"
|
||||
Height="600px">
|
||||
<HeaderContent>
|
||||
<MudTh>Code</MudTh>
|
||||
@foreach (var column in displayedColumns)
|
||||
{
|
||||
<MudTh>@column</MudTh>
|
||||
}
|
||||
@if (isEditable)
|
||||
{
|
||||
<MudTh>Actions</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@if (editingRecordId == context.Id)
|
||||
{
|
||||
<MudTd DataLabel="Code">
|
||||
<MudTextField @bind-Value="editingRecord.Code"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
FullWidth="true"/>
|
||||
</MudTd>
|
||||
@foreach (var column in displayedColumns)
|
||||
{
|
||||
@if (column == "Description1")
|
||||
{
|
||||
<MudTd DataLabel="@column">
|
||||
<MudTextField @bind-Value="editingRecord.Desc1"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
FullWidth="true"/>
|
||||
</MudTd>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTd DataLabel="@column">@GetRecordValue(context, column)</MudTd>
|
||||
}
|
||||
}
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Check"
|
||||
Color="Color.Success"
|
||||
Size="Size.Small"
|
||||
OnClick="SaveEdit"
|
||||
Title="Save"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Close"
|
||||
Color="Color.Default"
|
||||
Size="Size.Small"
|
||||
OnClick="CancelEdit"
|
||||
Title="Cancel"/>
|
||||
</MudTd>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTd DataLabel="Code">@context.Code</MudTd>
|
||||
@foreach (var column in displayedColumns)
|
||||
{
|
||||
<MudTd DataLabel="@column">@GetRecordValue(context, column)</MudTd>
|
||||
}
|
||||
@if (isEditable)
|
||||
{
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
OnClick="() => StartEdit(context)"
|
||||
Title="Edit"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
Size="Size.Small"
|
||||
OnClick="() => DeleteRecord(context)"
|
||||
Title="Delete"/>
|
||||
</MudTd>
|
||||
}
|
||||
}
|
||||
</RowTemplate>
|
||||
<FooterContent>
|
||||
<MudTd><b>Value1 sum</b></MudTd>
|
||||
@foreach (var column in displayedColumns)
|
||||
{
|
||||
@if (column == "Value1")
|
||||
{
|
||||
<MudTd><b>@valueSum.ToString("N2")</b></MudTd>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTd></MudTd>
|
||||
}
|
||||
}
|
||||
@if (isEditable)
|
||||
{
|
||||
<MudTd></MudTd>
|
||||
}
|
||||
</FooterContent>
|
||||
</MudTable>
|
||||
|
||||
@if (isEditable)
|
||||
{
|
||||
@if (isAddingNew)
|
||||
{
|
||||
<MudPaper Class="mt-4 pa-4" Outlined="true">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField @bind-Value="newRecord.Code"
|
||||
Label="Code"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField @bind-Value="newRecord.Desc1"
|
||||
Label="Description"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" Class="d-flex justify-end">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
OnClick="SaveNewRecord"
|
||||
StartIcon="@Icons.Material.Filled.Check"
|
||||
Class="mr-2">
|
||||
Save
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Default"
|
||||
OnClick="CancelAddNew"
|
||||
StartIcon="@Icons.Material.Filled.Close">
|
||||
Cancel
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
OnClick="StartAddNew"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
Class="mt-4">
|
||||
Add New Record
|
||||
</MudButton>
|
||||
}
|
||||
}
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="History" Icon="@Icons.Material.Filled.History">
|
||||
@if (isLoadingHistory)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else if (selectedRecordForHistory != null || selectedDeletedRecordForHistory != null)
|
||||
{
|
||||
<MudPaper Class="pa-4 mb-4" Outlined="true">
|
||||
<MudText Typo="Typo.h6">
|
||||
History for Record:
|
||||
@if (selectedDeletedRecordForHistory != null)
|
||||
{
|
||||
<MudText Typo="Typo.h6" Inline="true" Color="Color.Error">@selectedDeletedRecordForHistory.Code (Deleted)</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
@selectedRecordForHistory?.Code
|
||||
}
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
OnClick="ClearHistorySelection"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
Size="Size.Small">
|
||||
Back to list
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
|
||||
@if (!recordHistory.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">No history available for this record.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTimeline TimelineOrientation="TimelineOrientation.Vertical" TimelinePosition="TimelinePosition.Start">
|
||||
@foreach (var history in recordHistory)
|
||||
{
|
||||
<MudTimelineItem Color="@GetHistoryColor(history.ChangeType)" Size="Size.Small">
|
||||
<ItemOpposite>
|
||||
<MudText Color="Color.Default" Typo="Typo.body2">
|
||||
@history.ChangedAt.ToString("g")
|
||||
</MudText>
|
||||
</ItemOpposite>
|
||||
<ItemContent>
|
||||
<MudPaper Elevation="3" Class="pa-3">
|
||||
<MudText Typo="Typo.body1">
|
||||
<strong>@history.ChangeType</strong> by @history.ChangedByName
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mt-2">
|
||||
@history.FormattedChange
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</ItemContent>
|
||||
</MudTimelineItem>
|
||||
}
|
||||
</MudTimeline>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Active Records</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">Select a record to view its history:</MudText>
|
||||
|
||||
<MudTable Items="@records"
|
||||
Dense="true"
|
||||
Striped="true"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
Height="300px"
|
||||
OnRowClick="@((TableRowClickEventArgs<RecordDto> args) => OnRecordClickForHistory(args))"
|
||||
T="RecordDto"
|
||||
Style="cursor: pointer;">
|
||||
<HeaderContent>
|
||||
<MudTh>Code</MudTh>
|
||||
<MudTh>Description</MudTh>
|
||||
<MudTh>Modified</MudTh>
|
||||
<MudTh>Modified By</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Code">@context.Code</MudTd>
|
||||
<MudTd DataLabel="Description">@context.Desc1</MudTd>
|
||||
<MudTd DataLabel="Modified">@context.ModifiedAt.ToString("g")</MudTd>
|
||||
<MudTd DataLabel="Modified By">@GetModifiedByUsername(context.ModifiedById)</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
<MudDivider Class="my-4"/>
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Deleted Records</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">Select a deleted record to view its history:</MudText>
|
||||
|
||||
@if (deletedRecords.Any())
|
||||
{
|
||||
<MudTable Items="@deletedRecords"
|
||||
Dense="true"
|
||||
Striped="true"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
Height="200px"
|
||||
OnRowClick="@((TableRowClickEventArgs<DeletedRecordDto> args) => OnDeletedRecordClickForHistory(args))"
|
||||
T="DeletedRecordDto"
|
||||
Style="cursor: pointer;">
|
||||
<HeaderContent>
|
||||
<MudTh>Code</MudTh>
|
||||
<MudTh>Description</MudTh>
|
||||
<MudTh>Deleted</MudTh>
|
||||
<MudTh>Deleted By</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Code">
|
||||
<MudText Color="Color.Error">@context.Code</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Description">@context.Desc1</MudTd>
|
||||
<MudTd DataLabel="Deleted">@context.DeletedAt.ToString("g")</MudTd>
|
||||
<MudTd DataLabel="Deleted By">@context.DeletedByName</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Dense="true">No deleted records found.</MudAlert>
|
||||
}
|
||||
}
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
476
DiunaBI.UI.Shared/Pages/Layers/Details.razor.cs
Normal file
476
DiunaBI.UI.Shared/Pages/Layers/Details.razor.cs
Normal file
@@ -0,0 +1,476 @@
|
||||
using DiunaBI.Application.DTOModels;
|
||||
using DiunaBI.UI.Shared.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Reflection;
|
||||
|
||||
namespace DiunaBI.UI.Shared.Pages.Layers;
|
||||
|
||||
public partial class Details : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private LayerService LayerService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private JobService JobService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private NavigationManager NavigationManager { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; set; } = null!;
|
||||
|
||||
private LayerDto? layer;
|
||||
private List<RecordDto> records = new();
|
||||
private List<string> displayedColumns = new();
|
||||
private double valueSum = 0;
|
||||
private bool isLoading = false;
|
||||
private Guid? editingRecordId = null;
|
||||
private RecordDto? editingRecord = null;
|
||||
private bool isAddingNew = false;
|
||||
private RecordDto newRecord = new();
|
||||
private bool isEditable => layer?.Type == LayerType.Dictionary || layer?.Type == LayerType.Administration;
|
||||
|
||||
// History tab state
|
||||
private bool isLoadingHistory = false;
|
||||
private bool isHistoryTabInitialized = false;
|
||||
private RecordDto? selectedRecordForHistory = null;
|
||||
private DeletedRecordDto? selectedDeletedRecordForHistory = null;
|
||||
private List<RecordHistoryDto> recordHistory = new();
|
||||
private List<DeletedRecordDto> deletedRecords = new();
|
||||
private Dictionary<Guid, string> userCache = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadLayer();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await LoadLayer();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!isHistoryTabInitialized && !isLoadingHistory &&
|
||||
selectedRecordForHistory == null && selectedDeletedRecordForHistory == null &&
|
||||
deletedRecords.Count == 0)
|
||||
{
|
||||
await LoadDeletedRecordsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadLayer()
|
||||
{
|
||||
isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
layer = await LayerService.GetLayerByIdAsync(Id);
|
||||
|
||||
if (layer != null && layer.Records != null)
|
||||
{
|
||||
records = layer.Records.OrderBy(r => r.Code).ToList();
|
||||
CalculateDisplayedColumns();
|
||||
CalculateValueSum();
|
||||
BuildUserCache();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error loading layer: {ex.Message}");
|
||||
Snackbar.Add("Error loading layer", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateDisplayedColumns()
|
||||
{
|
||||
displayedColumns.Clear();
|
||||
|
||||
// Check which Value columns have data
|
||||
for (int i = 1; i <= 32; i++)
|
||||
{
|
||||
var columnName = $"Value{i}";
|
||||
var hasData = records.Any(r => GetRecordValueByName(r, columnName) != null);
|
||||
|
||||
if (hasData)
|
||||
{
|
||||
displayedColumns.Add(columnName);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Desc1 has data
|
||||
if (records.Any(r => !string.IsNullOrEmpty(r.Desc1)))
|
||||
{
|
||||
displayedColumns.Add("Description1");
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateValueSum()
|
||||
{
|
||||
valueSum = records
|
||||
.Where(r => r.Value1.HasValue)
|
||||
.Sum(r => r.Value1!.Value);
|
||||
}
|
||||
|
||||
private string GetRecordValue(RecordDto record, string columnName)
|
||||
{
|
||||
if (columnName == "Description1")
|
||||
{
|
||||
return record.Desc1 ?? string.Empty;
|
||||
}
|
||||
|
||||
var value = GetRecordValueByName(record, columnName);
|
||||
return value.HasValue ? value.Value.ToString("N2") : string.Empty;
|
||||
}
|
||||
|
||||
private double? GetRecordValueByName(RecordDto record, string columnName)
|
||||
{
|
||||
var property = typeof(RecordDto).GetProperty(columnName, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (property != null && property.PropertyType == typeof(double?))
|
||||
{
|
||||
return property.GetValue(record) as double?;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Export()
|
||||
{
|
||||
// TODO: Implement export functionality
|
||||
Snackbar.Add("Export functionality coming soon", Severity.Error);
|
||||
}
|
||||
|
||||
private void ProcessLayer()
|
||||
{
|
||||
// TODO: Implement process layer functionality
|
||||
Snackbar.Add("Process layer functionality coming soon", Severity.Error);
|
||||
}
|
||||
|
||||
private void GoBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/layers");
|
||||
}
|
||||
|
||||
// Record editing methods
|
||||
private void StartEdit(RecordDto record)
|
||||
{
|
||||
editingRecordId = record.Id;
|
||||
editingRecord = new RecordDto
|
||||
{
|
||||
Id = record.Id,
|
||||
Code = record.Code,
|
||||
Desc1 = record.Desc1,
|
||||
LayerId = record.LayerId
|
||||
};
|
||||
}
|
||||
|
||||
private void CancelEdit()
|
||||
{
|
||||
editingRecordId = null;
|
||||
editingRecord = null;
|
||||
}
|
||||
|
||||
private async Task SaveEdit()
|
||||
{
|
||||
if (editingRecord == null || layer == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(editingRecord.Code))
|
||||
{
|
||||
Snackbar.Add("Code is required", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(editingRecord.Desc1))
|
||||
{
|
||||
Snackbar.Add("Description is required", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var updated = await LayerService.UpdateRecordAsync(layer.Id, editingRecord.Id, editingRecord);
|
||||
if (updated != null)
|
||||
{
|
||||
var record = records.FirstOrDefault(r => r.Id == editingRecord.Id);
|
||||
if (record != null)
|
||||
{
|
||||
record.Code = updated.Code;
|
||||
record.Desc1 = updated.Desc1;
|
||||
record.ModifiedAt = updated.ModifiedAt;
|
||||
}
|
||||
editingRecordId = null;
|
||||
editingRecord = null;
|
||||
Snackbar.Add("Record updated successfully", Severity.Success);
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Failed to update record", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error updating record: {ex.Message}");
|
||||
Snackbar.Add("Error updating record", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteRecord(RecordDto record)
|
||||
{
|
||||
if (layer == null) return;
|
||||
|
||||
var result = await DialogService.ShowMessageBox(
|
||||
"Confirm Delete",
|
||||
$"Are you sure you want to delete record '{record.Code}'?",
|
||||
yesText: "Delete",
|
||||
cancelText: "Cancel");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await LayerService.DeleteRecordAsync(layer.Id, record.Id);
|
||||
if (success)
|
||||
{
|
||||
records.Remove(record);
|
||||
CalculateDisplayedColumns();
|
||||
CalculateValueSum();
|
||||
Snackbar.Add("Record deleted successfully", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Failed to delete record", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error deleting record: {ex.Message}");
|
||||
Snackbar.Add("Error deleting record", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAddNew()
|
||||
{
|
||||
isAddingNew = true;
|
||||
newRecord = new RecordDto { LayerId = layer?.Id ?? Guid.Empty };
|
||||
}
|
||||
|
||||
private void CancelAddNew()
|
||||
{
|
||||
isAddingNew = false;
|
||||
newRecord = new();
|
||||
}
|
||||
|
||||
private async Task SaveNewRecord()
|
||||
{
|
||||
if (layer == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newRecord.Code))
|
||||
{
|
||||
Snackbar.Add("Code is required", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newRecord.Desc1))
|
||||
{
|
||||
Snackbar.Add("Description is required", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var created = await LayerService.CreateRecordAsync(layer.Id, newRecord);
|
||||
if (created != null)
|
||||
{
|
||||
records.Add(created);
|
||||
CalculateDisplayedColumns();
|
||||
CalculateValueSum();
|
||||
isAddingNew = false;
|
||||
newRecord = new();
|
||||
Snackbar.Add("Record added successfully", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Failed to add record", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error adding record: {ex.Message}");
|
||||
Snackbar.Add("Error adding record", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// History tab methods
|
||||
private async Task LoadDeletedRecordsAsync()
|
||||
{
|
||||
if (isHistoryTabInitialized || layer == null) return;
|
||||
|
||||
isHistoryTabInitialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Loading deleted records for layer {layer.Id}");
|
||||
deletedRecords = await LayerService.GetDeletedRecordsAsync(layer.Id);
|
||||
Console.WriteLine($"Loaded {deletedRecords.Count} deleted records");
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error loading deleted records: {ex.Message}");
|
||||
Console.Error.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||
deletedRecords = new List<DeletedRecordDto>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnRecordClickForHistory(TableRowClickEventArgs<RecordDto> args)
|
||||
{
|
||||
if (args.Item == null || layer == null) return;
|
||||
|
||||
selectedRecordForHistory = args.Item;
|
||||
isLoadingHistory = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
recordHistory = await LayerService.GetRecordHistoryAsync(layer.Id, args.Item.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error loading record history: {ex.Message}");
|
||||
Snackbar.Add("Error loading record history", Severity.Error);
|
||||
recordHistory = new List<RecordHistoryDto>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingHistory = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearHistorySelection()
|
||||
{
|
||||
selectedRecordForHistory = null;
|
||||
selectedDeletedRecordForHistory = null;
|
||||
recordHistory.Clear();
|
||||
isHistoryTabInitialized = false; // Reset so deleted records reload when returning to list
|
||||
}
|
||||
|
||||
private async Task OnDeletedRecordClickForHistory(TableRowClickEventArgs<DeletedRecordDto> args)
|
||||
{
|
||||
if (args.Item == null || layer == null) return;
|
||||
|
||||
selectedDeletedRecordForHistory = args.Item;
|
||||
selectedRecordForHistory = null;
|
||||
isLoadingHistory = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
recordHistory = await LayerService.GetRecordHistoryAsync(layer.Id, args.Item.RecordId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error loading deleted record history: {ex.Message}");
|
||||
Snackbar.Add("Error loading record history", Severity.Error);
|
||||
recordHistory = new List<RecordHistoryDto>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingHistory = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetHistoryColor(string changeType)
|
||||
{
|
||||
return changeType switch
|
||||
{
|
||||
"Created" => Color.Success,
|
||||
"Updated" => Color.Info,
|
||||
"Deleted" => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private void BuildUserCache()
|
||||
{
|
||||
userCache.Clear();
|
||||
|
||||
if (layer == null) return;
|
||||
|
||||
// Add layer-level users to cache
|
||||
if (layer.CreatedBy != null)
|
||||
userCache.TryAdd(layer.CreatedBy.Id, layer.CreatedBy.Username ?? string.Empty);
|
||||
if (layer.ModifiedBy != null)
|
||||
userCache.TryAdd(layer.ModifiedBy.Id, layer.ModifiedBy.Username ?? string.Empty);
|
||||
}
|
||||
|
||||
private string GetModifiedByUsername(Guid userId)
|
||||
{
|
||||
return userCache.TryGetValue(userId, out var username) ? username : string.Empty;
|
||||
}
|
||||
|
||||
// Run Now button methods
|
||||
private bool isRunningJob = false;
|
||||
|
||||
private bool IsWorkerLayer()
|
||||
{
|
||||
if (layer?.Records == null) return false;
|
||||
|
||||
var typeRecord = layer.Records.FirstOrDefault(x => x.Code == "Type");
|
||||
return typeRecord?.Desc1 == "ImportWorker" || typeRecord?.Desc1 == "ProcessWorker";
|
||||
}
|
||||
|
||||
private async Task RunNow()
|
||||
{
|
||||
if (layer == null) return;
|
||||
|
||||
isRunningJob = true;
|
||||
try
|
||||
{
|
||||
var result = await JobService.CreateJobForLayerAsync(layer.Id);
|
||||
|
||||
if (result != null && result.Success)
|
||||
{
|
||||
if (result.Existing)
|
||||
{
|
||||
Snackbar.Add($"Job already exists: {result.Message}", Severity.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Job created successfully! Watch real-time status updates.", Severity.Success);
|
||||
}
|
||||
|
||||
// Navigate to job detail page to see real-time updates
|
||||
NavigationManager.NavigateTo($"/jobs/{result.JobId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Failed to create job", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error creating job: {ex.Message}");
|
||||
Snackbar.Add($"Error creating job: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isRunningJob = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
DiunaBI.UI.Shared/Pages/Layers/Index.razor
Normal file
95
DiunaBI.UI.Shared/Pages/Layers/Index.razor
Normal file
@@ -0,0 +1,95 @@
|
||||
@page "/layers"
|
||||
@using MudBlazor.Internal
|
||||
@using DiunaBI.Application.DTOModels
|
||||
|
||||
<PageTitle>Layers</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
|
||||
<MudExpansionPanels Class="mb-4">
|
||||
<MudExpansionPanel Icon="@Icons.Material.Filled.FilterList"
|
||||
Text="Filters"
|
||||
Expanded="true">
|
||||
<MudGrid AlignItems="Center">
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudTextField @bind-Value="filterRequest.Search"
|
||||
Label="Search"
|
||||
Placeholder="Name, number..."
|
||||
Immediate="true"
|
||||
DebounceInterval="500"
|
||||
OnDebounceIntervalElapsed="SearchLayers"
|
||||
Clearable="true"/>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudSelect T="LayerType?"
|
||||
Value="filterRequest.Type"
|
||||
ValueChanged="OnTypeChanged"
|
||||
Label="Type"
|
||||
Placeholder="All types"
|
||||
Clearable="true"
|
||||
OnClearButtonClick="OnTypeClear">
|
||||
@foreach (LayerType type in Enum.GetValues(typeof(LayerType)))
|
||||
{
|
||||
<MudSelectItem T="LayerType?" Value="@type">@type.ToString()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="12" md="4" Class="d-flex justify-end">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Clear"
|
||||
OnClick="ClearFilters"
|
||||
Color="Color.Default"
|
||||
Size="Size.Medium"
|
||||
Title="Clear filters"/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
|
||||
<MudDivider Class="my-4"></MudDivider>
|
||||
|
||||
<MudTable Items="layers.Items"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Loading="isLoading"
|
||||
LoadingProgressColor="Color.Primary"
|
||||
OnRowClick="@((TableRowClickEventArgs<LayerDto> args) => OnRowClick(args.Item))"
|
||||
T="LayerDto"
|
||||
Style="cursor: pointer;">
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Type</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate Context="row">
|
||||
<MudTd DataLabel="Name"><div @oncontextmenu="@(async (e) => await OnRowRightClick(e, row))" @oncontextmenu:preventDefault="true">@row.Name</div></MudTd>
|
||||
<MudTd DataLabel="Type"><div @oncontextmenu="@(async (e) => await OnRowRightClick(e, row))" @oncontextmenu:preventDefault="true">@row.Type</div></MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>No layers to display</MudText>
|
||||
</NoRecordsContent>
|
||||
<LoadingContent>
|
||||
Loading...
|
||||
</LoadingContent>
|
||||
</MudTable>
|
||||
|
||||
@if (layers.TotalCount > 0)
|
||||
{
|
||||
<MudGrid Class="mt-4" AlignItems="Center.Center">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudText Typo="Typo.body2">
|
||||
Results @((layers.Page - 1) * layers.PageSize + 1) - @Math.Min(layers.Page * layers.PageSize, layers.TotalCount)
|
||||
of @layers.TotalCount
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" Class="d-flex justify-end">
|
||||
<MudPagination Count="layers.TotalPages"
|
||||
Selected="layers.Page"
|
||||
SelectedChanged="OnPageChanged"
|
||||
ShowFirstButton="true"
|
||||
ShowLastButton="true"
|
||||
Variant="Variant.Outlined"
|
||||
/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
92
DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs
Normal file
92
DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using DiunaBI.UI.Shared.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using DiunaBI.Application.DTOModels;
|
||||
using DiunaBI.Application.DTOModels.Common;
|
||||
using MudBlazor;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace DiunaBI.UI.Shared.Pages.Layers;
|
||||
|
||||
public partial class Index : ComponentBase
|
||||
{
|
||||
[Inject] private LayerService LayerService { get; set; } = default!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = default!;
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] private LayerFilterStateService FilterStateService { get; set; } = default!;
|
||||
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
|
||||
private PagedResult<LayerDto> layers = new();
|
||||
private LayerFilterRequest filterRequest = new();
|
||||
private bool isLoading = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
filterRequest = FilterStateService.FilterRequest;
|
||||
await LoadLayers();
|
||||
}
|
||||
|
||||
private async Task LoadLayers()
|
||||
{
|
||||
isLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
FilterStateService.UpdateFilter(filterRequest);
|
||||
layers = await LayerService.GetLayersAsync(filterRequest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Loading layers failed: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SearchLayers()
|
||||
{
|
||||
filterRequest.Page = 1;
|
||||
await LoadLayers();
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int page)
|
||||
{
|
||||
filterRequest.Page = page;
|
||||
await LoadLayers();
|
||||
}
|
||||
|
||||
private async Task ClearFilters()
|
||||
{
|
||||
filterRequest = new LayerFilterRequest();
|
||||
FilterStateService.ClearFilter();
|
||||
await LoadLayers();
|
||||
}
|
||||
|
||||
private async Task OnTypeClear()
|
||||
{
|
||||
filterRequest.Type = null;
|
||||
filterRequest.Page = 1;
|
||||
await LoadLayers();
|
||||
}
|
||||
|
||||
private async Task OnTypeChanged(LayerType? type)
|
||||
{
|
||||
filterRequest.Type = type;
|
||||
filterRequest.Page = 1;
|
||||
await LoadLayers();
|
||||
}
|
||||
|
||||
private void OnRowClick(LayerDto layer)
|
||||
{
|
||||
NavigationManager.NavigateTo($"/layers/{layer.Id}");
|
||||
}
|
||||
|
||||
private async Task OnRowRightClick(MouseEventArgs e, LayerDto layer)
|
||||
{
|
||||
var url = NavigationManager.ToAbsoluteUri($"/layers/{layer.Id}").ToString();
|
||||
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user