From fa2595da06b49d2cb666ff1cbea0196143554e9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:51:56 +0000 Subject: [PATCH] Implement transport time improvements: departure station display and break time logic Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com> --- Server/Controllers/ShiftController.cs | 50 ++++++++++++++++++++++++--- Server/appsettings.json | 3 +- Services/IcsExportService.cs | 12 ++++--- Services/PdfExportService.cs | 10 +++--- Shared/ShiftWithTransport.cs | 7 ++-- Shared/TransportModels.cs | 1 + 6 files changed, 66 insertions(+), 17 deletions(-) diff --git a/Server/Controllers/ShiftController.cs b/Server/Controllers/ShiftController.cs index 5c8ca09..29d2e12 100644 --- a/Server/Controllers/ShiftController.cs +++ b/Server/Controllers/ShiftController.cs @@ -13,19 +13,22 @@ namespace ShiftScheduler.Server.Controllers private readonly PdfExportService _pdfExportService; private readonly TransportService _transportService; private readonly ShiftEnrichmentService _enrichmentService; + private readonly TransportConfiguration _transportConfig; public ShiftController( ShiftService shiftService, IcsExportService icsService, PdfExportService pdfExportService, TransportService transportService, - ShiftEnrichmentService enrichmentService) + ShiftEnrichmentService enrichmentService, + TransportConfiguration transportConfig) { _shiftService = shiftService; _icsService = icsService; _pdfExportService = pdfExportService; _transportService = transportService; _enrichmentService = enrichmentService; + _transportConfig = transportConfig; } [HttpGet("shifts")] @@ -64,12 +67,31 @@ namespace ShiftScheduler.Server.Controllers } // Get transport for afternoon shift if it has afternoon time + // But only if the break between morning and afternoon is long enough if (!string.IsNullOrEmpty(shift.AfternoonTime)) { - var afternoonStartTime = ParseShiftTime(request.Date, shift.AfternoonTime); - if (afternoonStartTime.HasValue) + var shouldLoadAfternoonTransport = true; + + // If both morning and afternoon shifts exist, check break duration + if (!string.IsNullOrEmpty(shift.MorningTime) && !string.IsNullOrEmpty(shift.AfternoonTime)) { - afternoonTransport = await _transportService.GetConnectionAsync(afternoonStartTime.Value); + var morningEndTime = ParseShiftEndTime(request.Date, shift.MorningTime); + var afternoonStartTime = ParseShiftTime(request.Date, shift.AfternoonTime); + + if (morningEndTime.HasValue && afternoonStartTime.HasValue) + { + var breakDurationMinutes = (afternoonStartTime.Value - morningEndTime.Value).TotalMinutes; + shouldLoadAfternoonTransport = breakDurationMinutes >= _transportConfig.MinBreakMinutes; + } + } + + if (shouldLoadAfternoonTransport) + { + var afternoonStartTime = ParseShiftTime(request.Date, shift.AfternoonTime); + if (afternoonStartTime.HasValue) + { + afternoonTransport = await _transportService.GetConnectionAsync(afternoonStartTime.Value); + } } } @@ -78,7 +100,8 @@ namespace ShiftScheduler.Server.Controllers Date = request.Date, Shift = shift, MorningTransport = morningTransport, - AfternoonTransport = afternoonTransport + AfternoonTransport = afternoonTransport, + DepartureStation = _transportConfig.StartStation }; return Ok(shiftWithTransport); @@ -101,6 +124,23 @@ namespace ShiftScheduler.Server.Controllers return null; } + private static DateTime? ParseShiftEndTime(DateTime date, string timeRange) + { + try + { + var times = timeRange.Split('-'); + if (times.Length > 1 && TimeSpan.TryParse(times[1], out var endTime)) + { + return date.Add(endTime); + } + } + catch + { + // Ignore parsing errors + } + return null; + } + [HttpPost("export_ics")] public IActionResult ExportIcsWithTransport([FromBody] List shiftsWithTransport) { diff --git a/Server/appsettings.json b/Server/appsettings.json index 4f52399..1d67124 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -11,7 +11,8 @@ "StartStation": "Zurich", "EndStation": "Basel", "ApiBaseUrl": "http://transport.opendata.ch/v1", - "SafetyBufferMinutes": 30 + "SafetyBufferMinutes": 30, + "MinBreakMinutes": 60 }, "Shifts": [ diff --git a/Services/IcsExportService.cs b/Services/IcsExportService.cs index 98217f9..689e7d1 100644 --- a/Services/IcsExportService.cs +++ b/Services/IcsExportService.cs @@ -28,7 +28,7 @@ namespace ShiftScheduler.Services if (shiftWithTransport.MorningTransport != null && !string.IsNullOrEmpty(shiftWithTransport.MorningTransport.DepartureTime)) { - var transportSummary = FormatTransportInfo(shiftWithTransport.MorningTransport); + var transportSummary = FormatTransportInfo(shiftWithTransport.MorningTransport, shiftWithTransport.DepartureStation); description = $"Transport: {transportSummary}"; } @@ -49,7 +49,7 @@ namespace ShiftScheduler.Services if (shiftWithTransport.AfternoonTransport != null && !string.IsNullOrEmpty(shiftWithTransport.AfternoonTransport.DepartureTime)) { - var transportSummary = FormatTransportInfo(shiftWithTransport.AfternoonTransport); + var transportSummary = FormatTransportInfo(shiftWithTransport.AfternoonTransport, shiftWithTransport.DepartureStation); description = $"Transport: {transportSummary}"; } @@ -66,18 +66,20 @@ namespace ShiftScheduler.Services return new CalendarSerializer().SerializeToString(calendar); } - private string FormatTransportInfo(TransportConnection transport) + private string FormatTransportInfo(TransportConnection transport, string departureStation) { var departure = DateTime.TryParse(transport.DepartureTime, out var dep) ? dep.ToString("HH:mm") : transport.DepartureTime; var arrival = DateTime.TryParse(transport.ArrivalTime, out var arr) ? arr.ToString("HH:mm") : transport.ArrivalTime; + var departureStationInfo = !string.IsNullOrEmpty(departureStation) ? $"{departureStation} " : ""; + var mainJourney = transport.Sections?.FirstOrDefault()?.Journey; if (mainJourney != null) { - return $"{mainJourney.Category} {mainJourney.Number}: {departure} → {arrival}"; + return $"{mainJourney.Category} {mainJourney.Number}: {departureStationInfo}{departure} → {arrival}"; } - return $"{departure} → {arrival}"; + return $"{departureStationInfo}{departure} → {arrival}"; } } } diff --git a/Services/PdfExportService.cs b/Services/PdfExportService.cs index 607fabe..f949403 100644 --- a/Services/PdfExportService.cs +++ b/Services/PdfExportService.cs @@ -155,20 +155,20 @@ namespace ShiftScheduler.Services if (shiftWithTransport.MorningTransport != null && !string.IsNullOrEmpty(shiftWithTransport.MorningTransport.DepartureTime)) { - var morningInfo = FormatTransportConnection(shiftWithTransport.MorningTransport, "Morning"); + var morningInfo = FormatTransportConnection(shiftWithTransport.MorningTransport, "Morning", shiftWithTransport.DepartureStation); transportLines.Add(morningInfo); } if (shiftWithTransport.AfternoonTransport != null && !string.IsNullOrEmpty(shiftWithTransport.AfternoonTransport.DepartureTime)) { - var afternoonInfo = FormatTransportConnection(shiftWithTransport.AfternoonTransport, "Afternoon"); + var afternoonInfo = FormatTransportConnection(shiftWithTransport.AfternoonTransport, "Afternoon", shiftWithTransport.DepartureStation); transportLines.Add(afternoonInfo); } return transportLines.Count > 0 ? string.Join("\n", transportLines) : "-"; } - private static string FormatTransportConnection(TransportConnection transport, string timeOfDay) + private static string FormatTransportConnection(TransportConnection transport, string timeOfDay, string departureStation) { var departure = DateTime.TryParse(transport.DepartureTime, out var dep) ? dep.ToString("HH:mm") : transport.DepartureTime; var arrival = DateTime.TryParse(transport.ArrivalTime, out var arr) ? arr.ToString("HH:mm") : transport.ArrivalTime; @@ -176,7 +176,9 @@ namespace ShiftScheduler.Services var mainJourney = transport.Sections?.FirstOrDefault()?.Journey; var trainInfo = mainJourney != null ? $"{mainJourney.Category} {mainJourney.Number}" : "Train"; - return $"{timeOfDay}: {trainInfo} {departure}→{arrival}"; + var departureStationInfo = !string.IsNullOrEmpty(departureStation) ? $"{departureStation} " : ""; + + return $"{timeOfDay}: {trainInfo} {departureStationInfo}{departure}→{arrival}"; } private static void RenderEmptyCell(TableDescriptor table) diff --git a/Shared/ShiftWithTransport.cs b/Shared/ShiftWithTransport.cs index 2c794cd..9cc538b 100644 --- a/Shared/ShiftWithTransport.cs +++ b/Shared/ShiftWithTransport.cs @@ -7,6 +7,7 @@ namespace ShiftScheduler.Shared.Models public Shift Shift { get; init; } = new(); public TransportConnection? MorningTransport { get; init; } public TransportConnection? AfternoonTransport { get; init; } + public string DepartureStation { get; init; } = string.Empty; public string GetMorningTransportSummary() { @@ -18,7 +19,7 @@ namespace ShiftScheduler.Shared.Models return AfternoonTransport != null ? FormatTransportSummary(AfternoonTransport) : string.Empty; } - private static string FormatTransportSummary(TransportConnection connection) + private string FormatTransportSummary(TransportConnection connection) { if (string.IsNullOrEmpty(connection.DepartureTime)) return string.Empty; @@ -28,7 +29,9 @@ namespace ShiftScheduler.Shared.Models var mainJourney = connection.Sections?.FirstOrDefault()?.Journey; var trainInfo = mainJourney != null ? $"{mainJourney.Category}{mainJourney.Number}" : "Train"; - return $"{trainInfo} {departure}→{arrival}"; + var departureStationInfo = !string.IsNullOrEmpty(DepartureStation) ? $"{DepartureStation} " : ""; + + return $"{trainInfo} {departureStationInfo}{departure}→{arrival}"; } } } \ No newline at end of file diff --git a/Shared/TransportModels.cs b/Shared/TransportModels.cs index db85461..8eaa52c 100644 --- a/Shared/TransportModels.cs +++ b/Shared/TransportModels.cs @@ -13,6 +13,7 @@ namespace ShiftScheduler.Shared.Models public string EndStation { get; set; } = string.Empty; public string ApiBaseUrl { get; set; } = string.Empty; public int SafetyBufferMinutes { get; set; } = 30; + public int MinBreakMinutes { get; set; } = 60; } public class TransportConnection