using ClosedXML.Excel; using SportsDivision.Application.DTOs; using SportsDivision.Application.DTOs.ReportDtos; namespace SportsDivision.Web.Reports; /// /// Builds Microsoft Excel (.xlsx) workbooks for each report. One worksheet per /// report, with a title, subtitle (tournament name), a styled header row, and /// flattened data rows for the nested reports. /// public static class ReportExcelExporter { public const string MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private static int WriteHeader(IXLWorksheet ws, string title, string subtitle, string[] columns) { ws.Cell(1, 1).Value = title; ws.Cell(1, 1).Style.Font.Bold = true; ws.Cell(1, 1).Style.Font.FontSize = 14; ws.Range(1, 1, 1, Math.Max(1, columns.Length)).Merge(); ws.Cell(2, 1).Value = subtitle; ws.Cell(2, 1).Style.Font.Italic = true; ws.Range(2, 1, 2, Math.Max(1, columns.Length)).Merge(); const int headerRow = 4; for (int i = 0; i < columns.Length; i++) { var c = ws.Cell(headerRow, i + 1); c.Value = columns[i]; c.Style.Font.Bold = true; c.Style.Fill.BackgroundColor = XLColor.FromHtml("#003366"); c.Style.Font.FontColor = XLColor.White; } return headerRow + 1; } private static byte[] Finish(XLWorkbook wb, params IXLWorksheet[] sheets) { foreach (var ws in sheets) { ws.SheetView.FreezeRows(4); ws.Columns().AdjustToContents(); } using var ms = new MemoryStream(); wb.SaveAs(ms); return ms.ToArray(); } // Missing values stay blank so the column keeps a uniform numeric type — // writing "-" would make Excel treat the column as text for sorting/aggregation. private static void SetInt(IXLCell cell, int? v) { if (v.HasValue) cell.Value = v.Value; } private static void SetDec(IXLCell cell, decimal? v) { if (v.HasValue) cell.Value = v.Value; } public static byte[] PopularEvents(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("Popular Events"); int r = WriteHeader(ws, "Popular Events Report", tournamentName, new[] { "Event", "Category", "Total", "Male", "Female" }); foreach (var e in data) { ws.Cell(r, 1).Value = e.EventName; ws.Cell(r, 2).Value = e.Category; ws.Cell(r, 3).Value = e.RegistrationCount; ws.Cell(r, 4).Value = e.MaleCount; ws.Cell(r, 5).Value = e.FemaleCount; r++; } return Finish(wb, ws); } public static byte[] RegistrationByGender(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("Registration by Gender"); int r = WriteHeader(ws, "Registration by Gender", tournamentName, new[] { "Event", "Level", "Male", "Female", "Total" }); foreach (var e in data) { ws.Cell(r, 1).Value = e.EventName; ws.Cell(r, 2).Value = e.EventLevelName; ws.Cell(r, 3).Value = e.MaleCount; ws.Cell(r, 4).Value = e.FemaleCount; ws.Cell(r, 5).Value = e.TotalCount; r++; } return Finish(wb, ws); } public static byte[] EventSchoolReport(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("Event School Report"); int r = WriteHeader(ws, "Event School Report", tournamentName, new[] { "Event", "Level", "School", "Student", "Performance", "Placement", "Points" }); foreach (var ev in data) foreach (var school in ev.Schools) foreach (var s in school.Students) { ws.Cell(r, 1).Value = ev.EventName; ws.Cell(r, 2).Value = ev.EventLevelName; ws.Cell(r, 3).Value = school.SchoolName; ws.Cell(r, 4).Value = s.StudentName; SetDec(ws.Cell(r, 5), s.RawPerformance); SetInt(ws.Cell(r, 6), s.Placement); ws.Cell(r, 7).Value = s.PlacementPoints; r++; } return Finish(wb, ws); } public static byte[] StudentsBySchool(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("Students by School"); int r = WriteHeader(ws, "Students by School", tournamentName, new[] { "School", "Zone", "Student", "Sex", "Events" }); foreach (var school in data) foreach (var s in school.Students) { ws.Cell(r, 1).Value = school.SchoolName; ws.Cell(r, 2).Value = school.ZoneName; ws.Cell(r, 3).Value = s.StudentName; ws.Cell(r, 4).Value = s.Sex; ws.Cell(r, 5).Value = string.Join(", ", s.Events); r++; } return Finish(wb, ws); } public static byte[] ScoresByEvent(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("Scores by Event"); int r = WriteHeader(ws, "Scores by Event", tournamentName, new[] { "Event", "Level", "Category", "Placement", "Student", "School", "Performance", "WA Points", "Placement Points" }); foreach (var ev in data) foreach (var s in ev.Scores) { ws.Cell(r, 1).Value = ev.EventName; ws.Cell(r, 2).Value = ev.EventLevelName; ws.Cell(r, 3).Value = ev.Category; SetInt(ws.Cell(r, 4), s.Placement); ws.Cell(r, 5).Value = s.StudentName; ws.Cell(r, 6).Value = s.SchoolName; ws.Cell(r, 7).Value = s.RawPerformance; ws.Cell(r, 8).Value = s.CalculatedPoints; ws.Cell(r, 9).Value = s.PlacementPoints; r++; } return Finish(wb, ws); } public static byte[] StudentPoints(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("Student Points"); int r = WriteHeader(ws, "Student Points", tournamentName, new[] { "Student", "School", "Sex", "Total Placement Points", "Events" }); foreach (var s in data) { ws.Cell(r, 1).Value = s.StudentName; ws.Cell(r, 2).Value = s.SchoolName; ws.Cell(r, 3).Value = s.Sex; ws.Cell(r, 4).Value = s.TotalPlacementPoints; ws.Cell(r, 5).Value = s.EventCount; r++; } return Finish(wb, ws); } public static byte[] SchoolStandings(IEnumerable data, string tournamentName) { using var wb = new XLWorkbook(); var ws = wb.AddWorksheet("School Standings"); int r = WriteHeader(ws, "School Standings", tournamentName, new[] { "Rank", "School", "Short Name", "Total Points", "1st Place", "2nd Place", "3rd Place" }); int rank = 1; foreach (var s in data) { ws.Cell(r, 1).Value = rank++; ws.Cell(r, 2).Value = s.SchoolName; ws.Cell(r, 3).Value = s.ShortName ?? string.Empty; ws.Cell(r, 4).Value = s.TotalPoints; ws.Cell(r, 5).Value = s.FirstPlaceCount; ws.Cell(r, 6).Value = s.SecondPlaceCount; ws.Cell(r, 7).Value = s.ThirdPlaceCount; r++; } return Finish(wb, ws); } }