Fix invalid ICS and PDF export returning validation errors as file content (#33)

* Initial plan

* Fix client-side export error handling and identify validation root cause

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

* Complete fix for invalid ICS and PDF export issue

Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>
This commit is contained in:
Copilot 2025-08-28 18:48:00 +02:00 committed by GitHub
parent e8db13191e
commit 1f7df973c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 79 additions and 15 deletions

View file

@ -10,6 +10,22 @@
<h3>Shift Scheduler - @CurrentMonthYear</h3> <h3>Shift Scheduler - @CurrentMonthYear</h3>
@if (!string.IsNullOrEmpty(_errorMessage))
{
<div class="alert alert-danger" role="alert">
@_errorMessage
<button type="button" class="btn-close" @onclick="() => _errorMessage = string.Empty"></button>
</div>
}
@if (!string.IsNullOrEmpty(_successMessage))
{
<div class="alert alert-success" role="alert">
@_successMessage
<button type="button" class="btn-close" @onclick="() => _successMessage = string.Empty"></button>
</div>
}
@if (_isLoadingInitial) @if (_isLoadingInitial)
{ {
<div class="loading-overlay"> <div class="loading-overlay">

View file

@ -20,6 +20,8 @@ namespace ShiftScheduler.Client.Pages
private int EditYear { get; set; } private int EditYear { get; set; }
private bool _isLoadingTransport = false; private bool _isLoadingTransport = false;
private bool _isLoadingInitial = false; private bool _isLoadingInitial = false;
private string _errorMessage = string.Empty;
private string _successMessage = string.Empty;
private string CurrentMonthYear => new DateTime(EditYear, EditMonth, 1).ToString("MMMM yyyy"); private string CurrentMonthYear => new DateTime(EditYear, EditMonth, 1).ToString("MMMM yyyy");
@ -98,21 +100,67 @@ namespace ShiftScheduler.Client.Pages
private async Task ExportToIcs() private async Task ExportToIcs()
{ {
try
{
_errorMessage = string.Empty;
_successMessage = string.Empty;
StateHasChanged();
var shiftsWithTransportList = SelectedShiftsWithTransport.Values.ToList(); var shiftsWithTransportList = SelectedShiftsWithTransport.Values.ToList();
var response = await HttpClient.PostAsJsonAsync("api/shift/export_ics", shiftsWithTransportList); var response = await HttpClient.PostAsJsonAsync("api/shift/export_ics", shiftsWithTransportList);
if (response.IsSuccessStatusCode)
{
var bytes = await response.Content.ReadAsByteArrayAsync(); var bytes = await response.Content.ReadAsByteArrayAsync();
var base64 = Convert.ToBase64String(bytes); var base64 = Convert.ToBase64String(bytes);
var fileUrl = $"data:text/calendar;base64,{base64}"; var fileUrl = $"data:text/calendar;base64,{base64}";
NavigationManager.NavigateTo(fileUrl, true); NavigationManager.NavigateTo(fileUrl, true);
_successMessage = "ICS file exported successfully!";
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
_errorMessage = $"Failed to export ICS file: {response.StatusCode}. {errorContent}";
}
}
catch (Exception ex)
{
_errorMessage = $"Error exporting ICS file: {ex.Message}";
}
StateHasChanged();
} }
private async Task ExportToPdf() private async Task ExportToPdf()
{ {
try
{
_errorMessage = string.Empty;
_successMessage = string.Empty;
StateHasChanged();
var shiftsWithTransportList = SelectedShiftsWithTransport.Values.ToList(); var shiftsWithTransportList = SelectedShiftsWithTransport.Values.ToList();
var response = await HttpClient.PostAsJsonAsync("api/shift/export_pdf", shiftsWithTransportList); var response = await HttpClient.PostAsJsonAsync("api/shift/export_pdf", shiftsWithTransportList);
if (response.IsSuccessStatusCode)
{
var bytes = await response.Content.ReadAsByteArrayAsync(); var bytes = await response.Content.ReadAsByteArrayAsync();
var base64 = Convert.ToBase64String(bytes); var base64 = Convert.ToBase64String(bytes);
await JSRuntime.InvokeVoidAsync("downloadFile", $"Schedule {EditYear}-{EditMonth:D2}.pdf", "application/pdf", base64); await JSRuntime.InvokeVoidAsync("downloadFile", $"Schedule {EditYear}-{EditMonth:D2}.pdf", "application/pdf", base64);
_successMessage = "PDF file exported successfully!";
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
_errorMessage = $"Failed to export PDF file: {response.StatusCode}. {errorContent}";
}
}
catch (Exception ex)
{
_errorMessage = $"Error exporting PDF file: {ex.Message}";
}
StateHasChanged();
} }
private async Task SaveScheduleToStorage() private async Task SaveScheduleToStorage()

View file

@ -17,7 +17,7 @@ namespace ShiftScheduler.Shared
public string DepartureTime { get; set; } = string.Empty; public string DepartureTime { get; set; } = string.Empty;
public string ArrivalTime { get; set; } = string.Empty; public string ArrivalTime { get; set; } = string.Empty;
public string Duration { get; set; } = string.Empty; public string Duration { get; set; } = string.Empty;
public string Platform { get; set; } = string.Empty; public string? Platform { get; set; } = string.Empty;
public List<TransportSection> Sections { get; set; } = new(); public List<TransportSection> Sections { get; set; } = new();
} }
@ -38,9 +38,9 @@ namespace ShiftScheduler.Shared
public class TransportCheckpoint public class TransportCheckpoint
{ {
public TransportStation? Station { get; set; } public TransportStation? Station { get; set; }
public string Departure { get; set; } = string.Empty; public string? Departure { get; set; } = string.Empty;
public string Arrival { get; set; } = string.Empty; public string? Arrival { get; set; } = string.Empty;
public string Platform { get; set; } = string.Empty; public string? Platform { get; set; } = string.Empty;
} }
public class TransportStation public class TransportStation