Add history display and enhance status handling

Introduced a "Historie" section in `EnvelopeSenderPage.razor` to display envelope history with status badges, actor info, and timestamps. Updated `ReadEnvelopeQueryHandler` to include histories in queries.

Refactored `GetStatusInfo` to use localized labels and expanded supported statuses. Added helper methods for history processing and display.

Enhanced UI with new styles for the history section in `sender-page.css` and improved responsive design. Added dependency injections for localization and history handling.
This commit is contained in:
2026-09-28 08:39:39 +02:00
parent 519a8bf3f9
commit 051a8ce211
3 changed files with 155 additions and 11 deletions

View File

@@ -150,8 +150,9 @@ public class ReadEnvelopeQueryHandler : IRequestHandler<ReadEnvelopeQuery, IEnum
var envelopes = await query
.Include(e => e.EnvelopeReceivers).ThenInclude(er => er.Receiver)
.Include(e => e.Histories)
.ToListAsync(cancel);
return _mapper.Map<IEnumerable<EnvelopeDto>>(envelopes);
}
}
}

View File

@@ -7,7 +7,9 @@
@using DevExpress.Blazor.PdfViewer
@using EnvelopeGenerator.Server.Client.Services
@using EnvelopeGenerator.Application.Common.Dto
@using EnvelopeGenerator.Application.Common.Dto.History
@using EnvelopeGenerator.Server.Services
@using Microsoft.Extensions.Localization
@inject EnvelopeGenerator.Server.Client.Services.EnvelopeService EnvelopeService
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
@inject EnvelopeGenerator.Server.Client.Services.ThemeService ThemeService
@@ -19,6 +21,7 @@
@inject NavigationManager Navigation
@inject IJSRuntime JSRuntime
@inject AppVersionService AppVersion
@inject IStringLocalizer<My.Resources.Model> ModelLocalizer
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
<link href="@AppVersion.GetVersionedUrl("css/sender-page.css")" rel="stylesheet" />
@@ -329,6 +332,38 @@
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
}
}
<h6 style="font-weight: 600; color: #374151; margin: 1rem 0 0.75rem;">Historie</h6>
@{
var historyEntries = envelope is null
? Enumerable.Empty<HistoryDto>()
: GetOrderedHistories(envelope);
if (historyEntries.Any())
{
<div class="sender-history-list">
@foreach (var history in historyEntries)
{
var statusInfo = GetStatusInfo(history.Status);
<div class="sender-history-item">
<div class="sender-history-item__left">
<div class="status-badge status-badge--@statusInfo.CssClass">
<span class="status-dot status-dot--@statusInfo.DotColor"></span>
@statusInfo.Label
</div>
<span class="sender-history-item__actor">@GetHistoryActor(history)</span>
</div>
<div class="sender-history-item__right">
@GetHistoryDate(history).ToString("dd.MM.yyyy HH:mm")
</div>
</div>
}
</div>
}
else
{
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Historie vorhanden</p>
}
}
</div>
</DetailRowTemplate>
</DxGrid>
@@ -451,6 +486,38 @@
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
}
}
<h6 style="font-weight: 600; color: #374151; margin: 1rem 0 0.75rem;">Historie</h6>
@{
var historyEntries = envelope is null
? Enumerable.Empty<HistoryDto>()
: GetOrderedHistories(envelope);
if (historyEntries.Any())
{
<div class="sender-history-list">
@foreach (var history in historyEntries)
{
var statusInfo = GetStatusInfo(history.Status);
<div class="sender-history-item">
<div class="sender-history-item__left">
<div class="status-badge status-badge--@statusInfo.CssClass">
<span class="status-dot status-dot--@statusInfo.DotColor"></span>
@statusInfo.Label
</div>
<span class="sender-history-item__actor">@GetHistoryActor(history)</span>
</div>
<div class="sender-history-item__right">
@GetHistoryDate(history).ToString("dd.MM.yyyy HH:mm")
</div>
</div>
}
</div>
}
else
{
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Historie vorhanden</p>
}
}
</div>
</DetailRowTemplate>
</DxGrid>
@@ -806,21 +873,59 @@
(string Label, string CssClass, string DotColor) GetStatusInfo(EnvelopeStatus status)
{
var label = GetStatusLabel(status);
return status switch
{
EnvelopeStatus.EnvelopePartlySigned => ("Teilweise unterschrieben", "partly-signed", "green"),
EnvelopeStatus.EnvelopeQueued => ("In Warteschlange", "queued", "orange"),
EnvelopeStatus.EnvelopeSent => ("Gesendet", "sent", "orange"),
EnvelopeStatus.EnvelopeCompletelySigned => ("Vollständig unterschrieben", "completed", "green"),
EnvelopeStatus.EnvelopeDeleted => ("Gelöscht", "deleted", "red"),
EnvelopeStatus.EnvelopeRejected => ("Abgelehnt", "rejected", "red"),
EnvelopeStatus.EnvelopeWithdrawn => ("Zurückgezogen", "withdrawn", "red"),
EnvelopeStatus.EnvelopeCreated => ("Erstellt", "created", "blue"),
EnvelopeStatus.EnvelopeSaved => ("Gespeichert", "saved", "blue"),
_ => ("Unbekannt", "unknown", "blue")
EnvelopeStatus.EnvelopePartlySigned => (label, "partly-signed", "green"),
EnvelopeStatus.EnvelopeCompletelySigned => (label, "completed", "green"),
EnvelopeStatus.DocumentSigned => (label, "partly-signed", "green"),
EnvelopeStatus.MessageCompletionSent => (label, "completed", "green"),
EnvelopeStatus.EnvelopeQueued => (label, "queued", "orange"),
EnvelopeStatus.EnvelopeSent => (label, "sent", "orange"),
EnvelopeStatus.MessageInvitationSent => (label, "queued", "orange"),
EnvelopeStatus.MessageAccessCodeSent => (label, "queued", "orange"),
EnvelopeStatus.MessageConfirmationSent => (label, "queued", "orange"),
EnvelopeStatus.EnvelopeDeleted => (label, "deleted", "red"),
EnvelopeStatus.EnvelopeRejected => (label, "rejected", "red"),
EnvelopeStatus.EnvelopeWithdrawn => (label, "withdrawn", "red"),
EnvelopeStatus.DocumentRejected => (label, "rejected", "red"),
EnvelopeStatus.AccessCodeIncorrect => (label, "rejected", "red"),
EnvelopeStatus.MessageDeletionSent => (label, "deleted", "red"),
_ => (label, "created", "blue")
};
}
string GetStatusLabel(EnvelopeStatus status)
{
var key = status.ToString();
var localized = ModelLocalizer[key];
return localized.ResourceNotFound ? key : localized.Value;
}
IEnumerable<HistoryDto> GetOrderedHistories(EnvelopeDto envelope)
{
return (envelope.Histories ?? Enumerable.Empty<HistoryDto>())
.OrderByDescending(GetHistoryDate)
.Take(20);
}
DateTime GetHistoryDate(HistoryDto history)
{
return history.ActionDate ?? history.AddedWhen;
}
string GetHistoryActor(HistoryDto history)
{
if (!string.IsNullOrWhiteSpace(history.UserReference))
return history.UserReference;
return "System";
}
void OnCustomizeElement(GridCustomizeElementEventArgs e)
{
// Future: Add custom row coloring based on status if needed

View File

@@ -381,6 +381,44 @@
color: #c62828;
}
.sender-history-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.sender-history-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 0.5rem;
background: white;
border-radius: 6px;
border: 1px solid #e5e7eb;
}
.sender-history-item__left {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
}
.sender-history-item__actor {
color: #4b5563;
font-size: 0.8rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sender-history-item__right {
color: #6b7280;
font-size: 0.8rem;
white-space: nowrap;
}
@media (max-width: 1320px) {
.sender-action-bar__inner {
flex-wrap: wrap;