WIP: Record history

This commit is contained in:
2025-12-01 18:37:09 +01:00
parent c8ded1f0a4
commit 0c6848556b
11 changed files with 1176 additions and 0 deletions

View File

@@ -72,6 +72,9 @@
<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"
@@ -218,6 +221,133 @@
</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>

View File

@@ -27,6 +27,15 @@ public partial class LayerDetailPage : ComponentBase
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();
@@ -37,6 +46,16 @@ public partial class LayerDetailPage : ComponentBase
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;
@@ -51,6 +70,7 @@ public partial class LayerDetailPage : ComponentBase
records = layer.Records;
CalculateDisplayedColumns();
CalculateValueSum();
BuildUserCache();
}
}
catch (Exception ex)
@@ -278,4 +298,114 @@ public partial class LayerDetailPage : ComponentBase
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;
}
}