diff --git a/Client/Pages/Index.razor b/Client/Pages/Index.razor
index fa4ad60..b444288 100644
--- a/Client/Pages/Index.razor
+++ b/Client/Pages/Index.razor
@@ -14,13 +14,27 @@
{
@shift.Name
}
+ @if (SelectedSchedule.ContainsKey(day))
+ {
+
+ @GetTransportSummary(day)
+
+ }
}
diff --git a/Client/Pages/Index.razor.cs b/Client/Pages/Index.razor.cs
index ea90886..5868049 100644
--- a/Client/Pages/Index.razor.cs
+++ b/Client/Pages/Index.razor.cs
@@ -15,8 +15,10 @@ namespace ShiftScheduler.Client.Pages
private List DaysInMonth { get; set; } = new();
private List Shifts { get; set; } = new();
private Dictionary SelectedSchedule { get; set; } = new();
+ private Dictionary SelectedShiftsWithTransport { get; set; } = new();
private int EditMonth { get; set; }
private int EditYear { get; set; }
+ private bool _isLoadingTransport = false;
protected override async Task OnInitializedAsync()
{
@@ -42,12 +44,54 @@ namespace ShiftScheduler.Client.Pages
private async Task SelectShift(DateTime day, string shiftName)
{
SelectedSchedule[day] = shiftName;
+
+ // Get transport information for this shift and date
+ _isLoadingTransport = true;
+ StateHasChanged();
+
+ try
+ {
+ var request = new { ShiftName = shiftName, Date = day };
+ var response = await HttpClient.PostAsJsonAsync("api/shift/shift_transport", request);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var shiftWithTransport = await response.Content.ReadFromJsonAsync();
+ if (shiftWithTransport != null)
+ {
+ SelectedShiftsWithTransport[day] = shiftWithTransport;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error fetching transport data: {ex.Message}");
+ // Create a fallback with just the shift data
+ var shift = Shifts.FirstOrDefault(s => s.Name == shiftName);
+ if (shift != null)
+ {
+ SelectedShiftsWithTransport[day] = new ShiftWithTransport
+ {
+ Date = day,
+ Shift = shift,
+ MorningTransport = null,
+ AfternoonTransport = null
+ };
+ }
+ }
+ finally
+ {
+ _isLoadingTransport = false;
+ StateHasChanged();
+ }
+
await SaveScheduleToStorage();
}
private async Task ExportToIcs()
{
- var response = await HttpClient.PostAsJsonAsync("api/shift/export_ics", SelectedSchedule);
+ var shiftsWithTransportList = SelectedShiftsWithTransport.Values.ToList();
+ var response = await HttpClient.PostAsJsonAsync("api/shift/export_ics_with_transport", shiftsWithTransportList);
var bytes = await response.Content.ReadAsByteArrayAsync();
var base64 = Convert.ToBase64String(bytes);
var fileUrl = $"data:text/calendar;base64,{base64}";
@@ -56,7 +100,8 @@ namespace ShiftScheduler.Client.Pages
private async Task ExportToPdf()
{
- var response = await HttpClient.PostAsJsonAsync("api/shift/export_pdf", SelectedSchedule);
+ var shiftsWithTransportList = SelectedShiftsWithTransport.Values.ToList();
+ var response = await HttpClient.PostAsJsonAsync("api/shift/export_pdf_with_transport", shiftsWithTransportList);
var bytes = await response.Content.ReadAsByteArrayAsync();
var base64 = Convert.ToBase64String(bytes);
var fileUrl = $"data:application/octet-stream;base64,{base64}";
@@ -99,18 +144,63 @@ namespace ShiftScheduler.Client.Pages
kvp => DateTime.Parse(kvp.Key),
kvp => kvp.Value
);
+
+ // Reload transport data for all selected shifts
+ await ReloadTransportDataForSchedule();
}
}
}
catch (Exception)
{
SelectedSchedule = new Dictionary();
+ SelectedShiftsWithTransport = new Dictionary();
+ }
+ }
+
+ private async Task ReloadTransportDataForSchedule()
+ {
+ foreach (var kvp in SelectedSchedule)
+ {
+ var day = kvp.Key;
+ var shiftName = kvp.Value;
+
+ try
+ {
+ var request = new { ShiftName = shiftName, Date = day };
+ var response = await HttpClient.PostAsJsonAsync("api/shift/shift_transport", request);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var shiftWithTransport = await response.Content.ReadFromJsonAsync();
+ if (shiftWithTransport != null)
+ {
+ SelectedShiftsWithTransport[day] = shiftWithTransport;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error reloading transport data for {day}: {ex.Message}");
+ // Create fallback without transport
+ var shift = Shifts.FirstOrDefault(s => s.Name == shiftName);
+ if (shift != null)
+ {
+ SelectedShiftsWithTransport[day] = new ShiftWithTransport
+ {
+ Date = day,
+ Shift = shift,
+ MorningTransport = null,
+ AfternoonTransport = null
+ };
+ }
+ }
}
}
private async Task ResetSchedule()
{
SelectedSchedule.Clear();
+ SelectedShiftsWithTransport.Clear();
try
{
@@ -122,5 +212,24 @@ namespace ShiftScheduler.Client.Pages
// ignored
}
}
+
+ // Helper method to get transport summary for display
+ public string GetTransportSummary(DateTime date)
+ {
+ if (!SelectedShiftsWithTransport.TryGetValue(date, out var shiftWithTransport))
+ return string.Empty;
+
+ var summaries = new List();
+
+ var morningTransport = shiftWithTransport.GetMorningTransportSummary();
+ if (!string.IsNullOrEmpty(morningTransport))
+ summaries.Add($"🌅 {morningTransport}");
+
+ var afternoonTransport = shiftWithTransport.GetAfternoonTransportSummary();
+ if (!string.IsNullOrEmpty(afternoonTransport))
+ summaries.Add($"🌆 {afternoonTransport}");
+
+ return string.Join(" | ", summaries);
+ }
}
}
diff --git a/Client/Pages/Index.razor.css b/Client/Pages/Index.razor.css
index c823f7e..1a04a16 100644
--- a/Client/Pages/Index.razor.css
+++ b/Client/Pages/Index.razor.css
@@ -141,3 +141,25 @@
visibility: visible;
opacity: 1;
}
+
+/* Transport information styling */
+.transport-info {
+ margin-top: 8px;
+ padding: 4px;
+ background-color: #e8f4fd;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ color: #2c5aa0;
+ word-wrap: break-word;
+ min-height: 20px;
+}
+
+/* Loading spinner */
+.loading-spinner {
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
diff --git a/Server/Controllers/ShiftController.cs b/Server/Controllers/ShiftController.cs
index 51898e2..61378c4 100644
--- a/Server/Controllers/ShiftController.cs
+++ b/Server/Controllers/ShiftController.cs
@@ -41,6 +41,66 @@ namespace ShiftScheduler.Server.Controllers
return Ok(connection);
}
+ [HttpPost("shift_transport")]
+ public async Task GetShiftTransport([FromBody] ShiftTransportRequest request)
+ {
+ var shift = _shiftService.GetShifts().FirstOrDefault(s => s.Name == request.ShiftName);
+ if (shift == null)
+ {
+ return NotFound($"Shift '{request.ShiftName}' not found");
+ }
+
+ TransportConnection? morningTransport = null;
+ TransportConnection? afternoonTransport = null;
+
+ // Get transport for morning shift if it has morning time
+ if (!string.IsNullOrEmpty(shift.MorningTime))
+ {
+ var morningStartTime = ParseShiftTime(request.Date, shift.MorningTime);
+ if (morningStartTime.HasValue)
+ {
+ morningTransport = await _transportService.GetConnectionAsync(morningStartTime.Value);
+ }
+ }
+
+ // Get transport for afternoon shift if it has afternoon time
+ if (!string.IsNullOrEmpty(shift.AfternoonTime))
+ {
+ var afternoonStartTime = ParseShiftTime(request.Date, shift.AfternoonTime);
+ if (afternoonStartTime.HasValue)
+ {
+ afternoonTransport = await _transportService.GetConnectionAsync(afternoonStartTime.Value);
+ }
+ }
+
+ var shiftWithTransport = new ShiftWithTransport
+ {
+ Date = request.Date,
+ Shift = shift,
+ MorningTransport = morningTransport,
+ AfternoonTransport = afternoonTransport
+ };
+
+ return Ok(shiftWithTransport);
+ }
+
+ private static DateTime? ParseShiftTime(DateTime date, string timeRange)
+ {
+ try
+ {
+ var times = timeRange.Split('-');
+ if (times.Length > 0 && TimeSpan.TryParse(times[0], out var startTime))
+ {
+ return date.Add(startTime);
+ }
+ }
+ catch
+ {
+ // Ignore parsing errors
+ }
+ return null;
+ }
+
[HttpPost("export_ics")]
public async Task ExportIcs([FromBody] Dictionary schedule)
{
@@ -50,6 +110,22 @@ namespace ShiftScheduler.Server.Controllers
return File(System.Text.Encoding.UTF8.GetBytes(ics), "text/calendar", "schedule.ics");
}
+ [HttpPost("export_ics_with_transport")]
+ public IActionResult ExportIcsWithTransport([FromBody] List shiftsWithTransport)
+ {
+ var schedule = shiftsWithTransport.ToDictionary(s => s.Date, s => s.Shift.Name);
+ var shifts = shiftsWithTransport.Select(s => s.Shift).DistinctBy(s => s.Name).ToList();
+ var shiftTransports = shiftsWithTransport.Select(s => new ShiftTransport
+ {
+ ShiftName = s.Shift.Name,
+ MorningTransport = s.MorningTransport,
+ AfternoonTransport = s.AfternoonTransport
+ }).DistinctBy(s => s.ShiftName).ToList();
+
+ var ics = _icsService.GenerateIcs(schedule, shifts, shiftTransports);
+ return File(System.Text.Encoding.UTF8.GetBytes(ics), "text/calendar", "schedule.ics");
+ }
+
[HttpPost("export_pdf")]
public async Task ExportPdf([FromBody] Dictionary schedule)
{
@@ -58,6 +134,22 @@ namespace ShiftScheduler.Server.Controllers
var pdf = _pdfExportService.GenerateMonthlySchedulePdf(schedule, shifts, shiftTransports);
return File(pdf, "application/pdf", "schedule.pdf");
}
+
+ [HttpPost("export_pdf_with_transport")]
+ public IActionResult ExportPdfWithTransport([FromBody] List shiftsWithTransport)
+ {
+ var schedule = shiftsWithTransport.ToDictionary(s => s.Date, s => s.Shift.Name);
+ var shifts = shiftsWithTransport.Select(s => s.Shift).DistinctBy(s => s.Name).ToList();
+ var shiftTransports = shiftsWithTransport.Select(s => new ShiftTransport
+ {
+ ShiftName = s.Shift.Name,
+ MorningTransport = s.MorningTransport,
+ AfternoonTransport = s.AfternoonTransport
+ }).DistinctBy(s => s.ShiftName).ToList();
+
+ var pdf = _pdfExportService.GenerateMonthlySchedulePdf(schedule, shifts, shiftTransports);
+ return File(pdf, "application/pdf", "schedule.pdf");
+ }
}
public class TransportConnectionRequest
@@ -65,4 +157,10 @@ namespace ShiftScheduler.Server.Controllers
public DateTime ArrivalTime { get; set; }
public string? EndStation { get; set; }
}
+
+ public class ShiftTransportRequest
+ {
+ public string ShiftName { get; set; } = string.Empty;
+ public DateTime Date { get; set; }
+ }
}
diff --git a/Shared/TransportModels.cs b/Shared/TransportModels.cs
index a31f824..1179b66 100644
--- a/Shared/TransportModels.cs
+++ b/Shared/TransportModels.cs
@@ -93,4 +93,36 @@ namespace ShiftScheduler.Shared.Models
public string Category { get; set; } = string.Empty;
public string Number { get; set; } = string.Empty;
}
+
+ // View model that combines shift information with transport data for a specific date
+ public record ShiftWithTransport
+ {
+ public DateTime Date { get; init; }
+ public Shift Shift { get; init; } = new();
+ public TransportConnection? MorningTransport { get; init; }
+ public TransportConnection? AfternoonTransport { get; init; }
+
+ public string GetMorningTransportSummary()
+ {
+ return MorningTransport != null ? FormatTransportSummary(MorningTransport) : string.Empty;
+ }
+
+ public string GetAfternoonTransportSummary()
+ {
+ return AfternoonTransport != null ? FormatTransportSummary(AfternoonTransport) : string.Empty;
+ }
+
+ private static string FormatTransportSummary(TransportConnection connection)
+ {
+ if (string.IsNullOrEmpty(connection.DepartureTime)) return string.Empty;
+
+ var departure = DateTime.TryParse(connection.DepartureTime, out var dep) ? dep.ToString("HH:mm") : connection.DepartureTime;
+ var arrival = DateTime.TryParse(connection.ArrivalTime, out var arr) ? arr.ToString("HH:mm") : connection.ArrivalTime;
+
+ var mainJourney = connection.Sections?.FirstOrDefault()?.Journey;
+ var trainInfo = mainJourney != null ? $"{mainJourney.Category}{mainJourney.Number}" : "Train";
+
+ return $"{trainInfo} {departure}→{arrival}";
+ }
+ }
}
\ No newline at end of file