feat(sender): add envelope delete popup and client call
This commit is contained in:
@@ -12,6 +12,8 @@ namespace EnvelopeGenerator.Server.Client.Services;
|
||||
public class EnvelopeService(IHttpClientFactory clientFactory)
|
||||
{
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private sealed record DeleteEnvelopeRequest(string Reason);
|
||||
private sealed record ErrorResponse(string? Message);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches envelopes from the API with optional filters.
|
||||
@@ -61,4 +63,35 @@ public class EnvelopeService(IHttpClientFactory clientFactory)
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<IEnumerable<EnvelopeDto>>(_jsonOptions, cancel);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int envelopeId, string reason, CancellationToken cancel = default)
|
||||
{
|
||||
var httpClient = clientFactory.CreateClient("EnvelopeGenerator.Server");
|
||||
using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/Envelope/{envelopeId}")
|
||||
{
|
||||
Content = JsonContent.Create(new DeleteEnvelopeRequest(reason), options: _jsonOptions)
|
||||
};
|
||||
|
||||
var response = await httpClient.SendAsync(request, cancel);
|
||||
if (response.IsSuccessStatusCode)
|
||||
return;
|
||||
|
||||
string? message = null;
|
||||
try
|
||||
{
|
||||
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>(_jsonOptions, cancel);
|
||||
message = error?.Message;
|
||||
}
|
||||
catch
|
||||
{
|
||||
message = null;
|
||||
}
|
||||
|
||||
var statusCode = (int)response.StatusCode;
|
||||
var reasonPhrase = response.ReasonPhrase ?? "Unknown error";
|
||||
throw new HttpRequestException(
|
||||
$"Failed to delete envelope. Status: {statusCode} ({reasonPhrase}){(string.IsNullOrWhiteSpace(message) ? string.Empty : $" - {message}")}",
|
||||
null,
|
||||
response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,6 +521,54 @@
|
||||
</BodyContentTemplate>
|
||||
</DxPopup>
|
||||
|
||||
<DxPopup @bind-Visible="@_deletePopupVisible"
|
||||
HeaderText="Umschlag löschen"
|
||||
Width="560px"
|
||||
MaxWidth="95vw"
|
||||
ShowFooter="true"
|
||||
CloseOnOutsideClick="false"
|
||||
ShowCloseButton="true"
|
||||
CloseOnEscape="true"
|
||||
ShowHeader="true"
|
||||
CssClass="sender-delete-popup">
|
||||
<BodyContentTemplate>
|
||||
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||
<p style="margin:0;color:#374151;font-size:0.9rem;line-height:1.45;">
|
||||
Bitte geben Sie einen Grund an, warum der Umschlag gelöscht/zurückgezogen werden soll.
|
||||
</p>
|
||||
@if (_selectedEnvelope is not null)
|
||||
{
|
||||
<p style="margin:0;color:#6b7280;font-size:0.825rem;">
|
||||
Umschlag: <strong>#@_selectedEnvelope.Id - @_selectedEnvelope.Title</strong>
|
||||
</p>
|
||||
}
|
||||
<DxMemo @bind-Text="_deleteReason"
|
||||
NullText="Begründung eingeben..."
|
||||
Rows="5"
|
||||
MaxLength="2000"
|
||||
CssClass="w-100" />
|
||||
@if (!string.IsNullOrWhiteSpace(_deleteErrorMessage))
|
||||
{
|
||||
<div class="alert alert-danger" style="margin:0;padding:0.5rem 0.75rem;font-size:0.85rem;">
|
||||
@_deleteErrorMessage
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</BodyContentTemplate>
|
||||
<FooterContentTemplate>
|
||||
<div style="display:flex;justify-content:flex-end;gap:0.5rem;width:100%;">
|
||||
<DxButton Text="Abbrechen"
|
||||
RenderStyle="ButtonRenderStyle.Secondary"
|
||||
Click="CloseDeletePopup"
|
||||
Enabled="@(!_isDeletingEnvelope)" />
|
||||
<DxButton Text="Löschen"
|
||||
RenderStyle="ButtonRenderStyle.Danger"
|
||||
Click="ConfirmDeleteEnvelopeAsync"
|
||||
Enabled="@(!_isDeletingEnvelope)" />
|
||||
</div>
|
||||
</FooterContentTemplate>
|
||||
</DxPopup>
|
||||
|
||||
@code {
|
||||
private IEnumerable<EnvelopeDto>? _allEnvelopes;
|
||||
private IEnumerable<EnvelopeDto>? _activeEnvelopes;
|
||||
@@ -549,6 +597,11 @@
|
||||
private bool _exportSelectedRowsOnly = false;
|
||||
private bool _isExporting = false;
|
||||
|
||||
private bool _deletePopupVisible = false;
|
||||
private bool _isDeletingEnvelope = false;
|
||||
private string _deleteReason = string.Empty;
|
||||
private string? _deleteErrorMessage;
|
||||
|
||||
// Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts
|
||||
private const string GridKeyActive = "sender.active-envelopes";
|
||||
private const string GridKeyCompleted = "sender.completed-envelopes";
|
||||
@@ -681,7 +734,51 @@
|
||||
void DeleteEnvelope()
|
||||
{
|
||||
if (_selectedEnvelope == null) return;
|
||||
// TODO: Show delete confirmation dialog
|
||||
_deleteReason = string.Empty;
|
||||
_deleteErrorMessage = null;
|
||||
_deletePopupVisible = true;
|
||||
}
|
||||
|
||||
void CloseDeletePopup()
|
||||
{
|
||||
if (_isDeletingEnvelope)
|
||||
return;
|
||||
|
||||
_deletePopupVisible = false;
|
||||
_deleteErrorMessage = null;
|
||||
}
|
||||
|
||||
async Task ConfirmDeleteEnvelopeAsync()
|
||||
{
|
||||
if (_selectedEnvelope is null)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_deleteReason))
|
||||
{
|
||||
_deleteErrorMessage = "Bitte geben Sie einen Löschgrund an.";
|
||||
return;
|
||||
}
|
||||
|
||||
_isDeletingEnvelope = true;
|
||||
_deleteErrorMessage = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
try
|
||||
{
|
||||
await EnvelopeService.DeleteAsync(_selectedEnvelope.Id, _deleteReason.Trim());
|
||||
_deletePopupVisible = false;
|
||||
_selectedEnvelope = null;
|
||||
await LoadEnvelopesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_deleteErrorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDeletingEnvelope = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
async Task LogoutAsync()
|
||||
|
||||
Reference in New Issue
Block a user