From 33c007e66c645d38ead91f4a078e1e280351e03e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:15:17 +0000 Subject: [PATCH] Refactor export methods to directly use ShiftWithTransport instead of converting to separate parameters Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com> --- Server/Controllers/ShiftController.cs | 22 +--- Services/IcsExportService.cs | 58 ++++++++++ Services/PdfExportService.cs | 152 ++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 20 deletions(-) diff --git a/Server/Controllers/ShiftController.cs b/Server/Controllers/ShiftController.cs index 61378c4..32808f6 100644 --- a/Server/Controllers/ShiftController.cs +++ b/Server/Controllers/ShiftController.cs @@ -113,16 +113,7 @@ namespace ShiftScheduler.Server.Controllers [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); + var ics = _icsService.GenerateIcs(shiftsWithTransport); return File(System.Text.Encoding.UTF8.GetBytes(ics), "text/calendar", "schedule.ics"); } @@ -138,16 +129,7 @@ namespace ShiftScheduler.Server.Controllers [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); + var pdf = _pdfExportService.GenerateMonthlySchedulePdf(shiftsWithTransport); return File(pdf, "application/pdf", "schedule.pdf"); } } diff --git a/Services/IcsExportService.cs b/Services/IcsExportService.cs index f6baafd..e6b7a7a 100644 --- a/Services/IcsExportService.cs +++ b/Services/IcsExportService.cs @@ -68,6 +68,64 @@ namespace ShiftScheduler.Services return new CalendarSerializer().SerializeToString(calendar); } + public string GenerateIcs(List shiftsWithTransport) + { + var calendar = new Calendar(); + + foreach (var shiftWithTransport in shiftsWithTransport) + { + var date = shiftWithTransport.Date; + var shift = shiftWithTransport.Shift; + + if (string.IsNullOrEmpty(shift.MorningTime) && string.IsNullOrEmpty(shift.AfternoonTime)) + continue; + + if (!string.IsNullOrEmpty(shift.MorningTime)) + { + var times = shift.MorningTime.Split('-'); + var summary = $"{shift.Name} (Morning)"; + var description = ""; + + if (shiftWithTransport.MorningTransport != null && !string.IsNullOrEmpty(shiftWithTransport.MorningTransport.DepartureTime)) + { + var transportSummary = FormatTransportInfo(shiftWithTransport.MorningTransport); + description = $"Transport: {transportSummary}"; + } + + calendar.Events.Add(new CalendarEvent + { + Summary = summary, + Description = description, + Start = new CalDateTime(date.Add(TimeSpan.Parse(times[0]))), + End = new CalDateTime(date.Add(TimeSpan.Parse(times[1]))) + }); + } + + if (!string.IsNullOrEmpty(shift.AfternoonTime)) + { + var times = shift.AfternoonTime.Split('-'); + var summary = $"{shift.Name} (Afternoon)"; + var description = ""; + + if (shiftWithTransport.AfternoonTransport != null && !string.IsNullOrEmpty(shiftWithTransport.AfternoonTransport.DepartureTime)) + { + var transportSummary = FormatTransportInfo(shiftWithTransport.AfternoonTransport); + description = $"Transport: {transportSummary}"; + } + + calendar.Events.Add(new CalendarEvent + { + Summary = summary, + Description = description, + Start = new CalDateTime(date.Add(TimeSpan.Parse(times[0]))), + End = new CalDateTime(date.Add(TimeSpan.Parse(times[1]))) + }); + } + } + + return new CalendarSerializer().SerializeToString(calendar); + } + private string FormatTransportInfo(TransportConnection transport) { var departure = DateTime.TryParse(transport.DepartureTime, out var dep) ? dep.ToString("HH:mm") : transport.DepartureTime; diff --git a/Services/PdfExportService.cs b/Services/PdfExportService.cs index 0c7e85d..511d9be 100644 --- a/Services/PdfExportService.cs +++ b/Services/PdfExportService.cs @@ -39,6 +39,33 @@ namespace ShiftScheduler.Services return document.GeneratePdf(); } + + public byte[] GenerateMonthlySchedulePdf(List shiftsWithTransport) + { + if (!shiftsWithTransport.Any()) + return new byte[0]; + + var year = shiftsWithTransport.First().Date.Year; + var month = shiftsWithTransport.First().Date.Month; + + var document = Document.Create(container => + { + container.Page(page => + { + page.Margin(30); + page.Size(PageSizes.A4.Landscape()); + page.DefaultTextStyle(x => x.FontSize(10)); + + page.Header() + .Text($"Shift Schedule for {CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month)} {year}") + .FontSize(16).Bold().AlignCenter(); + + page.Content().Element(e => ComposeTable(e, shiftsWithTransport)); + }); + }); + + return document.GeneratePdf(); + } void ComposeTable(IContainer container, IReadOnlyDictionary schedule, IReadOnlyList shifts, IReadOnlyList? shiftTransports) { @@ -72,6 +99,39 @@ namespace ShiftScheduler.Services }); } + void ComposeTable(IContainer container, List shiftsWithTransport) + { + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + }); + + var scheduleDictionary = shiftsWithTransport.ToDictionary(s => s.Date, s => s); + var year = shiftsWithTransport.First().Date.Year; + var month = shiftsWithTransport.First().Date.Month; + var daysInMonth = DateTime.DaysInMonth(year, month); + var monthDates = Enumerable.Range(1, daysInMonth) + .Select(day => new DateTime(year, month, day)) + .ToList(); + var weeks = monthDates + .GroupBy(d => CultureInfo.InvariantCulture.Calendar.GetWeekOfYear( + d, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday)) + .ToList(); + foreach (var week in weeks) + { + RenderShiftForWeek(table, week, scheduleDictionary); + } + }); + } + private static void RenderShiftForWeek(TableDescriptor table, IGrouping week, IReadOnlyDictionary schedule, IReadOnlyList shifts, IReadOnlyList? shiftTransports) { @@ -140,6 +200,77 @@ namespace ShiftScheduler.Services } } + private static void RenderShiftForWeek(TableDescriptor table, IGrouping week, + IReadOnlyDictionary shiftsWithTransport) + { + var weekDates = week.ToList(); + var dayInWeek = weekDates.Count; + foreach (var day in Enumerable.Range(0, dayInWeek)) + { + var dddd = weekDates.ElementAtOrDefault(day).ToString("dd.MM.yyyy (ddd)"); + table.Cell().Element(CellStyleFirst).Text(dddd); + } + foreach (var day in Enumerable.Range(dayInWeek, 7 - dayInWeek)) + { + RenderEmptyCell(table); + } + + foreach (var day in Enumerable.Range(0, dayInWeek)) + { + var shiftWithTransport = shiftsWithTransport.GetValueOrDefault(weekDates.ElementAtOrDefault(day)); + var shiftName = shiftWithTransport?.Shift.Name ?? ""; + table.Cell().Element(CellStyleMiddle).Text(shiftName); + } + foreach (var day in Enumerable.Range(dayInWeek, 7 - dayInWeek)) + { + RenderEmptyCell(table); + } + + foreach (var day in Enumerable.Range(0, dayInWeek)) + { + var shiftWithTransport = shiftsWithTransport.GetValueOrDefault(weekDates.ElementAtOrDefault(day)); + var morningTime = shiftWithTransport?.Shift.MorningTime ?? ""; + table.Cell().Element(CellStyleMiddle).Text(morningTime); + } + foreach (var day in Enumerable.Range(dayInWeek, 7 - dayInWeek)) + { + RenderEmptyCell(table); + } + + foreach (var day in Enumerable.Range(0, dayInWeek)) + { + var shiftWithTransport = shiftsWithTransport.GetValueOrDefault(weekDates.ElementAtOrDefault(day)); + var afternoonTime = shiftWithTransport?.Shift.AfternoonTime ?? ""; + table.Cell().Element(CellStyleMiddle).Text(afternoonTime); + } + foreach (var day in Enumerable.Range(dayInWeek, 7 - dayInWeek)) + { + RenderEmptyCell(table); + } + + foreach (var day in Enumerable.Range(0, dayInWeek)) + { + var shiftWithTransport = shiftsWithTransport.GetValueOrDefault(weekDates.ElementAtOrDefault(day)); + var icon = shiftWithTransport?.Shift.Icon ?? ""; + table.Cell().Element(CellStyleMiddle).Text(icon); + } + foreach (var day in Enumerable.Range(dayInWeek, 7 - dayInWeek)) + { + RenderEmptyCell(table); + } + + foreach (var day in Enumerable.Range(0, dayInWeek)) + { + var shiftWithTransport = shiftsWithTransport.GetValueOrDefault(weekDates.ElementAtOrDefault(day)); + var transportInfo = GetTransportSummary(shiftWithTransport); + table.Cell().Element(CellStyleLast).Text(transportInfo); + } + foreach (var day in Enumerable.Range(dayInWeek, 7 - dayInWeek)) + { + RenderEmptyCell(table); + } + } + private static string GetTransportSummary(ShiftTransport? transport) { var transportLines = new List(); @@ -159,6 +290,27 @@ namespace ShiftScheduler.Services return transportLines.Count > 0 ? string.Join("\n", transportLines) : "-"; } + private static string GetTransportSummary(ShiftWithTransport? shiftWithTransport) + { + if (shiftWithTransport == null) return "-"; + + var transportLines = new List(); + + if (shiftWithTransport.MorningTransport != null && !string.IsNullOrEmpty(shiftWithTransport.MorningTransport.DepartureTime)) + { + var morningInfo = FormatTransportConnection(shiftWithTransport.MorningTransport, "Morning"); + transportLines.Add(morningInfo); + } + + if (shiftWithTransport.AfternoonTransport != null && !string.IsNullOrEmpty(shiftWithTransport.AfternoonTransport.DepartureTime)) + { + var afternoonInfo = FormatTransportConnection(shiftWithTransport.AfternoonTransport, "Afternoon"); + transportLines.Add(afternoonInfo); + } + + return transportLines.Count > 0 ? string.Join("\n", transportLines) : "-"; + } + private static string FormatTransportConnection(TransportConnection transport, string timeOfDay) { var departure = DateTime.TryParse(transport.DepartureTime, out var dep) ? dep.ToString("HH:mm") : transport.DepartureTime;